diff --git a/contracts/ETHMixer.sol b/contracts/ETHMixer.sol index 107fe6c..02a7136 100644 --- a/contracts/ETHMixer.sol +++ b/contracts/ETHMixer.sol @@ -11,16 +11,16 @@ pragma solidity ^0.5.8; -import "./Mixer.sol"; +import "./GSNMixer.sol"; -contract ETHMixer is Mixer { +contract ETHMixer is GSNMixer { constructor( address _verifier, uint256 _mixDenomination, uint8 _merkleTreeHeight, uint256 _emptyElement, address payable _operator - ) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public { + ) GSNMixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public { } function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal { @@ -33,4 +33,21 @@ contract ETHMixer is Mixer { function _processDeposit() internal { require(msg.value == mixDenomination, "Please send `mixDenomination` ETH along with transaction"); } + + event Debug(uint actualCharge, bytes context, address recipient); + // this func is called by RelayerHub right after calling a target func + function postRelayedCall(bytes memory context, bool /*success*/, uint actualCharge, bytes32 /*preRetVal*/) public onlyHub { + IRelayHub relayHub = IRelayHub(getHubAddr()); + address payable recipient; + uint256 nullifierHash; + assembly { + recipient := mload(add(context, 32)) + nullifierHash := mload(add(context, 64)) + } + emit Debug(actualCharge, context, recipient); + + recipient.transfer(mixDenomination - actualCharge); + relayHub.depositFor.value(actualCharge)(address(this)); + emit Withdraw(recipient, nullifierHash, tx.origin, actualCharge); + } } diff --git a/contracts/GSNMixer.sol b/contracts/GSNMixer.sol new file mode 100644 index 0000000..52ddfb0 --- /dev/null +++ b/contracts/GSNMixer.sol @@ -0,0 +1,83 @@ +pragma solidity ^0.5.8; + +import "./Mixer.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/GSN/IRelayHub.sol"; + +contract GSNMixer is Mixer, GSNRecipient { + constructor( + address _verifier, + uint256 _mixDenomination, + uint8 _merkleTreeHeight, + uint256 _emptyElement, + address payable _operator + ) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public { + } + + modifier onlyHub() { + require(msg.sender == getHubAddr(), "only relay hub"); + _; + } + + function withdrawViaRelayer(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[5] memory input) public { + uint256 root = input[0]; + uint256 nullifierHash = input[1]; + 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; + + // we will process withdraw in postRelayedCall func + } + + // gsn related stuff + // this func is called by a Relayer via the RelayerHub before sending a tx + function acceptRelayedCall( + address /*relay*/, + address /*from*/, + bytes memory encodedFunction, + uint256 /*transactionFee*/, + uint256 /*gasPrice*/, + uint256 /*gasLimit*/, + uint256 /*nonce*/, + bytes memory /*approvalData*/, + uint256 /*maxPossibleCharge*/ + ) public view returns (uint256, bytes memory) { + // think of a withdraw dry-run + if (!compareBytesWithSelector(encodedFunction, this.withdrawViaRelayer.selector)) { + return (1, "Only withdrawViaRelayer can be called"); + } + bytes memory recipient; + assembly { + let dataPointer := add(encodedFunction, 32) + let nullifierPointer := mload(add(dataPointer, 292)) // 4 + (8 * 32) + (32) == selector + proof + root + let recipientPointer := mload(add(dataPointer, 324)) // 4 + (8 * 32) + (32) + (32) == selector + proof + root + nullifier + mstore(recipient, 64) // save array length + mstore(add(recipient, 32), recipientPointer) // save recipient address + mstore(add(recipient, 64), nullifierPointer) // save nullifier address + } + return (0, recipient); + } + + // this func is called by RelayerHub right before calling a target func + function preRelayedCall(bytes calldata /*context*/) external returns (bytes32) {} + function postRelayedCall(bytes memory context, bool /*success*/, uint actualCharge, bytes32 /*preRetVal*/) public {} + + function compareBytesWithSelector(bytes memory data, bytes4 sel) internal pure returns (bool) { + return data[0] == sel[0] + && data[1] == sel[1] + && data[2] == sel[2] + && data[3] == sel[3]; + } + + function withdrawFundsFromHub(uint256 amount, address payable dest) external { + require(msg.sender == operator, "unauthorized"); + IRelayHub(getHubAddr()).withdraw(amount, dest); + } + + function upgradeRelayHub(address newRelayHub) external { + require(msg.sender == operator, "unauthorized"); + _upgradeRelayHub(newRelayHub); + } +} diff --git a/migrations/4_deploy_eth_mixer.js b/migrations/4_deploy_eth_mixer.js index 1b535d3..3e075d5 100644 --- a/migrations/4_deploy_eth_mixer.js +++ b/migrations/4_deploy_eth_mixer.js @@ -13,6 +13,6 @@ module.exports = function(deployer, network, accounts) { await ETHMixer.link(MiMC, miMC.address) const mixer = await deployer.deploy(ETHMixer, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, accounts[0]) console.log('ETHMixer\'s address ', mixer.address) - // const tx = await mixer.initialize() + const tx = await mixer.initialize() }) } diff --git a/test/GSNsupport.test.js b/test/GSNsupport.test.js index 66c40dc..9b624b8 100644 --- a/test/GSNsupport.test.js +++ b/test/GSNsupport.test.js @@ -7,7 +7,7 @@ const fs = require('fs') const Web3 = require('web3') const { toBN, toHex, toChecksumAddress } = require('web3-utils') -const { takeSnapshot, revertSnapshot, traceTransaction } = require('../lib/ganacheHelper') +const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper') const { deployRelayHub, fundRecipient } = require('@openzeppelin/gsn-helpers') const { GSNDevProvider } = require('@openzeppelin/gsn-provider') const { ephemeral } = require('@openzeppelin/network') @@ -45,7 +45,7 @@ function getRandomReceiver() { return receiver } -contract.skip('GSN support', accounts => { +contract('GSN support', accounts => { let mixer let gsnMixer let relayHubAddress @@ -93,7 +93,7 @@ contract.skip('GSN support', accounts => { }) describe('#withdrawViaRelayer', () => { - it('should work', async () => { + it.only('should work', async () => { const gasPrice = toBN('20000000000') const relayerTxFee = 10 // 20% const deposit = generateDeposit() @@ -120,6 +120,8 @@ contract.skip('GSN support', accounts => { root, nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)), receiver, + relayer: operator, // this value wont be taken into account + fee: bigInt(1), // this value wont be taken into account // private nullifier: deposit.nullifier, @@ -158,8 +160,6 @@ contract.skip('GSN support', accounts => { value: 0 }) // console.log('tx', tx) - const debug = await traceTransaction(tx.transactionHash) - console.log('debug', debug.result.structLogs) const { events, gasUsed } = tx // console.log('events', events, gasUsed) const balanceMixerAfter = await web3.eth.getBalance(mixer.address)