test with real DAI and USDT

This commit is contained in:
Alexey 2019-08-30 13:06:17 +03:00
parent 5006006a20
commit b8c0c1898f
5 changed files with 242 additions and 10 deletions

View File

@ -5,3 +5,12 @@ TOKEN_AMOUNT=100000000000000000
EMPTY_ELEMENT=1337 EMPTY_ELEMENT=1337
PRIVATE_KEY= PRIVATE_KEY=
ERC20_TOKEN= ERC20_TOKEN=
# DAI mirror in Kovan
#ERC20_TOKEN=0xd2b1a6b34f4a68425e7c28b4db5a37be3b7a4947
# block when 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 has some DAI is 13146218
# USDT mirror in Kovan
#ERC20_TOKEN=0xf3e0d7bf58c5d455d31ef1c2d5375904df525105
#TOKEN_AMOUNT=1000000
# block when 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 has some USDT is 13147586

View File

@ -12,10 +12,9 @@
pragma solidity ^0.5.8; pragma solidity ^0.5.8;
import "./Mixer.sol"; import "./Mixer.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ERC20Mixer is Mixer { contract ERC20Mixer is Mixer {
IERC20 public token; address public token;
// mixed token amount // mixed token amount
uint256 public tokenDenomination; uint256 public tokenDenomination;
// ether value to cover network fee (for relayer) and to have some ETH on a brand new address // ether value to cover network fee (for relayer) and to have some ETH on a brand new address
@ -27,7 +26,7 @@ contract ERC20Mixer is Mixer {
uint8 _merkleTreeHeight, uint8 _merkleTreeHeight,
uint256 _emptyElement, uint256 _emptyElement,
address payable _operator, address payable _operator,
IERC20 _token, address _token,
uint256 _tokenDenomination uint256 _tokenDenomination
) Mixer(_verifier, _merkleTreeHeight, _emptyElement, _operator) public { ) Mixer(_verifier, _merkleTreeHeight, _emptyElement, _operator) public {
token = _token; token = _token;
@ -42,7 +41,7 @@ contract ERC20Mixer is Mixer {
*/ */
function deposit(uint256 commitment) public payable { function deposit(uint256 commitment) public payable {
require(msg.value == etherFeeDenomination, "Please send `etherFeeDenomination` ETH along with transaction"); require(msg.value == etherFeeDenomination, "Please send `etherFeeDenomination` ETH along with transaction");
require(token.transferFrom(msg.sender, address(this), tokenDenomination), "Approve before using"); transferFrom(msg.sender, address(this), tokenDenomination);
_deposit(commitment); _deposit(commitment);
emit Deposit(commitment, next_index - 1, block.timestamp); emit Deposit(commitment, next_index - 1, block.timestamp);
@ -69,8 +68,46 @@ contract ERC20Mixer is Mixer {
operator.transfer(fee); operator.transfer(fee);
} }
token.transfer(receiver, tokenDenomination); transfer(receiver, tokenDenomination);
emit Withdraw(receiver, nullifierHash, fee); emit Withdraw(receiver, nullifierHash, fee);
} }
function transferFrom(address from, address to, uint256 amount) internal {
bool success;
bytes memory data;
bytes4 transferFromSelector = 0x23b872dd;
(success, data) = token.call(
abi.encodeWithSelector(
transferFromSelector,
from, to, amount
)
);
require(success, "not enough allowed tokens");
if (data.length > 0) {
assembly {
success := mload(add(data, 0x20))
}
require(success, "not enough allowed tokens");
}
}
function transfer(address to, uint256 amount) internal {
bool success;
bytes memory data;
bytes4 transferSelector = 0xa9059cbb;
(success, data) = token.call(
abi.encodeWithSelector(
transferSelector,
to, amount
)
);
require(success, "not enough tokens");
if (data.length > 0) {
assembly {
success := mload(add(data, 0x20))
}
require(success, "not enough tokens");
}
}
} }

18
contracts/Mocks/IUSDT.sol Normal file
View File

@ -0,0 +1,18 @@
contract ERC20Basic {
uint public _totalSupply;
function totalSupply() public view returns (uint);
function balanceOf(address who) public view returns (uint);
function transfer(address to, uint value) public;
event Transfer(address indexed from, address indexed to, uint value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract IUSDT is ERC20Basic {
function allowance(address owner, address spender) public view returns (uint);
function transferFrom(address from, address to, uint value) public;
function approve(address spender, uint value) public;
event Approval(address indexed owner, address indexed spender, uint value);
}

View File

@ -13,7 +13,7 @@ module.exports = function(deployer, network, accounts) {
const miMC = await MiMC.deployed() const miMC = await MiMC.deployed()
await ERC20Mixer.link(MiMC, miMC.address) await ERC20Mixer.link(MiMC, miMC.address)
let token = ERC20_TOKEN let token = ERC20_TOKEN
if(deployer.network !== 'mainnet') { if(token === '') {
const tokenInstance = await deployer.deploy(ERC20Mock) const tokenInstance = await deployer.deploy(ERC20Mock)
token = tokenInstance.address token = tokenInstance.address
} }

View File

@ -10,7 +10,8 @@ const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
const Mixer = artifacts.require('./ERC20Mixer.sol') const Mixer = artifacts.require('./ERC20Mixer.sol')
const Token = artifacts.require('./ERC20Mock.sol') const Token = artifacts.require('./ERC20Mock.sol')
const { ETH_AMOUNT, TOKEN_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env const USDTToken = artifacts.require('./IUSDT.sol')
const { ETH_AMOUNT, TOKEN_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, ERC20_TOKEN } = process.env
const websnarkUtils = require('websnark/src/utils') const websnarkUtils = require('websnark/src/utils')
const buildGroth16 = require('websnark/src/groth16') const buildGroth16 = require('websnark/src/groth16')
@ -45,11 +46,12 @@ function getRandomReceiver() {
contract('ERC20Mixer', accounts => { contract('ERC20Mixer', accounts => {
let mixer let mixer
let token let token
let usdtToken
const sender = accounts[0] const sender = accounts[0]
const operator = accounts[0] const operator = accounts[0]
const levels = MERKLE_TREE_HEIGHT || 16 const levels = MERKLE_TREE_HEIGHT || 16
const zeroValue = EMPTY_ELEMENT || 1337 const zeroValue = EMPTY_ELEMENT || 1337
const tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether let tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
let snapshotId let snapshotId
let prefix = 'test' let prefix = 'test'
@ -69,8 +71,13 @@ contract('ERC20Mixer', accounts => {
prefix, prefix,
) )
mixer = await Mixer.deployed() mixer = await Mixer.deployed()
if (ERC20_TOKEN) {
token = await Token.at(ERC20_TOKEN)
usdtToken = await USDTToken.at(ERC20_TOKEN)
} else {
token = await Token.deployed() token = await Token.deployed()
await token.mint(sender, tokenDenomination) await token.mint(sender, tokenDenomination)
}
snapshotId = await takeSnapshot() snapshotId = await takeSnapshot()
groth16 = await buildGroth16() groth16 = await buildGroth16()
circuit = require('../build/circuits/withdraw.json') circuit = require('../build/circuits/withdraw.json')
@ -161,6 +168,167 @@ contract('ERC20Mixer', accounts => {
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)).sub(feeBN)) ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)).sub(feeBN))
logs[0].event.should.be.equal('Withdraw')
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
logs[0].args.fee.should.be.eq.BN(feeBN)
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
isSpent.should.be.equal(true)
})
it.skip('should work with REAL USDT', async () => {
// dont forget to specify your token in .env
// USDT decimals is 6, so TOKEN_AMOUNT=1000000
// and sent `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1)
// run ganache as
// ganache-cli --fork https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448@13147586 -d --keepAliveTimeout 20
const deposit = generateDeposit()
const user = accounts[4]
const userBal = await usdtToken.balanceOf(user)
console.log('userBal', userBal.toString())
const senderBal = await usdtToken.balanceOf(sender)
console.log('senderBal', senderBal.toString())
await tree.insert(deposit.commitment)
await usdtToken.transfer(user, tokenDenomination, { from: sender })
console.log('transfer done')
const balanceUserBefore = await usdtToken.balanceOf(user)
console.log('balanceUserBefore', balanceUserBefore.toString())
await usdtToken.approve(mixer.address, tokenDenomination, { from: user })
console.log('approve done')
const allowanceUser = await usdtToken.allowance(user, mixer.address)
console.log('allowanceUser', allowanceUser.toString())
await mixer.deposit(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
console.log('deposit done')
const balanceUserAfter = await usdtToken.balanceOf(user)
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
const { root, path_elements, path_index } = await tree.path(0)
// Circuit input
const input = stringifyBigInts({
// public
root,
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
receiver,
fee,
// private
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements: path_elements,
pathIndex: path_index,
})
const proof = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { pi_a, pi_b, pi_c, publicSignals } = websnarkUtils.toSolidityInput(proof)
const balanceMixerBefore = await usdtToken.balanceOf(mixer.address)
const balanceRelayerBefore = await usdtToken.balanceOf(relayer)
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
const balanceRecieverBefore = await usdtToken.balanceOf(toHex(receiver.toString()))
const ethBalanceRecieverBefore = await web3.eth.getBalance(toHex(receiver.toString()))
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
isSpent.should.be.equal(false)
// Uncomment to measure gas usage
// gas = await mixer.withdraw.estimateGas(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
// console.log('withdraw gas:', gas)
const { logs } = await mixer.withdraw(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
const balanceMixerAfter = await usdtToken.balanceOf(mixer.address)
const balanceRelayerAfter = await usdtToken.balanceOf(relayer)
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
const balanceRecieverAfter = await usdtToken.balanceOf(toHex(receiver.toString()))
const ethBalanceRecieverAfter = await web3.eth.getBalance(toHex(receiver.toString()))
const feeBN = toBN(fee.toString())
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)).sub(feeBN))
logs[0].event.should.be.equal('Withdraw')
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
logs[0].args.fee.should.be.eq.BN(feeBN)
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
isSpent.should.be.equal(true)
})
it.skip('should work with REAL DAI', async () => {
// dont forget to specify your token in .env
// and sent `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1)
// run ganache as
// ganache-cli --fork https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448@13146218 -d --keepAliveTimeout 20
const deposit = generateDeposit()
const user = accounts[4]
const userBal = await token.balanceOf(user)
console.log('userBal', userBal.toString())
const senderBal = await token.balanceOf(sender)
console.log('senderBal', senderBal.toString())
await tree.insert(deposit.commitment)
await token.transfer(user, tokenDenomination, { from: sender })
console.log('transfer done')
const balanceUserBefore = await token.balanceOf(user)
console.log('balanceUserBefore', balanceUserBefore.toString())
await token.approve(mixer.address, tokenDenomination, { from: user })
console.log('approve done')
await mixer.deposit(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
console.log('deposit done')
const balanceUserAfter = await token.balanceOf(user)
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
const { root, path_elements, path_index } = await tree.path(0)
// Circuit input
const input = stringifyBigInts({
// public
root,
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
receiver,
fee,
// private
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements: path_elements,
pathIndex: path_index,
})
const proof = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { pi_a, pi_b, pi_c, publicSignals } = websnarkUtils.toSolidityInput(proof)
const balanceMixerBefore = await token.balanceOf(mixer.address)
const balanceRelayerBefore = await token.balanceOf(relayer)
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
const balanceRecieverBefore = await token.balanceOf(toHex(receiver.toString()))
const ethBalanceRecieverBefore = await web3.eth.getBalance(toHex(receiver.toString()))
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
isSpent.should.be.equal(false)
// Uncomment to measure gas usage
// gas = await mixer.withdraw.estimateGas(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
// console.log('withdraw gas:', gas)
const { logs } = await mixer.withdraw(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
console.log('withdraw done')
const balanceMixerAfter = await token.balanceOf(mixer.address)
const balanceRelayerAfter = await token.balanceOf(relayer)
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
const balanceRecieverAfter = await token.balanceOf(toHex(receiver.toString()))
const ethBalanceRecieverAfter = await web3.eth.getBalance(toHex(receiver.toString()))
const feeBN = toBN(fee.toString())
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)).sub(feeBN))
logs[0].event.should.be.equal('Withdraw') logs[0].event.should.be.equal('Withdraw')
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString())) logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
logs[0].args.fee.should.be.eq.BN(feeBN) logs[0].args.fee.should.be.eq.BN(feeBN)