Skip to content

Commit

Permalink
Merge pull request #881 from oceanprotocol/feature/batch_payments
Browse files Browse the repository at this point in the history
batch payments contract
  • Loading branch information
alexcos20 authored Oct 15, 2024
2 parents cbe3a1e + 2e6f117 commit ab6c73c
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = v2.2.0
current_version = v2.2.1
commit = True
tag = True

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ jobs:
ALCHEMY_URL: ${{secrets.ALCHEMY_URL}}
- name: Delete dbg files
run: find ./artifacts/* -name "*.dbg.json" -type f -delete
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
with:
name: coverage
name: coverage-contracts
path: coverage/

slither:
Expand Down Expand Up @@ -104,9 +104,9 @@ jobs:
if: ${{ success() && github.actor != 'dependabot[bot]' }}
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
with:
name: coverage
name: coverage-contracts
path: coverage/
- uses: paambaati/[email protected]
env:
Expand Down
3 changes: 2 additions & 1 deletion addresses/address.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@
"DFRewards": "0xc37F8341Ac6e4a94538302bCd4d49Cf0852D30C0",
"DFStrategyV1": "0x3c21a90599b5B7f37014cA5Bf30d3f1b73d7e391",
"PredictoorHelper": "0xE9397625Df9B63f0C152f975234b7988b54710B8",
"AccessListFactory": "0x12bB8D85a091A69A07E22E52d4567dBB91568f52"
"AccessListFactory": "0x12bB8D85a091A69A07E22E52d4567dBB91568f52",
"BatchPayments": "0x9497d1d64F2aFeBcd4f9916Eef3d9094E5Df962f"
},
"optimism_sepolia": {
"chainId": 11155420,
Expand Down
38 changes: 38 additions & 0 deletions contracts/rewards/BatchPayments.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright BigchainDB GmbH and Ocean Protocol contributors
// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
// Code is Apache-2.0 and docs are CC-BY-4.0


pragma solidity 0.8.12;


interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}


contract BatchPayments {
function sendEther(address[] memory list, uint256[] memory amounts) external payable {
require(list.length == amounts.length,"Arrays must have same length");
for (uint256 i = 0; i < list.length; i++)
payable(list[i]).transfer(amounts[i]);
uint256 balance = address(this).balance;
// make sure that we return any excess to the caller
// Later TODO: Check for gas
if (balance > 23000)
payable(msg.sender).call{value: balance}("");
//payable(msg.sender).transfer(balance);
}

function sendToken(IERC20 token, address[] memory list, uint256[] memory amounts) external {
require(list.length == amounts.length,"Arrays must have same length");
uint256 total = 0;
uint256 i;
for (i = 0; i < list.length; i++)
total += amounts[i];
require(token.transferFrom(msg.sender, address(this), total));
for (i = 0; i < list.length; i++)
require(token.transfer(list[i], amounts[i]));
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oceanprotocol/contracts",
"version": "2.2.0",
"version": "2.2.1",
"description": "Ocean Protocol Smartcontracts",
"bugs": {
"url": "https://github.com/oceanprotocol/contracts/issues"
Expand Down
17 changes: 17 additions & 0 deletions scripts/deploy-contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,23 @@ async function main() {
}
}

// Batcher
if (logging) console.info("Deploying BatchPayment");
const BatchPayments = await ethers.getContractFactory(
"BatchPayments",
owner
);

const deployBatchPayments = await BatchPayments.connect(owner).deploy(options)
await deployBatchPayments.deployTransaction.wait();
if (show_verify) {
console.log("\tRun the following to verify on etherscan");
console.log("\tnpx hardhat verify --network " + networkName + " " + deployBatchPayments.address)
}
addresses.BatchPayments = deployBatchPayments.address;
if (sleepAmount > 0) await sleep(sleepAmount)


//DF contracts
if (shouldDeployDF) {
//DFRewards
Expand Down
113 changes: 113 additions & 0 deletions scripts/deploy_batchpayments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
const hre = require("hardhat");
const fs = require("fs");
const { address } = require("../test/helpers/constants");
const { Wallet } = require("ethers");
const { UV_FS_O_FILEMAP } = require("constants");
const ethers = hre.ethers;
require("dotenv").config();
const logging = true;
const show_verify = true;
const shouldDeployMock20 = false
async function main() {
const url = process.env.NETWORK_RPC_URL;
console.log("Using RPC: "+url);
if (!url) {
console.error("Missing NETWORK_RPC_URL. Aborting..");
return null;
}
const provider = new ethers.providers.JsonRpcProvider(url);
const network = provider.getNetwork();
// utils
const networkDetails = await network;


let wallet;
if (process.env.MNEMONIC)
wallet = new Wallet.fromMnemonic(process.env.MNEMONIC);
if (process.env.PRIVATE_KEY) wallet = new Wallet(process.env.PRIVATE_KEY);
if (!wallet) {
console.error("Missing MNEMONIC or PRIVATE_KEY. Aborting..");
return null;
}
owner = wallet.connect(provider);
let gasLimit = 3000000;
let gasPrice = null;
let sleepAmount = 10;
console.log("Using chain "+networkDetails.chainId);
switch (networkDetails.chainId) {
case 23294:
networkName = "oasis_saphire";
OceanTokenAddress = "0x39d22B78A7651A76Ffbde2aaAB5FD92666Aca520";
OPFOwner = '0x086E7F0588755af5AF5f8194542Fd8328238F3C1'
routerOwner = OPFOwner;
sleepAmount = 30
gasPrice = ethers.utils.parseUnits('100', 'gwei')
gasLimit = 15000000
break;
default:
OPFOwner = "0x0d27cd67c4A3fd3Eb9C7C757582f59089F058167";
networkName = "development";
routerOwner = OPFOwner;
shouldDeployOceanMock = true;
sleepAmount = 0
break;
}

let options
if(gasPrice){
options = {gasLimit: gasLimit, gasPrice: gasPrice}
}
else{
options = { gasLimit }
}


if (logging) console.info("Deploying BatchPayment");
const BatchPayments = await ethers.getContractFactory(
"BatchPayments",
owner
);

const deployBatchPayments = await BatchPayments.connect(owner).deploy(options)
await deployBatchPayments.deployTransaction.wait();
if (show_verify) {
console.log("\tRun the following to verify on etherscan");
console.log("\tnpx hardhat verify --network " + networkName + " " + deployBatchPayments.address)
}
if (shouldDeployMock20){
const Mock20 = await ethers.getContractFactory(
"MockERC20",
owner
);
const deployMock20 = await Mock20.connect(owner).deploy(owner.address,"MockERC20", 'MockERC20',options)
await deployMock20.deployTransaction.wait();
if (show_verify) {
console.log("\tRun the following to verify on etherscan");
console.log("\tnpx hardhat verify --network " + networkName + " " + deployMock20.address)
}
}
if (sleepAmount > 0) await sleep(sleepAmount)


}



async function sleep(s) {
return new Promise((resolve) => {
setTimeout(resolve, s*1000)
})
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = ocean-contracts
version = v2.2.0
version = v2.2.1
author = leucothia
author_email = [email protected]
description = 🐳 Ocean Protocol L1 - v4
Expand Down
136 changes: 136 additions & 0 deletions test/unit/rewards/BatchPayments.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const { expect } = require('chai');
const { ethers } = require("hardhat");
const { json } = require('hardhat/internal/core/params/argumentTypes');
const { web3 } = require("@openzeppelin/test-helpers/src/setup");

// Start test block
describe('Batch Payments tests', function () {
let Mock20Contract;
let Mock20DecimalsContract;
let BatchPaymentsContract;
let signers;
before(async function () {
// Get the contractOwner and collector address
signers = await ethers.getSigners();
const MockErc20 = await ethers.getContractFactory('MockERC20');
const MockErc20Decimals = await ethers.getContractFactory('MockERC20Decimals');
const BatchPayments = await ethers.getContractFactory('BatchPayments');
Mock20Contract = await MockErc20.deploy(signers[0].address,"MockERC20", 'MockERC20');
Mock20DecimalsContract = await MockErc20Decimals.deploy("Mock6Digits", 'Mock6Digits', 6);
BatchPaymentsContract = await BatchPayments.deploy();
console.log("Batch:" + BatchPaymentsContract.address)
console.log("Mock20Contract:" + Mock20Contract.address)

});


// Test cases
it('Check contract deployment', async function () {
expect(await BatchPaymentsContract.address).to.exist;

});

it('ERC20 - Should transfer tokens in batch', async function () {
const addresses = [signers[1].address, signers[2].address, signers[3].address];
const amounts = [web3.utils.toWei("100"), web3.utils.toWei("200"), web3.utils.toWei("300")];

// Approve the BatchPayments contract to transfer tokens
await Mock20Contract.approve(BatchPaymentsContract.address, web3.utils.toWei("10000"));

// Perform the batch transfer
await BatchPaymentsContract.sendToken(Mock20Contract.address, addresses, amounts);

// Check balances
expect(await Mock20Contract.balanceOf(signers[1].address)).to.equal(web3.utils.toWei("100"));
expect(await Mock20Contract.balanceOf(signers[2].address)).to.equal(web3.utils.toWei("200"));
expect(await Mock20Contract.balanceOf(signers[3].address)).to.equal(web3.utils.toWei("300"));
});

it('ERC20 - Should revert if arrays length mismatch', async function () {
const addresses = [signers[1].address, signers[2].address];
const amounts = [web3.utils.toWei("100"), web3.utils.toWei("200"), web3.utils.toWei("300")];

// Approve the BatchPayments contract to transfer tokens
await Mock20Contract.approve(BatchPaymentsContract.address, web3.utils.toWei("10000"));

// Perform the batch transfer
await expect(BatchPaymentsContract.sendToken(Mock20Contract.address, addresses, amounts)).to.be.revertedWith("Arrays must have same length");

});

it('ERC20 - Should revert if transfer fails(no prior allowence)', async function () {
const addresses = [signers[1].address, signers[2].address, signers[3].address];
const amounts = [web3.utils.toWei("100"), web3.utils.toWei("200"), web3.utils.toWei("1300")];

// Approve the BatchPayments contract to transfer tokens - insufficient amount
await Mock20Contract.approve(BatchPaymentsContract.address, web3.utils.toWei("1000"));

// Perform the batch transfer
await expect(BatchPaymentsContract.sendToken(Mock20Contract.address, addresses, amounts)).to.be.revertedWith("ERC20: insufficient allowance");

});

it('ERC20 - Should handle tokens with decimals correctly', async function () {
const addresses = [signers[1].address, signers[2].address, signers[3].address];
const amounts = [
ethers.utils.parseUnits("100", 6),
ethers.utils.parseUnits("200", 6),
ethers.utils.parseUnits("300", 6)
];

// Approve the BatchPayments contract to transfer tokens
await Mock20DecimalsContract.approve(BatchPaymentsContract.address, ethers.utils.parseUnits("10000", 6));

// Perform the batch transfer
await BatchPaymentsContract.sendToken(Mock20DecimalsContract.address, addresses, amounts);

// Check balances
expect(await Mock20DecimalsContract.balanceOf(signers[1].address)).to.equal(ethers.utils.parseUnits("100", 6));
expect(await Mock20DecimalsContract.balanceOf(signers[2].address)).to.equal(ethers.utils.parseUnits("200", 6));
expect(await Mock20DecimalsContract.balanceOf(signers[3].address)).to.equal(ethers.utils.parseUnits("300", 6));
});

it('NATIVE - Should transfer ETH in batch', async function () {
const addresses = [signers[1].address, signers[2].address, signers[3].address];
const amounts = [ethers.utils.parseEther("0.1"), ethers.utils.parseEther("0.2"), ethers.utils.parseEther("0.3")];

const initialBalances = await Promise.all(addresses.map(addr => ethers.provider.getBalance(addr)));

// Perform the batch transfer
await BatchPaymentsContract.sendEther(addresses, amounts, { value: ethers.utils.parseEther("0.6") });

// Check balances
const finalBalances = await Promise.all(addresses.map(addr => ethers.provider.getBalance(addr)));
expect(finalBalances[0]).to.equal(initialBalances[0].add(amounts[0]));
expect(finalBalances[1]).to.equal(initialBalances[1].add(amounts[1]));
expect(finalBalances[2]).to.equal(initialBalances[2].add(amounts[2]));
});

it('NATIVE - Should transfer ETH in batch and return remaining', async function () {
const owner = signers[0]
const addresses = [signers[1].address, signers[2].address, signers[3].address];
const amounts = [ethers.utils.parseEther("0.1"), ethers.utils.parseEther("0.2"), ethers.utils.parseEther("0.3")];

const initialOwnerBalance = await ethers.provider.getBalance(owner.address);
const initialContractBalance = await ethers.provider.getBalance(BatchPaymentsContract.address);
const initialBalances = await Promise.all(addresses.map(addr => ethers.provider.getBalance(addr)));

expect(initialContractBalance).to.equal(0);
// Perform the batch transfer
await BatchPaymentsContract.sendEther(addresses, amounts, { value: ethers.utils.parseEther("1.6") });

// Check balances
const finalBalances = await Promise.all(addresses.map(addr => ethers.provider.getBalance(addr)));
const finalOwnerBalance = await ethers.provider.getBalance(owner.address);
const finalContractBalance = await ethers.provider.getBalance(BatchPaymentsContract.address);
expect(finalBalances[0]).to.equal(initialBalances[0].add(amounts[0]));
expect(finalBalances[1]).to.equal(initialBalances[1].add(amounts[1]));
expect(finalBalances[2]).to.equal(initialBalances[2].add(amounts[2]));

expect(finalContractBalance).to.equal(0);
const diff=finalOwnerBalance.sub(initialOwnerBalance)
expect(Number(diff)).to.be.lessThan(Number(ethers.utils.parseEther("0.61")))
});


});

0 comments on commit ab6c73c

Please sign in to comment.