From bd4500d7ff94561e6808eb81e74aa50d6553c002 Mon Sep 17 00:00:00 2001 From: Drygin Date: Sat, 22 Jan 2022 03:21:34 +0300 Subject: [PATCH 01/20] add L1 fee from user --- .gitignore | 1 + .solhint.json | 3 +- contracts/Mocks/WETH.sol | 23 +++++ contracts/TornadoPool.sol | 3 +- .../bridge/{L1Helper.sol => L1Unwrapper.sol} | 31 +++++- contracts/libraries/Bytes.sol | 36 +++++++ package.json | 2 +- src/index.js | 5 +- src/utils.js | 4 +- test/full.test.js | 95 ++++++++++++++++++- test/tree.test.js | 2 +- test/utils.js | 2 +- yarn.lock | 10 +- 13 files changed, 199 insertions(+), 18 deletions(-) create mode 100644 contracts/Mocks/WETH.sol rename contracts/bridge/{L1Helper.sol => L1Unwrapper.sol} (67%) create mode 100644 contracts/libraries/Bytes.sol diff --git a/.gitignore b/.gitignore index b405677..050401f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build cache artifacts src/types +.vscode diff --git a/.solhint.json b/.solhint.json index b313abe..d516078 100644 --- a/.solhint.json +++ b/.solhint.json @@ -7,7 +7,8 @@ "printWidth": 110 } ], - "quotes": ["error", "double"] + "quotes": ["error", "double"], + "compiler-version": ["error", "^0.7.0"] }, "plugins": ["prettier"] } diff --git a/contracts/Mocks/WETH.sol b/contracts/Mocks/WETH.sol new file mode 100644 index 0000000..981eca4 --- /dev/null +++ b/contracts/Mocks/WETH.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract WETH is ERC20 { + + constructor( + string memory name, + string memory ticker + ) ERC20(name, ticker) {} + + function deposit() external payable { + _mint(msg.sender, msg.value); + } + + function withdraw(uint256 value) external { + _burn(msg.sender, value); + (bool success, ) = msg.sender.call{value: value}(""); + require(success, "WETH: ETH transfer failed"); + } +} diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index bdf379d..3f25299 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -46,6 +46,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, bytes encryptedOutput1; bytes encryptedOutput2; bool isL1Withdrawal; + uint256 l1Fee; } struct Proof { @@ -275,7 +276,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, if (_extData.extAmount < 0) { require(_extData.recipient != address(0), "Can't withdraw to zero address"); if (_extData.isL1Withdrawal) { - token.transferAndCall(omniBridge, uint256(-_extData.extAmount), abi.encodePacked(l1Unwrapper, _extData.recipient)); + token.transferAndCall(omniBridge, uint256(-_extData.extAmount), abi.encodePacked(l1Unwrapper, _extData.recipient, _extData.l1Fee)); } else { token.transfer(_extData.recipient, uint256(-_extData.extAmount)); } diff --git a/contracts/bridge/L1Helper.sol b/contracts/bridge/L1Unwrapper.sol similarity index 67% rename from contracts/bridge/L1Helper.sol rename to contracts/bridge/L1Unwrapper.sol index 4526c93..5b6f703 100644 --- a/contracts/bridge/L1Helper.sol +++ b/contracts/bridge/L1Unwrapper.sol @@ -14,9 +14,13 @@ pragma solidity ^0.7.0; pragma abicoder v2; import "omnibridge/contracts/helpers/WETHOmnibridgeRouter.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import { BytesHelper } from "../libraries/Bytes.sol"; /// @dev Extension for original WETHOmnibridgeRouter that stores TornadoPool account registrations. -contract L1Helper is WETHOmnibridgeRouter { +contract L1Unwrapper is WETHOmnibridgeRouter { + using SafeMath for uint256; + event PublicKey(address indexed owner, bytes key); struct Account { @@ -61,4 +65,29 @@ contract L1Helper is WETHOmnibridgeRouter { function _register(Account memory _account) internal { emit PublicKey(_account.owner, _account.publicKey); } + + /** + * @dev Bridged callback function used for unwrapping received tokens. + * Can only be called by the associated Omnibridge contract. + * @param _token bridged token contract address, should be WETH. + * @param _value amount of bridged/received tokens. + * @param _data extra data passed alongside with relayTokensAndCall on the other side of the bridge. + * Should contain coins receiver address and L1 executer fee amount. + */ + function onTokenBridged( + address _token, + uint256 _value, + bytes memory _data + ) override external { + require(_token == address(WETH)); + require(msg.sender == address(bridge)); + require(_data.length == 52); + + WETH.withdraw(_value); + + uint256 l1Fee = BytesHelper.sliceToUint(_data, 20); + + AddressHelper.safeSendValue(payable(BytesHelper.bytesToAddress(_data)), _value.sub(l1Fee)); + AddressHelper.safeSendValue(payable(tx.origin), l1Fee); + } } diff --git a/contracts/libraries/Bytes.sol b/contracts/libraries/Bytes.sol new file mode 100644 index 0000000..fcfef76 --- /dev/null +++ b/contracts/libraries/Bytes.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title Bytes + * @dev Helper methods to transform bytes to other solidity types. + */ +library BytesHelper { + /** + * @dev Truncate bytes array if its size is more than 20 bytes. + * NOTE: This function does not perform any checks on the received parameter. + * Make sure that the _bytes argument has a correct length, not less than 20 bytes. + * A case when _bytes has length less than 20 will lead to the undefined behaviour, + * since assembly will read data from memory that is not related to the _bytes argument. + * @param _bytes to be converted to address type + * @return addr address included in the firsts 20 bytes of the bytes array in parameter. + */ + function bytesToAddress(bytes memory _bytes) internal pure returns (address addr) { + assembly { + addr := mload(add(_bytes, 20)) + } + } + + /** + * @param _bytes it's 32 length slice to be converted to uint type + * @param _start start index of slice + * @return x uint included in the 32 length slice of the bytes array in parameter. + */ + function sliceToUint(bytes memory _bytes, uint _start) internal pure returns (uint x) + { + require(_bytes.length >= _start + 32, "slicing out of range"); + assembly { + x := mload(add(_bytes, add(0x20, _start))) + } + } +} diff --git a/package.json b/package.json index e56513c..49140c7 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "fixed-merkle-tree": "^0.5.1", "hardhat": "^2.3.0", "mocha": "^9.1.0", - "omnibridge": "git+https://github.com/peppersec/omnibridge.git#aa3a970c29752a4da5f3fc7ccf0733783c1acf0b", + "omnibridge": "git+https://github.com/peppersec/omnibridge.git#1f0baaa34bbfdc8f2ddb37c0554ad7d964a96803", "prompt-sync": "^4.2.0", "snarkjs": "git+https://github.com/tornadocash/snarkjs.git#f37f146948f3b28086493e71512006b030588fc2", "tmp-promise": "^3.0.2", diff --git a/src/index.js b/src/index.js index d99f991..4166969 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,7 @@ async function buildMerkleTree({ tornadoPool }) { return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 }) } -async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer, isL1Withdrawal }) { +async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer, isL1Withdrawal, l1Fee }) { inputs = shuffle(inputs) outputs = shuffle(outputs) @@ -45,6 +45,7 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela encryptedOutput1: outputs[0].encrypt(), encryptedOutput2: outputs[1].encrypt(), isL1Withdrawal, + l1Fee, } const extDataHash = getExtDataHash(extData) @@ -94,6 +95,7 @@ async function prepareTransaction({ recipient = 0, relayer = 0, isL1Withdrawal = false, + l1Fee = 0, }) { if (inputs.length > 16 || outputs.length > 2) { throw new Error('Incorrect inputs/outputs count') @@ -118,6 +120,7 @@ async function prepareTransaction({ recipient, relayer, isL1Withdrawal, + l1Fee, }) return { diff --git a/src/utils.js b/src/utils.js index 422622f..2ce811b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -22,12 +22,13 @@ function getExtDataHash({ encryptedOutput1, encryptedOutput2, isL1Withdrawal, + l1Fee, }) { const abi = new ethers.utils.AbiCoder() const encodedData = abi.encode( [ - 'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal)', + 'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal,uint256 l1Fee)', ], [ { @@ -38,6 +39,7 @@ function getExtDataHash({ encryptedOutput1: encryptedOutput1, encryptedOutput2: encryptedOutput2, isL1Withdrawal: isL1Withdrawal, + l1Fee: l1Fee, }, ], ) diff --git a/test/full.test.js b/test/full.test.js index e476e82..91d236e 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -26,7 +26,7 @@ describe('TornadoPool', function () { async function fixture() { require('../scripts/compileHasher') - const [sender, gov, l1Unwrapper, multisig] = await ethers.getSigners() + const [sender, gov, multisig] = await ethers.getSigners() const verifier2 = await deploy('Verifier2') const verifier16 = await deploy('Verifier16') const hasher = await deploy('Hasher') @@ -34,8 +34,12 @@ describe('TornadoPool', function () { const token = await deploy('PermittableToken', 'Wrapped ETH', 'WETH', 18, l1ChainId) await token.mint(sender.address, utils.parseEther('10000')) + const l1Token = await deploy('WETH', 'Wrapped ETH', 'WETH') + await l1Token.deposit({value: utils.parseEther('3')}) + const amb = await deploy('MockAMB', gov.address, l1ChainId) const omniBridge = await deploy('MockOmniBridge', amb.address) + const l1Unwrapper = await deploy('L1Unwrapper', amb.address, l1Token.address, gov.address) /** @type {TornadoPool} */ const tornadoPoolImpl = await deploy( @@ -69,7 +73,7 @@ describe('TornadoPool', function () { await token.approve(tornadoPool.address, utils.parseEther('10000')) - return { tornadoPool, token, proxy, omniBridge, amb, gov, multisig } + return { tornadoPool, token, proxy, omniBridge, amb, gov, multisig, l1Unwrapper, sender, l1Token } } describe('Upgradeability tests', () => { @@ -271,6 +275,93 @@ describe('TornadoPool', function () { expect(omniBridgeBalance).to.be.equal(aliceWithdrawAmount) }) + it('should withdraw with L1 fee', async function () { + const { tornadoPool, token, omniBridge, amb, l1Unwrapper, sender, l1Token } = await loadFixture(fixture) + const aliceKeypair = new Keypair() // contains private and public keys + + // regular L1 deposit ------------------------------------------- + const aliceDepositAmount = utils.parseEther('0.07') + const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount, keypair: aliceKeypair }) + const { args, extData } = await prepareTransaction({ + tornadoPool, + outputs: [aliceDepositUtxo], + }) + + let onTokenBridgedData = encodeDataForBridge({ + proof: args, + extData, + }) + + let onTokenBridgedTx = await tornadoPool.populateTransaction.onTokenBridged( + token.address, + aliceDepositUtxo.amount, + onTokenBridgedData, + ) + // emulating bridge. first it sends tokens to omnibridge mock then it sends to the pool + await token.transfer(omniBridge.address, aliceDepositAmount) + let transferTx = await token.populateTransaction.transfer(tornadoPool.address, aliceDepositAmount) + + await omniBridge.execute([ + { who: token.address, callData: transferTx.data }, // send tokens to pool + { who: tornadoPool.address, callData: onTokenBridgedTx.data }, // call onTokenBridgedTx + ]) + + // withdrawal with L1 fee --------------------------------------- + // withdraws a part of his funds from the shielded pool + const aliceWithdrawAmount = utils.parseEther('0.06') + const l1Fee = utils.parseEther('0.01') + // sum of desired withdraw amount and L1 fee are stored in extAmount + const extAmount = aliceWithdrawAmount.add(l1Fee) + const recipient = '0xDeaD00000000000000000000000000000000BEEf' + const aliceChangeUtxo = new Utxo({ + amount: aliceDepositAmount.sub(extAmount), + keypair: aliceKeypair, + }) + await transaction({ + tornadoPool, + inputs: [aliceDepositUtxo], + outputs: [aliceChangeUtxo], + recipient: recipient, + isL1Withdrawal: true, + l1Fee: l1Fee, + }) + + const filter = omniBridge.filters.OnTokenTransfer() + const fromBlock = await ethers.provider.getBlock() + const events = await omniBridge.queryFilter(filter, fromBlock.number) + onTokenBridgedData = events[0].args.data + const hexL1Fee = '0x' + events[0].args.data.toString().slice(42) + expect(ethers.BigNumber.from(hexL1Fee)).to.be.equal(l1Fee) + + const recipientBalance = await token.balanceOf(recipient) + expect(recipientBalance).to.be.equal(0) + const omniBridgeBalance = await token.balanceOf(omniBridge.address) + expect(omniBridgeBalance).to.be.equal(extAmount) + + // L1 transactions: + onTokenBridgedTx = await l1Unwrapper.populateTransaction.onTokenBridged( + l1Token.address, + extAmount, + onTokenBridgedData, + ) + // emulating bridge. first it sends tokens to amb mock then it sends to the recipient + await l1Token.transfer(amb.address, extAmount) + transferTx = await l1Token.populateTransaction.transfer(l1Unwrapper.address, extAmount) + + const senderBalanceBefore = await ethers.provider.getBalance(sender.address) + + let tx = await amb.execute([ + { who: l1Token.address, callData: transferTx.data }, // send tokens to L1Unwrapper + { who: l1Unwrapper.address, callData: onTokenBridgedTx.data }, // call onTokenBridged on L1Unwrapper + ]) + + let receipt = await tx.wait() + let txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice) + const senderBalanceAfter = await ethers.provider.getBalance(sender.address) + expect(senderBalanceAfter).to.be.equal(senderBalanceBefore.sub(txFee).add(l1Fee)) + expect(await ethers.provider.getBalance(recipient)).to.be.equal(aliceWithdrawAmount) + }) + it('should transfer funds to multisig in case of L1 deposit fail', async function () { const { tornadoPool, token, omniBridge, multisig } = await loadFixture(fixture) const aliceKeypair = new Keypair() // contains private and public keys diff --git a/test/tree.test.js b/test/tree.test.js index d77263d..7e0bb79 100644 --- a/test/tree.test.js +++ b/test/tree.test.js @@ -77,7 +77,7 @@ describe('MerkleTreeWithHistory', function () { merkleTreeWithHistory.insert(toFixedHex(678), toFixedHex(876)) tree.bulkInsert([678, 876]) - expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot()) + expect(tree.root()._hex).to.be.be.equal(await merkleTreeWithHistory.getLastRoot()) }) it('hasher gas', async () => { diff --git a/test/utils.js b/test/utils.js index 0d7d194..aa0fa36 100644 --- a/test/utils.js +++ b/test/utils.js @@ -6,7 +6,7 @@ function encodeDataForBridge({ proof, extData }) { return abi.encode( [ 'tuple(bytes proof,bytes32 root,bytes32[] inputNullifiers,bytes32[2] outputCommitments,uint256 publicAmount,bytes32 extDataHash)', - 'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal)', + 'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal,uint256 l1Fee)', ], [proof, extData], ) diff --git a/yarn.lock b/yarn.lock index 92d6463..2517f49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -736,11 +736,6 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3" integrity sha512-mDlBS17ymb2wpaLcrqRYdnBAmP1EwqhOXMvqWk2c5Q1N1pm5TkiCtXM9Xzznh4bYsQBq0aIWEkFFE2+iLSN1Tw== -"@openzeppelin/contracts@3.2.2-solc-0.7": - version "3.2.2-solc-0.7" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.2.2-solc-0.7.tgz#8ab169da64438d59f47ca285f1a10efe2f9ba19e" - integrity sha512-vFV53E4pvfsAEjzL9Um2VX9MEuXyq7Hyd9JjnP77AGsrEPxkJaYS06zZIVyhAt3rXTM6QGdW0C282Zv7fM93AA== - "@openzeppelin@git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f": version "3.4.1-solc-0.7-2" resolved "git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f" @@ -6918,11 +6913,10 @@ oboe@2.1.5: dependencies: http-https "^1.0.0" -"omnibridge@git+https://github.com/peppersec/omnibridge.git#aa3a970c29752a4da5f3fc7ccf0733783c1acf0b": +"omnibridge@git+https://github.com/peppersec/omnibridge.git#1f0baaa34bbfdc8f2ddb37c0554ad7d964a96803": version "1.1.0" - resolved "git+https://github.com/peppersec/omnibridge.git#aa3a970c29752a4da5f3fc7ccf0733783c1acf0b" + resolved "git+https://github.com/peppersec/omnibridge.git#1f0baaa34bbfdc8f2ddb37c0554ad7d964a96803" dependencies: - "@openzeppelin/contracts" "3.2.2-solc-0.7" axios "^0.21.0" bignumber.js "^9.0.1" dotenv "^8.2.0" From 1f1964417a7376899aa2de440021926942f267a3 Mon Sep 17 00:00:00 2001 From: Drygin Date: Sat, 22 Jan 2022 03:23:33 +0300 Subject: [PATCH 02/20] linter fixes --- contracts/Mocks/WETH.sol | 10 ++----- contracts/TornadoPool.sol | 6 +++- contracts/bridge/L1Unwrapper.sol | 18 ++++++------ contracts/libraries/Bytes.sol | 47 ++++++++++++++++---------------- src/index.js | 12 +++++++- test/full.test.js | 2 +- 6 files changed, 52 insertions(+), 43 deletions(-) diff --git a/contracts/Mocks/WETH.sol b/contracts/Mocks/WETH.sol index 981eca4..21231ef 100644 --- a/contracts/Mocks/WETH.sol +++ b/contracts/Mocks/WETH.sol @@ -5,19 +5,15 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract WETH is ERC20 { - - constructor( - string memory name, - string memory ticker - ) ERC20(name, ticker) {} + constructor(string memory name, string memory ticker) ERC20(name, ticker) {} function deposit() external payable { _mint(msg.sender, msg.value); } function withdraw(uint256 value) external { - _burn(msg.sender, value); - (bool success, ) = msg.sender.call{value: value}(""); + _burn(msg.sender, value); + (bool success, ) = msg.sender.call{ value: value }(""); require(success, "WETH: ETH transfer failed"); } } diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index 3f25299..ef2d02d 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -276,7 +276,11 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, if (_extData.extAmount < 0) { require(_extData.recipient != address(0), "Can't withdraw to zero address"); if (_extData.isL1Withdrawal) { - token.transferAndCall(omniBridge, uint256(-_extData.extAmount), abi.encodePacked(l1Unwrapper, _extData.recipient, _extData.l1Fee)); + token.transferAndCall( + omniBridge, + uint256(-_extData.extAmount), + abi.encodePacked(l1Unwrapper, _extData.recipient, _extData.l1Fee) + ); } else { token.transfer(_extData.recipient, uint256(-_extData.extAmount)); } diff --git a/contracts/bridge/L1Unwrapper.sol b/contracts/bridge/L1Unwrapper.sol index 5b6f703..e438cd6 100644 --- a/contracts/bridge/L1Unwrapper.sol +++ b/contracts/bridge/L1Unwrapper.sol @@ -67,22 +67,22 @@ contract L1Unwrapper is WETHOmnibridgeRouter { } /** - * @dev Bridged callback function used for unwrapping received tokens. - * Can only be called by the associated Omnibridge contract. - * @param _token bridged token contract address, should be WETH. - * @param _value amount of bridged/received tokens. - * @param _data extra data passed alongside with relayTokensAndCall on the other side of the bridge. - * Should contain coins receiver address and L1 executer fee amount. - */ + * @dev Bridged callback function used for unwrapping received tokens. + * Can only be called by the associated Omnibridge contract. + * @param _token bridged token contract address, should be WETH. + * @param _value amount of bridged/received tokens. + * @param _data extra data passed alongside with relayTokensAndCall on the other side of the bridge. + * Should contain coins receiver address and L1 executer fee amount. + */ function onTokenBridged( address _token, uint256 _value, bytes memory _data - ) override external { + ) external override { require(_token == address(WETH)); require(msg.sender == address(bridge)); require(_data.length == 52); - + WETH.withdraw(_value); uint256 l1Fee = BytesHelper.sliceToUint(_data, 20); diff --git a/contracts/libraries/Bytes.sol b/contracts/libraries/Bytes.sol index fcfef76..08dc98f 100644 --- a/contracts/libraries/Bytes.sol +++ b/contracts/libraries/Bytes.sol @@ -6,31 +6,30 @@ pragma solidity ^0.7.0; * @dev Helper methods to transform bytes to other solidity types. */ library BytesHelper { - /** - * @dev Truncate bytes array if its size is more than 20 bytes. - * NOTE: This function does not perform any checks on the received parameter. - * Make sure that the _bytes argument has a correct length, not less than 20 bytes. - * A case when _bytes has length less than 20 will lead to the undefined behaviour, - * since assembly will read data from memory that is not related to the _bytes argument. - * @param _bytes to be converted to address type - * @return addr address included in the firsts 20 bytes of the bytes array in parameter. - */ - function bytesToAddress(bytes memory _bytes) internal pure returns (address addr) { - assembly { - addr := mload(add(_bytes, 20)) - } + /** + * @dev Truncate bytes array if its size is more than 20 bytes. + * NOTE: This function does not perform any checks on the received parameter. + * Make sure that the _bytes argument has a correct length, not less than 20 bytes. + * A case when _bytes has length less than 20 will lead to the undefined behaviour, + * since assembly will read data from memory that is not related to the _bytes argument. + * @param _bytes to be converted to address type + * @return addr address included in the firsts 20 bytes of the bytes array in parameter. + */ + function bytesToAddress(bytes memory _bytes) internal pure returns (address addr) { + assembly { + addr := mload(add(_bytes, 20)) } + } - /** - * @param _bytes it's 32 length slice to be converted to uint type - * @param _start start index of slice - * @return x uint included in the 32 length slice of the bytes array in parameter. - */ - function sliceToUint(bytes memory _bytes, uint _start) internal pure returns (uint x) - { - require(_bytes.length >= _start + 32, "slicing out of range"); - assembly { - x := mload(add(_bytes, add(0x20, _start))) - } + /** + * @param _bytes it's 32 length slice to be converted to uint type + * @param _start start index of slice + * @return x uint included in the 32 length slice of the bytes array in parameter. + */ + function sliceToUint(bytes memory _bytes, uint256 _start) internal pure returns (uint256 x) { + require(_bytes.length >= _start + 32, "slicing out of range"); + assembly { + x := mload(add(_bytes, add(0x20, _start))) } + } } diff --git a/src/index.js b/src/index.js index 4166969..3aec910 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,17 @@ async function buildMerkleTree({ tornadoPool }) { return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 }) } -async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer, isL1Withdrawal, l1Fee }) { +async function getProof({ + inputs, + outputs, + tree, + extAmount, + fee, + recipient, + relayer, + isL1Withdrawal, + l1Fee, +}) { inputs = shuffle(inputs) outputs = shuffle(outputs) diff --git a/test/full.test.js b/test/full.test.js index 91d236e..15c3244 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -35,7 +35,7 @@ describe('TornadoPool', function () { await token.mint(sender.address, utils.parseEther('10000')) const l1Token = await deploy('WETH', 'Wrapped ETH', 'WETH') - await l1Token.deposit({value: utils.parseEther('3')}) + await l1Token.deposit({ value: utils.parseEther('3') }) const amb = await deploy('MockAMB', gov.address, l1ChainId) const omniBridge = await deploy('MockOmniBridge', amb.address) From bc2433be08fabf89a4e9029b9f18b05a14264433 Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 24 Jan 2022 17:56:28 +0300 Subject: [PATCH 03/20] change configureLimits() access to multisig --- contracts/TornadoPool.sol | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index ef2d02d..12a379e 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -67,11 +67,6 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, event NewNullifier(bytes32 nullifier); event PublicKey(address indexed owner, bytes key); - modifier onlyGovernance() { - require(isCalledByOwner(), "only governance"); - _; - } - modifier onlyMultisig() { require(msg.sender == multisig, "only governance"); _; @@ -192,7 +187,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, } } - function configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) public onlyGovernance { + function configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) public onlyMultisig { _configureLimits(_minimalWithdrawalAmount, _maximumDepositAmount); } From b615b9b756920babfdba504175c79654b536868a Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 24 Jan 2022 18:25:17 +0300 Subject: [PATCH 04/20] fix tests --- test/full.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/full.test.js b/test/full.test.js index 15c3244..2203658 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -92,17 +92,15 @@ describe('TornadoPool', function () { }) it('should configure', async () => { - const { tornadoPool, amb } = await loadFixture(fixture) + const { tornadoPool, multisig } = await loadFixture(fixture) const newWithdrawalLimit = utils.parseEther('0.01337') const newDepositLimit = utils.parseEther('1337') - const { data } = await tornadoPool.populateTransaction.configureLimits( + await tornadoPool.connect(multisig).configureLimits( newWithdrawalLimit, newDepositLimit, ) - await amb.execute([{ who: tornadoPool.address, callData: data }]) - expect(await tornadoPool.maximumDepositAmount()).to.be.equal(newDepositLimit) expect(await tornadoPool.minimalWithdrawalAmount()).to.be.equal(newWithdrawalLimit) }) From 391e8c090e4a9ef61dfed4365804c9a2dce1067f Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 24 Jan 2022 18:40:45 +0300 Subject: [PATCH 05/20] fix linter --- test/full.test.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/full.test.js b/test/full.test.js index 2203658..5fb53e4 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -96,10 +96,7 @@ describe('TornadoPool', function () { const newWithdrawalLimit = utils.parseEther('0.01337') const newDepositLimit = utils.parseEther('1337') - await tornadoPool.connect(multisig).configureLimits( - newWithdrawalLimit, - newDepositLimit, - ) + await tornadoPool.connect(multisig).configureLimits(newWithdrawalLimit, newDepositLimit) expect(await tornadoPool.maximumDepositAmount()).to.be.equal(newDepositLimit) expect(await tornadoPool.minimalWithdrawalAmount()).to.be.equal(newWithdrawalLimit) From 448ec8c1ae019d2abf34951b2ebd168ba5053ff7 Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 24 Jan 2022 20:16:55 +0300 Subject: [PATCH 06/20] add MIN_EXT_AMOUNT_LIMIT --- contracts/TornadoPool.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index 12a379e..e980a54 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -25,6 +25,7 @@ import "./MerkleTreeWithHistory.sol"; contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, CrossChainGuard { int256 public constant MAX_EXT_AMOUNT = 2**248; uint256 public constant MAX_FEE = 2**248; + uint256 public constant MIN_EXT_AMOUNT_LIMIT = 0.5 ether; IVerifier public immutable verifier2; IVerifier public immutable verifier16; @@ -295,6 +296,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, } function _configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) internal { + require(_minimalWithdrawalAmount < MIN_EXT_AMOUNT_LIMIT, "minimalWithdrawal over limit"); minimalWithdrawalAmount = _minimalWithdrawalAmount; maximumDepositAmount = _maximumDepositAmount; } From 9cecb7b3eca0ef4ab6b3794a643dc0bd2728d94d Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 26 Jan 2022 15:41:30 +0300 Subject: [PATCH 07/20] fix MIN_EXT_AMOUNT_LIMIT check --- contracts/TornadoPool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index e980a54..8ed1de1 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -296,7 +296,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, } function _configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) internal { - require(_minimalWithdrawalAmount < MIN_EXT_AMOUNT_LIMIT, "minimalWithdrawal over limit"); + require(_minimalWithdrawalAmount <= MIN_EXT_AMOUNT_LIMIT, "minimalWithdrawal over limit"); minimalWithdrawalAmount = _minimalWithdrawalAmount; maximumDepositAmount = _maximumDepositAmount; } From e21e1c87e7305bcbb9b214265a8695395bc62b98 Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 26 Jan 2022 16:58:50 +0300 Subject: [PATCH 08/20] fix should insert tree test --- test/tree.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tree.test.js b/test/tree.test.js index 7e0bb79..2b35b9c 100644 --- a/test/tree.test.js +++ b/test/tree.test.js @@ -71,13 +71,13 @@ describe('MerkleTreeWithHistory', function () { it('should insert', async () => { const { merkleTreeWithHistory } = await loadFixture(fixture) const tree = getNewTree() - merkleTreeWithHistory.insert(toFixedHex(123), toFixedHex(456)) + await merkleTreeWithHistory.insert(toFixedHex(123), toFixedHex(456)) tree.bulkInsert([123, 456]) expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot()) - merkleTreeWithHistory.insert(toFixedHex(678), toFixedHex(876)) + await merkleTreeWithHistory.insert(toFixedHex(678), toFixedHex(876)) tree.bulkInsert([678, 876]) - expect(tree.root()._hex).to.be.be.equal(await merkleTreeWithHistory.getLastRoot()) + expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot()) }) it('hasher gas', async () => { From cdde55f56481c6b53416ae73f352e8e69c6bff03 Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 8 Feb 2022 19:13:15 +0300 Subject: [PATCH 09/20] fix omnibridge version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 49140c7..b329627 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "fixed-merkle-tree": "^0.5.1", "hardhat": "^2.3.0", "mocha": "^9.1.0", - "omnibridge": "git+https://github.com/peppersec/omnibridge.git#1f0baaa34bbfdc8f2ddb37c0554ad7d964a96803", + "omnibridge": "git+https://github.com/peppersec/omnibridge.git#30081f7a735eb03c9d6821a9617cc28efe71a682", "prompt-sync": "^4.2.0", "snarkjs": "git+https://github.com/tornadocash/snarkjs.git#f37f146948f3b28086493e71512006b030588fc2", "tmp-promise": "^3.0.2", From b37d6d459f7e76eeb3de422c2f11d8400d88326e Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 8 Feb 2022 19:54:32 +0300 Subject: [PATCH 10/20] test BSC-GC deploy --- scripts/deployBSCHelper.js | 4 ++-- scripts/deployTornado.js | 39 +++++++++++++++++++++----------------- yarn.lock | 4 ++-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/scripts/deployBSCHelper.js b/scripts/deployBSCHelper.js index b3edb5e..27952c4 100644 --- a/scripts/deployBSCHelper.js +++ b/scripts/deployBSCHelper.js @@ -3,11 +3,11 @@ const { ethers } = require('hardhat') // This script deploys L1Helper to FOREIGN chain (mainnet) async function main() { - const owner = '0x03Ebd0748Aa4D1457cF479cce56309641e0a98F5' + const owner = '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f' const omniBridge = '0xf0b456250dc9990662a6f25808cc74a6d1131ea9' const token = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' // WBNB - const Helper = await ethers.getContractFactory('L1Helper') + const Helper = await ethers.getContractFactory('L1Unwrapper') const helper = await Helper.deploy(omniBridge, token, owner) await helper.deployed() console.log(`L1Helper address: ${helper.address}`) diff --git a/scripts/deployTornado.js b/scripts/deployTornado.js index a5d866d..4f1a095 100644 --- a/scripts/deployTornado.js +++ b/scripts/deployTornado.js @@ -1,18 +1,19 @@ const { ethers } = require('hardhat') const { utils } = ethers -const prompt = require('prompt-sync')() +// const prompt = require('prompt-sync')() const MERKLE_TREE_HEIGHT = 23 const { MINIMUM_WITHDRAWAL_AMOUNT, MAXIMUM_DEPOSIT_AMOUNT } = process.env async function main() { require('./compileHasher') - const govAddress = '0x03ebd0748aa4d1457cf479cce56309641e0a98f5' + const govAddress = '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f' const omniBridge = '0x59447362798334d3485c64D1e4870Fde2DDC0d75' const amb = '0x162e898bd0aacb578c8d5f8d6ca588c13d2a383f' const token = '0xCa8d20f3e0144a72C6B5d576e9Bd3Fd8557E2B04' // WBNB - const l1Unwrapper = '0x2353Dcda746fa1AAD17C5650Ddf2A20112862197' // WBNB -> BNB + const l1Unwrapper = '0x8845F740F8B01bC7D9A4C82a6fD4A60320c07AF1' // WBNB -> BNB const l1ChainId = 56 + const multisig = '0xE3611102E23a43136a13993E3a00BAD67da19119' const Verifier2 = await ethers.getContractFactory('Verifier2') const verifier2 = await Verifier2.deploy() @@ -41,24 +42,28 @@ async function main() { l1Unwrapper, govAddress, l1ChainId, + multisig, ]).slice(1, -1)}\n`, ) - const tornadoImpl = prompt('Deploy tornado pool implementation and provide address here:\n') - // const tornadoImpl = await Pool.deploy( - // verifier2.address, - // verifier16.address, - // MERKLE_TREE_HEIGHT, - // hasher.address, - // token, - // omniBridge, - // l1Unwrapper, - // govAddress, - // ) - // await tornadoImpl.deployed() - // console.log(`TornadoPool implementation address: ${tornadoImpl.address}`) + + //const tornadoImpl = prompt('Deploy tornado pool implementation and provide address here:\n') + const tornadoImpl = await Pool.deploy( + verifier2.address, + verifier16.address, + MERKLE_TREE_HEIGHT, + hasher.address, + token, + omniBridge, + l1Unwrapper, + govAddress, + l1ChainId, + multisig, + ) + await tornadoImpl.deployed() + console.log(`TornadoPool implementation address: ${tornadoImpl.address}`) const CrossChainUpgradeableProxy = await ethers.getContractFactory('CrossChainUpgradeableProxy') - const proxy = await CrossChainUpgradeableProxy.deploy(tornadoImpl, govAddress, [], amb, l1ChainId) + const proxy = await CrossChainUpgradeableProxy.deploy(tornadoImpl.address, govAddress, [], amb, l1ChainId) await proxy.deployed() console.log(`proxy address: ${proxy.address}`) diff --git a/yarn.lock b/yarn.lock index 2517f49..000367a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6913,9 +6913,9 @@ oboe@2.1.5: dependencies: http-https "^1.0.0" -"omnibridge@git+https://github.com/peppersec/omnibridge.git#1f0baaa34bbfdc8f2ddb37c0554ad7d964a96803": +"omnibridge@git+https://github.com/peppersec/omnibridge.git#30081f7a735eb03c9d6821a9617cc28efe71a682": version "1.1.0" - resolved "git+https://github.com/peppersec/omnibridge.git#1f0baaa34bbfdc8f2ddb37c0554ad7d964a96803" + resolved "git+https://github.com/peppersec/omnibridge.git#30081f7a735eb03c9d6821a9617cc28efe71a682" dependencies: axios "^0.21.0" bignumber.js "^9.0.1" From d6919f2797af20374b387ef163f0f11f2ccdb654 Mon Sep 17 00:00:00 2001 From: Drygin Date: Fri, 11 Feb 2022 23:53:36 +0300 Subject: [PATCH 11/20] add create2 deploy --- .env.example | 2 + .github/workflows/build.yml | 2 + README.md | 27 ++++++++++ config.js | 26 +++++++++ contracts/bridge/L1Unwrapper.sol | 6 +-- contracts/libraries/SingletonFactory.sol | 28 ++++++++++ hardhat.config.js | 24 +++++++++ scripts/deployBSCHelper.js | 21 -------- scripts/deployL1Unwrapper.js | 28 ++++++++++ scripts/deployTornadoUpgrade.js | 28 ++++++++++ src/0_generateAddresses.js | 68 ++++++++++++++++++++++++ test/full.test.js | 13 ++++- 12 files changed, 248 insertions(+), 25 deletions(-) create mode 100644 config.js create mode 100644 contracts/libraries/SingletonFactory.sol delete mode 100644 scripts/deployBSCHelper.js create mode 100644 scripts/deployL1Unwrapper.js create mode 100644 scripts/deployTornadoUpgrade.js create mode 100644 src/0_generateAddresses.js diff --git a/.env.example b/.env.example index a6eed85..16a0da8 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,5 @@ XDAI_RPC=https:// BSC_RPC=https:// MINIMUM_WITHDRAWAL_AMOUNT=0.05 MAXIMUM_DEPOSIT_AMOUNT=1 +ALCHEMY_KEY= +INFURA_API_KEY= diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ed858d..4d73f80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,8 @@ jobs: - run: yarn lint - run: yarn build - run: yarn test + env: + ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} - name: Telegram Failure Notification uses: appleboy/telegram-action@0.0.7 if: failure() diff --git a/README.md b/README.md index 448db99..47fdbef 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,30 @@ yarn download yarn build yarn test ``` + +## Deploy + +Check config.js for actual values. + +With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` addresses must be: + +1. `L1Unwrapper` - `0xfEADF5e7e453c664D903d1b1945c524c4328e1c5` +2. `TornadoPool` - `0xEb314843E39A2D67c7bA31150fA243b30b70e97c` + +Check addresses with current config: + +```shell +node -e 'require("./src/0_generateAddresses").generateWithLog()' +``` + +Deploy L1Unwrapper: + +```shell +npx hardhat run scripts/deployL1Unwrapper.js --network mainnet +``` + +Deploy TornadoPool: + +```shell +npx hardhat run scripts/deployTornadoUpgrade.js --network xdai +``` diff --git a/config.js b/config.js new file mode 100644 index 0000000..075eb6b --- /dev/null +++ b/config.js @@ -0,0 +1,26 @@ +module.exports = { + //// L1 ------------------- + // ETH + multisig: '0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4', + omniBridge: '0x88ad09518695c6c3712AC10a214bE5109a655671', + weth: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + // BSC + // multisig: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f' + // omniBridge: '0xf0b456250dc9990662a6f25808cc74a6d1131ea9' + // weth: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' // WBNB + singletonFactory: '0xce0042B868300000d44A59004Da54A005ffdcf9f', + salt: '0x0000000000000000000000000000000000000000000000000000000047941987', + + //// L2 ------------------- + // Gnosis chain + verifier2: '0xdf3a408c53e5078af6e8fb2a85088d46ee09a61b', + verifier16: '0x743494b60097a2230018079c02fe21a7b687eaa5', + MERKLE_TREE_HEIGHT: 23, + hasher: '0x94c92f096437ab9958fc0a37f09348f30389ae79', + gcWeth: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1', + gcOmniBridge: '0xf6a78083ca3e2a662d6dd1703c939c8ace2e268d', + l1Unwrapper: '0xfEADF5e7e453c664D903d1b1945c524c4328e1c5', + govAddress: '0x5efda50f22d34f262c29268506c5fa42cb56a1ce', + l1ChainId: 1, + gcMultisig: '0x1f727de610030a88863d7da45bdea4eb84655b52', +} diff --git a/contracts/bridge/L1Unwrapper.sol b/contracts/bridge/L1Unwrapper.sol index e438cd6..a17da04 100644 --- a/contracts/bridge/L1Unwrapper.sol +++ b/contracts/bridge/L1Unwrapper.sol @@ -79,9 +79,9 @@ contract L1Unwrapper is WETHOmnibridgeRouter { uint256 _value, bytes memory _data ) external override { - require(_token == address(WETH)); - require(msg.sender == address(bridge)); - require(_data.length == 52); + require(_token == address(WETH), "only WETH token"); + require(msg.sender == address(bridge), "only from bridge address"); + require(_data.length == 52, "incorrect data length"); WETH.withdraw(_value); diff --git a/contracts/libraries/SingletonFactory.sol b/contracts/libraries/SingletonFactory.sol new file mode 100644 index 0000000..13653e5 --- /dev/null +++ b/contracts/libraries/SingletonFactory.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +/** + *Submitted for verification at Etherscan.io on 2020-03-30 + */ + +pragma solidity 0.6.2; + +/** + * @title Singleton Factory (EIP-2470) + * @notice Exposes CREATE2 (EIP-1014) to deploy bytecode on deterministic addresses based on initialization code and salt. + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + */ +contract SingletonFactory { + /** + * @notice Deploys `_initCode` using `_salt` for defining the deterministic address. + * @param _initCode Initialization code. + * @param _salt Arbitrary value to modify resulting address. + * @return createdContract Created contract address. + */ + function deploy(bytes memory _initCode, bytes32 _salt) public returns (address payable createdContract) { + assembly { + createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt) + } + } +} +// IV is a value changed to generate the vanity address. +// IV: 6583047 diff --git a/hardhat.config.js b/hardhat.config.js index 2a1f3c9..48d83b9 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -21,6 +21,15 @@ const config = { }, }, }, + { + version: '0.6.2', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, { version: '0.7.5', settings: { @@ -42,6 +51,21 @@ const config = { ], }, networks: { + hardhat: { + forking: { + url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, + blockNumber: 13685625, + }, + chainId: 1, + initialBaseFeePerGas: 5, + loggingEnabled: false, + allowUnlimitedContractSize: false, + blockGasLimit: 50000000, + }, + rinkeby: { + url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, + accounts: [process.env.PRIVATE_KEY], + }, xdai: { url: process.env.ETH_RPC || 'https://rpc.xdaichain.com/', accounts: process.env.PRIVATE_KEY diff --git a/scripts/deployBSCHelper.js b/scripts/deployBSCHelper.js deleted file mode 100644 index 27952c4..0000000 --- a/scripts/deployBSCHelper.js +++ /dev/null @@ -1,21 +0,0 @@ -const { ethers } = require('hardhat') - -// This script deploys L1Helper to FOREIGN chain (mainnet) - -async function main() { - const owner = '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f' - const omniBridge = '0xf0b456250dc9990662a6f25808cc74a6d1131ea9' - const token = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' // WBNB - - const Helper = await ethers.getContractFactory('L1Unwrapper') - const helper = await Helper.deploy(omniBridge, token, owner) - await helper.deployed() - console.log(`L1Helper address: ${helper.address}`) -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/scripts/deployL1Unwrapper.js b/scripts/deployL1Unwrapper.js new file mode 100644 index 0000000..5c466de --- /dev/null +++ b/scripts/deployL1Unwrapper.js @@ -0,0 +1,28 @@ +const { ethers } = require('hardhat') +const config = require('../config') +const { generate } = require('../src/0_generateAddresses') + +// This script deploys L1Helper to FOREIGN chain (mainnet) + +async function deploy({ address, bytecode, singletonFactory }) { + const contractCode = await ethers.provider.getCode(address) + if (contractCode !== '0x') { + console.log(`Contract ${address} already deployed. Skipping...`) + return + } + await singletonFactory.deploy(bytecode, config.salt, { gasLimit: 3000000 }) +} + +async function main() { + const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory) + const contracts = await generate() + await deploy({ ...contracts.unwrapperContract, singletonFactory }) + console.log(`L1 unwrapper contract have been deployed on ${contracts.unwrapperContract.address} address`) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/deployTornadoUpgrade.js b/scripts/deployTornadoUpgrade.js new file mode 100644 index 0000000..b08d4c0 --- /dev/null +++ b/scripts/deployTornadoUpgrade.js @@ -0,0 +1,28 @@ +const { ethers } = require('hardhat') +const config = require('../config') +const { generate } = require('../src/0_generateAddresses') + +// This script deploys Tornado Pool upgrade to L2 (Gnosis Chain) + +async function deploy({ address, bytecode, singletonFactory }) { + const contractCode = await ethers.provider.getCode(address) + if (contractCode !== '0x') { + console.log(`Contract ${address} already deployed. Skipping...`) + return + } + await singletonFactory.deploy(bytecode, config.salt, { gasLimit: 5000000 }) +} + +async function main() { + const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory) + const contracts = await generate() + await deploy({ ...contracts.poolContract, singletonFactory }) + console.log(`Upgraded pool contract have been deployed on ${contracts.poolContract.address} address`) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/src/0_generateAddresses.js b/src/0_generateAddresses.js new file mode 100644 index 0000000..27ad3b3 --- /dev/null +++ b/src/0_generateAddresses.js @@ -0,0 +1,68 @@ +const { ethers } = require('hardhat') +const defaultConfig = require('../config') + +async function generate(config = defaultConfig) { + const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory) + + const UnwrapperFactory = await ethers.getContractFactory('L1Unwrapper') + const deploymentBytecodeUnwrapper = + UnwrapperFactory.bytecode + + UnwrapperFactory.interface.encodeDeploy([config.omniBridge, config.weth, config.multisig]).slice(2) + + const unwrapperAddress = ethers.utils.getCreate2Address( + singletonFactory.address, + config.salt, + ethers.utils.keccak256(deploymentBytecodeUnwrapper), + ) + + const PoolFactory = await ethers.getContractFactory('TornadoPool') + const deploymentBytecodePool = + PoolFactory.bytecode + + PoolFactory.interface + .encodeDeploy([ + config.verifier2, + config.verifier16, + config.MERKLE_TREE_HEIGHT, + config.hasher, + config.gcWeth, + config.gcOmniBridge, + config.l1Unwrapper, + config.govAddress, + config.l1ChainId, + config.gcMultisig, + ]) + .slice(2) + + const poolAddress = ethers.utils.getCreate2Address( + singletonFactory.address, + config.salt, + ethers.utils.keccak256(deploymentBytecodePool), + ) + + const result = { + unwrapperContract: { + address: unwrapperAddress, + bytecode: deploymentBytecodeUnwrapper, + isProxy: false, + }, + poolContract: { + address: poolAddress, + bytecode: deploymentBytecodePool, + isProxy: false, + }, + } + + return result +} + +async function generateWithLog() { + const contracts = await generate() + console.log('L1 unwrapper contract: ', contracts.unwrapperContract.address) + console.log('Upgraded pool contract: ', contracts.poolContract.address) + return contracts +} + +module.exports = { + generate, + generateWithLog, +} diff --git a/test/full.test.js b/test/full.test.js index 5fb53e4..9c3cd86 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -9,6 +9,8 @@ const { transaction, registerAndTransact, prepareTransaction, buildMerkleTree } const { toFixedHex, poseidonHash } = require('../src/utils') const { Keypair } = require('../src/keypair') const { encodeDataForBridge } = require('./utils') +const config = require('../config') +const { generate } = require('../src/0_generateAddresses') const MERKLE_TREE_HEIGHT = 5 const l1ChainId = 1 @@ -39,7 +41,16 @@ describe('TornadoPool', function () { const amb = await deploy('MockAMB', gov.address, l1ChainId) const omniBridge = await deploy('MockOmniBridge', amb.address) - const l1Unwrapper = await deploy('L1Unwrapper', amb.address, l1Token.address, gov.address) + + // deploy L1Unwrapper with CREATE2 + const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory) + + let customConfig = Object.assign({}, config) + customConfig.omniBridge = amb.address + customConfig.weth = l1Token.address + const contracts = await generate(customConfig) + await singletonFactory.deploy(contracts.unwrapperContract.bytecode, config.salt) + const l1Unwrapper = await ethers.getContractAt('L1Unwrapper', contracts.unwrapperContract.address) /** @type {TornadoPool} */ const tornadoPoolImpl = await deploy( From bb2e4186bf70dee7099ecc5a71db1f0c5d9876a3 Mon Sep 17 00:00:00 2001 From: Drygin Date: Sat, 12 Feb 2022 01:00:42 +0300 Subject: [PATCH 12/20] fix build --- hardhat.config.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hardhat.config.js b/hardhat.config.js index 48d83b9..c7c6f68 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -64,7 +64,11 @@ const config = { }, rinkeby: { url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, - accounts: [process.env.PRIVATE_KEY], + accounts: process.env.PRIVATE_KEY + ? [process.env.PRIVATE_KEY] + : { + mnemonic: 'test test test test test test test test test test test junk', + }, }, xdai: { url: process.env.ETH_RPC || 'https://rpc.xdaichain.com/', From f4df191ed2d915e253050106588256d7c47dec37 Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 14 Feb 2022 13:27:44 +0300 Subject: [PATCH 13/20] fix readme and tests --- README.md | 5 +++-- test/full.test.js | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 47fdbef..8a96e4a 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,12 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` addresses must be: 1. `L1Unwrapper` - `0xfEADF5e7e453c664D903d1b1945c524c4328e1c5` -2. `TornadoPool` - `0xEb314843E39A2D67c7bA31150fA243b30b70e97c` +2. `TornadoPool` - `0x6a1c06C4Af066a35291117611fd0418411c0dbD0` Check addresses with current config: ```shell +yarn compile node -e 'require("./src/0_generateAddresses").generateWithLog()' ``` @@ -42,7 +43,7 @@ Deploy L1Unwrapper: npx hardhat run scripts/deployL1Unwrapper.js --network mainnet ``` -Deploy TornadoPool: +Deploy TornadoPool Upgrade: ```shell npx hardhat run scripts/deployTornadoUpgrade.js --network xdai diff --git a/test/full.test.js b/test/full.test.js index 9c3cd86..d0504b0 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -46,7 +46,7 @@ describe('TornadoPool', function () { const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory) let customConfig = Object.assign({}, config) - customConfig.omniBridge = amb.address + customConfig.omniBridge = omniBridge.address customConfig.weth = l1Token.address const contracts = await generate(customConfig) await singletonFactory.deploy(contracts.unwrapperContract.bytecode, config.salt) @@ -350,13 +350,13 @@ describe('TornadoPool', function () { extAmount, onTokenBridgedData, ) - // emulating bridge. first it sends tokens to amb mock then it sends to the recipient - await l1Token.transfer(amb.address, extAmount) + // emulating bridge. first it sends tokens to omniBridge mock then it sends to the recipient + await l1Token.transfer(omniBridge.address, extAmount) transferTx = await l1Token.populateTransaction.transfer(l1Unwrapper.address, extAmount) const senderBalanceBefore = await ethers.provider.getBalance(sender.address) - let tx = await amb.execute([ + let tx = await omniBridge.execute([ { who: l1Token.address, callData: transferTx.data }, // send tokens to L1Unwrapper { who: l1Unwrapper.address, callData: onTokenBridgedTx.data }, // call onTokenBridged on L1Unwrapper ]) From abef8ad760c958bec9b551c95eb28cb52764ba79 Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 14 Feb 2022 13:34:03 +0300 Subject: [PATCH 14/20] fix linters --- test/full.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/full.test.js b/test/full.test.js index d0504b0..d49b8e5 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -282,7 +282,7 @@ describe('TornadoPool', function () { }) it('should withdraw with L1 fee', async function () { - const { tornadoPool, token, omniBridge, amb, l1Unwrapper, sender, l1Token } = await loadFixture(fixture) + const { tornadoPool, token, omniBridge, l1Unwrapper, sender, l1Token } = await loadFixture(fixture) const aliceKeypair = new Keypair() // contains private and public keys // regular L1 deposit ------------------------------------------- From 0530f00af5a83b704f3d5187ad8d18444cb72d1e Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 14 Feb 2022 15:24:33 +0300 Subject: [PATCH 15/20] add L1Receiver logic to unwrapper --- contracts/bridge/L1Unwrapper.sol | 25 ++++++- test/full.test.js | 109 +++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/contracts/bridge/L1Unwrapper.sol b/contracts/bridge/L1Unwrapper.sol index a17da04..77c894f 100644 --- a/contracts/bridge/L1Unwrapper.sol +++ b/contracts/bridge/L1Unwrapper.sol @@ -21,6 +21,13 @@ import { BytesHelper } from "../libraries/Bytes.sol"; contract L1Unwrapper is WETHOmnibridgeRouter { using SafeMath for uint256; + // If this address sets to not zero it receives L1_fee. + // It can be changed by the multisig. + // And should implement fee sharing logic: + // - some part to tx.origin - based on block base fee and can be subsidized + // - store surplus of ETH for future subsidizions + address payable public l1FeeReceiver; + event PublicKey(address indexed owner, bytes key); struct Account { @@ -88,6 +95,22 @@ contract L1Unwrapper is WETHOmnibridgeRouter { uint256 l1Fee = BytesHelper.sliceToUint(_data, 20); AddressHelper.safeSendValue(payable(BytesHelper.bytesToAddress(_data)), _value.sub(l1Fee)); - AddressHelper.safeSendValue(payable(tx.origin), l1Fee); + + address payable l1FeeTo; + if (l1FeeReceiver != payable(address(0))) { + l1FeeTo = l1FeeReceiver; + } else { + l1FeeTo = payable(tx.origin); + } + AddressHelper.safeSendValue(l1FeeTo, l1Fee); + } + + /** + * @dev Sets l1FeeReceiver address. + * Only contract owner can call this method. + * @param _receiver address of new L1FeeReceiver, address(0) for native tx.origin receiver. + */ + function setL1FeeReceiver(address payable _receiver) external onlyOwner { + l1FeeReceiver = _receiver; } } diff --git a/test/full.test.js b/test/full.test.js index d49b8e5..a4f65d4 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -48,6 +48,7 @@ describe('TornadoPool', function () { let customConfig = Object.assign({}, config) customConfig.omniBridge = omniBridge.address customConfig.weth = l1Token.address + customConfig.multisig = multisig.address const contracts = await generate(customConfig) await singletonFactory.deploy(contracts.unwrapperContract.bytecode, config.salt) const l1Unwrapper = await ethers.getContractAt('L1Unwrapper', contracts.unwrapperContract.address) @@ -368,6 +369,114 @@ describe('TornadoPool', function () { expect(await ethers.provider.getBalance(recipient)).to.be.equal(aliceWithdrawAmount) }) + it('should set L1FeeReceiver on L1Unwrapper contract', async function () { + const { tornadoPool, token, omniBridge, l1Unwrapper, sender, l1Token, multisig } = await loadFixture( + fixture, + ) + + // check init l1FeeReceiver + expect(await l1Unwrapper.l1FeeReceiver()).to.be.equal(ethers.constants.AddressZero) + + // should not set from not multisig + + await expect(l1Unwrapper.connect(sender).setL1FeeReceiver(multisig.address)).to.be.reverted + + expect(await l1Unwrapper.l1FeeReceiver()).to.be.equal(ethers.constants.AddressZero) + + // should set from multisig + await l1Unwrapper.connect(multisig).setL1FeeReceiver(multisig.address) + + expect(await l1Unwrapper.l1FeeReceiver()).to.be.equal(multisig.address) + + // ------------------------------------------------------------------------ + // check withdraw with L1 fee --------------------------------------------- + + const aliceKeypair = new Keypair() // contains private and public keys + + // regular L1 deposit ------------------------------------------- + const aliceDepositAmount = utils.parseEther('0.07') + const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount, keypair: aliceKeypair }) + const { args, extData } = await prepareTransaction({ + tornadoPool, + outputs: [aliceDepositUtxo], + }) + + let onTokenBridgedData = encodeDataForBridge({ + proof: args, + extData, + }) + + let onTokenBridgedTx = await tornadoPool.populateTransaction.onTokenBridged( + token.address, + aliceDepositUtxo.amount, + onTokenBridgedData, + ) + // emulating bridge. first it sends tokens to omnibridge mock then it sends to the pool + await token.transfer(omniBridge.address, aliceDepositAmount) + let transferTx = await token.populateTransaction.transfer(tornadoPool.address, aliceDepositAmount) + + await omniBridge.execute([ + { who: token.address, callData: transferTx.data }, // send tokens to pool + { who: tornadoPool.address, callData: onTokenBridgedTx.data }, // call onTokenBridgedTx + ]) + + // withdrawal with L1 fee --------------------------------------- + // withdraws a part of his funds from the shielded pool + const aliceWithdrawAmount = utils.parseEther('0.06') + const l1Fee = utils.parseEther('0.01') + // sum of desired withdraw amount and L1 fee are stored in extAmount + const extAmount = aliceWithdrawAmount.add(l1Fee) + const recipient = '0xDeaD00000000000000000000000000000000BEEf' + const aliceChangeUtxo = new Utxo({ + amount: aliceDepositAmount.sub(extAmount), + keypair: aliceKeypair, + }) + await transaction({ + tornadoPool, + inputs: [aliceDepositUtxo], + outputs: [aliceChangeUtxo], + recipient: recipient, + isL1Withdrawal: true, + l1Fee: l1Fee, + }) + + const filter = omniBridge.filters.OnTokenTransfer() + const fromBlock = await ethers.provider.getBlock() + const events = await omniBridge.queryFilter(filter, fromBlock.number) + onTokenBridgedData = events[0].args.data + const hexL1Fee = '0x' + events[0].args.data.toString().slice(42) + expect(ethers.BigNumber.from(hexL1Fee)).to.be.equal(l1Fee) + + const recipientBalance = await token.balanceOf(recipient) + expect(recipientBalance).to.be.equal(0) + const omniBridgeBalance = await token.balanceOf(omniBridge.address) + expect(omniBridgeBalance).to.be.equal(extAmount) + + // L1 transactions: + onTokenBridgedTx = await l1Unwrapper.populateTransaction.onTokenBridged( + l1Token.address, + extAmount, + onTokenBridgedData, + ) + // emulating bridge. first it sends tokens to omniBridge mock then it sends to the recipient + await l1Token.transfer(omniBridge.address, extAmount) + transferTx = await l1Token.populateTransaction.transfer(l1Unwrapper.address, extAmount) + + const senderBalanceBefore = await ethers.provider.getBalance(sender.address) + const multisigBalanceBefore = await ethers.provider.getBalance(multisig.address) + + let tx = await omniBridge.execute([ + { who: l1Token.address, callData: transferTx.data }, // send tokens to L1Unwrapper + { who: l1Unwrapper.address, callData: onTokenBridgedTx.data }, // call onTokenBridged on L1Unwrapper + ]) + + let receipt = await tx.wait() + let txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice) + expect(await ethers.provider.getBalance(sender.address)).to.be.equal(senderBalanceBefore.sub(txFee)) + expect(await ethers.provider.getBalance(multisig.address)).to.be.equal(multisigBalanceBefore.add(l1Fee)) + expect(await ethers.provider.getBalance(recipient)).to.be.equal(aliceWithdrawAmount) + }) + it('should transfer funds to multisig in case of L1 deposit fail', async function () { const { tornadoPool, token, omniBridge, multisig } = await loadFixture(fixture) const aliceKeypair = new Keypair() // contains private and public keys From d052e51095413800acf6a667d8a7468c41589622 Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 14 Feb 2022 16:45:51 +0300 Subject: [PATCH 16/20] add ternary opr + fix deploy addresses --- README.md | 4 ++-- config.js | 2 +- contracts/bridge/L1Unwrapper.sol | 7 +------ 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8a96e4a..bf5626d 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` addresses must be: -1. `L1Unwrapper` - `0xfEADF5e7e453c664D903d1b1945c524c4328e1c5` -2. `TornadoPool` - `0x6a1c06C4Af066a35291117611fd0418411c0dbD0` +1. `L1Unwrapper` - `0x03AF49dBa3F607BAD4646F0745a6C6473dF9c22d` +2. `TornadoPool` - `0xAEE471D6FD5c6B3f377f45B0a3c705505d172090` Check addresses with current config: diff --git a/config.js b/config.js index 075eb6b..66450dc 100644 --- a/config.js +++ b/config.js @@ -19,7 +19,7 @@ module.exports = { hasher: '0x94c92f096437ab9958fc0a37f09348f30389ae79', gcWeth: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1', gcOmniBridge: '0xf6a78083ca3e2a662d6dd1703c939c8ace2e268d', - l1Unwrapper: '0xfEADF5e7e453c664D903d1b1945c524c4328e1c5', + l1Unwrapper: '0x03AF49dBa3F607BAD4646F0745a6C6473dF9c22d', govAddress: '0x5efda50f22d34f262c29268506c5fa42cb56a1ce', l1ChainId: 1, gcMultisig: '0x1f727de610030a88863d7da45bdea4eb84655b52', diff --git a/contracts/bridge/L1Unwrapper.sol b/contracts/bridge/L1Unwrapper.sol index 77c894f..2f0c127 100644 --- a/contracts/bridge/L1Unwrapper.sol +++ b/contracts/bridge/L1Unwrapper.sol @@ -96,12 +96,7 @@ contract L1Unwrapper is WETHOmnibridgeRouter { AddressHelper.safeSendValue(payable(BytesHelper.bytesToAddress(_data)), _value.sub(l1Fee)); - address payable l1FeeTo; - if (l1FeeReceiver != payable(address(0))) { - l1FeeTo = l1FeeReceiver; - } else { - l1FeeTo = payable(tx.origin); - } + address payable l1FeeTo = (l1FeeReceiver != payable(address(0))) ? l1FeeReceiver : payable(tx.origin); AddressHelper.safeSendValue(l1FeeTo, l1Fee); } From 995302bd772e59674f1141f6df0e03480b943532 Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 14 Feb 2022 18:02:16 +0300 Subject: [PATCH 17/20] fix ternary opr --- README.md | 4 ++-- config.js | 2 +- contracts/bridge/L1Unwrapper.sol | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bf5626d..3b0661c 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` addresses must be: -1. `L1Unwrapper` - `0x03AF49dBa3F607BAD4646F0745a6C6473dF9c22d` -2. `TornadoPool` - `0xAEE471D6FD5c6B3f377f45B0a3c705505d172090` +1. `L1Unwrapper` - `0x095f741D37DfB3C798327377467eD40EE21b3B24` +2. `TornadoPool` - `0x85a3C2E4f0FcD4e881e0a3D1078496660C74bb35` Check addresses with current config: diff --git a/config.js b/config.js index 66450dc..0bd2fcd 100644 --- a/config.js +++ b/config.js @@ -19,7 +19,7 @@ module.exports = { hasher: '0x94c92f096437ab9958fc0a37f09348f30389ae79', gcWeth: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1', gcOmniBridge: '0xf6a78083ca3e2a662d6dd1703c939c8ace2e268d', - l1Unwrapper: '0x03AF49dBa3F607BAD4646F0745a6C6473dF9c22d', + l1Unwrapper: '0x095f741D37DfB3C798327377467eD40EE21b3B24', govAddress: '0x5efda50f22d34f262c29268506c5fa42cb56a1ce', l1ChainId: 1, gcMultisig: '0x1f727de610030a88863d7da45bdea4eb84655b52', diff --git a/contracts/bridge/L1Unwrapper.sol b/contracts/bridge/L1Unwrapper.sol index 2f0c127..1b3ea62 100644 --- a/contracts/bridge/L1Unwrapper.sol +++ b/contracts/bridge/L1Unwrapper.sol @@ -96,7 +96,7 @@ contract L1Unwrapper is WETHOmnibridgeRouter { AddressHelper.safeSendValue(payable(BytesHelper.bytesToAddress(_data)), _value.sub(l1Fee)); - address payable l1FeeTo = (l1FeeReceiver != payable(address(0))) ? l1FeeReceiver : payable(tx.origin); + address payable l1FeeTo = l1FeeReceiver != payable(address(0)) ? l1FeeReceiver : payable(tx.origin); AddressHelper.safeSendValue(l1FeeTo, l1Fee); } From 6fa861ce8fdebdf66d6f08ab20e313a918cf4503 Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 16 Feb 2022 23:26:27 +0300 Subject: [PATCH 18/20] encode fix --- README.md | 4 ++-- config.js | 2 +- contracts/TornadoPool.sol | 17 +++++++--------- contracts/bridge/L1Unwrapper.sol | 13 ++++++------ contracts/libraries/Bytes.sol | 35 -------------------------------- test/full.test.js | 10 +++------ 6 files changed, 20 insertions(+), 61 deletions(-) delete mode 100644 contracts/libraries/Bytes.sol diff --git a/README.md b/README.md index 3b0661c..631f2f7 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` addresses must be: -1. `L1Unwrapper` - `0x095f741D37DfB3C798327377467eD40EE21b3B24` -2. `TornadoPool` - `0x85a3C2E4f0FcD4e881e0a3D1078496660C74bb35` +1. `L1Unwrapper` - `0x3F615bA21Bc6Cc5D4a6D798c5950cc5c42937fbd` +2. `TornadoPool` - `0x0CDD3705aF7979fBe80A64288Ebf8A9Fe1151cE1` Check addresses with current config: diff --git a/config.js b/config.js index 0bd2fcd..669bb3e 100644 --- a/config.js +++ b/config.js @@ -19,7 +19,7 @@ module.exports = { hasher: '0x94c92f096437ab9958fc0a37f09348f30389ae79', gcWeth: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1', gcOmniBridge: '0xf6a78083ca3e2a662d6dd1703c939c8ace2e268d', - l1Unwrapper: '0x095f741D37DfB3C798327377467eD40EE21b3B24', + l1Unwrapper: '0x3F615bA21Bc6Cc5D4a6D798c5950cc5c42937fbd', govAddress: '0x5efda50f22d34f262c29268506c5fa42cb56a1ce', l1ChainId: 1, gcMultisig: '0x1f727de610030a88863d7da45bdea4eb84655b52', diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index 8ed1de1..8744774 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -35,7 +35,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, address public immutable multisig; uint256 public lastBalance; - uint256 public minimalWithdrawalAmount; + uint256 public __gap; // storage padding to prevent storage collision uint256 public maximumDepositAmount; mapping(bytes32 => bool) public nullifierHashes; @@ -109,8 +109,8 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, multisig = _multisig; } - function initialize(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) external initializer { - _configureLimits(_minimalWithdrawalAmount, _maximumDepositAmount); + function initialize(uint256 _maximumDepositAmount) external initializer { + _configureLimits(_maximumDepositAmount); super._initialize(); } @@ -188,8 +188,8 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, } } - function configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) public onlyMultisig { - _configureLimits(_minimalWithdrawalAmount, _maximumDepositAmount); + function configureLimits(uint256 _maximumDepositAmount) public onlyMultisig { + _configureLimits(_maximumDepositAmount); } function calculatePublicAmount(int256 _extAmount, uint256 _fee) public pure returns (uint256) { @@ -275,12 +275,11 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, token.transferAndCall( omniBridge, uint256(-_extData.extAmount), - abi.encodePacked(l1Unwrapper, _extData.recipient, _extData.l1Fee) + abi.encodePacked(l1Unwrapper, abi.encode(_extData.recipient, _extData.l1Fee)) ); } else { token.transfer(_extData.recipient, uint256(-_extData.extAmount)); } - require(uint256(-_extData.extAmount) >= minimalWithdrawalAmount, "amount is less than minimalWithdrawalAmount"); // prevents ddos attack to Bridge } if (_extData.fee > 0) { token.transfer(_extData.relayer, _extData.fee); @@ -295,9 +294,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, } } - function _configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) internal { - require(_minimalWithdrawalAmount <= MIN_EXT_AMOUNT_LIMIT, "minimalWithdrawal over limit"); - minimalWithdrawalAmount = _minimalWithdrawalAmount; + function _configureLimits(uint256 _maximumDepositAmount) internal { maximumDepositAmount = _maximumDepositAmount; } } diff --git a/contracts/bridge/L1Unwrapper.sol b/contracts/bridge/L1Unwrapper.sol index 1b3ea62..07491bc 100644 --- a/contracts/bridge/L1Unwrapper.sol +++ b/contracts/bridge/L1Unwrapper.sol @@ -15,7 +15,6 @@ pragma abicoder v2; import "omnibridge/contracts/helpers/WETHOmnibridgeRouter.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; -import { BytesHelper } from "../libraries/Bytes.sol"; /// @dev Extension for original WETHOmnibridgeRouter that stores TornadoPool account registrations. contract L1Unwrapper is WETHOmnibridgeRouter { @@ -88,16 +87,18 @@ contract L1Unwrapper is WETHOmnibridgeRouter { ) external override { require(_token == address(WETH), "only WETH token"); require(msg.sender == address(bridge), "only from bridge address"); - require(_data.length == 52, "incorrect data length"); + require(_data.length == 64, "incorrect data length"); WETH.withdraw(_value); - uint256 l1Fee = BytesHelper.sliceToUint(_data, 20); + (address payable receipient, uint256 l1Fee) = abi.decode(_data, (address, uint256)); - AddressHelper.safeSendValue(payable(BytesHelper.bytesToAddress(_data)), _value.sub(l1Fee)); + AddressHelper.safeSendValue(receipient, _value.sub(l1Fee)); - address payable l1FeeTo = l1FeeReceiver != payable(address(0)) ? l1FeeReceiver : payable(tx.origin); - AddressHelper.safeSendValue(l1FeeTo, l1Fee); + if (l1Fee > 0) { + address payable l1FeeTo = l1FeeReceiver != payable(address(0)) ? l1FeeReceiver : payable(tx.origin); + AddressHelper.safeSendValue(l1FeeTo, l1Fee); + } } /** diff --git a/contracts/libraries/Bytes.sol b/contracts/libraries/Bytes.sol deleted file mode 100644 index 08dc98f..0000000 --- a/contracts/libraries/Bytes.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.0; - -/** - * @title Bytes - * @dev Helper methods to transform bytes to other solidity types. - */ -library BytesHelper { - /** - * @dev Truncate bytes array if its size is more than 20 bytes. - * NOTE: This function does not perform any checks on the received parameter. - * Make sure that the _bytes argument has a correct length, not less than 20 bytes. - * A case when _bytes has length less than 20 will lead to the undefined behaviour, - * since assembly will read data from memory that is not related to the _bytes argument. - * @param _bytes to be converted to address type - * @return addr address included in the firsts 20 bytes of the bytes array in parameter. - */ - function bytesToAddress(bytes memory _bytes) internal pure returns (address addr) { - assembly { - addr := mload(add(_bytes, 20)) - } - } - - /** - * @param _bytes it's 32 length slice to be converted to uint type - * @param _start start index of slice - * @return x uint included in the 32 length slice of the bytes array in parameter. - */ - function sliceToUint(bytes memory _bytes, uint256 _start) internal pure returns (uint256 x) { - require(_bytes.length >= _start + 32, "slicing out of range"); - assembly { - x := mload(add(_bytes, add(0x20, _start))) - } - } -} diff --git a/test/full.test.js b/test/full.test.js index a4f65d4..68259a1 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -14,7 +14,6 @@ const { generate } = require('../src/0_generateAddresses') const MERKLE_TREE_HEIGHT = 5 const l1ChainId = 1 -const MINIMUM_WITHDRAWAL_AMOUNT = utils.parseEther(process.env.MINIMUM_WITHDRAWAL_AMOUNT || '0.05') const MAXIMUM_DEPOSIT_AMOUNT = utils.parseEther(process.env.MAXIMUM_DEPOSIT_AMOUNT || '1') describe('TornadoPool', function () { @@ -69,7 +68,6 @@ describe('TornadoPool', function () { ) const { data } = await tornadoPoolImpl.populateTransaction.initialize( - MINIMUM_WITHDRAWAL_AMOUNT, MAXIMUM_DEPOSIT_AMOUNT, ) const proxy = await deploy( @@ -105,13 +103,11 @@ describe('TornadoPool', function () { it('should configure', async () => { const { tornadoPool, multisig } = await loadFixture(fixture) - const newWithdrawalLimit = utils.parseEther('0.01337') const newDepositLimit = utils.parseEther('1337') - await tornadoPool.connect(multisig).configureLimits(newWithdrawalLimit, newDepositLimit) + await tornadoPool.connect(multisig).configureLimits(newDepositLimit) expect(await tornadoPool.maximumDepositAmount()).to.be.equal(newDepositLimit) - expect(await tornadoPool.minimalWithdrawalAmount()).to.be.equal(newWithdrawalLimit) }) }) @@ -337,7 +333,7 @@ describe('TornadoPool', function () { const fromBlock = await ethers.provider.getBlock() const events = await omniBridge.queryFilter(filter, fromBlock.number) onTokenBridgedData = events[0].args.data - const hexL1Fee = '0x' + events[0].args.data.toString().slice(42) + const hexL1Fee = '0x' + events[0].args.data.toString().slice(66) expect(ethers.BigNumber.from(hexL1Fee)).to.be.equal(l1Fee) const recipientBalance = await token.balanceOf(recipient) @@ -444,7 +440,7 @@ describe('TornadoPool', function () { const fromBlock = await ethers.provider.getBlock() const events = await omniBridge.queryFilter(filter, fromBlock.number) onTokenBridgedData = events[0].args.data - const hexL1Fee = '0x' + events[0].args.data.toString().slice(42) + const hexL1Fee = '0x' + events[0].args.data.toString().slice(66) expect(ethers.BigNumber.from(hexL1Fee)).to.be.equal(l1Fee) const recipientBalance = await token.balanceOf(recipient) From 29a804fb93b72472655a8593ab3303833b346478 Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 16 Feb 2022 23:28:15 +0300 Subject: [PATCH 19/20] lint fix --- test/full.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/full.test.js b/test/full.test.js index 68259a1..28ea23d 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -67,9 +67,7 @@ describe('TornadoPool', function () { multisig.address, ) - const { data } = await tornadoPoolImpl.populateTransaction.initialize( - MAXIMUM_DEPOSIT_AMOUNT, - ) + const { data } = await tornadoPoolImpl.populateTransaction.initialize(MAXIMUM_DEPOSIT_AMOUNT) const proxy = await deploy( 'CrossChainUpgradeableProxy', tornadoPoolImpl.address, From b19bac4d92878ba05c4fc4ae434cc739c2005ab8 Mon Sep 17 00:00:00 2001 From: Drygin Date: Thu, 17 Feb 2022 01:06:29 +0300 Subject: [PATCH 20/20] fix mainnet rpc --- .env.example | 1 + hardhat.config.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 16a0da8..55e2c41 100644 --- a/.env.example +++ b/.env.example @@ -6,3 +6,4 @@ MINIMUM_WITHDRAWAL_AMOUNT=0.05 MAXIMUM_DEPOSIT_AMOUNT=1 ALCHEMY_KEY= INFURA_API_KEY= +ETHERSCAN_KEY= diff --git a/hardhat.config.js b/hardhat.config.js index c7c6f68..304daed 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -88,7 +88,7 @@ const config = { }, }, mainnet: { - url: process.env.ETH_RPC || '', + url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : {