-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #881 from oceanprotocol/feature/batch_payments
batch payments contract
- Loading branch information
Showing
9 changed files
with
313 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
|
@@ -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: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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])); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"))) | ||
}); | ||
|
||
|
||
}); |