diff --git a/contracts/ERC20Mixer.sol b/contracts/ERC20Mixer.sol index d16a50b..b36a581 100644 --- a/contracts/ERC20Mixer.sol +++ b/contracts/ERC20Mixer.sol @@ -15,65 +15,37 @@ import "./Mixer.sol"; contract ERC20Mixer is Mixer { address public token; - // mixed token amount - uint256 public tokenDenomination; // ether value to cover network fee (for relayer) and to have some ETH on a brand new address - uint256 public etherFeeDenomination; + uint256 public userEther; constructor( address _verifier, - uint256 _etherFeeDenomination, + uint256 _userEther, uint8 _merkleTreeHeight, uint256 _emptyElement, address payable _operator, address _token, - uint256 _tokenDenomination - ) Mixer(_verifier, _merkleTreeHeight, _emptyElement, _operator) public { + uint256 _mixDenomination + ) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public { token = _token; - tokenDenomination = _tokenDenomination; - etherFeeDenomination = _etherFeeDenomination; + userEther = _userEther; } - /** - @dev Deposit funds into the mixer. The caller must send ETH value equal to `etherFeeDenomination` of this mixer. - The caller also has to have at least `tokenDenomination` amount approved for the mixer. - @param commitment the note commitment, which is PedersenHash(nullifier + secret) - */ - function deposit(uint256 commitment) public payable { - require(msg.value == etherFeeDenomination, "Please send `etherFeeDenomination` ETH along with transaction"); - transferFrom(msg.sender, address(this), tokenDenomination); - _deposit(commitment); - - emit Deposit(commitment, next_index - 1, block.timestamp); + function _processDeposit() internal { + require(msg.value == userEther, "Please send `userEther` ETH along with transaction"); + safeErc20TransferFrom(msg.sender, address(this), mixDenomination); } - /** - @dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs - `input` array consists of: - - merkle root of all deposits in the mixer - - hash of unique deposit nullifier to prevent double spends - - the receiver of funds - - optional fee that goes to the transaction sender (usually a relay) - */ - function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public { - _withdraw(a, b, c, input); - address payable receiver = address(input[2]); - uint256 fee = input[3]; - uint256 nullifierHash = input[1]; + function _processWithdraw(address payable _receiver, uint256 _fee) internal { + _receiver.transfer(userEther); - require(fee < etherFeeDenomination, "Fee exceeds transfer value"); - receiver.transfer(etherFeeDenomination - fee); - - if (fee > 0) { - operator.transfer(fee); + safeErc20Transfer(_receiver, mixDenomination - _fee); + if (_fee > 0) { + safeErc20Transfer(operator, _fee); } - - transfer(receiver, tokenDenomination); - - emit Withdraw(receiver, nullifierHash, fee); } - function transferFrom(address from, address to, uint256 amount) internal { + function safeErc20TransferFrom(address from, address to, uint256 amount) internal { bool success; bytes memory data; bytes4 transferFromSelector = 0x23b872dd; @@ -92,7 +64,7 @@ contract ERC20Mixer is Mixer { } } - function transfer(address to, uint256 amount) internal { + function safeErc20Transfer(address to, uint256 amount) internal { bool success; bytes memory data; bytes4 transferSelector = 0xa9059cbb; diff --git a/contracts/ETHMixer.sol b/contracts/ETHMixer.sol index f194740..5da14b9 100644 --- a/contracts/ETHMixer.sol +++ b/contracts/ETHMixer.sol @@ -14,49 +14,23 @@ pragma solidity ^0.5.8; import "./Mixer.sol"; contract ETHMixer is Mixer { - uint256 public etherDenomination; - constructor( address _verifier, - uint256 _etherDenomination, + uint256 _mixDenomination, uint8 _merkleTreeHeight, uint256 _emptyElement, address payable _operator - ) Mixer(_verifier, _merkleTreeHeight, _emptyElement, _operator) public { - etherDenomination = _etherDenomination; + ) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public { } - /** - @dev Deposit funds into mixer. The caller must send value equal to `etherDenomination` of this mixer. - @param commitment the note commitment, which is PedersenHash(nullifier + secret) - */ - function deposit(uint256 commitment) public payable { - require(msg.value == etherDenomination, "Please send `etherDenomination` ETH along with transaction"); - _deposit(commitment); - - emit Deposit(commitment, next_index - 1, block.timestamp); - } - - /** - @dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs - `input` array consists of: - - merkle root of all deposits in the mixer - - hash of unique deposit nullifier to prevent double spends - - the receiver of funds - - optional fee that goes to the transaction sender (usually a relay) - */ - function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public { - _withdraw(a, b, c, input); - address payable receiver = address(input[2]); - uint256 fee = input[3]; - uint256 nullifierHash = input[1]; - - require(fee < etherDenomination, "Fee exceeds transfer value"); - receiver.transfer(etherDenomination - fee); - if (fee > 0) { - operator.transfer(fee); + function _processWithdraw(address payable _receiver, uint256 _fee) internal { + _receiver.transfer(mixDenomination - _fee); + if (_fee > 0) { + operator.transfer(_fee); } + } - emit Withdraw(receiver, nullifierHash, fee); + function _processDeposit() internal { + require(msg.value == mixDenomination, "Please send `mixDenomination` ETH along with transaction"); } } diff --git a/contracts/Mixer.sol b/contracts/Mixer.sol index 653167a..2014d84 100644 --- a/contracts/Mixer.sol +++ b/contracts/Mixer.sol @@ -26,6 +26,7 @@ contract Mixer is MerkleTreeWithHistory { // we store all commitments just to prevent accidental deposits with the same commitment mapping(uint256 => bool) public commitments; IVerifier public verifier; + uint256 public mixDenomination; event Deposit(uint256 indexed commitment, uint256 leafIndex, uint256 timestamp); event Withdraw(address to, uint256 nullifierHash, uint256 fee); @@ -39,31 +40,54 @@ contract Mixer is MerkleTreeWithHistory { */ constructor( address _verifier, + uint256 _mixDenomination, uint8 _merkleTreeHeight, uint256 _emptyElement, address payable _operator ) MerkleTreeWithHistory(_merkleTreeHeight, _emptyElement) public { verifier = IVerifier(_verifier); operator = _operator; + mixDenomination = _mixDenomination; } - - function _deposit(uint256 commitment) internal { + /** + @dev Deposit funds into mixer. The caller must send value equal to `mixDenomination` of this mixer. + @param commitment the note commitment, which is PedersenHash(nullifier + secret) + */ + /** + @dev Deposit funds into the mixer. The caller must send ETH value equal to `userEther` of this mixer. + The caller also has to have at least `mixDenomination` amount approved for the mixer. + @param commitment the note commitment, which is PedersenHash(nullifier + secret) + */ + function deposit(uint256 commitment) public payable { require(isDepositsEnabled, "deposits disabled"); require(!commitments[commitment], "The commitment has been submitted"); + _processDeposit(); _insert(commitment); commitments[commitment] = true; - } - function _withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) internal { + emit Deposit(commitment, next_index - 1, block.timestamp); + } + /** + @dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs + `input` array consists of: + - merkle root of all deposits in the mixer + - hash of unique deposit nullifier to prevent double spends + - the receiver of funds + - optional fee that goes to the transaction sender (usually a relay) + */ + function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public { uint256 root = input[0]; uint256 nullifierHash = input[1]; - + address payable receiver = address(input[2]); + uint256 fee = input[3]; + require(fee < mixDenomination, "Fee exceeds transfer value"); require(!nullifierHashes[nullifierHash], "The note has been already spent"); require(isKnownRoot(root), "Cannot find your merkle root"); // Make sure to use a recent one require(verifier.verifyProof(a, b, c, input), "Invalid withdraw proof"); - nullifierHashes[nullifierHash] = true; + _processWithdraw(receiver, fee); + emit Withdraw(receiver, nullifierHash, fee); } function toggleDeposits() external { @@ -79,4 +103,8 @@ contract Mixer is MerkleTreeWithHistory { function isSpent(uint256 nullifier) public view returns(bool) { return nullifierHashes[nullifier]; } + + function _processDeposit() internal {} + function _processWithdraw(address payable _receiver, uint256 _fee) internal {} + } diff --git a/test/ERC20Mixer.test.js b/test/ERC20Mixer.test.js index a0432cd..4a8fa79 100644 --- a/test/ERC20Mixer.test.js +++ b/test/ERC20Mixer.test.js @@ -122,7 +122,6 @@ contract('ERC20Mixer', accounts => { 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 @@ -149,7 +148,6 @@ contract('ERC20Mixer', accounts => { 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) @@ -161,12 +159,11 @@ contract('ERC20Mixer', accounts => { 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))) + balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(value))) 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)) - + ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore)) + balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN))) + ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value))) logs[0].event.should.be.equal('Withdraw') logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString())) @@ -258,9 +255,9 @@ contract('ERC20Mixer', accounts => { }) it.skip('should work with REAL DAI', async () => { // dont forget to specify your token in .env - // and sent `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1) + // and send `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1) // run ganache as - // ganache-cli --fork https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448@13146218 -d --keepAliveTimeout 20 + // npx 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) diff --git a/test/ETHMixer.test.js b/test/ETHMixer.test.js index 14501c9..32a3721 100644 --- a/test/ETHMixer.test.js +++ b/test/ETHMixer.test.js @@ -90,7 +90,7 @@ contract('ETHMixer', accounts => { describe('#constructor', () => { it('should initialize', async () => { - const etherDenomination = await mixer.etherDenomination() + const etherDenomination = await mixer.mixDenomination() etherDenomination.should.be.eq.BN(toBN(value)) }) })