Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ability to send an array of tokens #119

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
48 changes: 44 additions & 4 deletions blockchain/contracts/SendToMany.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ pragma solidity ^0.4.23;
import "./IERC20.sol";

contract SendToMany {

address owner;
mapping(address => uint) private tokenSums;

constructor() {
constructor() public {
owner = msg.sender;
}

modifier isOwner() {
require(msg.sender == owner);
require(msg.sender == owner, "must be the owner address");
_;
}

function sendMany(address[] addresses, uint[] amounts, address tokenContract) public payable isOwner {
function sendMany(address[] memory addresses, uint[] memory amounts, address tokenContract) public payable isOwner {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reduce duplication

require(addresses.length == amounts.length);
uint sum = 0;
for(uint i = 0; i < amounts.length; i++) {
Expand All @@ -26,10 +28,48 @@ contract SendToMany {
require(token.transferFrom(msg.sender, addresses[i], amounts[i]), "token transfer failed");
}
} else {
require((address(this).balance + msg.value) >= sum, "ETH balance too low for this batch");
require((msg.value) >= sum, "must send enough ETH");
for(i = 0; i < addresses.length; i++) {
addresses[i].transfer(amounts[i]);
}
}
}


function validateBatch(address[] memory addresses, uint[] memory amounts, address[] memory tokenContracts, uint sentValue, address sender) public view returns(bool) {
require(addresses.length == amounts.length, "must provide same length addresses and amounts");
require(addresses.length == tokenContracts.length, "must provide same length addresses and tokenContracts");
for(uint i = 0; i < addresses.length; i++) {
address tokenContract = tokenContracts[i];
uint amount = amounts[i];
tokenSums[tokenContract] += amount;
uint sum = tokenSums[tokenContract];
if(tokenContract != 0x0) {
IERC20 token = IERC20(tokenContract);
require(token.allowance(sender, address(this)) >= sum, "This contract is not allowed enough funds for this batch");
} else {
require(sentValue >= sum, "must send enough ETH");
}
}
return true;
}

function batchSend(address[] memory addresses, uint[] memory amounts, address[] memory tokenContracts) public payable {
require(addresses.length == amounts.length, "must provide same length addresses and amounts");
require(addresses.length == tokenContracts.length, "must provide same length addresses and tokenContracts");
uint ethSum = 0;
for(uint i = 0; i < addresses.length; i++) {
address tokenContract = tokenContracts[i];
address recipient = addresses[i];
uint amount = amounts[i];
if(tokenContract != 0x0) {
IERC20 token = IERC20(tokenContract);
require(token.transferFrom(msg.sender, recipient, amount), "token transfer failed");
} else {
ethSum += amount;
require(msg.value >= ethSum, "must send enough ETH");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make exact equal

recipient.transfer(amount);
}
}
}
}
112 changes: 98 additions & 14 deletions blockchain/test/sendToMany.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const SendToMany = artifacts.require('SendToMany');
const CryptoErc20 = artifacts.require('CryptoErc20');
const ZERO_ADDR = '0x0000000000000000000000000000000000000000';
const receivers = [
'0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5',
'0x06b8c5883ec71bc3f4b332081519f23834c8706e',
'0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c'
]
contract('SendToMany', (accounts) => {
it('should exist', async() => {
const batcher = await SendToMany.deployed();
Expand All @@ -9,17 +14,18 @@ contract('SendToMany', (accounts) => {

it('should send ether', async() => {
const batcher = await SendToMany.deployed();
const receivers = accounts.slice(1);
const balances = await Promise.all(receivers.map( r => web3.eth.getBalance(r)));
const amounts = new Array(receivers.length).fill(1e18.toString());
const balanceBefore = await web3.eth.getBalance(accounts[0]);;
console.log('Token balance before', balanceBefore.toString());
const balanceBefore = await web3.eth.getBalance(accounts[0]);
const sum = (1e18*receivers.length).toString();
await batcher.sendMany(receivers, amounts, ZERO_ADDR, {value: sum});
const balanceAfter = await web3.eth.getBalance(accounts[0]);;
console.log('ETH balance after', balanceAfter.toString());
for(const receiver of receivers) {
const balanceAfter = await web3.eth.getBalance(accounts[0]);
assert(balanceBefore > balanceAfter, "Account zero should have lower balance");
for(let i = 0; i < receivers.length; i++) {
const receiver = receivers[i];
const balanceBefore = balances[i];
const balance = await web3.eth.getBalance(receiver);
console.log('ETH Balance', receiver, ':', balance.toString());
assert(balance > balanceBefore, "Balance should increase");
}
});

Expand All @@ -31,19 +37,97 @@ contract('SendToMany', (accounts) => {
it('should send tokens', async() => {
const batcher = await SendToMany.deployed();
const token = await CryptoErc20.deployed();
const receivers = accounts.slice(1);
const balances = await Promise.all(receivers.map( r => token.balanceOf(r)));
const amounts = new Array(receivers.length).fill(1e18.toString());
const sum = (1e18*receivers.length).toString();
const balanceBefore = await token.balanceOf(accounts[0]);
console.log('Token balance before', balanceBefore.toString());
await token.approve(batcher.address, sum);
await batcher.sendMany(receivers, amounts, token.address);
const balanceAfter = await token.balanceOf(accounts[0]);
console.log('Token balance after', balanceAfter.toString());
for(const receiver of receivers) {
for(let i = 0; i < receivers.length; i++) {
const receiver = receivers[i];
const balanceBefore = balances[i];
const balance = await token.balanceOf(receiver);
console.log('Token Balance', receiver, ':', balance.toString());
assert(balance > balanceBefore, "Balance should increase");
}
});


it('should send many different tokens', async() => {
const batcher = await SendToMany.deployed();
const token = await CryptoErc20.deployed();
const balances = await Promise.all(receivers.map( r => token.balanceOf(r)));
const amounts = new Array(receivers.length).fill(1e18.toString());
const sum = (1e18*receivers.length).toString();
await token.approve(batcher.address, sum);
const tokens = new Array(receivers.length).fill(token.address)

//send one ETH
tokens[0] = ZERO_ADDR;
balances[0] = await web3.eth.getBalance(receivers[0]);


await batcher.batchSend(receivers, amounts, tokens, {value: 1e18.toString()});
for(let i = 0; i < receivers.length; i++) {
const tokenAddress = tokens[i];
const receiver = receivers[i];
const balanceBefore = balances[i];
const ethBalance = await web3.eth.getBalance(receiver);
const tokenBalance = await token.balanceOf(receiver);
if(tokenAddress != ZERO_ADDR) {
assert(tokenBalance > balanceBefore, "Balance should increase");
} else {
assert(ethBalance > balanceBefore, "Balance should increase");
}
}
});


it('should validate a batch', async() => {
const batcher = await SendToMany.deployed();
const token = await CryptoErc20.deployed();
const balances = await Promise.all(receivers.map( r => token.balanceOf(r)));
const amounts = new Array(receivers.length).fill(1e18.toString());
const sum = (1e18*receivers.length).toString();
await token.approve(batcher.address, sum);
const tokens = new Array(receivers.length).fill(token.address)

//send one ETH
tokens[0] = ZERO_ADDR;
balances[0] = await web3.eth.getBalance(receivers[0]);

const isValid = await batcher.validateBatch.call(receivers, amounts, tokens, 1e18.toString(), accounts[0]);
assert(isValid, "Validate should return true");
console.log("Validate returned true");

try {
const isNotValid = await batcher.validateBatch.call(receivers, amounts, tokens, 1e17.toString(), accounts[0]);
assert(true == false, "Validate should have thrown");
} catch(e) {
console.log("Validate threw");
assert(e, "Validate threw");
}


try {
await token.approve(batcher.address, 0);
const isNotValid = await batcher.validateBatch.call(receivers, amounts, tokens, 1e18.toString(), accounts[0]);
assert(true == false, "Validate should have thrown");
} catch(e) {
console.log("Validate threw");
assert(e, "Validate threw");
}

let threw = false;
try {
await token.approve(batcher.address, sum);
await batcher.validateBatch(receivers, amounts, tokens, 1e18.toString(), accounts[0]);
await batcher.validateBatch(receivers, amounts, tokens, 1e18.toString(), accounts[0]);
await batcher.validateBatch.call(receivers, amounts, tokens, 1e18.toString(), accounts[0]);
} catch(e) {
threw = true;
console.log(e);
console.log("Validate (send) threw");
assert(e, "Validate threw");
}
assert(threw === false, "validateBatch should not throw");
});
});
7 changes: 7 additions & 0 deletions blockchain/truffle.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,18 @@ module.exports = {
// options below to some value.
//
development: {
host: 'localhost', // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
gas: 4700000, // Ropsten has a lower block limit than mainnet
network_id: '*' // Any network (default: none)
},
ci: {
host: 'parity', // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
gas: 4700000, // Ropsten has a lower block limit than mainnet
network_id: '*' // Any network (default: none)
},

},


Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"test:bats": "tests/cli_runner",
"lint": "./node_modules/.bin/eslint .",
"truffle:compile": "cd blockchain/ && ../node_modules/.bin/truffle compile",
"truffle:test": "cd blockchain/ && ../node_modules/.bin/truffle test",
"truffle:migrate": "cd blockchain/ && ../node_modules/.bin/truffle migrate"
"truffle:test": "cd blockchain/ && ../node_modules/.bin/truffle test --network ci",
"truffle:migrate": "cd blockchain/ && ../node_modules/.bin/truffle migrate --network ci"
},
"author": "Micah Riggan",
"license": "ISC",
Expand Down