From bffcaeacb922a2a1bf4c247b350fc76926bbdea2 Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 16 Feb 2022 12:52:53 +0300 Subject: [PATCH 01/18] move to Alchemy node --- .env.example | 1 + .gitignore | 4 +++- README.md | 2 ++ hardhat.config.js | 2 +- test/instance.factory.proposal.test.js | 28 ++++++++++++++------------ test/instance.proposal.test.js | 24 +++++++++++----------- 6 files changed, 34 insertions(+), 27 deletions(-) diff --git a/.env.example b/.env.example index 48a8535..45366da 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +ALCHEMY_KEY= etherscan_api_key= goerli_rpc_key= mainnet_rpc_key= diff --git a/.gitignore b/.gitignore index 376909a..02e59cc 100644 --- a/.gitignore +++ b/.gitignore @@ -106,4 +106,6 @@ dist artifacts cache coverage -coverage.json \ No newline at end of file +coverage.json + +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 38b585e..217da7c 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ yarn deploy:proposal:test The last step, or first depending on if you are simply proposing the proposal, is taking the address of the deployed proposal and calling: +// THIS IS ASSUMING YOU HAVE ENOUGH LOCKED TORN TO PROPOSE + ```bash yarn propose ``` diff --git a/hardhat.config.js b/hardhat.config.js index 0b39c3c..8cdeff9 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -37,7 +37,7 @@ module.exports = { networks: { hardhat: { forking: { - url: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`, + url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436, }, loggingEnabled: false, diff --git a/test/instance.factory.proposal.test.js b/test/instance.factory.proposal.test.js index 9bfe6cb..94d6e3f 100644 --- a/test/instance.factory.proposal.test.js +++ b/test/instance.factory.proposal.test.js @@ -29,7 +29,7 @@ describe('Deployments test setup', () => { let TornadoInstanceFactoryContract - /// HARDCODED + /// HARDCODED // TODO take from config let denominations = [ '33333333333333333333', '333333333333333333333', @@ -145,13 +145,15 @@ describe('Deployments test setup', () => { gasLimit: BigNumber.from('30000000'), } await GovernanceContract.execute(id, overrides) + + expect(await GovernanceContract.state(id)).to.be.equal(ProposalState.Executed) }) it('Should set correct params for factory', async () => { expect(await TornadoInstanceFactoryContract.verifier()).to.equal(Verifier) expect(await TornadoInstanceFactoryContract.hasher()).to.equal(Hasher) expect(await TornadoInstanceFactoryContract.merkleTreeHeight()).to.equal(20) - clog(await TornadoInstanceFactoryContract.implementation()) + // clog(await TornadoInstanceFactoryContract.implementation()) }) it('Factory should be able to generate an instance without reverting', async () => { @@ -251,11 +253,11 @@ describe('Deployments test setup', () => { secret: rbigint(31), }) - const note = toHex(depo.preimage, 62) - const noteString = `tornado-RAI-33-1-${note}` - clog('Note: ', note) - clog('Note string: ', noteString) - clog('Commitment: ', toHex(depo.commitment)) + // const note = toHex(depo.preimage, 62) + // const noteString = `tornado-RAI-33-1-${note}` + // clog('Note: ', note) + // clog('Note string: ', noteString) + // clog('Commitment: ', toHex(depo.commitment)) await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted TornadoInstance = await ethers.getContractAt( @@ -300,11 +302,11 @@ describe('Deployments test setup', () => { nullifier: rbigint(31), secret: rbigint(31), }) - const note = toHex(depo.preimage, 62) - const noteString = `tornado-RAI-33-1-${note}` - clog('Note: ', note) - clog('Note string: ', noteString) - clog('Commitment: ', toHex(depo.commitment)) + // const note = toHex(depo.preimage, 62) + // const noteString = `tornado-RAI-33-1-${note}` + // clog('Note: ', note) + // clog('Note string: ', noteString) + // clog('Commitment: ', toHex(depo.commitment)) const proxy = await TornadoProxy.connect(accounts[i]) await expect(() => @@ -337,7 +339,7 @@ describe('Deployments test setup', () => { await ethers.provider.send('hardhat_reset', [ { forking: { - jsonRpcUrl: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`, + jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436, }, }, diff --git a/test/instance.proposal.test.js b/test/instance.proposal.test.js index 12692b0..a643632 100644 --- a/test/instance.proposal.test.js +++ b/test/instance.proposal.test.js @@ -101,7 +101,7 @@ describe('Deployments test setup', () => { expect(await TornadoInstanceFactoryContract.verifier()).to.equal(Verifier) expect(await TornadoInstanceFactoryContract.hasher()).to.equal(Hasher) expect(await TornadoInstanceFactoryContract.merkleTreeHeight()).to.equal(20) - clog(await TornadoInstanceFactoryContract.implementation()) + // clog(await TornadoInstanceFactoryContract.implementation()) }) it('Factory should be able to generate an instance without reverting', async () => { @@ -260,11 +260,11 @@ describe('Deployments test setup', () => { secret: rbigint(31), }) - const note = toHex(depo.preimage, 62) - const noteString = `tornado-RAI-33-1-${note}` - clog('Note: ', note) - clog('Note string: ', noteString) - clog('Commitment: ', toHex(depo.commitment)) + // const note = toHex(depo.preimage, 62) + // const noteString = `tornado-RAI-33-1-${note}` + // clog('Note: ', note) + // clog('Note string: ', noteString) + // clog('Commitment: ', toHex(depo.commitment)) await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted TornadoInstance = await ethers.getContractAt( @@ -309,11 +309,11 @@ describe('Deployments test setup', () => { nullifier: rbigint(31), secret: rbigint(31), }) - const note = toHex(depo.preimage, 62) - const noteString = `tornado-RAI-33-1-${note}` - clog('Note: ', note) - clog('Note string: ', noteString) - clog('Commitment: ', toHex(depo.commitment)) + // const note = toHex(depo.preimage, 62) + // const noteString = `tornado-RAI-33-1-${note}` + // clog('Note: ', note) + // clog('Note string: ', noteString) + // clog('Commitment: ', toHex(depo.commitment)) const proxy = await TornadoProxy.connect(accounts[i]) await expect(() => @@ -346,7 +346,7 @@ describe('Deployments test setup', () => { await ethers.provider.send('hardhat_reset', [ { forking: { - jsonRpcUrl: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`, + jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436, }, }, From 35004bc184a6df7bc464294ce7806491355c83dd Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 16 Feb 2022 16:50:46 +0300 Subject: [PATCH 02/18] contracts compile successfully --- contracts/AddInstanceProposal.sol | 89 +++++ .../CreateFactoryAndAddInstancesProposal.sol | 71 ---- contracts/ERC20TornadoCloneable.sol | 7 +- contracts/ERC20TornadoVirtual.sol | 337 ------------------ ...ceCloneFactory.sol => InstanceFactory.sol} | 73 ++-- .../denomination_templates/Add1Instance.sol | 41 --- .../denomination_templates/Add2Instances.sol | 59 --- .../denomination_templates/Add3Instances.sol | 63 ---- .../denomination_templates/Add4Instances.sol | 67 ---- .../denomination_templates/Add5Instances.sol | 71 ---- .../denomination_templates/Add6Instances.sol | 75 ---- contracts/interfaces/IGovernance.sol | 7 + contracts/interfaces/IInstanceRegistry.sol | 28 ++ contracts/mock/CompileDummy.sol | 5 +- contracts/tornado_proxy/ITornadoInstance.sol | 21 -- contracts/tornado_proxy/ITornadoTrees.sol | 9 - contracts/tornado_proxy/TornadoProxy.sol | 148 -------- hardhat.config.js | 10 +- package.json | 25 +- resources/instances.js | 26 -- yarn.lock | 298 +++++++++++++++- 21 files changed, 488 insertions(+), 1042 deletions(-) create mode 100644 contracts/AddInstanceProposal.sol delete mode 100644 contracts/CreateFactoryAndAddInstancesProposal.sol delete mode 100644 contracts/ERC20TornadoVirtual.sol rename contracts/{TornadoInstanceCloneFactory.sol => InstanceFactory.sol} (72%) delete mode 100644 contracts/denomination_templates/Add1Instance.sol delete mode 100644 contracts/denomination_templates/Add2Instances.sol delete mode 100644 contracts/denomination_templates/Add3Instances.sol delete mode 100644 contracts/denomination_templates/Add4Instances.sol delete mode 100644 contracts/denomination_templates/Add5Instances.sol delete mode 100644 contracts/denomination_templates/Add6Instances.sol create mode 100644 contracts/interfaces/IGovernance.sol create mode 100644 contracts/interfaces/IInstanceRegistry.sol delete mode 100644 contracts/tornado_proxy/ITornadoInstance.sol delete mode 100644 contracts/tornado_proxy/ITornadoTrees.sol delete mode 100644 contracts/tornado_proxy/TornadoProxy.sol delete mode 100644 resources/instances.js diff --git a/contracts/AddInstanceProposal.sol b/contracts/AddInstanceProposal.sol new file mode 100644 index 0000000..30033ff --- /dev/null +++ b/contracts/AddInstanceProposal.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.7.6; +pragma abicoder v2; + +import "./interfaces/IInstanceRegistry.sol"; +import "./InstanceFactory.sol"; + +contract AddInstanceProposal { + InstanceFactory public immutable instanceFactory; + IInstanceRegistry public immutable instanceRegistry; + address public immutable token; + uint24 public immutable uniswapPoolSwappingFee; + + uint256 internal immutable numInstances; + uint256 internal immutable denomination0; + uint256 internal immutable denomination1; + uint256 internal immutable denomination2; + uint32 internal immutable protocolFee0; + uint32 internal immutable protocolFee1; + uint32 internal immutable protocolFee2; + + event AddInstanceForRegistry(address instance, address token, uint256 denomination); + + constructor( + address _instanceFactory, + address _instanceRegistry, + address _token, + uint24 _uniswapPoolSwappingFee, + uint256[] memory _denominations, + uint32[] memory _protocolFees + ) { + instanceFactory = InstanceFactory(_instanceFactory); + instanceRegistry = IInstanceRegistry(_instanceRegistry); + token = _token; + uniswapPoolSwappingFee = _uniswapPoolSwappingFee; + + require(_denominations.length == _protocolFees.length); + uint256 _numInstances = _denominations.length; + require(_numInstances > 0); + require(_numInstances < 4); + numInstances = _numInstances; + + denomination0 = _numInstances > 0 ? _denominations[0] : 0; + denomination1 = _numInstances > 1 ? _denominations[1] : 0; + denomination2 = _numInstances > 2 ? _denominations[2] : 0; + protocolFee0 = _numInstances > 0 ? _protocolFees[0] : 0; + protocolFee1 = _numInstances > 1 ? _protocolFees[1] : 0; + protocolFee2 = _numInstances > 2 ? _protocolFees[2] : 0; + } + + function executeProposal() external { + for (uint256 i = 0; i < numInstances; i++) { + address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token); + + IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance( + true, + IERC20(token), + IInstanceRegistry.InstanceState.ENABLED, + uniswapPoolSwappingFee, + protocolFeeByIndex(i) + ); + + IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData); + + instanceRegistry.updateInstance(tornadoForUpdate); + + emit AddInstanceForRegistry(address(instance), token, denominationByIndex(i)); + } + } + + function denominationByIndex(uint256 _index) public view returns (uint256) { + if (_index == 0) { return denomination0; } + else if (_index == 1) { return denomination1; } + else if (_index == 2) { return denomination2; } + else { + revert("Invalid instance index"); + } + } + + function protocolFeeByIndex(uint256 _index) public view returns (uint32) { + if (_index == 0) { return protocolFee0; } + else if (_index == 1) { return protocolFee1; } + else if (_index == 2) { return protocolFee2; } + else { + revert("Invalid instance index"); + } + } +} diff --git a/contracts/CreateFactoryAndAddInstancesProposal.sol b/contracts/CreateFactoryAndAddInstancesProposal.sol deleted file mode 100644 index 68886ec..0000000 --- a/contracts/CreateFactoryAndAddInstancesProposal.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -import "./TornadoInstanceCloneFactory.sol"; -import "./tornado_proxy/TornadoProxy.sol"; - -contract CreateFactoryAndAddInstancesProposal { - address public constant verifier = 0xce172ce1F20EC0B3728c9965470eaf994A03557A; - address public constant hasher = 0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe; - address public constant governance = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce; - - TornadoInstanceCloneFactory public immutable instanceFactory; - address public immutable token; - address public immutable proxyAddress; - - uint256 public immutable denomination1; - uint256 public immutable denomination2; - uint256 public immutable denomination3; - uint256 public immutable denomination4; - - event UpdatedInstanceForProxy(address indexed instance, address indexed token, uint256 indexed denomination); - - constructor( - address _proxyAddress, - uint256[4] memory _denominations, - address _token - ) { - TornadoInstanceCloneFactory cachedFactory = new TornadoInstanceCloneFactory(verifier, hasher, 20); - cachedFactory.transferOwnership(governance); - instanceFactory = cachedFactory; - - token = _token; - proxyAddress = _proxyAddress; - - denomination1 = _denominations[0]; - denomination2 = _denominations[1]; - denomination3 = _denominations[2]; - denomination4 = _denominations[3]; - } - - function executeProposal() external { - TornadoProxy tornadoProxy = TornadoProxy(proxyAddress); - - for (uint256 i = 0; i < 4; i++) { - ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token)); - TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance( - true, - IERC20(token), - TornadoProxy.InstanceState.ENABLED - ); - TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData); - - tornadoProxy.updateInstance(tornadoForUpdate); - - emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination()); - } - } - - function denominations(uint256 index) private view returns (uint256) { - if (index > 2) { - return denomination4; - } else if (index > 1) { - return denomination3; - } else if (index > 0) { - return denomination2; - } else { - return denomination1; - } - } -} diff --git a/contracts/ERC20TornadoCloneable.sol b/contracts/ERC20TornadoCloneable.sol index d9abb7d..d2f8f1e 100644 --- a/contracts/ERC20TornadoCloneable.sol +++ b/contracts/ERC20TornadoCloneable.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; + +pragma solidity 0.7.6; pragma abicoder v2; import "tornado-core/contracts/ERC20Tornado.sol"; @@ -13,12 +14,10 @@ contract ERC20TornadoCloneable is ERC20Tornado { address _token ) external { require(denomination == 0 && levels == 0, "already initialized"); - /// In Constructor: ERC20Tornado + token = IERC20(_token); - /// In Constructor: Tornado require(_denomination > 0, "denomination should be greater than 0"); denomination = _denomination; - /// In Constructor: MerkleTreeWithHistory require(_merkleTreeHeight > 0, "_levels should be greater than zero"); require(_merkleTreeHeight < 32, "_levels should be less than 32"); levels = _merkleTreeHeight; diff --git a/contracts/ERC20TornadoVirtual.sol b/contracts/ERC20TornadoVirtual.sol deleted file mode 100644 index 18974d6..0000000 --- a/contracts/ERC20TornadoVirtual.sol +++ /dev/null @@ -1,337 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -// https://tornado.cash -/* - * d888888P dP a88888b. dP - * 88 88 d8' `88 88 - * 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b. - * 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88 - * 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88 - * dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP - * ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo - */ - -interface IHasher { - function MiMCSponge(uint256 in_xL, uint256 in_xR) external pure returns (uint256 xL, uint256 xR); -} - -contract MerkleTreeWithHistory { - uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE - - IHasher public hasher; - uint32 public levels; - - // the following variables are made public for easier testing and debugging and - // are not supposed to be accessed in regular code - - // filledSubtrees and roots could be bytes32[size], but using mappings makes it cheaper because - // it removes index range check on every interaction - mapping(uint256 => bytes32) public filledSubtrees; - mapping(uint256 => bytes32) public roots; - uint32 public constant ROOT_HISTORY_SIZE = 30; - uint32 public currentRootIndex = 0; - uint32 public nextIndex = 0; - - constructor(uint32 _levels, IHasher _hasher) { - require(_levels > 0, "_levels should be greater than zero"); - require(_levels < 32, "_levels should be less than 32"); - levels = _levels; - hasher = _hasher; - - for (uint32 i = 0; i < _levels; i++) { - filledSubtrees[i] = zeros(i); - } - - roots[0] = zeros(_levels - 1); - } - - /** - @dev Hash 2 tree leaves, returns MiMC(_left, _right) - */ - function hashLeftRight( - IHasher _hasher, - bytes32 _left, - bytes32 _right - ) public pure returns (bytes32) { - require(uint256(_left) < FIELD_SIZE, "_left should be inside the field"); - require(uint256(_right) < FIELD_SIZE, "_right should be inside the field"); - uint256 R = uint256(_left); - uint256 C = 0; - (R, C) = _hasher.MiMCSponge(R, C); - R = addmod(R, uint256(_right), FIELD_SIZE); - (R, C) = _hasher.MiMCSponge(R, C); - return bytes32(R); - } - - function _insert(bytes32 _leaf) internal returns (uint32 index) { - uint32 _nextIndex = nextIndex; - require(_nextIndex != uint32(2)**levels, "Merkle tree is full. No more leaves can be added"); - uint32 currentIndex = _nextIndex; - bytes32 currentLevelHash = _leaf; - bytes32 left; - bytes32 right; - - for (uint32 i = 0; i < levels; i++) { - if (currentIndex % 2 == 0) { - left = currentLevelHash; - right = zeros(i); - filledSubtrees[i] = currentLevelHash; - } else { - left = filledSubtrees[i]; - right = currentLevelHash; - } - currentLevelHash = hashLeftRight(hasher, left, right); - currentIndex /= 2; - } - - uint32 newRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE; - currentRootIndex = newRootIndex; - roots[newRootIndex] = currentLevelHash; - nextIndex = _nextIndex + 1; - return _nextIndex; - } - - /** - @dev Whether the root is present in the root history - */ - function isKnownRoot(bytes32 _root) public view returns (bool) { - if (_root == 0) { - return false; - } - uint32 _currentRootIndex = currentRootIndex; - uint32 i = _currentRootIndex; - do { - if (_root == roots[i]) { - return true; - } - if (i == 0) { - i = ROOT_HISTORY_SIZE; - } - i--; - } while (i != _currentRootIndex); - return false; - } - - /** - @dev Returns the last root - */ - function getLastRoot() public view returns (bytes32) { - return roots[currentRootIndex]; - } - - /// @dev provides Zero (Empty) elements for a MiMC MerkleTree. Up to 32 levels - function zeros(uint256 i) public pure returns (bytes32) { - if (i == 0) return bytes32(0x2fe54c60d3acabf3343a35b6eba15db4821b340f76e741e2249685ed4899af6c); - else if (i == 1) return bytes32(0x256a6135777eee2fd26f54b8b7037a25439d5235caee224154186d2b8a52e31d); - else if (i == 2) return bytes32(0x1151949895e82ab19924de92c40a3d6f7bcb60d92b00504b8199613683f0c200); - else if (i == 3) return bytes32(0x20121ee811489ff8d61f09fb89e313f14959a0f28bb428a20dba6b0b068b3bdb); - else if (i == 4) return bytes32(0x0a89ca6ffa14cc462cfedb842c30ed221a50a3d6bf022a6a57dc82ab24c157c9); - else if (i == 5) return bytes32(0x24ca05c2b5cd42e890d6be94c68d0689f4f21c9cec9c0f13fe41d566dfb54959); - else if (i == 6) return bytes32(0x1ccb97c932565a92c60156bdba2d08f3bf1377464e025cee765679e604a7315c); - else if (i == 7) return bytes32(0x19156fbd7d1a8bf5cba8909367de1b624534ebab4f0f79e003bccdd1b182bdb4); - else if (i == 8) return bytes32(0x261af8c1f0912e465744641409f622d466c3920ac6e5ff37e36604cb11dfff80); - else if (i == 9) return bytes32(0x0058459724ff6ca5a1652fcbc3e82b93895cf08e975b19beab3f54c217d1c007); - else if (i == 10) return bytes32(0x1f04ef20dee48d39984d8eabe768a70eafa6310ad20849d4573c3c40c2ad1e30); - else if (i == 11) return bytes32(0x1bea3dec5dab51567ce7e200a30f7ba6d4276aeaa53e2686f962a46c66d511e5); - else if (i == 12) return bytes32(0x0ee0f941e2da4b9e31c3ca97a40d8fa9ce68d97c084177071b3cb46cd3372f0f); - else if (i == 13) return bytes32(0x1ca9503e8935884501bbaf20be14eb4c46b89772c97b96e3b2ebf3a36a948bbd); - else if (i == 14) return bytes32(0x133a80e30697cd55d8f7d4b0965b7be24057ba5dc3da898ee2187232446cb108); - else if (i == 15) return bytes32(0x13e6d8fc88839ed76e182c2a779af5b2c0da9dd18c90427a644f7e148a6253b6); - else if (i == 16) return bytes32(0x1eb16b057a477f4bc8f572ea6bee39561098f78f15bfb3699dcbb7bd8db61854); - else if (i == 17) return bytes32(0x0da2cb16a1ceaabf1c16b838f7a9e3f2a3a3088d9e0a6debaa748114620696ea); - else if (i == 18) return bytes32(0x24a3b3d822420b14b5d8cb6c28a574f01e98ea9e940551d2ebd75cee12649f9d); - else if (i == 19) return bytes32(0x198622acbd783d1b0d9064105b1fc8e4d8889de95c4c519b3f635809fe6afc05); - else if (i == 20) return bytes32(0x29d7ed391256ccc3ea596c86e933b89ff339d25ea8ddced975ae2fe30b5296d4); - else if (i == 21) return bytes32(0x19be59f2f0413ce78c0c3703a3a5451b1d7f39629fa33abd11548a76065b2967); - else if (i == 22) return bytes32(0x1ff3f61797e538b70e619310d33f2a063e7eb59104e112e95738da1254dc3453); - else if (i == 23) return bytes32(0x10c16ae9959cf8358980d9dd9616e48228737310a10e2b6b731c1a548f036c48); - else if (i == 24) return bytes32(0x0ba433a63174a90ac20992e75e3095496812b652685b5e1a2eae0b1bf4e8fcd1); - else if (i == 25) return bytes32(0x019ddb9df2bc98d987d0dfeca9d2b643deafab8f7036562e627c3667266a044c); - else if (i == 26) return bytes32(0x2d3c88b23175c5a5565db928414c66d1912b11acf974b2e644caaac04739ce99); - else if (i == 27) return bytes32(0x2eab55f6ae4e66e32c5189eed5c470840863445760f5ed7e7b69b2a62600f354); - else if (i == 28) return bytes32(0x002df37a2642621802383cf952bf4dd1f32e05433beeb1fd41031fb7eace979d); - else if (i == 29) return bytes32(0x104aeb41435db66c3e62feccc1d6f5d98d0a0ed75d1374db457cf462e3a1f427); - else if (i == 30) return bytes32(0x1f3c6fd858e9a7d4b0d1f38e256a09d81d5a5e3c963987e2d4b814cfab7c6ebb); - else if (i == 31) return bytes32(0x2c7a07d20dff79d01fecedc1134284a8d08436606c93693b67e333f671bf69cc); - else revert("Index out of bounds"); - } -} - -// https://tornado.cash -/* - * d888888P dP a88888b. dP - * 88 88 d8' `88 88 - * 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b. - * 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88 - * 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88 - * dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP - * ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo - */ - -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; - -interface IVerifier { - function verifyProof(bytes memory _proof, uint256[6] memory _input) external returns (bool); -} - -abstract contract Tornado is MerkleTreeWithHistory, ReentrancyGuard { - IVerifier public verifier; - uint256 public denomination; - - mapping(bytes32 => bool) public nullifierHashes; - // we store all commitments just to prevent accidental deposits with the same commitment - mapping(bytes32 => bool) public commitments; - - event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp); - event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee); - - /** - @dev The constructor - @param _verifier the address of SNARK verifier for this contract - @param _hasher the address of MiMC hash contract - @param _denomination transfer amount for each deposit - @param _merkleTreeHeight the height of deposits' Merkle Tree - */ - constructor( - IVerifier _verifier, - IHasher _hasher, - uint256 _denomination, - uint32 _merkleTreeHeight - ) MerkleTreeWithHistory(_merkleTreeHeight, _hasher) { - require(_denomination > 0, "denomination should be greater than 0"); - verifier = _verifier; - denomination = _denomination; - } - - /** - @dev Deposit funds into the contract. The caller must send (for ETH) or approve (for ERC20) value equal to or `denomination` of this instance. - @param _commitment the note commitment, which is PedersenHash(nullifier + secret) - */ - function deposit(bytes32 _commitment) external payable nonReentrant { - require(!commitments[_commitment], "The commitment has been submitted"); - - uint32 insertedIndex = _insert(_commitment); - commitments[_commitment] = true; - _processDeposit(); - - emit Deposit(_commitment, insertedIndex, block.timestamp); - } - - /** @dev this function is defined in a child contract */ - function _processDeposit() internal virtual; - - /** - @dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs - `input` array consists of: - - merkle root of all deposits in the contract - - hash of unique deposit nullifier to prevent double spends - - the recipient of funds - - optional fee that goes to the transaction sender (usually a relay) - */ - function withdraw( - bytes calldata _proof, - bytes32 _root, - bytes32 _nullifierHash, - address payable _recipient, - address payable _relayer, - uint256 _fee, - uint256 _refund - ) external payable nonReentrant { - require(_fee <= denomination, "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( - _proof, - [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _fee, _refund] - ), - "Invalid withdraw proof" - ); - - nullifierHashes[_nullifierHash] = true; - _processWithdraw(_recipient, _relayer, _fee, _refund); - emit Withdrawal(_recipient, _nullifierHash, _relayer, _fee); - } - - /** @dev this function is defined in a child contract */ - function _processWithdraw( - address payable _recipient, - address payable _relayer, - uint256 _fee, - uint256 _refund - ) internal virtual; - - /** @dev whether a note is already spent */ - function isSpent(bytes32 _nullifierHash) public view returns (bool) { - return nullifierHashes[_nullifierHash]; - } - - /** @dev whether an array of notes is already spent */ - function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns (bool[] memory spent) { - spent = new bool[](_nullifierHashes.length); - for (uint256 i = 0; i < _nullifierHashes.length; i++) { - if (isSpent(_nullifierHashes[i])) { - spent[i] = true; - } - } - } -} - -// https://tornado.cash -/* - * d888888P dP a88888b. dP - * 88 88 d8' `88 88 - * 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b. - * 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88 - * 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88 - * dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP - * ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo - */ - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; - -contract ERC20Tornado is Tornado { - using SafeERC20 for IERC20; - IERC20 public token; - - constructor( - IVerifier _verifier, - IHasher _hasher, - uint256 _denomination, - uint32 _merkleTreeHeight, - IERC20 _token - ) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) { - token = _token; - } - - function _processDeposit() internal override { - require(msg.value == 0, "ETH value is supposed to be 0 for ERC20 instance"); - token.safeTransferFrom(msg.sender, address(this), denomination); - } - - function _processWithdraw( - address payable _recipient, - address payable _relayer, - uint256 _fee, - uint256 _refund - ) internal override { - require(msg.value == _refund, "Incorrect refund amount received by the contract"); - - token.safeTransfer(_recipient, denomination - _fee); - if (_fee > 0) { - token.safeTransfer(_relayer, _fee); - } - - if (_refund > 0) { - (bool success, ) = _recipient.call{ value: _refund }(""); - if (!success) { - // let's return _refund back to the relayer - _relayer.transfer(_refund); - } - } - } -} diff --git a/contracts/TornadoInstanceCloneFactory.sol b/contracts/InstanceFactory.sol similarity index 72% rename from contracts/TornadoInstanceCloneFactory.sol rename to contracts/InstanceFactory.sol index 944913a..b10d48e 100644 --- a/contracts/TornadoInstanceCloneFactory.sol +++ b/contracts/InstanceFactory.sol @@ -1,17 +1,20 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; + +pragma solidity 0.7.6; pragma abicoder v2; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; - import "@openzeppelin/contracts/proxy/Clones.sol"; import "./ERC20TornadoCloneable.sol"; +import "./AddInstanceProposal.sol"; +import "./interfaces/IGovernance.sol"; -contract TornadoInstanceCloneFactory is Ownable { +contract InstanceFactory is Ownable { using Clones for address; using Address for address; + address immutable governance; address public implementation; address public verifier; address public hasher; @@ -26,13 +29,58 @@ contract TornadoInstanceCloneFactory is Ownable { constructor( address _verifier, address _hasher, - uint32 _merkleTreeHeight + uint32 _merkleTreeHeight, + address _governance ) { verifier = _verifier; hasher = _hasher; merkleTreeHeight = _merkleTreeHeight; ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher); implementation = address(implContract); + governance = _governance; + transferOwnership(_governance); + } + + function createInstanceClone(uint256 _denomination, address _token) external onlyOwner returns (address) { + bytes32 salt = keccak256(abi.encodePacked(_denomination, _token)); + + require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance already exists"); + + address newClone = implementation.cloneDeterministic(salt); + + emit NewInstanceCloneCreated(newClone); + ERC20TornadoCloneable(newClone).init(_denomination, merkleTreeHeight, _token); + return newClone; + } + + function getInstanceAddress(uint256 _denomination, address _token) public view returns (address) { + bytes32 salt = keccak256(abi.encodePacked(_denomination, _token)); + return implementation.predictDeterministicAddress(salt); + } + + function createNewProposal( + string calldata _description, + address _instanceRegistry, + address _token, + uint24 _uniswapPoolSwappingFee, + uint256[] memory _denominations, + uint32[] memory _protocolFees + ) external returns (address) { + // TODO test params + require(_denominations.length == _protocolFees.length); + + address proposal = address(new AddInstanceProposal( + address(this), + _instanceRegistry, + _token, + _uniswapPoolSwappingFee, + _denominations, + _protocolFees + )); + + IGovernance(governance).propose(proposal, _description); + + return proposal; } function setVerifier(address _verifier) external onlyOwner { @@ -58,21 +106,4 @@ contract TornadoInstanceCloneFactory is Ownable { function generateNewImplementation() external onlyOwner { implementation = address(new ERC20TornadoCloneable(verifier, hasher)); } - - function createInstanceClone(uint256 _denomination, address _token) external onlyOwner returns (address) { - bytes32 salt = keccak256(abi.encodePacked(_denomination, _token)); - - require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance for this denomination already exists"); - - address newClone = implementation.cloneDeterministic(salt); - - emit NewInstanceCloneCreated(newClone); - ERC20TornadoCloneable(newClone).init(_denomination, merkleTreeHeight, _token); - return newClone; - } - - function getInstanceAddress(uint256 _denomination, address _token) public view returns (address) { - bytes32 salt = keccak256(abi.encodePacked(_denomination, _token)); - return implementation.predictDeterministicAddress(salt); - } } diff --git a/contracts/denomination_templates/Add1Instance.sol b/contracts/denomination_templates/Add1Instance.sol deleted file mode 100644 index a369ba1..0000000 --- a/contracts/denomination_templates/Add1Instance.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -import "../TornadoInstanceCloneFactory.sol"; -import "../tornado_proxy/TornadoProxy.sol"; - -contract Add1Instance { - TornadoInstanceCloneFactory public immutable instanceFactory; - address public immutable token; - address public immutable proxyAddress; - uint256 public immutable denomination; - - event UpdatedInstanceForProxy(address instance, address token, uint256 denomination); - - constructor( - address _proxyAddress, - address _instanceFactory, - uint256 _denomination, - address _token - ) { - instanceFactory = TornadoInstanceCloneFactory(_instanceFactory); - token = _token; - proxyAddress = _proxyAddress; - denomination = _denomination; - } - - function executeProposal() external { - TornadoProxy tornadoProxy = TornadoProxy(proxyAddress); - - ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denomination, token)); - - TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(true, IERC20(token), TornadoProxy.InstanceState.ENABLED); - - TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData); - - tornadoProxy.updateInstance(tornadoForUpdate); - - emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination()); - } -} diff --git a/contracts/denomination_templates/Add2Instances.sol b/contracts/denomination_templates/Add2Instances.sol deleted file mode 100644 index bbc27fb..0000000 --- a/contracts/denomination_templates/Add2Instances.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -import "../TornadoInstanceCloneFactory.sol"; -import "../tornado_proxy/TornadoProxy.sol"; - -contract Add2Instances { - TornadoInstanceCloneFactory public immutable instanceFactory; - address public immutable token; - address public immutable proxyAddress; - - uint256 public immutable denomination1; - uint256 public immutable denomination2; - - event UpdatedInstanceForProxy(address instance, address token, uint256 denomination); - - constructor( - address _proxyAddress, - address _instanceFactory, - uint256[2] memory _denominations, - address _token - ) { - instanceFactory = TornadoInstanceCloneFactory(_instanceFactory); - token = _token; - proxyAddress = _proxyAddress; - - denomination1 = _denominations[0]; - denomination2 = _denominations[1]; - } - - function executeProposal() external { - TornadoProxy tornadoProxy = TornadoProxy(proxyAddress); - - for (uint256 i = 0; i < 2; i++) { - ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token)); - - TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance( - true, - IERC20(token), - TornadoProxy.InstanceState.ENABLED - ); - - TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData); - - tornadoProxy.updateInstance(tornadoForUpdate); - - emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination()); - } - } - - function denominations(uint256 index) private view returns (uint256) { - if (index > 0) { - return denomination2; - } else { - return denomination1; - } - } -} diff --git a/contracts/denomination_templates/Add3Instances.sol b/contracts/denomination_templates/Add3Instances.sol deleted file mode 100644 index b4c7b35..0000000 --- a/contracts/denomination_templates/Add3Instances.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -import "../TornadoInstanceCloneFactory.sol"; -import "../tornado_proxy/TornadoProxy.sol"; - -contract Add3Instances { - TornadoInstanceCloneFactory public immutable instanceFactory; - address public immutable token; - address public immutable proxyAddress; - - uint256 public immutable denomination1; - uint256 public immutable denomination2; - uint256 public immutable denomination3; - - event UpdatedInstanceForProxy(address instance, address token, uint256 denomination); - - constructor( - address _proxyAddress, - address _instanceFactory, - uint256[3] memory _denominations, - address _token - ) { - instanceFactory = TornadoInstanceCloneFactory(_instanceFactory); - token = _token; - proxyAddress = _proxyAddress; - - denomination1 = _denominations[0]; - denomination2 = _denominations[1]; - denomination3 = _denominations[2]; - } - - function executeProposal() external { - TornadoProxy tornadoProxy = TornadoProxy(proxyAddress); - - for (uint256 i = 0; i < 3; i++) { - ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token)); - - TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance( - true, - IERC20(token), - TornadoProxy.InstanceState.ENABLED - ); - - TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData); - - tornadoProxy.updateInstance(tornadoForUpdate); - - emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination()); - } - } - - function denominations(uint256 index) private view returns (uint256) { - if (index > 1) { - return denomination3; - } else if (index > 0) { - return denomination2; - } else { - return denomination1; - } - } -} diff --git a/contracts/denomination_templates/Add4Instances.sol b/contracts/denomination_templates/Add4Instances.sol deleted file mode 100644 index a6178aa..0000000 --- a/contracts/denomination_templates/Add4Instances.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -import "../TornadoInstanceCloneFactory.sol"; -import "../tornado_proxy/TornadoProxy.sol"; - -contract Add4Instances { - TornadoInstanceCloneFactory public immutable instanceFactory; - address public immutable token; - address public immutable proxyAddress; - - uint256 public immutable denomination1; - uint256 public immutable denomination2; - uint256 public immutable denomination3; - uint256 public immutable denomination4; - - event UpdatedInstanceForProxy(address instance, address token, uint256 denomination); - - constructor( - address _proxyAddress, - address _instanceFactory, - uint256[4] memory _denominations, - address _token - ) { - instanceFactory = TornadoInstanceCloneFactory(_instanceFactory); - token = _token; - proxyAddress = _proxyAddress; - - denomination1 = _denominations[0]; - denomination2 = _denominations[1]; - denomination3 = _denominations[2]; - denomination4 = _denominations[3]; - } - - function executeProposal() external { - TornadoProxy tornadoProxy = TornadoProxy(proxyAddress); - - for (uint256 i = 0; i < 4; i++) { - ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token)); - - TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance( - true, - IERC20(token), - TornadoProxy.InstanceState.ENABLED - ); - - TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData); - - tornadoProxy.updateInstance(tornadoForUpdate); - - emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination()); - } - } - - function denominations(uint256 index) private view returns (uint256) { - if (index > 2) { - return denomination4; - } else if (index > 1) { - return denomination3; - } else if (index > 0) { - return denomination2; - } else { - return denomination1; - } - } -} diff --git a/contracts/denomination_templates/Add5Instances.sol b/contracts/denomination_templates/Add5Instances.sol deleted file mode 100644 index 195a953..0000000 --- a/contracts/denomination_templates/Add5Instances.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -import "../TornadoInstanceCloneFactory.sol"; -import "../tornado_proxy/TornadoProxy.sol"; - -contract Add5Instances { - TornadoInstanceCloneFactory public immutable instanceFactory; - address public immutable token; - address public immutable proxyAddress; - - uint256 public immutable denomination1; - uint256 public immutable denomination2; - uint256 public immutable denomination3; - uint256 public immutable denomination4; - uint256 public immutable denomination5; - - event UpdatedInstanceForProxy(address instance, address token, uint256 denomination); - - constructor( - address _proxyAddress, - address _instanceFactory, - uint256[5] memory _denominations, - address _token - ) { - instanceFactory = TornadoInstanceCloneFactory(_instanceFactory); - token = _token; - proxyAddress = _proxyAddress; - - denomination1 = _denominations[0]; - denomination2 = _denominations[1]; - denomination3 = _denominations[2]; - denomination4 = _denominations[3]; - denomination5 = _denominations[4]; - } - - function executeProposal() external { - TornadoProxy tornadoProxy = TornadoProxy(proxyAddress); - - for (uint256 i = 0; i < 5; i++) { - ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token)); - - TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance( - true, - IERC20(token), - TornadoProxy.InstanceState.ENABLED - ); - - TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData); - - tornadoProxy.updateInstance(tornadoForUpdate); - - emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination()); - } - } - - function denominations(uint256 index) private view returns (uint256) { - if (index > 3) { - return denomination5; - } else if (index > 2) { - return denomination4; - } else if (index > 1) { - return denomination3; - } else if (index > 0) { - return denomination2; - } else { - return denomination1; - } - } -} diff --git a/contracts/denomination_templates/Add6Instances.sol b/contracts/denomination_templates/Add6Instances.sol deleted file mode 100644 index 79640ad..0000000 --- a/contracts/denomination_templates/Add6Instances.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.6; -pragma abicoder v2; - -import "../TornadoInstanceCloneFactory.sol"; -import "../tornado_proxy/TornadoProxy.sol"; - -contract Add6Instances { - TornadoInstanceCloneFactory public immutable instanceFactory; - address public immutable token; - address public immutable proxyAddress; - - uint256 public immutable denomination1; - uint256 public immutable denomination2; - uint256 public immutable denomination3; - uint256 public immutable denomination4; - uint256 public immutable denomination5; - uint256 public immutable denomination6; - - event UpdatedInstanceForProxy(address instance, address token, uint256 denomination); - - constructor( - address _proxyAddress, - address _instanceFactory, - uint256[6] memory _denominations, - address _token - ) { - instanceFactory = TornadoInstanceCloneFactory(_instanceFactory); - token = _token; - proxyAddress = _proxyAddress; - - denomination1 = _denominations[0]; - denomination2 = _denominations[1]; - denomination3 = _denominations[2]; - denomination4 = _denominations[3]; - denomination5 = _denominations[4]; - denomination6 = _denominations[5]; - } - - function executeProposal() external { - TornadoProxy tornadoProxy = TornadoProxy(proxyAddress); - - for (uint256 i = 0; i < 6; i++) { - ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token)); - - TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance( - true, - IERC20(token), - TornadoProxy.InstanceState.ENABLED - ); - - TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData); - - tornadoProxy.updateInstance(tornadoForUpdate); - - emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination()); - } - } - - function denominations(uint256 index) private view returns (uint256) { - if (index > 4) { - return denomination6; - } else if (index > 3) { - return denomination5; - } else if (index > 2) { - return denomination4; - } else if (index > 1) { - return denomination3; - } else if (index > 0) { - return denomination2; - } else { - return denomination1; - } - } -} diff --git a/contracts/interfaces/IGovernance.sol b/contracts/interfaces/IGovernance.sol new file mode 100644 index 0000000..c7b16d4 --- /dev/null +++ b/contracts/interfaces/IGovernance.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.7.6; + +interface IGovernance { + function propose(address target, string memory description) external returns (uint256); +} diff --git a/contracts/interfaces/IInstanceRegistry.sol b/contracts/interfaces/IInstanceRegistry.sol new file mode 100644 index 0000000..657e340 --- /dev/null +++ b/contracts/interfaces/IInstanceRegistry.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IInstanceRegistry { + + enum InstanceState { DISABLED, ENABLED} + + struct Instance { + bool isERC20; + IERC20 token; + InstanceState state; + // the fee of the uniswap pool which will be used to get a TWAP + uint24 uniswapPoolSwappingFee; + // the fee the protocol takes from relayer, it should be multiplied by PROTOCOL_FEE_DIVIDER from FeeManager.sol + uint32 protocolFeePercentage; + } + + struct Tornado { + address addr; + Instance instance; + } + + function updateInstance(Tornado calldata _tornado) external; +} diff --git a/contracts/mock/CompileDummy.sol b/contracts/mock/CompileDummy.sol index 05e7374..50b31cf 100644 --- a/contracts/mock/CompileDummy.sol +++ b/contracts/mock/CompileDummy.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; -import { Governance } from "tornado-governance/contracts/Governance.sol"; +pragma solidity 0.6.12 || 0.7.6; + +import { Governance } from "tornado-governance/contracts/v1/Governance.sol"; contract CompileDummy {} diff --git a/contracts/tornado_proxy/ITornadoInstance.sol b/contracts/tornado_proxy/ITornadoInstance.sol deleted file mode 100644 index 07cf927..0000000 --- a/contracts/tornado_proxy/ITornadoInstance.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.7.6; - -interface ITornadoInstance { - function token() external view returns (address); - - function denomination() external view returns (uint256); - - function deposit(bytes32 commitment) external payable; - - function withdraw( - bytes calldata proof, - bytes32 root, - bytes32 nullifierHash, - address payable recipient, - address payable relayer, - uint256 fee, - uint256 refund - ) external payable; -} diff --git a/contracts/tornado_proxy/ITornadoTrees.sol b/contracts/tornado_proxy/ITornadoTrees.sol deleted file mode 100644 index 9b4d3e0..0000000 --- a/contracts/tornado_proxy/ITornadoTrees.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.7.6; - -interface ITornadoTrees { - function registerDeposit(address instance, bytes32 commitment) external; - - function registerWithdrawal(address instance, bytes32 nullifier) external; -} diff --git a/contracts/tornado_proxy/TornadoProxy.sol b/contracts/tornado_proxy/TornadoProxy.sol deleted file mode 100644 index c546405..0000000 --- a/contracts/tornado_proxy/TornadoProxy.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.7.6; -pragma experimental ABIEncoderV2; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "@openzeppelin/contracts/math/Math.sol"; -import "./ITornadoInstance.sol"; -import "./ITornadoTrees.sol"; - -contract TornadoProxy { - using SafeERC20 for IERC20; - - event EncryptedNote(address indexed sender, bytes encryptedNote); - event InstanceStateUpdated(ITornadoInstance indexed instance, InstanceState state); - event TornadoTreesUpdated(ITornadoTrees addr); - - enum InstanceState { - DISABLED, - ENABLED, - MINEABLE - } - - struct Instance { - bool isERC20; - IERC20 token; - InstanceState state; - } - - struct Tornado { - ITornadoInstance addr; - Instance instance; - } - - ITornadoTrees public tornadoTrees; - address public immutable governance; - mapping(ITornadoInstance => Instance) public instances; - - modifier onlyGovernance() { - require(msg.sender == governance, "Not authorized"); - _; - } - - constructor( - address _tornadoTrees, - address _governance, - Tornado[] memory _instances - ) public { - tornadoTrees = ITornadoTrees(_tornadoTrees); - governance = _governance; - - for (uint256 i = 0; i < _instances.length; i++) { - _updateInstance(_instances[i]); - } - } - - function deposit( - ITornadoInstance _tornado, - bytes32 _commitment, - bytes calldata _encryptedNote - ) external payable { - Instance memory instance = instances[_tornado]; - require(instance.state != InstanceState.DISABLED, "The instance is not supported"); - - if (instance.isERC20) { - instance.token.safeTransferFrom(msg.sender, address(this), _tornado.denomination()); - } - _tornado.deposit{ value: msg.value }(_commitment); - - if (instance.state == InstanceState.MINEABLE) { - tornadoTrees.registerDeposit(address(_tornado), _commitment); - } - emit EncryptedNote(msg.sender, _encryptedNote); - } - - function withdraw( - ITornadoInstance _tornado, - bytes calldata _proof, - bytes32 _root, - bytes32 _nullifierHash, - address payable _recipient, - address payable _relayer, - uint256 _fee, - uint256 _refund - ) external payable { - Instance memory instance = instances[_tornado]; - require(instance.state != InstanceState.DISABLED, "The instance is not supported"); - - _tornado.withdraw{ value: msg.value }(_proof, _root, _nullifierHash, _recipient, _relayer, _fee, _refund); - if (instance.state == InstanceState.MINEABLE) { - tornadoTrees.registerWithdrawal(address(_tornado), _nullifierHash); - } - } - - function backupNotes(bytes[] calldata _encryptedNotes) external { - for (uint256 i = 0; i < _encryptedNotes.length; i++) { - emit EncryptedNote(msg.sender, _encryptedNotes[i]); - } - } - - function updateInstance(Tornado calldata _tornado) external onlyGovernance { - _updateInstance(_tornado); - } - - function setTornadoTreesContract(ITornadoTrees _tornadoTrees) external onlyGovernance { - tornadoTrees = _tornadoTrees; - emit TornadoTreesUpdated(_tornadoTrees); - } - - /// @dev Method to claim junk and accidentally sent tokens - function rescueTokens( - IERC20 _token, - address payable _to, - uint256 _amount - ) external onlyGovernance { - require(_to != address(0), "TORN: can not send to zero address"); - - if (_token == IERC20(0)) { - // for Ether - uint256 totalBalance = address(this).balance; - uint256 balance = Math.min(totalBalance, _amount); - _to.transfer(balance); - } else { - // any other erc20 - uint256 totalBalance = _token.balanceOf(address(this)); - uint256 balance = Math.min(totalBalance, _amount); - require(balance > 0, "TORN: trying to send 0 balance"); - _token.safeTransfer(_to, balance); - } - } - - function _updateInstance(Tornado memory _tornado) internal { - instances[_tornado.addr] = _tornado.instance; - if (_tornado.instance.isERC20) { - IERC20 token = IERC20(_tornado.addr.token()); - require(token == _tornado.instance.token, "Incorrect token"); - uint256 allowance = token.allowance(address(this), address(_tornado.addr)); - - if (_tornado.instance.state != InstanceState.DISABLED && allowance == 0) { - token.safeApprove(address(_tornado.addr), uint256(-1)); - } else if (_tornado.instance.state == InstanceState.DISABLED && allowance != 0) { - token.safeApprove(address(_tornado.addr), 0); - } - } - emit InstanceStateUpdated(_tornado.addr, _tornado.instance.state); - } -} diff --git a/hardhat.config.js b/hardhat.config.js index 8cdeff9..a068bdb 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -5,9 +5,9 @@ require('@nomiclabs/hardhat-waffle') require('hardhat-log-remover') require('solidity-coverage') -require('./tasks/deploy_proposal.js') -require('./tasks/deploy_factory_proposal.js') -require('./tasks/propose_proposal.js') +// require('./tasks/deploy_proposal.js') +// require('./tasks/deploy_factory_proposal.js') +// require('./tasks/propose_proposal.js') /** * @type import('hardhat/config').HardhatUserConfig */ @@ -19,7 +19,7 @@ module.exports = { settings: { optimizer: { enabled: true, - runs: 2000, + runs: 200, }, }, }, @@ -28,7 +28,7 @@ module.exports = { settings: { optimizer: { enabled: true, - runs: 2000, + runs: 200, }, }, }, diff --git a/package.json b/package.json index 8e01431..8e7d3b1 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,25 @@ { "name": "tornado-instances", "version": "1.0.0", - "main": "index.js", "license": "MIT", "scripts": { - "start": "node src/index.js", "eslint": "eslint --ext .js --ignore-path .gitignore .", "prettier:check": "prettier --check . --config .prettierrc", "prettier:fix": "prettier --write . --config .prettierrc", "lint": "yarn eslint && yarn prettier:check", - "deploy:proposal": "yarn hardhat --network mainnet deploy_proposal --factory-address", - "deploy:proposal:test": "yarn hardhat --network goerli deploy_proposal --factory-address", - "deploy:proposal:factory": "yarn hardhat --network mainnet deploy_factory_proposal", - "deploy:proposal:factory:test": "yarn hardhat --network goerli deploy_factory_proposal", - "propose": "yarn hardhat --network mainnet propose_proposal --proposal-address", + "compile": "yarn hardhat compile", "test": "yarn hardhat test", - "f:test": "yarn test && yarn clean", "clean": "yarn prettier:fix && yarn lint", "coverage": "yarn hardhat coverage --testfiles \"test/*.js\"" }, + "dependencies": { + "@openzeppelin/contracts": "3.4.2", + "@openzeppelin/upgrades-core": "^1.0.1", + "torn-token": "^1.0.4", + "tornado-cli": "^0.0.1", + "tornado-core": "https://github.com/tornadocash/tornado-core.git", + "tornado-governance": "2.0.0" + }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-etherscan": "^2.1.4", @@ -39,13 +40,5 @@ "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.7.17", "websnark": "^0.0.5" - }, - "dependencies": { - "@openzeppelin/contracts": "3.4.2", - "@openzeppelin/upgrades-core": "^1.0.1", - "torn-token": "^1.0.4", - "tornado-cli": "^0.0.1", - "tornado-core": "https://github.com/tornadocash/tornado-core.git", - "tornado-governance": "^1.0.2" } } diff --git a/resources/instances.js b/resources/instances.js deleted file mode 100644 index 52b2995..0000000 --- a/resources/instances.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = [ - { - tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919', - denomination: '33333333333333333333', - symbol: 'RAI', - decimals: 18, - }, - { - tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919', - denomination: '333333333333333333333', - symbol: 'RAI', - decimals: 18, - }, - { - tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919', - denomination: '3333333333333333333333', - symbol: 'RAI', - decimals: 18, - }, - { - tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919', - denomination: '33333333333333333333333', - symbol: 'RAI', - decimals: 18, - }, -] diff --git a/yarn.lock b/yarn.lock index 6510af4..2cd4925 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1232,6 +1232,13 @@ dependencies: "@ethersproject/logger" "^5.5.0" +"@ethersproject/networks@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.2.tgz#784c8b1283cd2a931114ab428dae1bd00c07630b" + integrity sha512-NEqPxbGBfy6O3x4ZTISb90SjEDkWYDUbEeIFhJly0F7sZjoQMnj5KYzMSkMkLKZ+1fGpx00EDpHQCy6PrDupkQ== + dependencies: + "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks@^5.4.0": version "5.4.2" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35" @@ -1319,6 +1326,31 @@ bech32 "1.1.4" ws "7.4.6" +"@ethersproject/providers@5.5.3": + version "5.5.3" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.3.tgz#56c2b070542ac44eb5de2ed3cf6784acd60a3130" + integrity sha512-ZHXxXXXWHuwCQKrgdpIkbzMNJMvs+9YWemanwp1fA7XZEv7QlilseysPvQe0D7Q7DlkJX/w/bGA1MdgK2TbGvA== + dependencies: + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/basex" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/random" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/web" "^5.5.0" + bech32 "1.1.4" + ws "7.4.6" + "@ethersproject/random@5.4.0", "@ethersproject/random@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.4.0.tgz#9cdde60e160d024be39cc16f8de3b9ce39191e16" @@ -1335,6 +1367,14 @@ "@ethersproject/bytes" "^5.5.0" "@ethersproject/logger" "^5.5.0" +"@ethersproject/random@5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.1.tgz#7cdf38ea93dc0b1ed1d8e480ccdaf3535c555415" + integrity sha512-YaU2dQ7DuhL5Au7KbcQLHxcRHfgyNgvFV4sQOo0HrtW3Zkrc9ctWNz8wXQ4uCSfSDsqX2vcjhroxU5RQRV0nqA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/rlp@5.4.0", "@ethersproject/rlp@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.4.0.tgz#de61afda5ff979454e76d3b3310a6c32ad060931" @@ -1546,6 +1586,17 @@ "@ethersproject/properties" "^5.5.0" "@ethersproject/strings" "^5.5.0" +"@ethersproject/web@5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.1.tgz#cfcc4a074a6936c657878ac58917a61341681316" + integrity sha512-olvLvc1CB12sREc1ROPSHTdFCdvMh0J5GSJYiQg2D0hdD4QmJDy8QYDb1CvoqD/bF1c++aeKv2sR5uduuG9dQg== + dependencies: + "@ethersproject/base64" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/wordlists@5.4.0", "@ethersproject/wordlists@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.4.0.tgz#f34205ec3bbc9e2c49cadaee774cf0b07e7573d7" @@ -1568,6 +1619,37 @@ "@ethersproject/properties" "^5.5.0" "@ethersproject/strings" "^5.5.0" +"@gnosis.pm/ido-contracts@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@gnosis.pm/ido-contracts/-/ido-contracts-0.5.0.tgz#83de88dece517d5f1069ec32bd2eebd1857ef3ac" + integrity sha512-4q7bB4c+7zFvIlolg/Xo1rRFcecAO+zLdIQ2am5zXKnii0Txz3Ki4yXzg7/nbBHMPZZANLmVgXe0ZDiGS5OsIw== + dependencies: + "@gnosis.pm/solidity-data-structures" "^1.3.4" + "@types/yargs" "^15.0.10" + argv "^0.0.2" + axios "^0.21.1" + bn.js "^5.1.1" + dotenv "^8.2.0" + ethers "^5.0.22" + solc "0.6.8" + yargs "^16.1.1" + +"@gnosis.pm/solidity-data-structures@^1.3.4": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@gnosis.pm/solidity-data-structures/-/solidity-data-structures-1.3.5.tgz#a38cab984a5a4c7d379454d5e82a2ac2f1a6b65e" + integrity sha512-qU32sUlH1cE26uhSSTbCYpskYzMm4KBXaIZdpu9v2xsO6jjedgfg9rKxJcor1V4ZRBp4EQyFqWzPx7fPTTPoUQ== + dependencies: + "@gnosis.pm/util-contracts" "^2.0.4" + ethereumjs-util "^6.1.0" + merkletreejs "0.0.22" + +"@gnosis.pm/util-contracts@^2.0.4": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@gnosis.pm/util-contracts/-/util-contracts-2.0.7.tgz#b28073890a6a6be8458a7d7952c8541788fd020f" + integrity sha512-+8V0C/E4aGMudeMQBNDLidE3GwfM4XSySuT39wON8wpj9ocNyIsV4U+4YTfEOyUQ9PafKloWr78W5OhkOX9eqQ== + dependencies: + "@truffle/hdwallet-provider" "^1.0.42" + "@graphql-tools/batch-delegate@^6.2.4", "@graphql-tools/batch-delegate@^6.2.6": version "6.2.6" resolved "https://registry.yarnpkg.com/@graphql-tools/batch-delegate/-/batch-delegate-6.2.6.tgz#fbea98dc825f87ef29ea5f3f371912c2a2aa2f2c" @@ -2051,6 +2133,11 @@ "@types/sinon-chai" "^3.2.3" "@types/web3" "1.0.19" +"@openzeppelin/contracts@3.2.0-rc.0": + version "3.2.0-rc.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.2.0-rc.0.tgz#1f39e49df5f7a7b42fd49343ac1d758bbd24b826" + integrity sha512-EcEho5UFNZN1ZUHuD5ka38qgs+XWlzBM1FFfpu4YNVoo0xtwWeg7X52jm8pK+NYq8tJAI8ySjGYPM+4S5+N8iA== + "@openzeppelin/contracts@3.4.2", "@openzeppelin/contracts@^3.2.0-rc.0", "@openzeppelin/contracts@^3.4.1": version "3.4.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" @@ -2818,6 +2905,20 @@ ethereumjs-util "^6.1.0" ethereumjs-wallet "^1.0.1" +"@truffle/hdwallet-provider@^1.0.42": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@truffle/hdwallet-provider/-/hdwallet-provider-1.7.0.tgz#5cfa8bc67c2a30b3943d3dab78f74c6a191cde02" + integrity sha512-nT7BPJJ2jPCLJc5uZdVtRnRMny5he5d3kO9Hi80ZSqe5xlnK905grBptM/+CwOfbeqHKQirI1btwm6r3wIBM8A== + dependencies: + "@ethereumjs/common" "^2.4.0" + "@ethereumjs/tx" "^3.3.0" + "@trufflesuite/web3-provider-engine" "15.0.14" + eth-sig-util "^3.0.1" + ethereum-cryptography "^0.1.3" + ethereum-protocol "^1.0.1" + ethereumjs-util "^6.1.0" + ethereumjs-wallet "^1.0.1" + "@truffle/interface-adapter@^0.5.8": version "0.5.8" resolved "https://registry.yarnpkg.com/@truffle/interface-adapter/-/interface-adapter-0.5.8.tgz#76cfd34374d85849e1164de1a3d5a3dce0dc5d01" @@ -3014,6 +3115,34 @@ xhr "^2.2.0" xtend "^4.0.1" +"@trufflesuite/web3-provider-engine@15.0.14": + version "15.0.14" + resolved "https://registry.yarnpkg.com/@trufflesuite/web3-provider-engine/-/web3-provider-engine-15.0.14.tgz#8f9696f434585cc0ab2e57c312090c1f138bc471" + integrity sha512-6/LoWvNMxYf0oaYzJldK2a9AdnkAdIeJhHW4nuUBAeO29eK9xezEaEYQ0ph1QRTaICxGxvn+1Azp4u8bQ8NEZw== + dependencies: + "@ethereumjs/tx" "^3.3.0" + "@trufflesuite/eth-json-rpc-filters" "^4.1.2-1" + "@trufflesuite/eth-json-rpc-infura" "^4.0.3-0" + "@trufflesuite/eth-json-rpc-middleware" "^4.4.2-1" + "@trufflesuite/eth-sig-util" "^1.4.2" + async "^2.5.0" + backoff "^2.5.0" + clone "^2.0.0" + cross-fetch "^2.1.0" + eth-block-tracker "^4.4.2" + eth-json-rpc-errors "^2.0.2" + ethereumjs-block "^1.2.2" + ethereumjs-util "^5.1.5" + ethereumjs-vm "^2.3.4" + json-stable-stringify "^1.0.1" + promise-to-callback "^1.0.0" + readable-stream "^2.2.9" + request "^2.85.0" + semaphore "^1.0.3" + ws "^5.1.1" + xhr "^2.2.0" + xtend "^4.0.1" + "@typechain/ethers-v5@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz#cd3ca1590240d587ca301f4c029b67bfccd08810" @@ -3354,6 +3483,18 @@ dependencies: "@types/node" "*" +"@types/yargs-parser@*": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== + +"@types/yargs@^15.0.10": + version "15.0.14" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" + integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + dependencies: + "@types/yargs-parser" "*" + "@types/zen-observable@0.8.3": version "0.8.3" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" @@ -3971,6 +4112,11 @@ argsarray@0.0.1, argsarray@^0.0.1: resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb" integrity sha1-bnIHtOzbObCviDA/pa4ivajfYcs= +argv@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" + integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas= + arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -4214,6 +4360,13 @@ axios@^0.20.0: dependencies: follow-redirects "^1.10.0" +axios@^0.21.1: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -5377,6 +5530,11 @@ buffer-pipe@0.0.3: dependencies: safe-buffer "^5.1.2" +buffer-reverse@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" + integrity sha1-SSg8jvpvkBvAH6MwTQYCeXGuL2A= + buffer-to-arraybuffer@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" @@ -5976,6 +6134,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-buffer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -6429,6 +6596,11 @@ crypto-browserify@3.12.0, crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^3.1.9-1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" + integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== + css-select@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" @@ -8234,6 +8406,42 @@ ethers@^5.0.13: "@ethersproject/web" "5.5.0" "@ethersproject/wordlists" "5.5.0" +ethers@^5.0.22: + version "5.5.4" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.4.tgz#e1155b73376a2f5da448e4a33351b57a885f4352" + integrity sha512-N9IAXsF8iKhgHIC6pquzRgPBJEzc9auw3JoRkaKe+y4Wl/LFBtDDunNe7YmdomontECAcC5APaAgWZBiu1kirw== + dependencies: + "@ethersproject/abi" "5.5.0" + "@ethersproject/abstract-provider" "5.5.1" + "@ethersproject/abstract-signer" "5.5.0" + "@ethersproject/address" "5.5.0" + "@ethersproject/base64" "5.5.0" + "@ethersproject/basex" "5.5.0" + "@ethersproject/bignumber" "5.5.0" + "@ethersproject/bytes" "5.5.0" + "@ethersproject/constants" "5.5.0" + "@ethersproject/contracts" "5.5.0" + "@ethersproject/hash" "5.5.0" + "@ethersproject/hdnode" "5.5.0" + "@ethersproject/json-wallets" "5.5.0" + "@ethersproject/keccak256" "5.5.0" + "@ethersproject/logger" "5.5.0" + "@ethersproject/networks" "5.5.2" + "@ethersproject/pbkdf2" "5.5.0" + "@ethersproject/properties" "5.5.0" + "@ethersproject/providers" "5.5.3" + "@ethersproject/random" "5.5.1" + "@ethersproject/rlp" "5.5.0" + "@ethersproject/sha2" "5.5.0" + "@ethersproject/signing-key" "5.5.0" + "@ethersproject/solidity" "5.5.0" + "@ethersproject/strings" "5.5.0" + "@ethersproject/transactions" "5.5.0" + "@ethersproject/units" "5.5.0" + "@ethersproject/wallet" "5.5.0" + "@ethersproject/web" "5.5.1" + "@ethersproject/wordlists" "5.5.0" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -8836,6 +9044,11 @@ follow-redirects@^1.12.1: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== +follow-redirects@^1.14.0: + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== + for-each@^0.3.3, for-each@~0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -9118,7 +9331,7 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -10362,7 +10575,7 @@ is-buffer@^1.1.0, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@~2.0.3: +is-buffer@^2.0.3, is-buffer@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== @@ -12176,6 +12389,16 @@ merkle-patricia-tree@^4.2.0: rlp "^2.2.4" semaphore-async-await "^1.5.1" +merkletreejs@0.0.22: + version "0.0.22" + resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.0.22.tgz#ccc1008cdfa8f5a01a4f735f91c7a29c34070b6c" + integrity sha512-oZF3PIoRRmbG1ikN/3c0n0i421ZzdyOpNbm6wDNt5LVLIpW2FCany8ERwOyAZq83vM+Nx1zTp7Br7cIpicWzNQ== + dependencies: + buffer-reverse "^1.0.1" + crypto-js "^3.1.9-1" + is-buffer "^2.0.3" + treeify "^1.1.0" + meros@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/meros/-/meros-1.1.4.tgz#c17994d3133db8b23807f62bec7f0cb276cfd948" @@ -15581,6 +15804,20 @@ socketcluster-client@^14.2.1: uuid "3.2.1" ws "^7.5.0" +solc@0.6.8: + version "0.6.8" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.6.8.tgz#accf03634554938e166ba9b9853d17ca5c728131" + integrity sha512-7URBAisWVjO7dwWNpEkQ5dpRSpSF4Wm0aD5EB82D5BQKh+q7jhOxhgkG4K5gax/geM0kPZUAxnaLcgl2ZXBgMQ== + dependencies: + command-exists "^1.2.8" + commander "3.0.2" + fs-extra "^0.30.0" + js-sha3 "0.8.0" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + solc@0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a" @@ -16593,10 +16830,22 @@ tornado-cli@^0.0.1: web3-utils "^1.3.4" websnark "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d" -tornado-governance@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/tornado-governance/-/tornado-governance-1.0.2.tgz#c6f32d3ccdf777937eab0f01ac7bab8e5845a0be" - integrity sha512-Kk+1FuG30FNeJ6Xxym4uDWb2s1stymn9KsYVu/NuQhYCJpn06MimHT8OQgY5kh0pcYikPH+45jVNX+luoDZoFg== +tornado-governance@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tornado-governance/-/tornado-governance-2.0.0.tgz#07f6b52fe86430815ee9603d1b36ec411202cb34" + integrity sha512-JmXzxEBhGGJDxsRDp1M0RkN4ZWmm9MzrIy1agUbMEwQUXtRirY+rK4sYDXEvSRJIj8qDl3Pr8nYwny3O2sNRnA== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@gnosis.pm/ido-contracts" "^0.5.0" + "@openzeppelin/contracts" "3.2.0-rc.0" + "@openzeppelin/upgrades-core" "^1.0.1" + torn-token "^1.0.4" + tornado-governance "^1.0.3" + +tornado-governance@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tornado-governance/-/tornado-governance-1.0.3.tgz#2b11ebe698af42cdec35ff77583ac4c5d65847f6" + integrity sha512-T1PYCDCzbabjv0lRKBLIIshmDMKwckrwZQzu8nAXsHygNrRtg052QP5YvGjRx0JpfKEBnf5fKjljC3RZAaA8rQ== dependencies: "@openzeppelin/contracts" "^3.2.0-rc.0" "@openzeppelin/upgrades-core" "^1.0.1" @@ -16624,6 +16873,11 @@ tr46@~0.0.1: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" @@ -18788,6 +19042,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -18920,6 +19183,11 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yaeti@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" @@ -18980,6 +19248,11 @@ yargs-parser@^2.4.0, yargs-parser@^2.4.1: camelcase "^3.0.0" lodash.assign "^4.0.6" +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" @@ -19110,6 +19383,19 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^16.1.1: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@^4.7.1: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" From 41ddd8023d1fb8ec7f1ecaffe208265fa6f4a470 Mon Sep 17 00:00:00 2001 From: Drygin Date: Thu, 17 Feb 2022 23:15:19 +0300 Subject: [PATCH 03/18] add instance factory tests --- .env.example | 15 +- .env.example.workflow | 8 - config.js | 12 + contracts/AddInstanceProposal.sol | 2 +- contracts/InstanceFactory.sol | 26 +- contracts/mock/CompileDummy.sol | 1 + hardhat.config.js | 27 +- package.json | 3 +- scripts/helper/propose_proposal.js | 22 - tasks/deploy_factory_proposal.js | 27 - tasks/deploy_proposal.js | 40 -- tasks/propose_proposal.js | 20 - test/instance.factory.proposal.test.js | 348 ------------ test/instance.factory.test.js | 318 +++++++++++ test/instance.proposal.test.js | 355 ------------ test/utils.js | 43 ++ yarn.lock | 740 ++++++++++++++++++++++++- 17 files changed, 1121 insertions(+), 886 deletions(-) delete mode 100644 .env.example.workflow create mode 100644 config.js delete mode 100644 scripts/helper/propose_proposal.js delete mode 100644 tasks/deploy_factory_proposal.js delete mode 100644 tasks/deploy_proposal.js delete mode 100644 tasks/propose_proposal.js delete mode 100644 test/instance.factory.proposal.test.js create mode 100644 test/instance.factory.test.js delete mode 100644 test/instance.proposal.test.js create mode 100644 test/utils.js diff --git a/.env.example b/.env.example index 45366da..f8818b5 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,3 @@ +ETHERSCAN_KEY= ALCHEMY_KEY= -etherscan_api_key= -goerli_rpc_key= -mainnet_rpc_key= -goerli_account_pk= -mainnet_account_pk= -use_latest_block=false - -PROXY=0x722122dF12D4e14e13Ac3b6895a86e84145b6967 -DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80 -HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe -VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A -SALT=0x0000000000000000000000000000000000000000000000000000000047941987 -COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888 \ No newline at end of file +PRIVATE_KEY= diff --git a/.env.example.workflow b/.env.example.workflow deleted file mode 100644 index 50fdc29..0000000 --- a/.env.example.workflow +++ /dev/null @@ -1,8 +0,0 @@ -use_latest_block=false - -PROXY=0x722122dF12D4e14e13Ac3b6895a86e84145b6967 -DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80 -HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe -VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A -SALT=0x0000000000000000000000000000000000000000000000000000000047941987 -COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888 \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..1a06dbc --- /dev/null +++ b/config.js @@ -0,0 +1,12 @@ +module.exports = { + verifier: '0xce172ce1F20EC0B3728c9965470eaf994A03557A', + hasher: '0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe', + governance: '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce', + instanceRegistry: '0xB20c66C4DE72433F3cE747b58B86830c459CA911', + merkleTreeHeight: 20, + singletonFactory: '0xce0042B868300000d44A59004Da54A005ffdcf9f', + salt: '0x0000000000000000000000000000000000000000000000000000000047941987', + COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', + TORN: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', + tornWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', +} diff --git a/contracts/AddInstanceProposal.sol b/contracts/AddInstanceProposal.sol index 30033ff..fdae4e7 100644 --- a/contracts/AddInstanceProposal.sol +++ b/contracts/AddInstanceProposal.sol @@ -12,7 +12,7 @@ contract AddInstanceProposal { address public immutable token; uint24 public immutable uniswapPoolSwappingFee; - uint256 internal immutable numInstances; + uint256 public immutable numInstances; uint256 internal immutable denomination0; uint256 internal immutable denomination1; uint256 internal immutable denomination2; diff --git a/contracts/InstanceFactory.sol b/contracts/InstanceFactory.sol index b10d48e..322c801 100644 --- a/contracts/InstanceFactory.sol +++ b/contracts/InstanceFactory.sol @@ -10,11 +10,13 @@ import "./ERC20TornadoCloneable.sol"; import "./AddInstanceProposal.sol"; import "./interfaces/IGovernance.sol"; + contract InstanceFactory is Ownable { using Clones for address; using Address for address; - address immutable governance; + address public immutable governance; + address public immutable instanceRegistry; address public implementation; address public verifier; address public hasher; @@ -25,19 +27,24 @@ contract InstanceFactory is Ownable { event NewTreeHeightSet(uint32 indexed newTreeHeight); event NewImplementationSet(address indexed newImplemenentation); event NewInstanceCloneCreated(address indexed clone); + event NewGovernanceProposalCreated(address indexed proposal); constructor( address _verifier, address _hasher, uint32 _merkleTreeHeight, - address _governance + address _governance, + address _instanceRegistry ) { verifier = _verifier; hasher = _hasher; merkleTreeHeight = _merkleTreeHeight; + governance = _governance; + instanceRegistry = _instanceRegistry; + ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher); implementation = address(implContract); - governance = _governance; + transferOwnership(_governance); } @@ -59,26 +66,25 @@ contract InstanceFactory is Ownable { } function createNewProposal( - string calldata _description, - address _instanceRegistry, address _token, uint24 _uniswapPoolSwappingFee, uint256[] memory _denominations, uint32[] memory _protocolFees ) external returns (address) { - // TODO test params - require(_denominations.length == _protocolFees.length); + require(_token.isContract(), "Token is not contract"); // TODO should we check that such instance already exist? + require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); // TODO should we check > 0 ? + require(_denominations.length > 0, "Empty denominations"); + require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); address proposal = address(new AddInstanceProposal( address(this), - _instanceRegistry, + instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees )); - - IGovernance(governance).propose(proposal, _description); + emit NewGovernanceProposalCreated(proposal); return proposal; } diff --git a/contracts/mock/CompileDummy.sol b/contracts/mock/CompileDummy.sol index 50b31cf..0bad471 100644 --- a/contracts/mock/CompileDummy.sol +++ b/contracts/mock/CompileDummy.sol @@ -3,5 +3,6 @@ pragma solidity 0.6.12 || 0.7.6; import { Governance } from "tornado-governance/contracts/v1/Governance.sol"; +import { InstanceRegistry } from "tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol"; contract CompileDummy {} diff --git a/hardhat.config.js b/hardhat.config.js index a068bdb..d34fd1c 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -1,13 +1,9 @@ require('dotenv').config() -require('@nomiclabs/hardhat-ethers') -require('@nomiclabs/hardhat-etherscan') require('@nomiclabs/hardhat-waffle') +require('@nomiclabs/hardhat-etherscan') require('hardhat-log-remover') require('solidity-coverage') -// require('./tasks/deploy_proposal.js') -// require('./tasks/deploy_factory_proposal.js') -// require('./tasks/propose_proposal.js') /** * @type import('hardhat/config').HardhatUserConfig */ @@ -38,22 +34,17 @@ module.exports = { hardhat: { forking: { url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, - blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436, + blockNumber: 14220000, }, + chainId: 1, + initialBaseFeePerGas: 5, loggingEnabled: false, - }, - localhost: { - url: 'http://localhost:8545', - timeout: 120000, + allowUnlimitedContractSize: false, + blockGasLimit: 50000000, }, mainnet: { - url: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`, - accounts: [`${process.env.mainnet_account_pk}`], - timeout: 2147483647, - }, - goerli: { - url: `https://goerli.infura.io/v3/${process.env.goerli_rpc_key}`, - accounts: [`${process.env.goerli_account_pk}`], + url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, + accounts: [process.env.PRIVATE_KEY], timeout: 2147483647, }, }, @@ -63,6 +54,6 @@ module.exports = { runOnCompile: true, }, etherscan: { - apiKey: `${process.env.etherscan_api_key}`, + apiKey: `${process.env.ETHERSCAN_KEY}`, }, } diff --git a/package.json b/package.json index 8e7d3b1..6d94639 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "torn-token": "^1.0.4", "tornado-cli": "^0.0.1", "tornado-core": "https://github.com/tornadocash/tornado-core.git", - "tornado-governance": "2.0.0" + "tornado-governance": "2.0.0", + "tornado-relayer-registry": "https://github.com/tornadocash/tornado-relayer-registry.git" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.2", diff --git a/scripts/helper/propose_proposal.js b/scripts/helper/propose_proposal.js deleted file mode 100644 index f29cc86..0000000 --- a/scripts/helper/propose_proposal.js +++ /dev/null @@ -1,22 +0,0 @@ -require('dotenv').config() -const { ethers } = require('hardhat') - -// THIS IS ASSUMING YOU HAVE ENOUGH LOCKED TORN TO PROPOSE -async function propose(proposalArgs) { - const proposer = proposalArgs[0] - const ProposalContract = proposalArgs[1] - - let GovernanceContract = await ethers.getContractAt( - '../artifacts/tornado-governance/contracts/Governance.sol:Governance', - '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce', - ) - GovernanceContract = await GovernanceContract.connect(proposer) - - const response = await GovernanceContract.propose(ProposalContract.address, proposalArgs[2]) - - const id = await GovernanceContract.latestProposalIds(proposer.address) - const state = await GovernanceContract.state(id) - - return [response, id, state] -} -module.exports.propose = propose diff --git a/tasks/deploy_factory_proposal.js b/tasks/deploy_factory_proposal.js deleted file mode 100644 index 1e0381b..0000000 --- a/tasks/deploy_factory_proposal.js +++ /dev/null @@ -1,27 +0,0 @@ -require('dotenv').config() -const { task } = require('hardhat/config') -const { BigNumber } = require('@ethersproject/bignumber') -const instancesData = require('../resources/instances') - -task('deploy_factory_proposal', 'deploy the proposal that creates the factory').setAction( - async (taskArgs, hre) => { - const ProposalFactory = await hre.ethers.getContractFactory('CreateFactoryAndAddInstancesProposal') - - let denominations = [] - for (let i = 0; i < 4; i++) { - denominations[i] = BigNumber.from(instancesData[i].denomination) - } - const tokenAddress = instancesData[0].tokenAddress - - const ProposalContract = await ProposalFactory.deploy(`${process.env.PROXY}`, denominations, tokenAddress) - - await ProposalContract.deployTransaction.wait(5) - - await hre.run('verify:verify', { - address: ProposalContract.address, - constructorArguments: [`${process.env.PROXY}`, denominations, tokenAddress], - }) - - console.log('Verified CreateFactoryAndAddInstancesProposal deployed at: ', ProposalContract.address) - }, -) diff --git a/tasks/deploy_proposal.js b/tasks/deploy_proposal.js deleted file mode 100644 index 7d39b79..0000000 --- a/tasks/deploy_proposal.js +++ /dev/null @@ -1,40 +0,0 @@ -require('dotenv').config() -const { task } = require('hardhat/config') -const { BigNumber } = require('@ethersproject/bignumber') -const instancesData = require('../resources/instances') - -task('deploy_proposal', 'deploy proposal that uses factory') - .addParam('factoryAddress', 'address of factory') - .setAction(async (taskArgs, hre) => { - const contractName = `Add${instancesData.length}Instance${instancesData.length == 1 ? '' : 's'}` - - const ProposalFactory = await hre.ethers.getContractFactory(contractName) - - let denominations = [] - for (let i = 0; i < instancesData.length; i++) { - denominations[i] = BigNumber.from(instancesData[i].denomination) - } - - const tokenAddress = instancesData[0].tokenAddress - - const ProposalContract = await ProposalFactory.deploy( - `${process.env.PROXY}`, - taskArgs.factoryAddress, - denominations.length == 1 ? denominations[0] : denominations, - tokenAddress, - ) - - await ProposalContract.deployTransaction.wait(5) - - await hre.run('verify:verify', { - address: ProposalContract.address, - constructorArguments: [ - `${process.env.PROXY}`, - taskArgs.factoryAddress, - denominations.length == 1 ? denominations[0] : denominations, - tokenAddress, - ], - }) - - console.log(`Verified ${contractName} deployed at: `, ProposalContract.address) - }) diff --git a/tasks/propose_proposal.js b/tasks/propose_proposal.js deleted file mode 100644 index 8284af8..0000000 --- a/tasks/propose_proposal.js +++ /dev/null @@ -1,20 +0,0 @@ -require('dotenv').config() -const { task } = require('hardhat/config') -const instancesData = require('../resources/instances') - -task('propose_proposal', 'propose proposal that uses factory') - .addParam('proposalAddress', 'address of proposal') - .setAction(async (taskArgs, hre) => { - const proposalName = `add-${instancesData[0].symbol}-instances` - - const GovernanceContract = await hre.ethers.getContractAt( - '../artifacts/tornado-governance/contracts/Governance.sol:Governance', - '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce', - ) - await GovernanceContract.propose(taskArgs.proposalAddress, proposalName) - - const id = await GovernanceContract.latestProposalIds((await hre.ethers.getSigners())[0].address) - const state = await GovernanceContract.state(id) - - console.log('Proposal with name: ', proposalName, ' proposed with id: ', id, ', has state: ', state) - }) diff --git a/test/instance.factory.proposal.test.js b/test/instance.factory.proposal.test.js deleted file mode 100644 index 94d6e3f..0000000 --- a/test/instance.factory.proposal.test.js +++ /dev/null @@ -1,348 +0,0 @@ -require('dotenv').config() -const { ethers } = require('hardhat') -const { expect } = require('chai') -const { BigNumber } = require('@ethersproject/bignumber') -const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') - -const { propose } = require('../scripts/helper/propose_proposal.js') - -const MixerContractABI = require('tornado-cli/build/contracts/Mixer.abi.json') - -describe('Deployments test setup', () => { - const Verifier = `${process.env.VERIFIER}` - const Hasher = `${process.env.HASHER}` - const Proxy = `${process.env.PROXY}` - - //// IMPERSONATED ACCOUNTS - let accounts - let whale - let impGov - - //// CONTRACTS / FACTORIES - let ProposalFactory - let ProposalContract - - let GovernanceContract - let TornToken - let RAIToken - let TornadoProxy - - let TornadoInstanceFactoryContract - - /// HARDCODED // TODO take from config - let denominations = [ - '33333333333333333333', - '333333333333333333333', - '3333333333333333333333', - '33333333333333333333333', - ] - let tokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919' - - let minewait = async (time) => { - await ethers.provider.send('evm_increaseTime', [time]) - await ethers.provider.send('evm_mine', []) - } - - let sendr = async (method, params) => { - return await ethers.provider.send(method, params) - } - - let clog = (...x) => { - console.log(x) - } - - let pE = (x) => { - return ethers.utils.parseEther(`${x}`) - } - - const ProposalState = { - Pending: 0, - Active: 1, - Defeated: 2, - Timelocked: 3, - AwaitingExecution: 4, - Executed: 5, - Expired: 6, - } - - before(async () => { - accounts = await ethers.getSigners() - ProposalFactory = await ethers.getContractFactory('CreateFactoryAndAddInstancesProposal') - GovernanceContract = await ethers.getContractAt( - 'Governance', - '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce', - ) - TornToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', - '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', - ) - TornadoProxy = await ethers.getContractAt('TornadoProxy', '0x722122dF12D4e14e13Ac3b6895a86e84145b6967') - }) - - describe('Test instance deployment', () => { - let snapshotId - - it('Should have initialized all successfully', () => { - expect(accounts[0].address).to.exist - expect(GovernanceContract.address).to.exist - expect(TornToken.address).to.exist - expect(TornadoProxy.address).to.exist - }) - - it('Should successfully imitate whale', async () => { - await sendr('hardhat_impersonateAccount', ['0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3']) - whale = await ethers.getSigner('0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3') - GovernanceContract = await GovernanceContract.connect(whale) - - let balance = await TornToken.balanceOf(whale.address) - TornToken = await TornToken.connect(whale) - - await TornToken.approve(GovernanceContract.address, ethers.utils.parseEther('8000000000')) - await expect(GovernanceContract.lockWithApproval(balance)).to.not.be.reverted - - expect((await GovernanceContract.lockedBalance(whale.address)).toString()).to.equal(balance.toString()) - }) - - it('Should successfully deploy proposal', async () => { - ProposalContract = await ProposalFactory.deploy(Proxy, denominations, tokenAddress) - expect(await ProposalContract.token()).to.equal(tokenAddress) - expect(await ProposalContract.denomination1()).to.equal(denominations[0]) - expect(await ProposalContract.denomination2()).to.equal(denominations[1]) - expect(await ProposalContract.denomination3()).to.equal(denominations[2]) - expect(await ProposalContract.denomination4()).to.equal(denominations[3]) - - TornadoInstanceFactoryContract = await ethers.getContractAt( - 'TornadoInstanceCloneFactory', - await ProposalContract.instanceFactory(), - ) - }) - - it('Should successfully pass the proposal', async () => { - let response, id, state - ;[response, id, state] = await propose([whale, ProposalContract, 'Instances']) - - let { events } = await response.wait() - let args = events.find(({ event }) => event == 'ProposalCreated').args - expect(args.id).to.be.equal(id) - expect(args.proposer).to.be.equal(whale.address) - expect(args.target).to.be.equal(ProposalContract.address) - expect(args.description).to.be.equal('Instances') - expect(state).to.be.equal(ProposalState.Pending) - - await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber()) - await expect(GovernanceContract.castVote(id, true)).to.not.be.reverted - state = await GovernanceContract.state(id) - expect(state).to.be.equal(ProposalState.Active) - await minewait( - ( - await GovernanceContract.VOTING_PERIOD() - ) - .add(await GovernanceContract.EXECUTION_DELAY()) - .add(86400) - .toNumber(), - ) - const overrides = { - gasLimit: BigNumber.from('30000000'), - } - await GovernanceContract.execute(id, overrides) - - expect(await GovernanceContract.state(id)).to.be.equal(ProposalState.Executed) - }) - - it('Should set correct params for factory', async () => { - expect(await TornadoInstanceFactoryContract.verifier()).to.equal(Verifier) - expect(await TornadoInstanceFactoryContract.hasher()).to.equal(Hasher) - expect(await TornadoInstanceFactoryContract.merkleTreeHeight()).to.equal(20) - // clog(await TornadoInstanceFactoryContract.implementation()) - }) - - it('Factory should be able to generate an instance without reverting', async () => { - const OHMAddress = '0x383518188C0C6d7730D91b2c03a03C837814a899' - - await sendr('hardhat_impersonateAccount', [GovernanceContract.address]) - await sendr('hardhat_setBalance', [GovernanceContract.address, '0xDE0B6B3A764000000']) - impGov = await ethers.getSigner(GovernanceContract.address) - - const factory = await TornadoInstanceFactoryContract.connect(impGov) - - await factory.createInstanceClone(333, OHMAddress) - const instanceAddress = await TornadoInstanceFactoryContract.getInstanceAddress(333, OHMAddress) - - const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddress) - - const token = await instance.token() - const denomination = await instance.denomination() - const verifier = await instance.verifier() - const hasher = await instance.hasher() - const levels = await instance.levels() - - expect(token).to.equal(OHMAddress) - expect(denomination).to.equal(333) - expect(verifier).to.equal(Verifier) - expect(hasher).to.equal(Hasher) - expect(levels).to.equal(20) - }) - - it('Governance should be able to set factory params', async () => { - const zeroAddress = '0x0000000000000000000000000000000000000000' - - const factory = await TornadoInstanceFactoryContract.connect(impGov) - await factory.setVerifier(zeroAddress) - await factory.setHasher(zeroAddress) - await factory.setMerkleTreeHeight(25) - - let fverifier = await factory.verifier() - let fhasher = await factory.hasher() - let merkleTreeHeight = await factory.merkleTreeHeight() - - expect(fverifier).to.equal(zeroAddress) - expect(fhasher).to.equal(zeroAddress) - expect(merkleTreeHeight).to.equal(25) - - await factory.setVerifier(Verifier) - await factory.setHasher(Hasher) - await factory.setMerkleTreeHeight(20) - - fverifier = await factory.verifier() - fhasher = await factory.hasher() - merkleTreeHeight = await factory.merkleTreeHeight() - - expect(fverifier).to.equal(Verifier) - expect(fhasher).to.equal(Hasher) - expect(merkleTreeHeight).to.equal(20) - }) - - let whaleRAI, whaleRAIBalance, TornadoInstance, mixerContract, instanceAddresses - instanceAddresses = [] - - it('Should prepare data for instance deposit/withdraw tests', async () => { - const RAITokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919' - await sendr('hardhat_impersonateAccount', ['0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100']) - RAIToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', - RAITokenAddress, - ) - whaleRAI = await ethers.getSigner('0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100') - - const tx = { - to: whaleRAI.address, - value: pE(50), - } - await accounts[0].sendTransaction(tx) - - whaleRAIBalance = await RAIToken.balanceOf(whaleRAI.address) - RAIToken = await RAIToken.connect(whaleRAI) - TornadoProxy = await TornadoProxy.connect(whaleRAI) - - for (let i = 0; i < 4; i++) { - instanceAddresses[i] = await TornadoInstanceFactoryContract.getInstanceAddress( - denominations[i], - RAIToken.address, - ) - } - - mixerContract = await ethers.getContractAt(MixerContractABI, instanceAddresses[0]) - mixerContract = await mixerContract.connect(whaleRAI) - - snapshotId = await sendr('evm_snapshot', []) - }) - - it('Should test depositing and withdrawing into the new instance over proxy', async () => { - const depo = createDeposit({ - nullifier: rbigint(31), - secret: rbigint(31), - }) - - // const note = toHex(depo.preimage, 62) - // const noteString = `tornado-RAI-33-1-${note}` - // clog('Note: ', note) - // clog('Note string: ', noteString) - // clog('Commitment: ', toHex(depo.commitment)) - - await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted - TornadoInstance = await ethers.getContractAt( - 'contracts/tornado_proxy/ITornadoInstance.sol:ITornadoInstance', - instanceAddresses[0], - ) - - await expect(() => - TornadoProxy.deposit(instanceAddresses[0], toHex(depo.commitment), []), - ).to.changeTokenBalance(RAIToken, whaleRAI, BigNumber.from(0).sub(await TornadoInstance.denomination())) - - let pevents = await mixerContract.queryFilter('Deposit') - await initialize({ merkleTreeHeight: 20 }) - - const { proof, args } = await generateProof({ - deposit: depo, - recipient: whaleRAI.address, - events: pevents, - }) - - await expect(() => - TornadoProxy.withdraw(TornadoInstance.address, proof, ...args), - ).to.changeTokenBalance(RAIToken, whaleRAI, await TornadoInstance.denomination()) - - await sendr('evm_revert', [snapshotId]) - snapshotId = await sendr('evm_snapshot', []) - }) - - it('Should prepare for multiple account deposits', async () => { - let toSend = whaleRAIBalance.div(5) - - for (let i = 0; i < 3; i++) { - await RAIToken.transfer(accounts[i].address, toSend) - const rai = await RAIToken.connect(accounts[i]) - await rai.approve(TornadoProxy.address, pE(600000)) - } - }) - - it('Should test depositing with multiple accounts over proxy', async () => { - for (let i = 0; i < 3; i++) { - const depo = createDeposit({ - nullifier: rbigint(31), - secret: rbigint(31), - }) - // const note = toHex(depo.preimage, 62) - // const noteString = `tornado-RAI-33-1-${note}` - // clog('Note: ', note) - // clog('Note string: ', noteString) - // clog('Commitment: ', toHex(depo.commitment)) - const proxy = await TornadoProxy.connect(accounts[i]) - - await expect(() => - proxy.deposit(TornadoInstance.address, toHex(depo.commitment), []), - ).to.changeTokenBalance( - RAIToken, - accounts[i], - BigNumber.from(0).sub(await TornadoInstance.denomination()), - ) - - let pevents = await mixerContract.queryFilter('Deposit') - await initialize({ merkleTreeHeight: 20 }) - - const { proof, args } = await generateProof({ - deposit: depo, - recipient: accounts[i].address, - events: pevents, - }) - - await expect(() => proxy.withdraw(TornadoInstance.address, proof, ...args)).to.changeTokenBalance( - RAIToken, - accounts[i], - await TornadoInstance.denomination(), - ) - } - }) - }) - - after(async function () { - await ethers.provider.send('hardhat_reset', [ - { - forking: { - jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, - blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436, - }, - }, - ]) - }) -}) diff --git a/test/instance.factory.test.js b/test/instance.factory.test.js new file mode 100644 index 0000000..50bfc7d --- /dev/null +++ b/test/instance.factory.test.js @@ -0,0 +1,318 @@ +const hre = require('hardhat') +const { ethers, waffle } = hre +const { loadFixture } = waffle +const { expect } = require('chai') +const { BigNumber } = require('@ethersproject/bignumber') +const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') +const MixerContractABI = require('tornado-cli/build/contracts/Mixer.abi.json') +const config = require('../config') +const { getSignerFromAddress, minewait } = require('./utils') + + +describe('Instance Factory Tests', () => { + const ProposalState = { + Pending: 0, + Active: 1, + Defeated: 2, + Timelocked: 3, + AwaitingExecution: 4, + Executed: 5, + Expired: 6, + } + + const addressZero = ethers.constants.AddressZero + + async function fixture() { + const [sender, deployer, multisig] = await ethers.getSigners() + + const tornWhale = await getSignerFromAddress(config.tornWhale) + + const gov = await ethers.getContractAt( + 'Governance', + config.governance, + ) + + const tornToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', + config.TORN, + ) + + const compToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', + config.COMP, + ) + + instanceRegistry = await ethers.getContractAt( + 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', + config.instanceRegistry, + ) + + // execute relayer registry proposal + const proposalId = await gov.proposalCount() + expect(await gov.state(proposalId)).to.be.equal(ProposalState.Active) + await minewait(596400) + expect(await gov.state(proposalId)).to.be.equal(ProposalState.AwaitingExecution) + + await gov.execute(proposalId) + expect(await gov.state(proposalId)).to.be.equal(ProposalState.Executed) + + // deploy instance factory + InstanceFactory = await ethers.getContractFactory('InstanceFactory') + const instanceFactory = await InstanceFactory.connect(deployer).deploy( + config.verifier, + config.hasher, + config.merkleTreeHeight, + config.governance, + config.instanceRegistry + ) + await instanceFactory.deployed() + + return { sender, deployer, multisig, tornWhale, gov, tornToken, compToken, instanceRegistry, instanceFactory } + } + + it('Should have initialized all successfully', async function () { + const { sender, gov, tornToken, instanceRegistry, instanceFactory } = await loadFixture(fixture) + expect(sender.address).to.exist + expect(gov.address).to.exist + expect(tornToken.address).to.exist + expect(instanceRegistry.address).to.exist + expect(instanceFactory.address).to.exist + }) + + it('Should set correct params for factory', async function () { + const { instanceFactory } = await loadFixture(fixture) + + expect( await instanceFactory.governance()).to.be.equal(config.governance) + expect( await instanceFactory.verifier()).to.be.equal(config.verifier) + expect( await instanceFactory.hasher()).to.be.equal(config.hasher) + expect( await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) + expect(await instanceFactory.implementation()).to.exist + }) + + it('Governance should be able to set factory params', async function () { + let { instanceFactory, gov } = await loadFixture(fixture) + + await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted + + const govSigner = await getSignerFromAddress(gov.address) + instanceFactory = await instanceFactory.connect(govSigner) + + await instanceFactory.setVerifier(addressZero) + await instanceFactory.setHasher(addressZero) + await instanceFactory.setMerkleTreeHeight(1) + + expect( await instanceFactory.verifier()).to.be.equal(addressZero) + expect( await instanceFactory.hasher()).to.be.equal(addressZero) + expect( await instanceFactory.merkleTreeHeight()).to.be.equal(1) + + await instanceFactory.setVerifier(config.verifier) + await instanceFactory.setHasher(config.hasher) + await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) + + expect( await instanceFactory.verifier()).to.be.equal(config.verifier) + expect( await instanceFactory.hasher()).to.be.equal(config.hasher) + expect( await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) + }) + + it('Should successfully deploy/propose/execute proposal - add instance', async function () { + let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + + // deploy proposal ---------------------------------------------- + let tx = await instanceFactory.createNewProposal( + config.COMP, + 3000, + [ethers.utils.parseEther('100')], + [30] + ) + let receipt = await tx.wait() + + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + receipt.events[0].args[0], + ) + + expect( await proposal.instanceFactory()).to.be.equal(instanceFactory.address) + expect( await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) + expect( await proposal.token()).to.be.equal(config.COMP) + expect( await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) + expect( await proposal.numInstances()).to.be.equal(1) + expect( await proposal.protocolFeeByIndex(0)).to.be.equal(30) + expect( await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + + // propose proposal --------------------------------------------- + let response, id, state + gov = await gov.connect(tornWhale) + await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) + await gov.lockWithApproval(ethers.utils.parseEther('26000')) + + response = await gov.propose(proposal.address, 'COMP token instance proposal') + id = await gov.latestProposalIds(tornWhale.address) + state = await gov.state(id) + + const { events } = await response.wait() + const args = events.find(({ event }) => event == 'ProposalCreated').args + expect(args.id).to.be.equal(id) + expect(args.proposer).to.be.equal(tornWhale.address) + expect(args.target).to.be.equal(proposal.address) + expect(args.description).to.be.equal('COMP token instance proposal') + expect(state).to.be.equal(ProposalState.Pending) + + // execute proposal --------------------------------------------- + await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) + await expect(gov.castVote(id, true)).to.not.be.reverted + expect(await gov.state(id)).to.be.equal(ProposalState.Active) + await minewait( + ( + await gov.VOTING_PERIOD() + ) + .add(await gov.EXECUTION_DELAY()) + .add(96400) + .toNumber(), + ) + expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) + + tx = await gov.execute(id) + + expect(await gov.state(id)).to.be.equal(ProposalState.Executed) + + // check instance initialization -------------------------------- + receipt = await tx.wait() + const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40) + const instance = await ethers.getContractAt( + 'ERC20TornadoCloneable', + instanceAddr, + ) + + expect( await instance.token()).to.be.equal(config.COMP) + expect( await instance.verifier()).to.be.equal(config.verifier) + expect( await instance.hasher()).to.be.equal(config.hasher) + expect( await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect( await instance.denomination()).to.equal(ethers.utils.parseEther('100')) + + const instanceData = await instanceRegistry.instances(instance.address) + expect(instanceData.isERC20).to.be.equal(true) + expect(instanceData.token).to.be.equal(config.COMP) + expect(instanceData.state).to.be.equal(1) + expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) + expect(instanceData.protocolFeePercentage).to.be.equal(30) + }) + + // it('Should prepare data for instance deposit/withdraw tests', async () => { + // const RAITokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919' + // await sendr('hardhat_impersonateAccount', ['0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100']) + // RAIToken = await ethers.getContractAt( + // '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', + // RAITokenAddress, + // ) + // whaleRAI = await ethers.getSigner('0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100') + + // const tx = { + // to: whaleRAI.address, + // value: pE(50), + // } + // await accounts[0].sendTransaction(tx) + + // whaleRAIBalance = await RAIToken.balanceOf(whaleRAI.address) + // RAIToken = await RAIToken.connect(whaleRAI) + // TornadoProxy = await TornadoProxy.connect(whaleRAI) + + // for (let i = 0; i < 4; i++) { + // instanceAddresses[i] = await TornadoInstanceFactoryContract.getInstanceAddress( + // denominations[i], + // RAIToken.address, + // ) + // } + + // mixerContract = await ethers.getContractAt(MixerContractABI, instanceAddresses[0]) + // mixerContract = await mixerContract.connect(whaleRAI) + + // snapshotId = await sendr('evm_snapshot', []) + // }) + + // it('Should test depositing and withdrawing into the new instance over proxy', async () => { + // const depo = createDeposit({ + // nullifier: rbigint(31), + // secret: rbigint(31), + // }) + + // // const note = toHex(depo.preimage, 62) + // // const noteString = `tornado-RAI-33-1-${note}` + // // clog('Note: ', note) + // // clog('Note string: ', noteString) + // // clog('Commitment: ', toHex(depo.commitment)) + + // await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted + // TornadoInstance = await ethers.getContractAt( + // 'contracts/tornado_proxy/ITornadoInstance.sol:ITornadoInstance', + // instanceAddresses[0], + // ) + + // await expect(() => + // TornadoProxy.deposit(instanceAddresses[0], toHex(depo.commitment), []), + // ).to.changeTokenBalance(RAIToken, whaleRAI, BigNumber.from(0).sub(await TornadoInstance.denomination())) + + // let pevents = await mixerContract.queryFilter('Deposit') + // await initialize({ merkleTreeHeight: 20 }) + + // const { proof, args } = await generateProof({ + // deposit: depo, + // recipient: whaleRAI.address, + // events: pevents, + // }) + + // await expect(() => + // TornadoProxy.withdraw(TornadoInstance.address, proof, ...args), + // ).to.changeTokenBalance(RAIToken, whaleRAI, await TornadoInstance.denomination()) + + // await sendr('evm_revert', [snapshotId]) + // snapshotId = await sendr('evm_snapshot', []) + // }) + + // it('Should prepare for multiple account deposits', async () => { + // let toSend = whaleRAIBalance.div(5) + + // for (let i = 0; i < 3; i++) { + // await RAIToken.transfer(accounts[i].address, toSend) + // const rai = await RAIToken.connect(accounts[i]) + // await rai.approve(TornadoProxy.address, pE(600000)) + // } + // }) + + // it('Should test depositing with multiple accounts over proxy', async () => { + // for (let i = 0; i < 3; i++) { + // const depo = createDeposit({ + // nullifier: rbigint(31), + // secret: rbigint(31), + // }) + // // const note = toHex(depo.preimage, 62) + // // const noteString = `tornado-RAI-33-1-${note}` + // // clog('Note: ', note) + // // clog('Note string: ', noteString) + // // clog('Commitment: ', toHex(depo.commitment)) + // const proxy = await TornadoProxy.connect(accounts[i]) + + // await expect(() => + // proxy.deposit(TornadoInstance.address, toHex(depo.commitment), []), + // ).to.changeTokenBalance( + // RAIToken, + // accounts[i], + // BigNumber.from(0).sub(await TornadoInstance.denomination()), + // ) + + // let pevents = await mixerContract.queryFilter('Deposit') + // await initialize({ merkleTreeHeight: 20 }) + + // const { proof, args } = await generateProof({ + // deposit: depo, + // recipient: accounts[i].address, + // events: pevents, + // }) + + // await expect(() => proxy.withdraw(TornadoInstance.address, proof, ...args)).to.changeTokenBalance( + // RAIToken, + // accounts[i], + // await TornadoInstance.denomination(), + // ) + // } + // }) +}) diff --git a/test/instance.proposal.test.js b/test/instance.proposal.test.js deleted file mode 100644 index a643632..0000000 --- a/test/instance.proposal.test.js +++ /dev/null @@ -1,355 +0,0 @@ -require('dotenv').config() -const { ethers } = require('hardhat') -const { expect } = require('chai') -const { BigNumber } = require('@ethersproject/bignumber') -const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') - -const { propose } = require('../scripts/helper/propose_proposal.js') - -const MixerContractABI = require('tornado-cli/build/contracts/Mixer.abi.json') - -describe('Deployments test setup', () => { - const Verifier = `${process.env.VERIFIER}` - const Hasher = `${process.env.HASHER}` - const Proxy = `${process.env.PROXY}` - - //// IMPERSONATED ACCOUNTS - let accounts - let whale - let impGov - - //// CONTRACTS / FACTORIES - let ProposalFactory - let ProposalContract - - let GovernanceContract - let TornToken - let RAIToken - let TornadoProxy - - let TornadoInstanceFactoryFactory - let TornadoInstanceFactoryContract - - /// HARDCODED - let denominations = [ - '33333333333333333333', - '333333333333333333333', - '3333333333333333333333', - '33333333333333333333333', - ] - let tokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919' - - let minewait = async (time) => { - await ethers.provider.send('evm_increaseTime', [time]) - await ethers.provider.send('evm_mine', []) - } - - let sendr = async (method, params) => { - return await ethers.provider.send(method, params) - } - - let clog = (...x) => { - console.log(x) - } - - let pE = (x) => { - return ethers.utils.parseEther(`${x}`) - } - - const ProposalState = { - Pending: 0, - Active: 1, - Defeated: 2, - Timelocked: 3, - AwaitingExecution: 4, - Executed: 5, - Expired: 6, - } - - before(async () => { - accounts = await ethers.getSigners() - ProposalFactory = await ethers.getContractFactory('Add4Instances') - GovernanceContract = await ethers.getContractAt( - 'Governance', - '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce', - ) - TornToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', - '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', - ) - TornadoProxy = await ethers.getContractAt('TornadoProxy', '0x722122dF12D4e14e13Ac3b6895a86e84145b6967') - TornadoInstanceFactoryFactory = await ethers.getContractFactory('TornadoInstanceCloneFactory') - }) - - describe('Test instance deployment', () => { - let snapshotId - - it('Should have initialized all successfully', () => { - expect(accounts[0].address).to.exist - expect(GovernanceContract.address).to.exist - expect(TornToken.address).to.exist - expect(TornadoProxy.address).to.exist - }) - - it('Should set correct params for factory', async () => { - TornadoInstanceFactoryContract = await TornadoInstanceFactoryFactory.deploy( - Verifier, - Hasher, - BigNumber.from(20), - ) - await TornadoInstanceFactoryContract.transferOwnership(GovernanceContract.address) - expect(await TornadoInstanceFactoryContract.verifier()).to.equal(Verifier) - expect(await TornadoInstanceFactoryContract.hasher()).to.equal(Hasher) - expect(await TornadoInstanceFactoryContract.merkleTreeHeight()).to.equal(20) - // clog(await TornadoInstanceFactoryContract.implementation()) - }) - - it('Factory should be able to generate an instance without reverting', async () => { - const OHMAddress = '0x383518188C0C6d7730D91b2c03a03C837814a899' - - await sendr('hardhat_impersonateAccount', [GovernanceContract.address]) - await sendr('hardhat_setBalance', [GovernanceContract.address, '0xDE0B6B3A764000000']) - impGov = await ethers.getSigner(GovernanceContract.address) - - const factory = await TornadoInstanceFactoryContract.connect(impGov) - - await factory.createInstanceClone(333, OHMAddress) - const instanceAddress = await TornadoInstanceFactoryContract.getInstanceAddress(333, OHMAddress) - - const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddress) - - const token = await instance.token() - const denomination = await instance.denomination() - const verifier = await instance.verifier() - const hasher = await instance.hasher() - const levels = await instance.levels() - - expect(token).to.equal(OHMAddress) - expect(denomination).to.equal(333) - expect(verifier).to.equal(Verifier) - expect(hasher).to.equal(Hasher) - expect(levels).to.equal(20) - }) - - it('Governance should be able to set factory params', async () => { - const zeroAddress = '0x0000000000000000000000000000000000000000' - - const factory = await TornadoInstanceFactoryContract.connect(impGov) - await factory.setVerifier(zeroAddress) - await factory.setHasher(zeroAddress) - await factory.setMerkleTreeHeight(25) - - let fverifier = await factory.verifier() - let fhasher = await factory.hasher() - let merkleTreeHeight = await factory.merkleTreeHeight() - - expect(fverifier).to.equal(zeroAddress) - expect(fhasher).to.equal(zeroAddress) - expect(merkleTreeHeight).to.equal(25) - - await factory.setVerifier(Verifier) - await factory.setHasher(Hasher) - await factory.setMerkleTreeHeight(20) - - fverifier = await factory.verifier() - fhasher = await factory.hasher() - merkleTreeHeight = await factory.merkleTreeHeight() - - expect(fverifier).to.equal(Verifier) - expect(fhasher).to.equal(Hasher) - expect(merkleTreeHeight).to.equal(20) - }) - - it('Should successfully imitate whale', async () => { - await sendr('hardhat_impersonateAccount', ['0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3']) - whale = await ethers.getSigner('0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3') - GovernanceContract = await GovernanceContract.connect(whale) - - let balance = await TornToken.balanceOf(whale.address) - TornToken = await TornToken.connect(whale) - - await TornToken.approve(GovernanceContract.address, ethers.utils.parseEther('8000000000')) - await expect(GovernanceContract.lockWithApproval(balance)).to.not.be.reverted - - expect((await GovernanceContract.lockedBalance(whale.address)).toString()).to.equal(balance.toString()) - }) - - it('Should successfully deploy proposal', async () => { - ProposalContract = await ProposalFactory.deploy( - Proxy, - TornadoInstanceFactoryContract.address, - denominations, - tokenAddress, - ) - expect(await ProposalContract.token()).to.equal(tokenAddress) - expect(await ProposalContract.instanceFactory()).to.equal(TornadoInstanceFactoryContract.address) - expect(await ProposalContract.denomination1()).to.equal(denominations[0]) - expect(await ProposalContract.denomination2()).to.equal(denominations[1]) - expect(await ProposalContract.denomination3()).to.equal(denominations[2]) - expect(await ProposalContract.denomination4()).to.equal(denominations[3]) - }) - - it('Should successfully pass the proposal', async () => { - let response, id, state - ;[response, id, state] = await propose([whale, ProposalContract, 'Instances']) - - let { events } = await response.wait() - let args = events.find(({ event }) => event == 'ProposalCreated').args - expect(args.id).to.be.equal(id) - expect(args.proposer).to.be.equal(whale.address) - expect(args.target).to.be.equal(ProposalContract.address) - expect(args.description).to.be.equal('Instances') - expect(state).to.be.equal(ProposalState.Pending) - - await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber()) - await expect(GovernanceContract.castVote(id, true)).to.not.be.reverted - state = await GovernanceContract.state(id) - expect(state).to.be.equal(ProposalState.Active) - await minewait( - ( - await GovernanceContract.VOTING_PERIOD() - ) - .add(await GovernanceContract.EXECUTION_DELAY()) - .add(86400) - .toNumber(), - ) - const overrides = { - gasLimit: BigNumber.from('30000000'), - } - await GovernanceContract.execute(id, overrides) - }) - - let whaleRAI, whaleRAIBalance, TornadoInstance, mixerContract, instanceAddresses - instanceAddresses = [] - - it('Should prepare data for instance deposit/withdraw tests', async () => { - const RAITokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919' - await sendr('hardhat_impersonateAccount', ['0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100']) - RAIToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', - RAITokenAddress, - ) - whaleRAI = await ethers.getSigner('0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100') - - const tx = { - to: whaleRAI.address, - value: pE(50), - } - await accounts[0].sendTransaction(tx) - - whaleRAIBalance = await RAIToken.balanceOf(whaleRAI.address) - RAIToken = await RAIToken.connect(whaleRAI) - TornadoProxy = await TornadoProxy.connect(whaleRAI) - - for (let i = 0; i < 4; i++) { - instanceAddresses[i] = await TornadoInstanceFactoryContract.getInstanceAddress( - denominations[i], - RAIToken.address, - ) - } - - mixerContract = await ethers.getContractAt(MixerContractABI, instanceAddresses[0]) - mixerContract = await mixerContract.connect(whaleRAI) - - snapshotId = await sendr('evm_snapshot', []) - }) - - it('Should test depositing and withdrawing into the new instance over proxy', async () => { - const depo = createDeposit({ - nullifier: rbigint(31), - secret: rbigint(31), - }) - - // const note = toHex(depo.preimage, 62) - // const noteString = `tornado-RAI-33-1-${note}` - // clog('Note: ', note) - // clog('Note string: ', noteString) - // clog('Commitment: ', toHex(depo.commitment)) - - await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted - TornadoInstance = await ethers.getContractAt( - 'contracts/tornado_proxy/ITornadoInstance.sol:ITornadoInstance', - instanceAddresses[0], - ) - - await expect(() => - TornadoProxy.deposit(instanceAddresses[0], toHex(depo.commitment), []), - ).to.changeTokenBalance(RAIToken, whaleRAI, BigNumber.from(0).sub(await TornadoInstance.denomination())) - - let pevents = await mixerContract.queryFilter('Deposit') - await initialize({ merkleTreeHeight: 20 }) - - const { proof, args } = await generateProof({ - deposit: depo, - recipient: whaleRAI.address, - events: pevents, - }) - - await expect(() => - TornadoProxy.withdraw(TornadoInstance.address, proof, ...args), - ).to.changeTokenBalance(RAIToken, whaleRAI, await TornadoInstance.denomination()) - - await sendr('evm_revert', [snapshotId]) - snapshotId = await sendr('evm_snapshot', []) - }) - - it('Should prepare for multiple account deposits', async () => { - let toSend = whaleRAIBalance.div(5) - - for (let i = 0; i < 3; i++) { - await RAIToken.transfer(accounts[i].address, toSend) - const rai = await RAIToken.connect(accounts[i]) - await rai.approve(TornadoProxy.address, pE(600000)) - } - }) - - it('Should test depositing with multiple accounts over proxy', async () => { - for (let i = 0; i < 3; i++) { - const depo = createDeposit({ - nullifier: rbigint(31), - secret: rbigint(31), - }) - // const note = toHex(depo.preimage, 62) - // const noteString = `tornado-RAI-33-1-${note}` - // clog('Note: ', note) - // clog('Note string: ', noteString) - // clog('Commitment: ', toHex(depo.commitment)) - const proxy = await TornadoProxy.connect(accounts[i]) - - await expect(() => - proxy.deposit(TornadoInstance.address, toHex(depo.commitment), []), - ).to.changeTokenBalance( - RAIToken, - accounts[i], - BigNumber.from(0).sub(await TornadoInstance.denomination()), - ) - - let pevents = await mixerContract.queryFilter('Deposit') - await initialize({ merkleTreeHeight: 20 }) - - const { proof, args } = await generateProof({ - deposit: depo, - recipient: accounts[i].address, - events: pevents, - }) - - await expect(() => proxy.withdraw(TornadoInstance.address, proof, ...args)).to.changeTokenBalance( - RAIToken, - accounts[i], - await TornadoInstance.denomination(), - ) - } - }) - }) - - after(async function () { - await ethers.provider.send('hardhat_reset', [ - { - forking: { - jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, - blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436, - }, - }, - ]) - }) -}) diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..7745595 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,43 @@ +/* global ethers, network */ + +async function setTime(timestamp) { + await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp]) +} + +async function takeSnapshot() { + return await ethers.provider.send('evm_snapshot', []) +} + +async function revertSnapshot(id) { + await ethers.provider.send('evm_revert', [id]) +} + +async function advanceTime(sec) { + const now = (await ethers.provider.getBlock('latest')).timestamp + await setTime(now + sec) +} + +async function getSignerFromAddress(address) { + await network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [address], + }) + + let signer = await ethers.provider.getSigner(address) + signer.address = signer._address + return signer +} + +async function minewait(time) { + await ethers.provider.send('evm_increaseTime', [time]) + await ethers.provider.send('evm_mine', []) +} + +module.exports = { + setTime, + advanceTime, + takeSnapshot, + revertSnapshot, + getSignerFromAddress, + minewait, +} diff --git a/yarn.lock b/yarn.lock index 2cd4925..73802ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1996,6 +1996,19 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== +"@iden3/bigarray@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@iden3/bigarray/-/bigarray-0.0.2.tgz#6fc4ba5be18daf8a26ee393f2fb62b80d98c05e9" + integrity sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g== + +"@iden3/binfileutils@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@iden3/binfileutils/-/binfileutils-0.0.8.tgz#d1d349bdbaa9f0a99644232c7d75ea0db98ea1c7" + integrity sha512-/GqTsujUssGuQY+sd/XaLrA+OiCwzm+6yH28C57QQDWCHET2Logry9fGxU10n6XKdhCQBjZ7T/YMQkLwwkpRTQ== + dependencies: + fastfile "0.0.19" + ffjavascript "^0.2.30" + "@improbable-eng/grpc-web@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web/-/grpc-web-0.12.0.tgz#9b10a7edf2a1d7672f8997e34a60e7b70e49738f" @@ -2133,12 +2146,22 @@ "@types/sinon-chai" "^3.2.3" "@types/web3" "1.0.19" +"@openzeppelin/contracts@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.2.0.tgz#3e6b3a7662d8ed64271ade96ef42655db983fd9d" + integrity sha512-bUOmkSoPkjnUyMiKo6RYnb0VHBk5D9KKDAgNLzF41aqAM3TeE0yGdFF5dVRcV60pZdJLlyFT/jjXIZCWyyEzAQ== + "@openzeppelin/contracts@3.2.0-rc.0": version "3.2.0-rc.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.2.0-rc.0.tgz#1f39e49df5f7a7b42fd49343ac1d758bbd24b826" integrity sha512-EcEho5UFNZN1ZUHuD5ka38qgs+XWlzBM1FFfpu4YNVoo0xtwWeg7X52jm8pK+NYq8tJAI8ySjGYPM+4S5+N8iA== -"@openzeppelin/contracts@3.4.2", "@openzeppelin/contracts@^3.2.0-rc.0", "@openzeppelin/contracts@^3.4.1": +"@openzeppelin/contracts@3.4.1-solc-0.7-2": + version "3.4.1-solc-0.7-2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" + integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q== + +"@openzeppelin/contracts@3.4.2", "@openzeppelin/contracts@^3.2.0-rc.0", "@openzeppelin/contracts@^3.4.0", "@openzeppelin/contracts@^3.4.1": version "3.4.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA== @@ -2148,6 +2171,13 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1.tgz#03c891fec7f93be0ae44ed74e57a122a38732ce7" integrity sha512-cUriqMauq1ylzP2TxePNdPqkwI7Le3Annh4K9rrpvKfSBB/bdW+Iu1ihBaTIABTAAJ85LmKL5SSPPL9ry8d1gQ== +"@openzeppelin/hardhat-upgrades@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.10.0.tgz#e7751e3b9a005ccc9cef4e0de190628b181b59b6" + integrity sha512-iGe058iV7Ba/g11RxlbqBG47nbqbZn1FRdg1FCQq7xPmvjRhXmFsoI/5gGw5el0aZlLDRtpFOBZbzMZvI/S7iw== + dependencies: + "@openzeppelin/upgrades-core" "^1.9.0" + "@openzeppelin/upgrades-core@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.0.1.tgz#2a3d81e3d3bdf5805b5492c47cdb21941ef57fdc" @@ -2164,6 +2194,20 @@ proper-lockfile "^4.1.1" solidity-ast "^0.4.1" +"@openzeppelin/upgrades-core@^1.5.1", "@openzeppelin/upgrades-core@^1.9.0": + version "1.12.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.12.0.tgz#847aeeba38780040672f8288795c824a4c9d97e6" + integrity sha512-gu/ijQW+RJqGlniNkpNmiwBus3R1cuJNT0/MEJASWRFNr4Qvn0d7LZONaAkhnvlBpxdiiPenMcFIrRlwvZL4iw== + dependencies: + bn.js "^5.1.2" + cbor "^8.0.0" + chalk "^4.1.0" + compare-versions "^4.0.0" + debug "^4.1.1" + ethereumjs-util "^7.0.3" + proper-lockfile "^4.1.1" + solidity-ast "^0.4.15" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -2678,6 +2722,14 @@ event-iterator "^2.0.0" loglevel "^1.7.0" +"@ticket721/e712@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@ticket721/e712/-/e712-0.4.1.tgz#b9be3f5d9d01a8468e0a49eeed67fe4454c87582" + integrity sha512-JHqyb2HntsmLJ4PtlXQ2AoU+s42X1itRtxeCAlA1AxOMORXQudbhly9wvGhN/Hbcl8VYpfgLJKp4hK3AT6xDiw== + dependencies: + bn.js "5.1.1" + ethers "4.0.41" + "@truffle/abi-utils@^0.1.0": version "0.1.6" resolved "https://registry.yarnpkg.com/@truffle/abi-utils/-/abi-utils-0.1.6.tgz#d754a54caec2577efaa05f0ca66c58e73676884e" @@ -3500,6 +3552,36 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + +"@uniswap/lib@^4.0.1-alpha": + version "4.0.1-alpha" + resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" + integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== + +"@uniswap/v2-core@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + +"@uniswap/v3-core@1.0.0", "@uniswap/v3-core@https://github.com/Tisamenus/uniswap-v3-core": + version "1.0.0" + resolved "https://github.com/Tisamenus/uniswap-v3-core#08bec74e0b902f97f34bdd47229ca4e61fbea1bd" + +"@uniswap/v3-periphery@https://github.com/Tisamenus/uniswap-v3-periphery": + version "1.1.1" + resolved "https://github.com/Tisamenus/uniswap-v3-periphery#928c03c5d67a4d6c9032fb000cf861c8b659d941" + dependencies: + "@openzeppelin/contracts" "3.4.1-solc-0.7-2" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + base64-sol "1.0.1" + hardhat-watcher "^2.1.1" + "@wry/context@^0.6.0": version "0.6.1" resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.1.tgz#c3c29c0ad622adb00f6a53303c4f965ee06ebeb2" @@ -4107,6 +4189,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + argsarray@0.0.1, argsarray@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb" @@ -4287,6 +4374,11 @@ async-retry@^1.2.1: dependencies: retry "0.13.1" +async@0.9.x: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= + async@1.x, async@^1.4.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -5008,6 +5100,11 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64-sol@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6" + integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -5061,6 +5158,11 @@ big-integer@^1.6.32, big-integer@^1.6.42, big-integer@^1.6.43: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== +big-integer@^1.6.48: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -5173,6 +5275,12 @@ blake2b-wasm@^1.1.0: dependencies: nanoassert "^1.0.0" +"blake2b-wasm@git+https://github.com/jbaylina/blake2b-wasm.git": + version "2.1.0" + resolved "git+https://github.com/jbaylina/blake2b-wasm.git#0d5f024b212429c7f50a7f533aa3a2406b5b42b3" + dependencies: + nanoassert "^1.0.0" + blake2b@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/blake2b/-/blake2b-2.1.3.tgz#f5388be424768e7c6327025dad0c3c6d83351bca" @@ -5213,7 +5321,12 @@ bn.js@4.11.8, bn.js@=4.11.8: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.8.0: +bn.js@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5" + integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0, bn.js@^4.8.0: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== @@ -5727,6 +5840,11 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + caniuse-lite@^1.0.30000844: version "1.0.30001246" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001246.tgz#fe17d9919f87124d6bb416ef7b325356d69dc76c" @@ -5750,6 +5868,13 @@ cbor@^5.0.2, cbor@^5.1.0: bignumber.js "^9.0.1" nofilter "^1.0.4" +cbor@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5" + integrity sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg== + dependencies: + nofilter "^3.1.0" + center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" @@ -5933,6 +6058,21 @@ chokidar@3.4.2: optionalDependencies: fsevents "~2.1.2" +chokidar@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -5967,6 +6107,21 @@ chokidar@^3.4.0, chokidar@^3.4.1: optionalDependencies: fsevents "~2.3.2" +chokidar@^3.4.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.1.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -6015,6 +6170,69 @@ circom@0.0.35, circom@^0.0.35: optimist "^0.6.1" yargs "^12.0.2" +circom@0.5.33: + version "0.5.33" + resolved "https://registry.yarnpkg.com/circom/-/circom-0.5.33.tgz#6943d5799adf5388989bfbb3ef8f502fb1b4f662" + integrity sha512-UdL8fr6GckhQ4VoWjIvuYwCHneJe8z/AyJpDxgKLyuaX51ijd4gBP6jlwHDbQJsha2aU2GR9qgDsxd0jfari1Q== + dependencies: + chai "^4.2.0" + circom_runtime "0.1.8" + fastfile "0.0.18" + ffiasm "0.1.1" + ffjavascript "0.2.22" + ffwasm "0.0.7" + fnv-plus "^1.3.1" + r1csfile "0.0.16" + tmp-promise "^2.0.2" + wasmbuilder "0.0.10" + +circom@0.5.42: + version "0.5.42" + resolved "https://registry.yarnpkg.com/circom/-/circom-0.5.42.tgz#96a456f9538f4425654df091d15e3158e9da2acc" + integrity sha512-v6+f9g3z2ia17NQvQmyZjvh8cE8O3GtxRE36KfJfx/a+s58Y7aEDWsUG+GFRJhp1ajiQELdj3NehY9vHSf5Rkg== + dependencies: + chai "^4.2.0" + circom_runtime "0.1.12" + fastfile "0.0.18" + ffiasm "0.1.1" + ffjavascript "0.2.22" + ffwasm "0.0.7" + fnv-plus "^1.3.1" + r1csfile "0.0.16" + tmp-promise "^2.0.2" + wasmbuilder "0.0.10" + +circom_runtime@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.12.tgz#e1a302c6fe8cec390f035c2e7a8496cfa7cfb4a2" + integrity sha512-R+QT9HS9w71cmGmWIn+PSyD3aHyR5JZBiVvxOjCfn12wwnpuFwBjdMG7he+v8h/oQD1mDRAu2KrBeL4mAt5s4A== + dependencies: + ffjavascript "0.2.34" + fnv-plus "^1.3.1" + +circom_runtime@0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.13.tgz#90f86f35d989c48d4c27595b94664ea6918fbede" + integrity sha512-vmv19/0p5OTe5uCI7PWqPtB5vPoYWjczqKYnabaC5HOxX99R4K1MuNqEXsNEAoEfZrmfAQd7vXLcATN9NVnsPA== + dependencies: + ffjavascript "0.2.35" + fnv-plus "^1.3.1" + +circom_runtime@0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.8.tgz#d967a1618fe5290849f9c0bbffb6b97b95c0f1c8" + integrity sha512-5ZmzCyidkNPb1zZsJGRXTuWcJ6kW6+gRBtHgf2tFqTh5dUyWVVPH0Zg7AsU2ijPr1AmYZUlme0yORUZK5HrjOA== + dependencies: + ffjavascript "0.2.10" + fnv-plus "^1.3.1" + +circom_runtime@^0.1.12: + version "0.1.17" + resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.17.tgz#9360017d6b5d9291128da4fe05830384ef293ec1" + integrity sha512-FCOCPz7ZbqL4TpzBlISRZ7/fcYHkdZz0DMfju1DYHiRU/+ZzJQfDS8JYENlnb9PO+HsLTr6/QtzphqvnEBp9AQ== + dependencies: + ffjavascript "0.2.48" + "circomlib@git+https://github.com/tornadocash/circomlib.git#3b492f9801573eebcfe1b6c584afe8a3beecf2b4": version "0.0.20" resolved "git+https://github.com/tornadocash/circomlib.git#3b492f9801573eebcfe1b6c584afe8a3beecf2b4" @@ -6045,6 +6263,15 @@ circom@0.0.35, circom@^0.0.35: typedarray-to-buffer "^3.1.5" web3 "^1.0.0-beta.55" +"circomlib@git+https://github.com/tornadocash/circomlib.git#d20d53411d1bef61f38c99a8b36d5d0cc4836aa1": + version "0.4.1" + resolved "git+https://github.com/tornadocash/circomlib.git#d20d53411d1bef61f38c99a8b36d5d0cc4836aa1" + dependencies: + blake-hash "^1.1.0" + blake2b "^2.1.3" + circom "0.5.33" + ffjavascript "0.1.0" + circular-json@^0.5.9: version "0.5.9" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d" @@ -6288,6 +6515,11 @@ compare-versions@^3.6.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +compare-versions@^4.0.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== + component-emitter@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -6740,6 +6972,13 @@ debug@4.1.1: dependencies: ms "^2.1.1" +debug@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + debug@^3.1.0, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -6752,6 +6991,16 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +decimal.js@^10.2.0: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -6993,6 +7242,11 @@ diff@4.0.2: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -7194,6 +7448,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +ejs@^3.0.1: + version "3.1.6" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" + integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + dependencies: + jake "^10.6.1" + electron-fetch@^1.7.2: version "1.7.4" resolved "https://registry.yarnpkg.com/electron-fetch/-/electron-fetch-1.7.4.tgz#af975ab92a14798bfaa025f88dcd2e54a7b0b769" @@ -7211,6 +7472,19 @@ electron-to-chromium@^1.3.878: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.879.tgz#4aba9700cfb241fb95c6ed69e31785e3d1605a43" integrity sha512-zJo+D9GwbJvM31IdFmwcGvychhk4KKbKYo2GWlsn+C/dxz2NwmbhGJjWwTfFSF2+eFH7VvfA8MCZ8SOqTrlnpw== +elliptic@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -8319,6 +8593,21 @@ ethereumjs-wallet@^1.0.1: utf8 "^3.0.0" uuid "^8.3.2" +ethers@4.0.41: + version "4.0.41" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.41.tgz#a0cff526f08c2e08c525cf82ef4483f6333b8000" + integrity sha512-QpW2CPZajquwiA7rVDozMksOuvdUBKIruamAakt0++EKBB/VWtLB9zSRZDInLDpp9fZYgOT/0trPD38r6CzLIg== + dependencies: + aes-js "3.0.0" + bn.js "^4.4.0" + elliptic "6.5.2" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.4" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + ethers@^4.0.32: version "4.0.49" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" @@ -8406,7 +8695,7 @@ ethers@^5.0.13: "@ethersproject/web" "5.5.0" "@ethersproject/wordlists" "5.5.0" -ethers@^5.0.22: +ethers@^5.0.22, ethers@^5.5.1: version "5.5.4" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.4.tgz#e1155b73376a2f5da448e4a33351b57a885f4352" integrity sha512-N9IAXsF8iKhgHIC6pquzRgPBJEzc9auw3JoRkaKe+y4Wl/LFBtDDunNe7YmdomontECAcC5APaAgWZBiu1kirw== @@ -8757,6 +9046,16 @@ fastestsmallesttextencoderdecoder@^1.0.22: resolved "https://registry.yarnpkg.com/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz#59b47e7b965f45258629cc6c127bf783281c5e93" integrity sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw== +fastfile@0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/fastfile/-/fastfile-0.0.18.tgz#2b69bbbfd2fcccc9bc8099c27de1379b89756a4b" + integrity sha512-q03PTKc+wptis4WmuFOwPNQx2p5myFUrl/dMgRlW9mymc1Egyc14JPHgiGnWK+sJ0+dBl2Vwtfh5GfSQltYOpw== + +fastfile@0.0.19: + version "0.0.19" + resolved "https://registry.yarnpkg.com/fastfile/-/fastfile-0.0.19.tgz#02cef9ade123b0a74adb794f4a1abcfa5719fd46" + integrity sha512-tz9nWR5KYb6eR2odFQ7oxqEkx8F3YQZ6NBJoJR92YEG3DqYOqyxMck8PKvTVNKx3uwvOqGnLXNScnqpdHRdHGQ== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -8811,6 +9110,86 @@ fetch-ponyfill@^4.0.0: dependencies: node-fetch "~1.7.1" +ffiasm@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ffiasm/-/ffiasm-0.1.1.tgz#34ca6a00a875b5a926f66fd46e79530194e9c312" + integrity sha512-irMMHiR9JJ7BVBrAhtliUawxVdPYSdyl81taUYJ4C1mJ0iw2ueThE/qtr0J8B83tsIY8HJvh0lg5F+6ClK4xpA== + dependencies: + big-integer "^1.6.48" + ejs "^3.0.1" + yargs "^15.3.1" + +ffiasm@^0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/ffiasm/-/ffiasm-0.1.4.tgz#d84b6d656e6b28b1aff1eefbc3d31609c07127a3" + integrity sha512-3WmLCxWAr9n/lyvMJuB+rXXnQ4C0Md9UxQIL6ZT3a+Ux9OcMR0+Qj6UE0LfOYny0Lq7KuvyYtDCZL9+wwMNGUg== + dependencies: + big-integer "^1.6.48" + ejs "^3.0.1" + yargs "^15.3.1" + +ffjavascript@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.1.0.tgz#456256c259654cc1ce864c6762b0e76ee1714100" + integrity sha512-dmKlUasSfvUcxBm8nCSKl2x7EFJsXA7OVP8XLFA03T2+6mAc3IiVLC2ambEVOcMOhyhl0vJfVZjM9f9d38D1rw== + dependencies: + big-integer "^1.6.48" + +ffjavascript@0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.10.tgz#b0bf88d69be0b51e0bd28e1966c4a6fb29a86682" + integrity sha512-GQI6gHYYG5/iD4Kt3VzezzK7fARJzP0zkc82V/+JAdjfeKBXhDSo5rpKFuK3cDcrdW0Fu2emuYNMEAuFqhEQvQ== + dependencies: + big-integer "^1.6.48" + wasmcurves "0.0.5" + worker-threads "^1.0.0" + +ffjavascript@0.2.22: + version "0.2.22" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.22.tgz#101f33db330b0f6a0c10dec22ebf5725618a8a7d" + integrity sha512-EsVqap2Txm17bKW0z/jXCX3M7rQ++nQUAJY8alWDpyhjRj90xjl6GLeVSKZQ8rOFDQ/SFFXcEB8w9X8Boxid+w== + dependencies: + big-integer "^1.6.48" + wasmcurves "0.0.12" + worker-threads "^1.0.0" + +ffjavascript@0.2.34: + version "0.2.34" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.34.tgz#e0607d1635ad06e8519268af475bc90deac60fbd" + integrity sha512-fq/qfJluC4spiOD1lp5jfckZVnS0o0kI5eKXVLw7UKwIwbNr+NBMBveBVcidSfMizF87T6wb7NBtLSdckQiAnQ== + dependencies: + big-integer "^1.6.48" + mocha "^8.2.1" + wasmcurves "0.0.14" + worker-threads "^1.0.0" + +ffjavascript@0.2.35: + version "0.2.35" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.35.tgz#9166d95173b1c0a743b455bb03a72b581922a42e" + integrity sha512-xnC51tWbi0ah4SH+02jEfJyO+P+NiZWnxQrLDLtBYY1Dv3QM5ydxzd+gxnLEfWdT8i1bMM5pIh5P25l6fNCaVQ== + dependencies: + big-integer "^1.6.48" + wasmcurves "0.0.14" + web-worker "^1.0.0" + +ffjavascript@0.2.48, ffjavascript@^0.2.30, ffjavascript@^0.2.35: + version "0.2.48" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.48.tgz#0ca408471d7b18bfc096a9631aa3ef3549c8c82b" + integrity sha512-uNrWP+odLofNmmO+iCCPi/Xt/sJh1ku3pVKmKWVWCLFfdCP69hvRrogKUIGnsdiINcWn0lGxcEh5oEjStMFXQQ== + dependencies: + big-integer "^1.6.48" + wasmbuilder "^0.0.12" + wasmcurves "0.1.0" + web-worker "^1.2.0" + +ffwasm@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ffwasm/-/ffwasm-0.0.7.tgz#23bb9a3537ecc87c0f24fcfb3a9ddd0e86855fff" + integrity sha512-17cTLzv7HHAKqZbX8MvHxjSrR0yDdn1sh4TVsTbAvO9e6klhFicnyoVXc/sCuViV/M8g65sCmVrAmoPCZp1YkQ== + dependencies: + big-integer "^1.6.48" + wasmbuilder "0.0.10" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -8860,6 +9239,13 @@ filecoin.js@^0.0.5-alpha: websocket "^1.0.31" ws "^7.3.1" +filelist@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" + integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + dependencies: + minimatch "^3.0.4" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -8972,6 +9358,14 @@ first-chunk-stream@^1.0.0: resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04= +fixed-merkle-tree@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/fixed-merkle-tree/-/fixed-merkle-tree-0.3.4.tgz#533888cfacca00f0b4e26c952173be7e456ae63a" + integrity sha512-0nDwPR/WV9nViiItNGr3A8LDCh27JDEqemW2I0i1HK6kkpvPzXfoBSolPXmqg0eo2i/UMwlxF2d6m0F/vyfeMA== + dependencies: + circomlib "git+https://github.com/tornadocash/circomlib.git#5beb6aee94923052faeecea40135d45b6ce6172c" + snarkjs "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5" + fixed-merkle-tree@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/fixed-merkle-tree/-/fixed-merkle-tree-0.5.1.tgz#770bbf64b174e88b1133841721b79ea99a63d0b5" @@ -9012,6 +9406,11 @@ flat@^4.1.0: dependencies: is-buffer "~2.0.3" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + flatted@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" @@ -9027,6 +9426,11 @@ flow-stoplight@^1.0.0: resolved "https://registry.yarnpkg.com/flow-stoplight/-/flow-stoplight-1.0.0.tgz#4a292c5bcff8b39fa6cc0cb1a853d86f27eeff7b" integrity sha1-SiksW8/4s5+mzAyxqFPYbyfu/3s= +fnv-plus@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/fnv-plus/-/fnv-plus-1.3.1.tgz#c34cb4572565434acb08ba257e4044ce2b006d67" + integrity sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw== + follow-redirects@1.5.10: version "1.5.10" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" @@ -9233,7 +9637,7 @@ fsevents@~2.1.1, fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== -fsevents@~2.3.2: +fsevents@~2.3.1, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -9817,6 +10221,13 @@ hardhat-log-remover@^2.0.2: resolved "https://registry.yarnpkg.com/hardhat-log-remover/-/hardhat-log-remover-2.0.2.tgz#6014fe2c515ced1e0eaa7a4d854e37695aaac61a" integrity sha512-TvEWOisQmZUZ7Mlb7s4XYS/MxgZzjFJSjDI8v3uTcrD6aaXy1QtomW6D6WNsISEWtwwRlSntOGpHQwFjrz2MCw== +hardhat-watcher@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.1.1.tgz#8b05fec429ed45da11808bbf6054a90f3e34c51a" + integrity sha512-zilmvxAYD34IofBrwOliQn4z92UiDmt2c949DW4Gokf0vS0qk4YTfVCi/LmUBICThGygNANE3WfnRTpjCJGtDA== + dependencies: + chokidar "^3.4.3" + hardhat@^2.4.3: version "2.6.0" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.6.0.tgz#a00a44d36559a880c170dca6363cc33f7545364b" @@ -10022,7 +10433,7 @@ highlightjs-solidity@^2.0.1: resolved "https://registry.yarnpkg.com/highlightjs-solidity/-/highlightjs-solidity-2.0.1.tgz#ee1beb6f353d4503aa3a011bbb86577976365b59" integrity sha512-9YY+HQpXMTrF8HgRByjeQhd21GXAz2ktMPTcs6oWSj5HJR52fgsNoelMOmgigwcpt9j4tu4IVSaWaJB2n2TbvQ== -hmac-drbg@^1.0.1: +hmac-drbg@^1.0.0, hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -10830,7 +11241,7 @@ is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-obj@^2.0.0: +is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== @@ -11140,6 +11551,16 @@ iterate-value@^1.0.0: es-get-iterator "^1.0.2" iterate-iterator "^1.0.1" +jake@^10.6.1: + version "10.8.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" + integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== + dependencies: + async "0.9.x" + chalk "^2.4.2" + filelist "^1.0.1" + minimatch "^3.0.4" + js-sha3@0.5.7, js-sha3@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" @@ -11184,6 +11605,13 @@ js-yaml@3.x, js-yaml@^3.13.0, js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" + integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== + dependencies: + argparse "^2.0.1" + jsan@^3.1.13: version "3.1.13" resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.13.tgz#4de8c7bf8d1cfcd020c313d438f930cec4b91d86" @@ -11426,6 +11854,11 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jssha@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jssha/-/jssha-3.2.0.tgz#88ec50b866dd1411deaddbe6b3e3692e4c710f16" + integrity sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q== + keccak@3.0.1, keccak@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" @@ -12105,6 +12538,11 @@ loglevel@^1.6.6, loglevel@^1.6.7, loglevel@^1.6.8, loglevel@^1.7.0: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== +logplease@^1.2.15: + version "1.2.15" + resolved "https://registry.yarnpkg.com/logplease/-/logplease-1.2.15.tgz#3da442e93751a5992cc19010a826b08d0293c48a" + integrity sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA== + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -12517,7 +12955,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= @@ -12676,6 +13114,37 @@ mocha@^7.1.2: yargs-parser "13.1.2" yargs-unparser "1.6.0" +mocha@^8.2.1: + version "8.4.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" + integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.1" + debug "4.3.1" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.1.6" + growl "1.10.5" + he "1.2.0" + js-yaml "4.0.0" + log-symbols "4.0.0" + minimatch "3.0.4" + ms "2.1.3" + nanoid "3.1.20" + serialize-javascript "5.0.1" + strip-json-comments "3.1.1" + supports-color "8.1.1" + which "2.0.2" + wide-align "1.1.3" + workerpool "6.1.0" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + mock-fs@^4.1.0: version "4.14.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" @@ -12730,7 +13199,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -12892,6 +13361,11 @@ nanoassert@^1.0.0: resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-1.1.0.tgz#4f3152e09540fde28c76f44b19bbcd1d5a42478d" integrity sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40= +nanoid@3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== + nanoid@^2.0.0: version "2.1.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" @@ -13144,6 +13618,11 @@ nofilter@^1.0.4: resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.4.tgz#78d6f4b6a613e7ced8b015cec534625f7667006e" integrity sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA== +nofilter@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" + integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g== + noop-fn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/noop-fn/-/noop-fn-1.0.0.tgz#5f33d47f13d2150df93e0cb036699e982f78ffbf" @@ -14637,6 +15116,25 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +r1csfile@0.0.16: + version "0.0.16" + resolved "https://registry.yarnpkg.com/r1csfile/-/r1csfile-0.0.16.tgz#53c66a79b50eebc2d15a1048e39d548ce9da7ccd" + integrity sha512-A2jRVWzGgmXeG2lVAc0H4suJmzt50it5UvBnycJgBCpMXM3tH/M6RguP7nvs6suY/yYnkN6jX6iTScSiDUF3FA== + dependencies: + "@iden3/bigarray" "0.0.2" + fastfile "0.0.18" + ffjavascript "0.2.22" + +r1csfile@0.0.32: + version "0.0.32" + resolved "https://registry.yarnpkg.com/r1csfile/-/r1csfile-0.0.32.tgz#64a6c63ff76b737b3ee22bcedb2bb9a033cbeb1a" + integrity sha512-DkRXeOg0iRmfhgIuWICvdkOiLHpyb7+AcUd/WHpqBJEUp27pe7wKXBR4Jr3TPYCT4sTV9a/F3bovyAC4wystnQ== + dependencies: + "@iden3/bigarray" "0.0.2" + "@iden3/binfileutils" "0.0.8" + fastfile "0.0.19" + ffjavascript "0.2.35" + randomatic@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" @@ -14844,6 +15342,13 @@ readdirp@~3.4.0: dependencies: picomatch "^2.2.1" +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -14851,6 +15356,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +readline@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c" + integrity sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw= + receptacle@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/receptacle/-/receptacle-1.3.2.tgz#a7994c7efafc7a01d0e2041839dab6c4951360d2" @@ -15262,7 +15772,7 @@ rimraf@^2.2.8, rimraf@^2.6.1, rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -15521,6 +16031,13 @@ serialize-javascript@4.0.0: dependencies: randombytes "^2.1.0" +serialize-javascript@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -15765,6 +16282,20 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +snarkjs@^0.3.57: + version "0.3.60" + resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.3.60.tgz#fe573e347a924af8ed162154e866e02ef8d8230c" + integrity sha512-l3QMKvr+KUetxlJq9TCS0KNxiUquUDYFqHIzn3TxNSPPlcQfIq6V0isZKCjuML+XNGaoJ7s+kfdAZ8qp/2yOYQ== + dependencies: + "@iden3/binfileutils" "0.0.8" + blake2b-wasm "https://github.com/jbaylina/blake2b-wasm.git" + circom_runtime "0.1.13" + fastfile "0.0.19" + ffjavascript "0.2.35" + logplease "^1.2.15" + r1csfile "0.0.32" + readline "^1.3.0" + "snarkjs@git+https://github.com/peppersec/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5": version "0.1.20" uid "869181cfaf7526fe8972073d31655493a04326d5" @@ -15870,6 +16401,11 @@ solidity-ast@^0.4.1: resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.26.tgz#6e644bdb897c245dd07695a63ffa95edfe02c11f" integrity sha512-UR9Ip3QoiEvNON5lOA28JNEzKT+1fLFA4xpIbZSEl4CEnYr/a4Pj0qMJh0652UQ51pKplI/nncZsDOMzdHdCcg== +solidity-ast@^0.4.15: + version "0.4.30" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.30.tgz#402d8277311d6680c786f756ba27e1c19f809293" + integrity sha512-3xsQIbZEPx6w7+sQokuOvk1RkMb5GIpuK0GblQDIH6IAkU4+uyJQVJIRNP+8KwhzkViwRKq0hS4zLqQNLKpxOA== + solidity-comments-extractor@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" @@ -16324,7 +16860,7 @@ strip-json-comments@3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -16376,6 +16912,13 @@ supports-color@7.1.0: dependencies: has-flag "^4.0.0" +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -16676,6 +17219,20 @@ title-case@^2.1.0: no-case "^2.2.0" upper-case "^1.0.3" +tmp-promise@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-2.1.1.tgz#eb97c038995af74efbfe8156f5e07fdd0c935539" + integrity sha512-Z048AOz/w9b6lCbJUpevIJpRpUztENl8zdv1bmAKVHimfqRFl92ROkmT9rp7TVBnrEw2gtMTol/2Cp2S2kJa4Q== + dependencies: + tmp "0.1.0" + +tmp-promise@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" + integrity sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== + dependencies: + tmp "^0.2.0" + tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -16690,6 +17247,13 @@ tmp@0.1.0: dependencies: rimraf "^2.6.3" +tmp@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-absolute-glob@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" @@ -16781,6 +17345,20 @@ torn-token@^1.0.0, torn-token@^1.0.4: ethereumjs-util "^7.0.3" web3 "^1.2.11" +tornado-anonymity-mining@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/tornado-anonymity-mining/-/tornado-anonymity-mining-2.1.5.tgz#7dbc7f099ce9667f2cc91fbb7ce8f78663afc4cc" + integrity sha512-hYXKHyPs7nqpGjPgWpuBJrnDSTZ4FifU8Mv/ujueYXVOzkcL64ZOq30JFDE6ANQ7D09AhD7B7pSx/Odgn+nlgw== + dependencies: + circomlib "git+https://github.com/tornadocash/circomlib.git#3b492f9801573eebcfe1b6c584afe8a3beecf2b4" + decimal.js "^10.2.0" + eth-sig-util "^2.5.3" + fixed-merkle-tree "^0.3.4" + snarkjs "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5" + tornado-trees "^0.0.11" + web3 "^1.2.11" + websnark "git+https://github.com/tornadocash/websnark.git#86a526718cd6f6f5d31bdb1fe26a9ec8819f633e" + tornado-cli@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/tornado-cli/-/tornado-cli-0.0.1.tgz#ee11f6cf781043640c42323bf75ac5a8400876dc" @@ -16851,6 +17429,40 @@ tornado-governance@^1.0.3: "@openzeppelin/upgrades-core" "^1.0.1" torn-token "^1.0.0" +"tornado-relayer-registry@https://github.com/tornadocash/tornado-relayer-registry.git": + version "1.0.0" + resolved "https://github.com/tornadocash/tornado-relayer-registry.git#a5bdbdb4cd44d3951ef6445cce027ee06b5d3400" + dependencies: + "@openzeppelin/contracts" "3.2.0" + "@openzeppelin/hardhat-upgrades" "1.10.0" + "@ticket721/e712" "^0.4.1" + "@uniswap/v3-core" "https://github.com/Tisamenus/uniswap-v3-core" + "@uniswap/v3-periphery" "https://github.com/Tisamenus/uniswap-v3-periphery" + eth-ens-namehash "^2.0.8" + ethereum-waffle "^3.4.0" + ethers "^5.5.1" + tornado-anonymity-mining "^2.1.5" + tornado-cli "^0.0.1" + tornado-governance "2.0.0" + +tornado-trees@^0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/tornado-trees/-/tornado-trees-0.0.11.tgz#444b9b65bcb72350c0a535f2f37c4d6d3efd93a7" + integrity sha512-dlVGyA821feaglqyLT40oxzRT2LERz1Sothisbu64DxjrFlLlZA1ATDs76sq6En5yCoTJ9eyoE4D+UsDUpq2Fw== + dependencies: + "@openzeppelin/contracts" "^3.4.0" + "@openzeppelin/upgrades-core" "^1.5.1" + circom "0.5.42" + circom_runtime "^0.1.12" + circomlib "git+https://github.com/tornadocash/circomlib.git#d20d53411d1bef61f38c99a8b36d5d0cc4836aa1" + dotenv "^8.2.0" + ffiasm "^0.1.1" + ffjavascript "^0.2.35" + fixed-merkle-tree "^0.5.0" + jssha "^3.2.0" + snarkjs "^0.3.57" + tmp-promise "^3.0.2" + tough-cookie@^2.2.0, tough-cookie@^2.3.1, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -17569,6 +18181,52 @@ vuvuzela@1.0.3: resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b" integrity sha1-O+FF5YJxxzylUnndhR8SpoIRSws= +wasmbuilder@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/wasmbuilder/-/wasmbuilder-0.0.10.tgz#b8298b2095ef9979d32f3881d1feef1705ec868a" + integrity sha512-zQSvZ7d74d9OvN+mCN6ucNne4QS5/cBBYTHldX0Oe+u9gStY21orapvuX1ajisA7RVIpuFhYg+ZgdySsPfeh0A== + dependencies: + big-integer "^1.6.48" + +wasmbuilder@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/wasmbuilder/-/wasmbuilder-0.0.12.tgz#a60cb25d6d11f314fe5ab3f4ee041ccb493cb78a" + integrity sha512-dTMpBgrnLOXrN58i2zakn2ScynsBhq9LfyQIsPz4CyxRF9k1GAORniuqn3xmE9NnI1l7g3iiVCkoB2Cl0/oG8w== + dependencies: + big-integer "^1.6.48" + +wasmcurves@0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/wasmcurves/-/wasmcurves-0.0.12.tgz#1496e2219ac07f9a420f527803ae13b1d7a89246" + integrity sha512-1Jl9mkatyHSNj80ILjf85SZUNuZQBCkTjJlhzqHnZQXUmIimCIWkugaVaYNjozLs1Gun4h/keZe1MBeBN0sRpg== + dependencies: + big-integer "^1.6.42" + blakejs "^1.1.0" + +wasmcurves@0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/wasmcurves/-/wasmcurves-0.0.14.tgz#cbe0f19650d9554937154afdbed66b305bd2a348" + integrity sha512-G1iMkxlRaQSdqQ1JrwHcU+awLmwyH6kFKfT8g9obd8MWe+u5oSdFXrODB0zmSI5aGGvJPG+4cAmqCGYv9R+7qg== + dependencies: + big-integer "^1.6.42" + blakejs "^1.1.0" + +wasmcurves@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/wasmcurves/-/wasmcurves-0.0.5.tgz#d0b58e803c0b1c09c966b7dc0fad6dd405d18547" + integrity sha512-BmI4GXLjLawGg2YkvHa8zRsnWec+d1uwoxE+Iov8cqOpDL7GA5XO2pk2yuDbXHMzwIug2exnKot3baRZ86R0pA== + dependencies: + big-integer "^1.6.42" + blakejs "^1.1.0" + +wasmcurves@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/wasmcurves/-/wasmcurves-0.1.0.tgz#0bc3f9d465367fcd8243395cb0094a05577e5609" + integrity sha512-kIlcgbVUAv2uQ6lGsepGz/m5V40+Z6rvTBkqCYn3Y2+OcXst+UaP4filJYLh/xDxjJl62FFjZZeAnpeli1Y5/Q== + dependencies: + big-integer "^1.6.42" + blakejs "^1.1.0" + watchpack-chokidar2@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" @@ -17603,6 +18261,11 @@ web-encoding@^1.0.2, web-encoding@^1.0.6: optionalDependencies: "@zxing/text-encoding" "0.9.0" +web-worker@^1.0.0, web-worker@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da" + integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA== + web3-bzz@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.11.tgz#41bc19a77444bd5365744596d778b811880f707f" @@ -18865,6 +19528,12 @@ websnark@^0.0.5: dependencies: big-integer "^1.6.42" +"websnark@git+https://github.com/tornadocash/websnark.git#86a526718cd6f6f5d31bdb1fe26a9ec8819f633e": + version "0.0.4" + resolved "git+https://github.com/tornadocash/websnark.git#86a526718cd6f6f5d31bdb1fe26a9ec8819f633e" + dependencies: + big-integer "^1.6.42" + websocket@1.0.32: version "1.0.32" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.32.tgz#1f16ddab3a21a2d929dec1687ab21cfdc6d3dbb1" @@ -19011,11 +19680,21 @@ wordwrap@~0.0.2: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= +worker-threads@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/worker-threads/-/worker-threads-1.0.0.tgz#2b49ea7c9692ba737d9148f2c9b2be65e14e3470" + integrity sha512-vK6Hhvph8oLxocEJIlc3YfGAZhm210uGzjZsXSu+JYLAQ/s/w4Tqgl60JrdH58hW8NSGP4m3bp8a92qPXgX05w== + workerpool@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.0.tgz#85aad67fa1a2c8ef9386a1b43539900f61d03d58" integrity sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA== +workerpool@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" + integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -19216,6 +19895,11 @@ yargs-parser@13.1.2, yargs-parser@^13.1.0, yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-parser@^11.1.1: version "11.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" @@ -19280,6 +19964,16 @@ yargs-unparser@1.6.1: is-plain-obj "^1.1.0" yargs "^14.2.3" +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + yargs@13.2.4: version "13.2.4" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" @@ -19313,6 +20007,19 @@ yargs@13.3.2, yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.2" +yargs@16.2.0, yargs@^16.1.1: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.6.0.tgz#cb4050c0159bfb6bb649c0f4af550526a84619dc" @@ -19383,19 +20090,6 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.1.1: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - yargs@^4.7.1: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" From ecbed3dd292523e38b83ecb282113366fe0412b0 Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 22 Feb 2022 14:55:02 +0300 Subject: [PATCH 04/18] move relayer registry init --- hardhat.config.js | 2 +- test/instance.factory.test.js | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index d34fd1c..6c85282 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -34,7 +34,7 @@ module.exports = { hardhat: { forking: { url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, - blockNumber: 14220000, + blockNumber: 14250000, }, chainId: 1, initialBaseFeePerGas: 5, diff --git a/test/instance.factory.test.js b/test/instance.factory.test.js index 50bfc7d..ccef3bb 100644 --- a/test/instance.factory.test.js +++ b/test/instance.factory.test.js @@ -47,15 +47,6 @@ describe('Instance Factory Tests', () => { config.instanceRegistry, ) - // execute relayer registry proposal - const proposalId = await gov.proposalCount() - expect(await gov.state(proposalId)).to.be.equal(ProposalState.Active) - await minewait(596400) - expect(await gov.state(proposalId)).to.be.equal(ProposalState.AwaitingExecution) - - await gov.execute(proposalId) - expect(await gov.state(proposalId)).to.be.equal(ProposalState.Executed) - // deploy instance factory InstanceFactory = await ethers.getContractFactory('InstanceFactory') const instanceFactory = await InstanceFactory.connect(deployer).deploy( From 238363d3481e1be1def3c5645d40afb0efba635e Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 22 Feb 2022 19:20:33 +0300 Subject: [PATCH 05/18] add creation fee logic --- config.js | 1 + contracts/AddInstanceProposal.sol | 22 ++- contracts/ERC20TornadoCloneable.sol | 2 +- contracts/InstanceFactory.sol | 56 ++++-- contracts/interfaces/IInstanceRegistry.sol | 6 +- contracts/mock/CompileDummy.sol | 2 +- scripts/permit.js | 44 +++++ test/instance.factory.test.js | 194 +++++++++++++++------ 8 files changed, 251 insertions(+), 76 deletions(-) create mode 100644 scripts/permit.js diff --git a/config.js b/config.js index 1a06dbc..07a8148 100644 --- a/config.js +++ b/config.js @@ -9,4 +9,5 @@ module.exports = { COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', TORN: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', tornWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', + creationFee: '200000000000000000000', // 200 TORN } diff --git a/contracts/AddInstanceProposal.sol b/contracts/AddInstanceProposal.sol index fdae4e7..acfb386 100644 --- a/contracts/AddInstanceProposal.sol +++ b/contracts/AddInstanceProposal.sol @@ -70,19 +70,25 @@ contract AddInstanceProposal { } function denominationByIndex(uint256 _index) public view returns (uint256) { - if (_index == 0) { return denomination0; } - else if (_index == 1) { return denomination1; } - else if (_index == 2) { return denomination2; } - else { + if (_index == 0) { + return denomination0; + } else if (_index == 1) { + return denomination1; + } else if (_index == 2) { + return denomination2; + } else { revert("Invalid instance index"); } } function protocolFeeByIndex(uint256 _index) public view returns (uint32) { - if (_index == 0) { return protocolFee0; } - else if (_index == 1) { return protocolFee1; } - else if (_index == 2) { return protocolFee2; } - else { + if (_index == 0) { + return protocolFee0; + } else if (_index == 1) { + return protocolFee1; + } else if (_index == 2) { + return protocolFee2; + } else { revert("Invalid instance index"); } } diff --git a/contracts/ERC20TornadoCloneable.sol b/contracts/ERC20TornadoCloneable.sol index d2f8f1e..45fde4d 100644 --- a/contracts/ERC20TornadoCloneable.sol +++ b/contracts/ERC20TornadoCloneable.sol @@ -14,7 +14,7 @@ contract ERC20TornadoCloneable is ERC20Tornado { address _token ) external { require(denomination == 0 && levels == 0, "already initialized"); - + token = IERC20(_token); require(_denomination > 0, "denomination should be greater than 0"); denomination = _denomination; diff --git a/contracts/InstanceFactory.sol b/contracts/InstanceFactory.sol index 322c801..c7d9d66 100644 --- a/contracts/InstanceFactory.sol +++ b/contracts/InstanceFactory.sol @@ -9,22 +9,26 @@ import "@openzeppelin/contracts/proxy/Clones.sol"; import "./ERC20TornadoCloneable.sol"; import "./AddInstanceProposal.sol"; import "./interfaces/IGovernance.sol"; - +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol"; contract InstanceFactory is Ownable { using Clones for address; using Address for address; address public immutable governance; + address public immutable torn; address public immutable instanceRegistry; address public implementation; address public verifier; address public hasher; uint32 public merkleTreeHeight; + uint256 public creationFee; event NewVerifierSet(address indexed newVerifier); event NewHasherSet(address indexed newHasher); event NewTreeHeightSet(uint32 indexed newTreeHeight); + event NewCreationFeeSet(uint256 indexed newCreationFee); event NewImplementationSet(address indexed newImplemenentation); event NewInstanceCloneCreated(address indexed clone); event NewGovernanceProposalCreated(address indexed proposal); @@ -34,13 +38,17 @@ contract InstanceFactory is Ownable { address _hasher, uint32 _merkleTreeHeight, address _governance, - address _instanceRegistry + address _instanceRegistry, + address _torn, + uint256 _creationFee ) { verifier = _verifier; hasher = _hasher; merkleTreeHeight = _merkleTreeHeight; governance = _governance; instanceRegistry = _instanceRegistry; + torn = _torn; + creationFee = _creationFee; ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher); implementation = address(implContract); @@ -65,25 +73,46 @@ contract InstanceFactory is Ownable { return implementation.predictDeterministicAddress(salt); } - function createNewProposal( + function createProposalApprove( address _token, uint24 _uniswapPoolSwappingFee, uint256[] memory _denominations, uint32[] memory _protocolFees ) external returns (address) { + require(IERC20(torn).transferFrom(msg.sender, governance, creationFee)); + return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees); + } + + function createProposalPermit( + address _token, + uint24 _uniswapPoolSwappingFee, + uint256[] memory _denominations, + uint32[] memory _protocolFees, + address creater, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (address) { + IERC20Permit(torn).permit(creater, address(this), creationFee, deadline, v, r, s); + require(IERC20(torn).transferFrom(creater, governance, creationFee)); + return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees); + } + + function _createProposal( + address _token, + uint24 _uniswapPoolSwappingFee, + uint256[] memory _denominations, + uint32[] memory _protocolFees + ) internal returns (address) { require(_token.isContract(), "Token is not contract"); // TODO should we check that such instance already exist? require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); // TODO should we check > 0 ? require(_denominations.length > 0, "Empty denominations"); require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); - address proposal = address(new AddInstanceProposal( - address(this), - instanceRegistry, - _token, - _uniswapPoolSwappingFee, - _denominations, - _protocolFees - )); + address proposal = address( + new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees) + ); emit NewGovernanceProposalCreated(proposal); return proposal; @@ -104,6 +133,11 @@ contract InstanceFactory is Ownable { emit NewTreeHeightSet(merkleTreeHeight); } + function setCreationFee(uint256 _creationFee) external onlyOwner { + creationFee = _creationFee; + emit NewCreationFeeSet(_creationFee); + } + function setImplementation(address _newImplementation) external onlyOwner { implementation = _newImplementation; emit NewImplementationSet(implementation); diff --git a/contracts/interfaces/IInstanceRegistry.sol b/contracts/interfaces/IInstanceRegistry.sol index 657e340..e5d37df 100644 --- a/contracts/interfaces/IInstanceRegistry.sol +++ b/contracts/interfaces/IInstanceRegistry.sol @@ -6,8 +6,10 @@ pragma abicoder v2; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IInstanceRegistry { - - enum InstanceState { DISABLED, ENABLED} + enum InstanceState { + DISABLED, + ENABLED + } struct Instance { bool isERC20; diff --git a/contracts/mock/CompileDummy.sol b/contracts/mock/CompileDummy.sol index 0bad471..b69bca4 100644 --- a/contracts/mock/CompileDummy.sol +++ b/contracts/mock/CompileDummy.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.6.12 || 0.7.6; +pragma solidity 0.6.12||0.7.6; import { Governance } from "tornado-governance/contracts/v1/Governance.sol"; import { InstanceRegistry } from "tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol"; diff --git a/scripts/permit.js b/scripts/permit.js new file mode 100644 index 0000000..36ce381 --- /dev/null +++ b/scripts/permit.js @@ -0,0 +1,44 @@ +const { EIP712Signer } = require('@ticket721/e712') + +const Permit = [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, +] + +class PermitSigner extends EIP712Signer { + constructor(_domain, _permitArgs) { + super(_domain, ['Permit', Permit]) + this.permitArgs = _permitArgs + } + + // Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline) + setPermitInfo(_permitArgs) { + this.permitArgs = _permitArgs + } + + getPayload() { + return this.generatePayload(this.permitArgs, 'Permit') + } + + async getSignature(privateKey) { + let payload = this.getPayload() + payload.message.owner = payload.message.owner.address + const { hex, v, r, s } = await this.sign(privateKey, payload) + return { + hex, + v, + r: '0x' + r, + s: '0x' + s, + } + } + + getSignerAddress(permitArgs, signature) { + const original_payload = this.generatePayload(permitArgs, 'Permit') + return this.verify(original_payload, signature) + } +} + +module.exports = { PermitSigner } diff --git a/test/instance.factory.test.js b/test/instance.factory.test.js index ccef3bb..327c7e2 100644 --- a/test/instance.factory.test.js +++ b/test/instance.factory.test.js @@ -7,7 +7,7 @@ const { rbigint, createDeposit, toHex, generateProof, initialize } = require('to const MixerContractABI = require('tornado-cli/build/contracts/Mixer.abi.json') const config = require('../config') const { getSignerFromAddress, minewait } = require('./utils') - +const { PermitSigner } = require('../scripts/permit.js') describe('Instance Factory Tests', () => { const ProposalState = { @@ -26,14 +26,11 @@ describe('Instance Factory Tests', () => { const [sender, deployer, multisig] = await ethers.getSigners() const tornWhale = await getSignerFromAddress(config.tornWhale) - - const gov = await ethers.getContractAt( - 'Governance', - config.governance, - ) + + const gov = await ethers.getContractAt('Governance', config.governance) const tornToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', config.TORN, ) @@ -43,7 +40,7 @@ describe('Instance Factory Tests', () => { ) instanceRegistry = await ethers.getContractAt( - 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', + 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', config.instanceRegistry, ) @@ -54,11 +51,23 @@ describe('Instance Factory Tests', () => { config.hasher, config.merkleTreeHeight, config.governance, - config.instanceRegistry + config.instanceRegistry, + config.TORN, + config.creationFee, ) await instanceFactory.deployed() - return { sender, deployer, multisig, tornWhale, gov, tornToken, compToken, instanceRegistry, instanceFactory } + return { + sender, + deployer, + multisig, + tornWhale, + gov, + tornToken, + compToken, + instanceRegistry, + instanceFactory, + } } it('Should have initialized all successfully', async function () { @@ -73,11 +82,13 @@ describe('Instance Factory Tests', () => { it('Should set correct params for factory', async function () { const { instanceFactory } = await loadFixture(fixture) - expect( await instanceFactory.governance()).to.be.equal(config.governance) - expect( await instanceFactory.verifier()).to.be.equal(config.verifier) - expect( await instanceFactory.hasher()).to.be.equal(config.hasher) - expect( await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) + expect(await instanceFactory.governance()).to.be.equal(config.governance) + expect(await instanceFactory.verifier()).to.be.equal(config.verifier) + expect(await instanceFactory.hasher()).to.be.equal(config.hasher) + expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) expect(await instanceFactory.implementation()).to.exist + expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) + expect(await instanceFactory.torn()).to.be.equal(config.TORN) }) it('Governance should be able to set factory params', async function () { @@ -87,48 +98,58 @@ describe('Instance Factory Tests', () => { const govSigner = await getSignerFromAddress(gov.address) instanceFactory = await instanceFactory.connect(govSigner) - + await instanceFactory.setVerifier(addressZero) await instanceFactory.setHasher(addressZero) await instanceFactory.setMerkleTreeHeight(1) + await instanceFactory.setCreationFee(0) - expect( await instanceFactory.verifier()).to.be.equal(addressZero) - expect( await instanceFactory.hasher()).to.be.equal(addressZero) - expect( await instanceFactory.merkleTreeHeight()).to.be.equal(1) + expect(await instanceFactory.verifier()).to.be.equal(addressZero) + expect(await instanceFactory.hasher()).to.be.equal(addressZero) + expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1) + expect(await instanceFactory.creationFee()).to.be.equal(0) await instanceFactory.setVerifier(config.verifier) await instanceFactory.setHasher(config.hasher) await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) + await instanceFactory.setCreationFee(config.creationFee) - expect( await instanceFactory.verifier()).to.be.equal(config.verifier) - expect( await instanceFactory.hasher()).to.be.equal(config.hasher) - expect( await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) + expect(await instanceFactory.verifier()).to.be.equal(config.verifier) + expect(await instanceFactory.hasher()).to.be.equal(config.hasher) + expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) + expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) }) it('Should successfully deploy/propose/execute proposal - add instance', async function () { - let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) // deploy proposal ---------------------------------------------- - let tx = await instanceFactory.createNewProposal( - config.COMP, - 3000, - [ethers.utils.parseEther('100')], - [30] - ) - let receipt = await tx.wait() + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) - const proposal = await ethers.getContractAt( - 'AddInstanceProposal', - receipt.events[0].args[0], + await expect(() => + instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]), + ).to.changeTokenBalances( + tornToken, + [sender, gov], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], ) - - expect( await proposal.instanceFactory()).to.be.equal(instanceFactory.address) - expect( await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) - expect( await proposal.token()).to.be.equal(config.COMP) - expect( await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) - expect( await proposal.numInstances()).to.be.equal(1) - expect( await proposal.protocolFeeByIndex(0)).to.be.equal(30) - expect( await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + + let logs = await ethers.provider.getLogs(instanceFactory.filters.NewGovernanceProposalCreated()) + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[0].topics[1].slice(26)), + ) + + expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) + expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) + expect(await proposal.token()).to.be.equal(config.COMP) + expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) + expect(await proposal.numInstances()).to.be.equal(1) + expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) + expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) // propose proposal --------------------------------------------- let response, id, state @@ -147,7 +168,7 @@ describe('Instance Factory Tests', () => { expect(args.target).to.be.equal(proposal.address) expect(args.description).to.be.equal('COMP token instance proposal') expect(state).to.be.equal(ProposalState.Pending) - + // execute proposal --------------------------------------------- await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) await expect(gov.castVote(id, true)).to.not.be.reverted @@ -161,7 +182,7 @@ describe('Instance Factory Tests', () => { .toNumber(), ) expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) - + tx = await gov.execute(id) expect(await gov.state(id)).to.be.equal(ProposalState.Executed) @@ -169,25 +190,92 @@ describe('Instance Factory Tests', () => { // check instance initialization -------------------------------- receipt = await tx.wait() const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40) - const instance = await ethers.getContractAt( - 'ERC20TornadoCloneable', - instanceAddr, - ) + const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) - expect( await instance.token()).to.be.equal(config.COMP) - expect( await instance.verifier()).to.be.equal(config.verifier) - expect( await instance.hasher()).to.be.equal(config.hasher) - expect( await instance.levels()).to.be.equal(config.merkleTreeHeight) - expect( await instance.denomination()).to.equal(ethers.utils.parseEther('100')) + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) - const instanceData = await instanceRegistry.instances(instance.address) + const instanceData = await instanceRegistry.instances(instance.address) expect(instanceData.isERC20).to.be.equal(true) expect(instanceData.token).to.be.equal(config.COMP) expect(instanceData.state).to.be.equal(1) expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) expect(instanceData.protocolFeePercentage).to.be.equal(30) }) - + + it('Should successfully deploy proposal with permit', async function () { + let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + + const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3' + const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex')) + const sender = await ethers.getSigner(publicKey.slice(2)) + + await expect(() => + tornToken.connect(tornWhale).transfer(sender.address, config.creationFee), + ).to.changeTokenBalances( + tornToken, + [tornWhale, sender], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + // prepare permit data + const domain = { + name: await tornToken.name(), + version: '1', + chainId: 1, + verifyingContract: tornToken.address, + } + + const curTimestamp = Math.trunc(new Date().getTime() / 1000) + const args = { + owner: sender, + spender: instanceFactory.address, + value: config.creationFee, + nonce: 0, + deadline: curTimestamp + 1000, + } + + const permitSigner = new PermitSigner(domain, args) + const signature = await permitSigner.getSignature(privateKey) + const signer = await permitSigner.getSignerAddress(args, signature.hex) + expect(signer).to.equal(sender.address) + + await expect(() => + instanceFactory.createProposalPermit( + config.COMP, + 3000, + [ethers.utils.parseEther('100')], + [30], + sender.address, + args.deadline.toString(), + signature.v, + signature.r, + signature.s, + ), + ).to.changeTokenBalances( + tornToken, + [sender, gov], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + let logs = await ethers.provider.getLogs(instanceFactory.filters.NewGovernanceProposalCreated()) + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[0].topics[1].slice(26)), + ) + + expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) + expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) + expect(await proposal.token()).to.be.equal(config.COMP) + expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) + expect(await proposal.numInstances()).to.be.equal(1) + expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) + expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + }) + // it('Should prepare data for instance deposit/withdraw tests', async () => { // const RAITokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919' // await sendr('hardhat_impersonateAccount', ['0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100']) From 160b2f4cb031156e37e11c833be6ff08571f1d93 Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 22 Feb 2022 20:18:35 +0300 Subject: [PATCH 06/18] add new instance tests --- config.js | 2 + ...stance.factory.test.js => factory.test.js} | 137 +-------------- test/instance.tests.js | 165 ++++++++++++++++++ 3 files changed, 175 insertions(+), 129 deletions(-) rename test/{instance.factory.test.js => factory.test.js} (66%) create mode 100644 test/instance.tests.js diff --git a/config.js b/config.js index 07a8148..1d11ea8 100644 --- a/config.js +++ b/config.js @@ -3,11 +3,13 @@ module.exports = { hasher: '0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe', governance: '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce', instanceRegistry: '0xB20c66C4DE72433F3cE747b58B86830c459CA911', + router: '0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b', merkleTreeHeight: 20, singletonFactory: '0xce0042B868300000d44A59004Da54A005ffdcf9f', salt: '0x0000000000000000000000000000000000000000000000000000000047941987', COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', TORN: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', tornWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', + compWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', creationFee: '200000000000000000000', // 200 TORN } diff --git a/test/instance.factory.test.js b/test/factory.test.js similarity index 66% rename from test/instance.factory.test.js rename to test/factory.test.js index 327c7e2..5dd1c04 100644 --- a/test/instance.factory.test.js +++ b/test/factory.test.js @@ -3,8 +3,6 @@ const { ethers, waffle } = hre const { loadFixture } = waffle const { expect } = require('chai') const { BigNumber } = require('@ethersproject/bignumber') -const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') -const MixerContractABI = require('tornado-cli/build/contracts/Mixer.abi.json') const config = require('../config') const { getSignerFromAddress, minewait } = require('./utils') const { PermitSigner } = require('../scripts/permit.js') @@ -39,13 +37,13 @@ describe('Instance Factory Tests', () => { config.COMP, ) - instanceRegistry = await ethers.getContractAt( + const instanceRegistry = await ethers.getContractAt( 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', config.instanceRegistry, ) // deploy instance factory - InstanceFactory = await ethers.getContractFactory('InstanceFactory') + const InstanceFactory = await ethers.getContractFactory('InstanceFactory') const instanceFactory = await InstanceFactory.connect(deployer).deploy( config.verifier, config.hasher, @@ -137,10 +135,10 @@ describe('Instance Factory Tests', () => { [BigNumber.from(0).sub(config.creationFee), config.creationFee], ) - let logs = await ethers.provider.getLogs(instanceFactory.filters.NewGovernanceProposalCreated()) + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') const proposal = await ethers.getContractAt( 'AddInstanceProposal', - ethers.utils.getAddress('0x' + logs[0].topics[1].slice(26)), + ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), ) expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) @@ -183,12 +181,12 @@ describe('Instance Factory Tests', () => { ) expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) - tx = await gov.execute(id) + let tx = await gov.execute(id) expect(await gov.state(id)).to.be.equal(ProposalState.Executed) // check instance initialization -------------------------------- - receipt = await tx.wait() + let receipt = await tx.wait() const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40) const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) @@ -261,10 +259,10 @@ describe('Instance Factory Tests', () => { [BigNumber.from(0).sub(config.creationFee), config.creationFee], ) - let logs = await ethers.provider.getLogs(instanceFactory.filters.NewGovernanceProposalCreated()) + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') const proposal = await ethers.getContractAt( 'AddInstanceProposal', - ethers.utils.getAddress('0x' + logs[0].topics[1].slice(26)), + ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), ) expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) @@ -275,123 +273,4 @@ describe('Instance Factory Tests', () => { expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) }) - - // it('Should prepare data for instance deposit/withdraw tests', async () => { - // const RAITokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919' - // await sendr('hardhat_impersonateAccount', ['0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100']) - // RAIToken = await ethers.getContractAt( - // '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', - // RAITokenAddress, - // ) - // whaleRAI = await ethers.getSigner('0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100') - - // const tx = { - // to: whaleRAI.address, - // value: pE(50), - // } - // await accounts[0].sendTransaction(tx) - - // whaleRAIBalance = await RAIToken.balanceOf(whaleRAI.address) - // RAIToken = await RAIToken.connect(whaleRAI) - // TornadoProxy = await TornadoProxy.connect(whaleRAI) - - // for (let i = 0; i < 4; i++) { - // instanceAddresses[i] = await TornadoInstanceFactoryContract.getInstanceAddress( - // denominations[i], - // RAIToken.address, - // ) - // } - - // mixerContract = await ethers.getContractAt(MixerContractABI, instanceAddresses[0]) - // mixerContract = await mixerContract.connect(whaleRAI) - - // snapshotId = await sendr('evm_snapshot', []) - // }) - - // it('Should test depositing and withdrawing into the new instance over proxy', async () => { - // const depo = createDeposit({ - // nullifier: rbigint(31), - // secret: rbigint(31), - // }) - - // // const note = toHex(depo.preimage, 62) - // // const noteString = `tornado-RAI-33-1-${note}` - // // clog('Note: ', note) - // // clog('Note string: ', noteString) - // // clog('Commitment: ', toHex(depo.commitment)) - - // await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted - // TornadoInstance = await ethers.getContractAt( - // 'contracts/tornado_proxy/ITornadoInstance.sol:ITornadoInstance', - // instanceAddresses[0], - // ) - - // await expect(() => - // TornadoProxy.deposit(instanceAddresses[0], toHex(depo.commitment), []), - // ).to.changeTokenBalance(RAIToken, whaleRAI, BigNumber.from(0).sub(await TornadoInstance.denomination())) - - // let pevents = await mixerContract.queryFilter('Deposit') - // await initialize({ merkleTreeHeight: 20 }) - - // const { proof, args } = await generateProof({ - // deposit: depo, - // recipient: whaleRAI.address, - // events: pevents, - // }) - - // await expect(() => - // TornadoProxy.withdraw(TornadoInstance.address, proof, ...args), - // ).to.changeTokenBalance(RAIToken, whaleRAI, await TornadoInstance.denomination()) - - // await sendr('evm_revert', [snapshotId]) - // snapshotId = await sendr('evm_snapshot', []) - // }) - - // it('Should prepare for multiple account deposits', async () => { - // let toSend = whaleRAIBalance.div(5) - - // for (let i = 0; i < 3; i++) { - // await RAIToken.transfer(accounts[i].address, toSend) - // const rai = await RAIToken.connect(accounts[i]) - // await rai.approve(TornadoProxy.address, pE(600000)) - // } - // }) - - // it('Should test depositing with multiple accounts over proxy', async () => { - // for (let i = 0; i < 3; i++) { - // const depo = createDeposit({ - // nullifier: rbigint(31), - // secret: rbigint(31), - // }) - // // const note = toHex(depo.preimage, 62) - // // const noteString = `tornado-RAI-33-1-${note}` - // // clog('Note: ', note) - // // clog('Note string: ', noteString) - // // clog('Commitment: ', toHex(depo.commitment)) - // const proxy = await TornadoProxy.connect(accounts[i]) - - // await expect(() => - // proxy.deposit(TornadoInstance.address, toHex(depo.commitment), []), - // ).to.changeTokenBalance( - // RAIToken, - // accounts[i], - // BigNumber.from(0).sub(await TornadoInstance.denomination()), - // ) - - // let pevents = await mixerContract.queryFilter('Deposit') - // await initialize({ merkleTreeHeight: 20 }) - - // const { proof, args } = await generateProof({ - // deposit: depo, - // recipient: accounts[i].address, - // events: pevents, - // }) - - // await expect(() => proxy.withdraw(TornadoInstance.address, proof, ...args)).to.changeTokenBalance( - // RAIToken, - // accounts[i], - // await TornadoInstance.denomination(), - // ) - // } - // }) }) diff --git a/test/instance.tests.js b/test/instance.tests.js new file mode 100644 index 0000000..6b61805 --- /dev/null +++ b/test/instance.tests.js @@ -0,0 +1,165 @@ +const hre = require('hardhat') +const { ethers, waffle } = hre +const { loadFixture } = waffle +const { expect } = require('chai') +const { BigNumber } = require('@ethersproject/bignumber') +const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') +const config = require('../config') +const { getSignerFromAddress, minewait } = require('./utils') + +describe('Instance Factory Tests', () => { + const ProposalState = { + Pending: 0, + Active: 1, + Defeated: 2, + Timelocked: 3, + AwaitingExecution: 4, + Executed: 5, + Expired: 6, + } + + async function fixture() { + const [sender, deployer, multisig] = await ethers.getSigners() + + const tornWhale = await getSignerFromAddress(config.tornWhale) + const compWhale = await getSignerFromAddress(config.compWhale) + + let gov = await ethers.getContractAt('Governance', config.governance) + + const tornToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + config.TORN, + ) + + const compToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', + config.COMP, + ) + + const instanceRegistry = await ethers.getContractAt( + 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', + config.instanceRegistry, + ) + + const router = await ethers.getContractAt( + 'tornado-relayer-registry/contracts/tornado-proxy/TornadoRouter.sol:TornadoRouter', + config.router, + ) + + // deploy instance factory + const InstanceFactory = await ethers.getContractFactory('InstanceFactory') + const instanceFactory = await InstanceFactory.connect(deployer).deploy( + config.verifier, + config.hasher, + config.merkleTreeHeight, + config.governance, + config.instanceRegistry, + config.TORN, + config.creationFee, + ) + await instanceFactory.deployed() + + // deploy proposal + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) + + await instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]) + + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), + ) + + // propose proposal + gov = await gov.connect(tornWhale) + await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) + await gov.lockWithApproval(ethers.utils.parseEther('26000')) + await gov.propose(proposal.address, 'COMP token instance proposal') + const id = await gov.latestProposalIds(tornWhale.address) + + // execute proposal + await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) + await expect(gov.castVote(id, true)).to.not.be.reverted + expect(await gov.state(id)).to.be.equal(ProposalState.Active) + await minewait( + ( + await gov.VOTING_PERIOD() + ) + .add(await gov.EXECUTION_DELAY()) + .add(96400) + .toNumber(), + ) + expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) + await gov.execute(id) + expect(await gov.state(id)).to.be.equal(ProposalState.Executed) + + logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') + const instance = await ethers.getContractAt( + 'ERC20TornadoCloneable', + ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), + ) + + return { + sender, + deployer, + multisig, + tornWhale, + compWhale, + gov, + tornToken, + compToken, + instanceRegistry, + router, + instanceFactory, + instance, + } + } + + it('Should set correct params for factory', async function () { + const { instance } = await loadFixture(fixture) + + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) + }) + + it('Should deposit and withdraw into the new instance', async function () { + const { sender, instance, compToken, compWhale, router } = await loadFixture(fixture) + + const depo = createDeposit({ + nullifier: rbigint(31), + secret: rbigint(31), + }) + + const value = ethers.utils.parseEther('100') + + await compToken.connect(compWhale).transfer(sender.address, value) + await compToken.connect(sender).approve(router.address, value) + + await expect(() => router.deposit(instance.address, toHex(depo.commitment), [])).to.changeTokenBalances( + compToken, + [sender, instance], + [BigNumber.from(0).sub(value), value], + ) + + let pevents = await instance.queryFilter('Deposit') + await initialize({ merkleTreeHeight: 20 }) + + const { proof, args } = await generateProof({ + deposit: depo, + recipient: sender.address, + events: pevents, + }) + + await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeTokenBalances( + compToken, + [instance, sender], + [BigNumber.from(0).sub(value), value], + ) + }) +}) From e356c38c8ae5ab9c6c509a558979690b87bd5604 Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 22 Feb 2022 20:21:22 +0300 Subject: [PATCH 07/18] add ci --- .github/workflows/build.js.yml | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.js.yml b/.github/workflows/build.js.yml index 7440c01..af872c1 100644 --- a/.github/workflows/build.js.yml +++ b/.github/workflows/build.js.yml @@ -3,34 +3,23 @@ name: build on: push: branches: ['*'] + tags: ['v[0-9]+.[0-9]+.[0-9]+'] pull_request: + jobs: build: runs-on: ubuntu-latest - environment: secrets - env: - etherscan_api_key: ${{ secrets.ETHERSCAN_API_KEY }} - goerli_rpc_key: ${{ secrets.GOERLI_RPC_KEY }} - mainnet_rpc_key: ${{ secrets.MAINNET_RPC_KEY }} - goerli_account_pk: ${{ secrets.GOERLI_ACCOUNT_PK }} - mainnet_account_pk: ${{ secrets.MAINNET_ACCOUNT_PK }} steps: - - name: Tests and setup + - name: Checkout uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12 - - run: yarn install - - run: cp .env.example.workflow .env - - run: yarn prettier:fix + node-version: 14 + - run: yarn cache clean --all + - run: yarn install --network-concurrency 1 - run: yarn lint + - run: yarn build - run: yarn test - - - name: Generate coverage - run: yarn coverage - - - name: Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} + env: + ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} From 5a903bf9937172f4e1248a5bc28095f10efc3585 Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 22 Feb 2022 22:15:37 +0300 Subject: [PATCH 08/18] update readme --- README.md | 79 +++++++++---------------------------------------------- 1 file changed, 12 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 217da7c..0c7c304 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ -# Tornado Instances - -[![build](https://img.shields.io/github/workflow/status/mirru2532/tornado-instances/build)](https://github.com/h-ivor/tornado-instances/actions) [![Coveralls](https://img.shields.io/coveralls/github/mirru2532/tornado-instances)](https://coveralls.io/github/mirru2532/tornado-instances) - +# Tornado Instances Factory ## About -This repository serves as a general template repository for deploying proposals for governance for the addition of new tornado ERC20 instances to the tornado proxy. +This repository contains governance proposal factory for the addition of new Tornado ERC20 instances to the Tornado router. -The contracts offer a template for the addition of 4 new instances for an ERC20 token (the standard way to add new instances) and can also be slightly modified to add a custom amount. The scripts deploy the contracts. +Anyone can create governance proposal for the addition of a new ERC20 instance by calling `createProposalApprove/createProposalPermit` method of the factory with parameters: -The resources folder contains data necessary for the instances to be deployed and must be filled out. They are initially filled out with the RAI instance data. - -### How-To: +1. `address token` - address of ERC20 token for a new instance +2. `uint24 uniswapPoolSwappingFee` - fee value of Uniswap instance which will be used for `TORN/token` price determination. `3000` means 0.3% fee Uniswap pool. +3. `uint256[] denominations` - list of denominations for each new instance (tokens can only be deposited in certain denominations into instances). +4. `uint32[] protocolFees` - list of protocol fees for each new instance (this fee is only charged from registrated relayer during withdrawal process). `100` means 1% of instance denomination fee for withdrawal throw registrated relayer. +## Factory parameters +1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 3 instances at once. +2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`. +## Tests Setting up the repository: @@ -23,67 +25,10 @@ cp .env.example .env Please fill out .env according to the template provided in it. Please ensure that all of the example values are set to the correct addresses. -### Testing and running scripts: - To run test scripts: ```bash yarn test ``` -Test scripts cover instance factory deployment, proposal deployment and executing proposal (RAI instances). - -Running **tasks:** - -```bash -# a list of yarn scripts specifically for instance deployment -"deploy:proposal": "yarn hardhat --network mainnet deploy_proposal --factory-address", -"deploy:proposal:test": "yarn hardhat --network goerli deploy_proposal --factory-address", -"deploy:proposal:factory": "yarn hardhat --network mainnet deploy_factory_proposal", -"deploy:proposal:factory:test": "yarn hardhat --network goerli deploy_factory_proposal", -"propose": "yarn hardhat --network mainnet propose_proposal --proposal-address", - -# as an example -yarn deploy:proposal:factory - -# to call a specific task -yarn hardhat --network -``` - -## Deploying a proposal for an instance update - -Open `resources/instances.js`, a single object which generates an instance contains the following fields (RAI as an example): - -```js -{ - tokenAddress: "0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919", - denomination: "33333333333333333333", - symbol: "RAI", - decimals: 18 -} -``` - -`denomination` - tokens can only be deposited in certain denominations into instances, the above considers this instance to have a 100$ denomination, assuming RAI at 3$. -Fill out each of these fields for your own token in the `instance.js` file. This repo supports the addition of a maximum of 6 instances at once. It will automatically detect how many instances are to use. - -Now find the factory contract address: - -```bash -yarn deploy:proposal -``` - -If testing: - -```bash -yarn deploy:proposal:test -``` - -The last step, or first depending on if you are simply proposing the proposal, is taking the address of the deployed proposal and calling: - -// THIS IS ASSUMING YOU HAVE ENOUGH LOCKED TORN TO PROPOSE - -```bash -yarn propose -``` - -There is not test implementation for this. +Test scripts cover instance factory deployment, proposal deployment and executing proposal. From 53eae70de7720097c8d9b64a4da4414887198210 Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 22 Feb 2022 23:03:16 +0300 Subject: [PATCH 09/18] add two instance creation test --- .github/workflows/build.js.yml | 1 - README.md | 6 +- test/factory.test.js | 109 +++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.js.yml b/.github/workflows/build.js.yml index af872c1..52f1a54 100644 --- a/.github/workflows/build.js.yml +++ b/.github/workflows/build.js.yml @@ -6,7 +6,6 @@ on: tags: ['v[0-9]+.[0-9]+.[0-9]+'] pull_request: - jobs: build: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 0c7c304..d746d1d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ # Tornado Instances Factory + ## About This repository contains governance proposal factory for the addition of new Tornado ERC20 instances to the Tornado router. -Anyone can create governance proposal for the addition of a new ERC20 instance by calling `createProposalApprove/createProposalPermit` method of the factory with parameters: +Anyone can create governance proposal for the addition of a new ERC20 instance by calling `createProposalApprove/createProposalPermit` method of the factory with parameters (proposal creation fee in TORN is charged from sender): 1. `address token` - address of ERC20 token for a new instance 2. `uint24 uniswapPoolSwappingFee` - fee value of Uniswap instance which will be used for `TORN/token` price determination. `3000` means 0.3% fee Uniswap pool. 3. `uint256[] denominations` - list of denominations for each new instance (tokens can only be deposited in certain denominations into instances). 4. `uint32[] protocolFees` - list of protocol fees for each new instance (this fee is only charged from registrated relayer during withdrawal process). `100` means 1% of instance denomination fee for withdrawal throw registrated relayer. + ## Factory parameters + 1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 3 instances at once. 2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`. + ## Tests Setting up the repository: diff --git a/test/factory.test.js b/test/factory.test.js index 5dd1c04..1f7fa56 100644 --- a/test/factory.test.js +++ b/test/factory.test.js @@ -204,6 +204,115 @@ describe('Instance Factory Tests', () => { expect(instanceData.protocolFeePercentage).to.be.equal(30) }) + it('Should successfully deploy/propose/execute proposal - add instances', async function () { + let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + + // deploy proposal ---------------------------------------------- + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) + + await expect(() => + instanceFactory + .connect(sender) + .createProposalApprove( + config.COMP, + 3000, + [ethers.utils.parseEther('100'), ethers.utils.parseEther('1000')], + [30, 30], + ), + ).to.changeTokenBalances( + tornToken, + [sender, gov], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), + ) + + expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) + expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) + expect(await proposal.token()).to.be.equal(config.COMP) + expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) + expect(await proposal.numInstances()).to.be.equal(2) + expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) + expect(await proposal.protocolFeeByIndex(1)).to.be.equal(30) + expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + expect(await proposal.denominationByIndex(1)).to.be.equal(ethers.utils.parseEther('1000')) + + // propose proposal --------------------------------------------- + let response, id, state + gov = await gov.connect(tornWhale) + await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) + await gov.lockWithApproval(ethers.utils.parseEther('26000')) + + response = await gov.propose(proposal.address, 'COMP token instances proposal') + id = await gov.latestProposalIds(tornWhale.address) + state = await gov.state(id) + + const { events } = await response.wait() + const args = events.find(({ event }) => event == 'ProposalCreated').args + expect(args.id).to.be.equal(id) + expect(args.proposer).to.be.equal(tornWhale.address) + expect(args.target).to.be.equal(proposal.address) + expect(args.description).to.be.equal('COMP token instances proposal') + expect(state).to.be.equal(ProposalState.Pending) + + // execute proposal --------------------------------------------- + await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) + await expect(gov.castVote(id, true)).to.not.be.reverted + expect(await gov.state(id)).to.be.equal(ProposalState.Active) + await minewait( + ( + await gov.VOTING_PERIOD() + ) + .add(await gov.EXECUTION_DELAY()) + .add(96400) + .toNumber(), + ) + expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) + + await gov.execute(id) + + expect(await gov.state(id)).to.be.equal(ProposalState.Executed) + + // check instances initialization ------------------------------- + logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') + let instanceAddr = '0x' + logs[0].topics[1].slice(-40) + let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) + + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) + + let instanceData = await instanceRegistry.instances(instance.address) + expect(instanceData.isERC20).to.be.equal(true) + expect(instanceData.token).to.be.equal(config.COMP) + expect(instanceData.state).to.be.equal(1) + expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) + expect(instanceData.protocolFeePercentage).to.be.equal(30) + + instanceAddr = '0x' + logs[1].topics[1].slice(-40) + instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) + + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000')) + + instanceData = await instanceRegistry.instances(instance.address) + expect(instanceData.isERC20).to.be.equal(true) + expect(instanceData.token).to.be.equal(config.COMP) + expect(instanceData.state).to.be.equal(1) + expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) + expect(instanceData.protocolFeePercentage).to.be.equal(30) + }) + it('Should successfully deploy proposal with permit', async function () { let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) From 657ad0a497ec833917b01c5118e4e33cc50f4ac2 Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 23 Feb 2022 17:03:05 +0300 Subject: [PATCH 10/18] add warnings --- README.md | 5 +++++ contracts/InstanceFactory.sol | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d746d1d..ccfdc1f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b 1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 3 instances at once. 2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`. +## Warnings + +1. This version of the factory creates a proposal for **immutable** Tornado instance initialization. +2. Users should manually propose a proposal after its creation using the factory (in governance UI for example). As `propose()` method caller must have 1000 TORN locked in the governance. Moreover, the proposer can't propose more than one proposal simultaneously. + ## Tests Setting up the repository: diff --git a/contracts/InstanceFactory.sol b/contracts/InstanceFactory.sol index c7d9d66..a820b59 100644 --- a/contracts/InstanceFactory.sol +++ b/contracts/InstanceFactory.sol @@ -105,8 +105,8 @@ contract InstanceFactory is Ownable { uint256[] memory _denominations, uint32[] memory _protocolFees ) internal returns (address) { - require(_token.isContract(), "Token is not contract"); // TODO should we check that such instance already exist? - require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); // TODO should we check > 0 ? + require(_token.isContract(), "Token is not contract"); + require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); require(_denominations.length > 0, "Empty denominations"); require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); From a6680852ed37728bfe8027f56d8481036134d1a9 Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 23 Feb 2022 19:27:44 +0300 Subject: [PATCH 11/18] add deploy --- .env.example | 1 + README.md | 21 +++++++++++ config.js | 2 ++ contracts/libraries/SingletonFactory.sol | 28 +++++++++++++++ hardhat.config.js | 20 +++++++++-- package.json | 2 +- scripts/deployInstanceFactory.js | 26 ++++++++++++++ src/generateAddresses.js | 46 ++++++++++++++++++++++++ {scripts => src}/permit.js | 0 test/factory.test.js | 25 ++++++------- test/instance.tests.js | 23 ++++++------ yarn.lock | 9 ++--- 12 files changed, 173 insertions(+), 30 deletions(-) create mode 100644 contracts/libraries/SingletonFactory.sol create mode 100644 scripts/deployInstanceFactory.js create mode 100644 src/generateAddresses.js rename {scripts => src}/permit.js (100%) diff --git a/.env.example b/.env.example index f8818b5..166127c 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ ETHERSCAN_KEY= ALCHEMY_KEY= PRIVATE_KEY= +INFURA_API_KEY= diff --git a/README.md b/README.md index ccfdc1f..de8f727 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,24 @@ yarn test ``` Test scripts cover instance factory deployment, proposal deployment and executing proposal. + +## Deploy + +Check config.js for actual values. + +With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be: + +1. `InstanceFactory` - `0x7a6e627DC6F66617b4A74Be097A8f56c622fa24c` + +Check addresses with current config: + +```shell +yarn compile +node -e 'require("./src/generateAddresses").generateWithLog()' +``` + +Deploy InstanceFactory: + +```shell +npx hardhat run scripts/deployInstanceFactory.js --network mainnet +``` diff --git a/config.js b/config.js index 1d11ea8..fa4b8c3 100644 --- a/config.js +++ b/config.js @@ -6,10 +6,12 @@ module.exports = { router: '0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b', merkleTreeHeight: 20, singletonFactory: '0xce0042B868300000d44A59004Da54A005ffdcf9f', + singletonFactoryVerboseWrapper: '0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80', salt: '0x0000000000000000000000000000000000000000000000000000000047941987', COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', TORN: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', tornWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', compWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', creationFee: '200000000000000000000', // 200 TORN + deployGasLimit: 7000000, } 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 6c85282..cec68c9 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -10,6 +10,15 @@ require('solidity-coverage') module.exports = { solidity: { compilers: [ + { + version: '0.6.2', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, { version: '0.6.12', settings: { @@ -42,10 +51,17 @@ module.exports = { allowUnlimitedContractSize: false, blockGasLimit: 50000000, }, + rinkeby: { + url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, + accounts: process.env.PRIVATE_KEY + ? [process.env.PRIVATE_KEY] + : { mnemonic: 'test test test test test junk' }, + }, mainnet: { url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, - accounts: [process.env.PRIVATE_KEY], - timeout: 2147483647, + accounts: process.env.PRIVATE_KEY + ? [process.env.PRIVATE_KEY] + : { mnemonic: 'test test test test test junk' }, }, }, mocha: { timeout: 9999999999 }, diff --git a/package.json b/package.json index 6d94639..3b3705f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "tornado-cli": "^0.0.1", "tornado-core": "https://github.com/tornadocash/tornado-core.git", "tornado-governance": "2.0.0", - "tornado-relayer-registry": "https://github.com/tornadocash/tornado-relayer-registry.git" + "tornado-relayer-registry": "https://github.com/Rezan-vm/tornado-relayer-registry.git" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.2", diff --git a/scripts/deployInstanceFactory.js b/scripts/deployInstanceFactory.js new file mode 100644 index 0000000..60d639a --- /dev/null +++ b/scripts/deployInstanceFactory.js @@ -0,0 +1,26 @@ +const { ethers } = require('hardhat') +const config = require('../config') +const { generate } = require('../src/generateAddresses') + +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: config.deployGasLimit }) +} + +async function main() { + const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory) + const contracts = await generate() + await deploy({ ...contracts.factoryContract, singletonFactory }) + console.log(`Instance factory contract have been deployed on ${contracts.factoryContract.address} address`) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/src/generateAddresses.js b/src/generateAddresses.js new file mode 100644 index 0000000..21ea249 --- /dev/null +++ b/src/generateAddresses.js @@ -0,0 +1,46 @@ +const { ethers } = require('hardhat') +const defaultConfig = require('../config') + +async function generate(config = defaultConfig) { + const FactoryFactory = await ethers.getContractFactory('InstanceFactory') + const deploymentBytecodeFactory = + FactoryFactory.bytecode + + FactoryFactory.interface + .encodeDeploy([ + config.verifier, + config.hasher, + config.merkleTreeHeight, + config.governance, + config.instanceRegistry, + config.TORN, + config.creationFee, + ]) + .slice(2) + + const factoryAddress = ethers.utils.getCreate2Address( + config.singletonFactory, + config.salt, + ethers.utils.keccak256(deploymentBytecodeFactory), + ) + + const result = { + factoryContract: { + address: factoryAddress, + bytecode: deploymentBytecodeFactory, + isProxy: false, + }, + } + + return result +} + +async function generateWithLog() { + const contracts = await generate() + console.log('Instance factory contract: ', contracts.factoryContract.address) + return contracts +} + +module.exports = { + generate, + generateWithLog, +} diff --git a/scripts/permit.js b/src/permit.js similarity index 100% rename from scripts/permit.js rename to src/permit.js diff --git a/test/factory.test.js b/test/factory.test.js index 1f7fa56..fa0b9a7 100644 --- a/test/factory.test.js +++ b/test/factory.test.js @@ -5,7 +5,8 @@ const { expect } = require('chai') const { BigNumber } = require('@ethersproject/bignumber') const config = require('../config') const { getSignerFromAddress, minewait } = require('./utils') -const { PermitSigner } = require('../scripts/permit.js') +const { PermitSigner } = require('../src/permit.js') +const { generate } = require('../src/generateAddresses') describe('Instance Factory Tests', () => { const ProposalState = { @@ -42,18 +43,18 @@ describe('Instance Factory Tests', () => { config.instanceRegistry, ) - // deploy instance factory - const InstanceFactory = await ethers.getContractFactory('InstanceFactory') - const instanceFactory = await InstanceFactory.connect(deployer).deploy( - config.verifier, - config.hasher, - config.merkleTreeHeight, - config.governance, - config.instanceRegistry, - config.TORN, - config.creationFee, + // deploy InstanceFactory with CREATE2 + const singletonFactory = await ethers.getContractAt( + 'SingletonFactory', + config.singletonFactoryVerboseWrapper, ) - await instanceFactory.deployed() + const contracts = await generate() + if ((await ethers.provider.getCode(contracts.factoryContract.address)) == '0x') { + await singletonFactory.deploy(contracts.factoryContract.bytecode, config.salt, { + gasLimit: config.deployGasLimit, + }) + } + const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factoryContract.address) return { sender, diff --git a/test/instance.tests.js b/test/instance.tests.js index 6b61805..a85af3b 100644 --- a/test/instance.tests.js +++ b/test/instance.tests.js @@ -6,6 +6,7 @@ const { BigNumber } = require('@ethersproject/bignumber') const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') const config = require('../config') const { getSignerFromAddress, minewait } = require('./utils') +const { generate } = require('../src/generateAddresses') describe('Instance Factory Tests', () => { const ProposalState = { @@ -46,18 +47,18 @@ describe('Instance Factory Tests', () => { config.router, ) - // deploy instance factory - const InstanceFactory = await ethers.getContractFactory('InstanceFactory') - const instanceFactory = await InstanceFactory.connect(deployer).deploy( - config.verifier, - config.hasher, - config.merkleTreeHeight, - config.governance, - config.instanceRegistry, - config.TORN, - config.creationFee, + // deploy InstanceFactory with CREATE2 + const singletonFactory = await ethers.getContractAt( + 'SingletonFactory', + config.singletonFactoryVerboseWrapper, ) - await instanceFactory.deployed() + const contracts = await generate() + if ((await ethers.provider.getCode(contracts.factoryContract.address)) == '0x') { + await singletonFactory.deploy(contracts.factoryContract.bytecode, config.salt, { + gasLimit: config.deployGasLimit, + }) + } + const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factoryContract.address) // deploy proposal await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) diff --git a/yarn.lock b/yarn.lock index 73802ed..69b1d7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5275,9 +5275,9 @@ blake2b-wasm@^1.1.0: dependencies: nanoassert "^1.0.0" -"blake2b-wasm@git+https://github.com/jbaylina/blake2b-wasm.git": +"blake2b-wasm@https://github.com/jbaylina/blake2b-wasm.git": version "2.1.0" - resolved "git+https://github.com/jbaylina/blake2b-wasm.git#0d5f024b212429c7f50a7f533aa3a2406b5b42b3" + resolved "https://github.com/jbaylina/blake2b-wasm.git#0d5f024b212429c7f50a7f533aa3a2406b5b42b3" dependencies: nanoassert "^1.0.0" @@ -17429,9 +17429,9 @@ tornado-governance@^1.0.3: "@openzeppelin/upgrades-core" "^1.0.1" torn-token "^1.0.0" -"tornado-relayer-registry@https://github.com/tornadocash/tornado-relayer-registry.git": +"tornado-relayer-registry@https://github.com/Rezan-vm/tornado-relayer-registry.git": version "1.0.0" - resolved "https://github.com/tornadocash/tornado-relayer-registry.git#a5bdbdb4cd44d3951ef6445cce027ee06b5d3400" + resolved "https://github.com/Rezan-vm/tornado-relayer-registry.git#c462d274778e57008414efe9e432dbc03bfbf739" dependencies: "@openzeppelin/contracts" "3.2.0" "@openzeppelin/hardhat-upgrades" "1.10.0" @@ -17444,6 +17444,7 @@ tornado-governance@^1.0.3: tornado-anonymity-mining "^2.1.5" tornado-cli "^0.0.1" tornado-governance "2.0.0" + tornado-trees "^0.0.11" tornado-trees@^0.0.11: version "0.0.11" From f21a4f69bd9c2bb5238115f74cbd60f28d79dacb Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 23 Feb 2022 20:27:05 +0300 Subject: [PATCH 12/18] add Etherscan verification --- .github/workflows/build.js.yml | 2 +- README.md | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.js.yml b/.github/workflows/build.js.yml index 52f1a54..c6aca03 100644 --- a/.github/workflows/build.js.yml +++ b/.github/workflows/build.js.yml @@ -18,7 +18,7 @@ jobs: - run: yarn cache clean --all - run: yarn install --network-concurrency 1 - run: yarn lint - - run: yarn build + - run: yarn compile - run: yarn test env: ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} diff --git a/README.md b/README.md index de8f727..140f781 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b Setting up the repository: ```bash -git clone https://github.com/mirru2532/tornado-instances.git -cd tornado-instances -yarn -cp .env.example .env + git clone https://github.com/mirru2532/tornado-instances.git + cd tornado-instances + yarn + cp .env.example .env ``` Please fill out .env according to the template provided in it. Please ensure that all of the example values are set to the correct addresses. @@ -37,7 +37,7 @@ Please fill out .env according to the template provided in it. Please ensure tha To run test scripts: ```bash -yarn test + yarn test ``` Test scripts cover instance factory deployment, proposal deployment and executing proposal. @@ -53,12 +53,23 @@ With `salt` = `0x000000000000000000000000000000000000000000000000000000004794198 Check addresses with current config: ```shell -yarn compile -node -e 'require("./src/generateAddresses").generateWithLog()' + yarn compile + node -e 'require("./src/generateAddresses").generateWithLog()' ``` Deploy InstanceFactory: ```shell -npx hardhat run scripts/deployInstanceFactory.js --network mainnet + yarn hardhat run scripts/deployInstanceFactory.js --network mainnet +``` + +Verify InstanceFactory on Etherscan: + +``` + yarn hardhat verify --network +``` + +With current config: +``` + yarn hardhat verify --network mainnet 0x7a6e627DC6F66617b4A74Be097A8f56c622fa24c 0xce172ce1F20EC0B3728c9965470eaf994A03557A 0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe 20 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce 0xB20c66C4DE72433F3cE747b58B86830c459CA911 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C 200000000000000000000 ``` From 189c33cba143e9b49d3b8c2f98761292363e4a61 Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 23 Feb 2022 20:34:01 +0300 Subject: [PATCH 13/18] fix linter --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 140f781..b6baf79 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Verify InstanceFactory on Etherscan: ``` With current config: + ``` yarn hardhat verify --network mainnet 0x7a6e627DC6F66617b4A74Be097A8f56c622fa24c 0xce172ce1F20EC0B3728c9965470eaf994A03557A 0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe 20 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce 0xB20c66C4DE72433F3cE747b58B86830c459CA911 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C 200000000000000000000 ``` From da2f8cbc73023409d40cb88be6dd18d8c3354023 Mon Sep 17 00:00:00 2001 From: Drygin Date: Mon, 28 Feb 2022 23:22:40 +0300 Subject: [PATCH 14/18] use onlyGovernance instead of onlyOwner --- README.md | 2 +- contracts/InstanceFactory.sol | 46 +++++++++++++++++++++++----- contracts/interfaces/IGovernance.sol | 7 ----- 3 files changed, 39 insertions(+), 16 deletions(-) delete mode 100644 contracts/interfaces/IGovernance.sol diff --git a/README.md b/README.md index b6baf79..9bc3c46 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be: -1. `InstanceFactory` - `0x7a6e627DC6F66617b4A74Be097A8f56c622fa24c` +1. `InstanceFactory` - `0xBb3bd4849F88E709Ea6e5dC8F2C4cDc5293a12d5` Check addresses with current config: diff --git a/contracts/InstanceFactory.sol b/contracts/InstanceFactory.sol index a820b59..39527cd 100644 --- a/contracts/InstanceFactory.sol +++ b/contracts/InstanceFactory.sol @@ -8,7 +8,6 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/proxy/Clones.sol"; import "./ERC20TornadoCloneable.sol"; import "./AddInstanceProposal.sol"; -import "./interfaces/IGovernance.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol"; @@ -33,6 +32,14 @@ contract InstanceFactory is Ownable { event NewInstanceCloneCreated(address indexed clone); event NewGovernanceProposalCreated(address indexed proposal); + /** + * @dev Throws if called by any account other than the Governance. + */ + modifier onlyGovernance() { + require(owner() == _msgSender(), "Caller is not the Governance"); + _; + } + constructor( address _verifier, address _hasher, @@ -56,7 +63,12 @@ contract InstanceFactory is Ownable { transferOwnership(_governance); } - function createInstanceClone(uint256 _denomination, address _token) external onlyOwner returns (address) { + /** + * @dev Throws if called by any account other than the Governance. + * @param _denomination denomination of new Tornado instance + * @param _token address of ERC20 token for a new instance + */ + function createInstanceClone(uint256 _denomination, address _token) external onlyGovernance returns (address) { bytes32 salt = keccak256(abi.encodePacked(_denomination, _token)); require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance already exists"); @@ -73,6 +85,15 @@ contract InstanceFactory is Ownable { return implementation.predictDeterministicAddress(salt); } + /** + * @dev Creates AddInstanceProposal with approve. + * @param _token address of ERC20 token for a new instance + * @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination. + * `3000` means 0.3% fee Uniswap pool. + * @param _denominations list of denominations for each new instance + * @param _protocolFees list of protocol fees for each new instance. + * `100` means that instance withdrawal fee is 1% of denomination. + */ function createProposalApprove( address _token, uint24 _uniswapPoolSwappingFee, @@ -83,6 +104,15 @@ contract InstanceFactory is Ownable { return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees); } + /** + * @dev Creates AddInstanceProposal with approve. + * @param _token address of ERC20 token for a new instance + * @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination. + * `3000` means 0.3% fee Uniswap pool. + * @param _denominations list of denominations for each new instance + * @param _protocolFees list of protocol fees for each new instance. + * `100` means that instance withdrawal fee is 1% of denomination. + */ function createProposalPermit( address _token, uint24 _uniswapPoolSwappingFee, @@ -118,32 +148,32 @@ contract InstanceFactory is Ownable { return proposal; } - function setVerifier(address _verifier) external onlyOwner { + function setVerifier(address _verifier) external onlyGovernance { verifier = _verifier; emit NewVerifierSet(verifier); } - function setHasher(address _hasher) external onlyOwner { + function setHasher(address _hasher) external onlyGovernance { hasher = _hasher; emit NewHasherSet(hasher); } - function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyOwner { + function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyGovernance { merkleTreeHeight = _merkleTreeHeight; emit NewTreeHeightSet(merkleTreeHeight); } - function setCreationFee(uint256 _creationFee) external onlyOwner { + function setCreationFee(uint256 _creationFee) external onlyGovernance { creationFee = _creationFee; emit NewCreationFeeSet(_creationFee); } - function setImplementation(address _newImplementation) external onlyOwner { + function setImplementation(address _newImplementation) external onlyGovernance { implementation = _newImplementation; emit NewImplementationSet(implementation); } - function generateNewImplementation() external onlyOwner { + function generateNewImplementation() external onlyGovernance { implementation = address(new ERC20TornadoCloneable(verifier, hasher)); } } diff --git a/contracts/interfaces/IGovernance.sol b/contracts/interfaces/IGovernance.sol deleted file mode 100644 index c7b16d4..0000000 --- a/contracts/interfaces/IGovernance.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.7.6; - -interface IGovernance { - function propose(address target, string memory description) external returns (uint256); -} From 39659876fcba7b7837b22b53cb1d27d15a42c74b Mon Sep 17 00:00:00 2001 From: Drygin Date: Wed, 2 Mar 2022 01:14:46 +0300 Subject: [PATCH 15/18] clean readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 9bc3c46..e71fcbb 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,6 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b Setting up the repository: ```bash - git clone https://github.com/mirru2532/tornado-instances.git - cd tornado-instances yarn cp .env.example .env ``` From ce1f8f43de110b1af5b3988b4b3dfff4995b60e9 Mon Sep 17 00:00:00 2001 From: Drygin Date: Thu, 10 Mar 2022 00:43:57 +0300 Subject: [PATCH 16/18] add InstanceFactory version without registry --- .solcover.js | 13 +- README.md | 33 +- config.js | 1 + contracts/InstanceFactory.sol | 110 +---- contracts/InstanceFactoryWithRegistry.sol | 122 +++++ scripts/deployInstanceFactoryWithRegistry.js | 28 ++ src/generateAddresses.js | 23 +- test/factory.test.js | 342 +++---------- test/factory.with.registry.test.js | 482 +++++++++++++++++++ test/instance.tests.js | 166 ------- 10 files changed, 745 insertions(+), 575 deletions(-) create mode 100644 contracts/InstanceFactoryWithRegistry.sol create mode 100644 scripts/deployInstanceFactoryWithRegistry.js create mode 100644 test/factory.with.registry.test.js delete mode 100644 test/instance.tests.js diff --git a/.solcover.js b/.solcover.js index db331df..ba8ea30 100644 --- a/.solcover.js +++ b/.solcover.js @@ -1,14 +1,3 @@ module.exports = { - skipFiles: [ - 'tornado_proxy/TornadoProxy.sol', - 'tornado_proxy/ITornadoTrees.sol', - 'tornado_proxy/ITornadoInstance.sol', - 'ERC20TornadoVirtual.sol', - 'denomination_templates/Add1Instance.sol', - 'denomination_templates/Add2Instances.sol', - 'denomination_templates/Add3Instances.sol', - 'denomination_templates/Add4Instances.sol', - 'denomination_templates/Add5Instances.sol', - 'denomination_templates/Add6Instances.sol', - ], + skipFiles: [], } diff --git a/README.md b/README.md index e71fcbb..3dc2b65 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,19 @@ ## About -This repository contains governance proposal factory for the addition of new Tornado ERC20 instances to the Tornado router. +This repository contains: + +1. `InstanceFactory` - instance factory for the creation new Tornado ERC20 pools +2. `InstanceFactoryWithRegistry` - governance proposal factory for the addition of new Tornado ERC20 instances to the Tornado router + +### InstanceFactory + +Anyone can create a new ERC20 instance by calling `createInstanceClone` method of the factory with parameters: + +1. `address token` - address of ERC20 token for a new instance +2. `uint256 denomination` - denomination for new instance (tokens can only be deposited in certain denominations into instances) + +### InstanceFactoryWithRegistry Anyone can create governance proposal for the addition of a new ERC20 instance by calling `createProposalApprove/createProposalPermit` method of the factory with parameters (proposal creation fee in TORN is charged from sender): @@ -13,13 +25,15 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b ## Factory parameters +### InstanceFactoryWithRegistry + 1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 3 instances at once. 2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`. ## Warnings 1. This version of the factory creates a proposal for **immutable** Tornado instance initialization. -2. Users should manually propose a proposal after its creation using the factory (in governance UI for example). As `propose()` method caller must have 1000 TORN locked in the governance. Moreover, the proposer can't propose more than one proposal simultaneously. +2. For `InstanceFactoryWithRegistry` users should manually propose a proposal after its creation using the factory (in governance UI for example). As `propose()` method caller must have 1000 TORN locked in the governance. Moreover, the proposer can't propose more than one proposal simultaneously. ## Tests @@ -46,7 +60,8 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be: -1. `InstanceFactory` - `0xBb3bd4849F88E709Ea6e5dC8F2C4cDc5293a12d5` +1. `InstanceFactory` - `0x9A04e3F1091A69CB53D163abE7ad9bbc86C23823` +1. `InstanceFactoryWithRegistry` - `0xee994E045B9Ec5a37f3f85d34f9fD087A0c69236` Check addresses with current config: @@ -61,14 +76,14 @@ Deploy InstanceFactory: yarn hardhat run scripts/deployInstanceFactory.js --network mainnet ``` +Deploy InstanceFactoryWithRegistry: + +```shell + yarn hardhat run scripts/deployInstanceFactoryWithRegistry.js --network mainnet +``` + Verify InstanceFactory on Etherscan: ``` yarn hardhat verify --network ``` - -With current config: - -``` - yarn hardhat verify --network mainnet 0x7a6e627DC6F66617b4A74Be097A8f56c622fa24c 0xce172ce1F20EC0B3728c9965470eaf994A03557A 0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe 20 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce 0xB20c66C4DE72433F3cE747b58B86830c459CA911 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C 200000000000000000000 -``` diff --git a/config.js b/config.js index fa4b8c3..5c9c57e 100644 --- a/config.js +++ b/config.js @@ -14,4 +14,5 @@ module.exports = { compWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', creationFee: '200000000000000000000', // 200 TORN deployGasLimit: 7000000, + owner: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', } diff --git a/contracts/InstanceFactory.sol b/contracts/InstanceFactory.sol index 39527cd..4c4331a 100644 --- a/contracts/InstanceFactory.sol +++ b/contracts/InstanceFactory.sol @@ -7,68 +7,44 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/proxy/Clones.sol"; import "./ERC20TornadoCloneable.sol"; -import "./AddInstanceProposal.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol"; contract InstanceFactory is Ownable { using Clones for address; using Address for address; - address public immutable governance; - address public immutable torn; - address public immutable instanceRegistry; address public implementation; address public verifier; address public hasher; uint32 public merkleTreeHeight; - uint256 public creationFee; event NewVerifierSet(address indexed newVerifier); event NewHasherSet(address indexed newHasher); event NewTreeHeightSet(uint32 indexed newTreeHeight); - event NewCreationFeeSet(uint256 indexed newCreationFee); event NewImplementationSet(address indexed newImplemenentation); event NewInstanceCloneCreated(address indexed clone); - event NewGovernanceProposalCreated(address indexed proposal); - - /** - * @dev Throws if called by any account other than the Governance. - */ - modifier onlyGovernance() { - require(owner() == _msgSender(), "Caller is not the Governance"); - _; - } constructor( address _verifier, address _hasher, uint32 _merkleTreeHeight, - address _governance, - address _instanceRegistry, - address _torn, - uint256 _creationFee + address _owner ) { verifier = _verifier; hasher = _hasher; merkleTreeHeight = _merkleTreeHeight; - governance = _governance; - instanceRegistry = _instanceRegistry; - torn = _torn; - creationFee = _creationFee; ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher); implementation = address(implContract); - transferOwnership(_governance); + transferOwnership(_owner); } /** - * @dev Throws if called by any account other than the Governance. + * @dev Creates new Tornado instance. * @param _denomination denomination of new Tornado instance * @param _token address of ERC20 token for a new instance */ - function createInstanceClone(uint256 _denomination, address _token) external onlyGovernance returns (address) { + function createInstanceClone(uint256 _denomination, address _token) public virtual returns (address) { bytes32 salt = keccak256(abi.encodePacked(_denomination, _token)); require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance already exists"); @@ -85,95 +61,27 @@ contract InstanceFactory is Ownable { return implementation.predictDeterministicAddress(salt); } - /** - * @dev Creates AddInstanceProposal with approve. - * @param _token address of ERC20 token for a new instance - * @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination. - * `3000` means 0.3% fee Uniswap pool. - * @param _denominations list of denominations for each new instance - * @param _protocolFees list of protocol fees for each new instance. - * `100` means that instance withdrawal fee is 1% of denomination. - */ - function createProposalApprove( - address _token, - uint24 _uniswapPoolSwappingFee, - uint256[] memory _denominations, - uint32[] memory _protocolFees - ) external returns (address) { - require(IERC20(torn).transferFrom(msg.sender, governance, creationFee)); - return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees); - } - - /** - * @dev Creates AddInstanceProposal with approve. - * @param _token address of ERC20 token for a new instance - * @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination. - * `3000` means 0.3% fee Uniswap pool. - * @param _denominations list of denominations for each new instance - * @param _protocolFees list of protocol fees for each new instance. - * `100` means that instance withdrawal fee is 1% of denomination. - */ - function createProposalPermit( - address _token, - uint24 _uniswapPoolSwappingFee, - uint256[] memory _denominations, - uint32[] memory _protocolFees, - address creater, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external returns (address) { - IERC20Permit(torn).permit(creater, address(this), creationFee, deadline, v, r, s); - require(IERC20(torn).transferFrom(creater, governance, creationFee)); - return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees); - } - - function _createProposal( - address _token, - uint24 _uniswapPoolSwappingFee, - uint256[] memory _denominations, - uint32[] memory _protocolFees - ) internal returns (address) { - require(_token.isContract(), "Token is not contract"); - require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); - require(_denominations.length > 0, "Empty denominations"); - require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); - - address proposal = address( - new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees) - ); - emit NewGovernanceProposalCreated(proposal); - - return proposal; - } - - function setVerifier(address _verifier) external onlyGovernance { + function setVerifier(address _verifier) external onlyOwner { verifier = _verifier; emit NewVerifierSet(verifier); } - function setHasher(address _hasher) external onlyGovernance { + function setHasher(address _hasher) external onlyOwner { hasher = _hasher; emit NewHasherSet(hasher); } - function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyGovernance { + function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyOwner { merkleTreeHeight = _merkleTreeHeight; emit NewTreeHeightSet(merkleTreeHeight); } - function setCreationFee(uint256 _creationFee) external onlyGovernance { - creationFee = _creationFee; - emit NewCreationFeeSet(_creationFee); - } - - function setImplementation(address _newImplementation) external onlyGovernance { + function setImplementation(address _newImplementation) external onlyOwner { implementation = _newImplementation; emit NewImplementationSet(implementation); } - function generateNewImplementation() external onlyGovernance { + function generateNewImplementation() external onlyOwner { implementation = address(new ERC20TornadoCloneable(verifier, hasher)); } } diff --git a/contracts/InstanceFactoryWithRegistry.sol b/contracts/InstanceFactoryWithRegistry.sol new file mode 100644 index 0000000..9046df7 --- /dev/null +++ b/contracts/InstanceFactoryWithRegistry.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.7.6; +pragma abicoder v2; + +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import "./AddInstanceProposal.sol"; +import "./InstanceFactory.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol"; + +contract InstanceFactoryWithRegistry is InstanceFactory { + using Address for address; + + address public immutable governance; + address public immutable torn; + address public immutable instanceRegistry; + uint256 public creationFee; + + event NewCreationFeeSet(uint256 indexed newCreationFee); + event NewGovernanceProposalCreated(address indexed proposal); + + /** + * @dev Throws if called by any account other than the Governance. + */ + modifier onlyGovernance() { + require(owner() == _msgSender(), "Caller is not the Governance"); + _; + } + + constructor( + address _verifier, + address _hasher, + uint32 _merkleTreeHeight, + address _governance, + address _instanceRegistry, + address _torn, + uint256 _creationFee + ) InstanceFactory(_verifier, _hasher, _merkleTreeHeight, _governance) { + governance = _governance; + instanceRegistry = _instanceRegistry; + torn = _torn; + creationFee = _creationFee; + } + + /** + * @dev Throws if called by any account other than the Governance. + * @param _denomination denomination of new Tornado instance + * @param _token address of ERC20 token for a new instance + */ + function createInstanceClone(uint256 _denomination, address _token) public override onlyGovernance returns (address) { + return super.createInstanceClone(_denomination, _token); + } + + /** + * @dev Creates AddInstanceProposal with approve. + * @param _token address of ERC20 token for a new instance + * @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination. + * `3000` means 0.3% fee Uniswap pool. + * @param _denominations list of denominations for each new instance + * @param _protocolFees list of protocol fees for each new instance. + * `100` means that instance withdrawal fee is 1% of denomination. + */ + function createProposalApprove( + address _token, + uint24 _uniswapPoolSwappingFee, + uint256[] memory _denominations, + uint32[] memory _protocolFees + ) external returns (address) { + require(IERC20(torn).transferFrom(msg.sender, governance, creationFee)); + return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees); + } + + /** + * @dev Creates AddInstanceProposal with permit. + * @param _token address of ERC20 token for a new instance + * @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination. + * `3000` means 0.3% fee Uniswap pool. + * @param _denominations list of denominations for each new instance + * @param _protocolFees list of protocol fees for each new instance. + * `100` means that instance withdrawal fee is 1% of denomination. + */ + function createProposalPermit( + address _token, + uint24 _uniswapPoolSwappingFee, + uint256[] memory _denominations, + uint32[] memory _protocolFees, + address creater, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (address) { + IERC20Permit(torn).permit(creater, address(this), creationFee, deadline, v, r, s); + require(IERC20(torn).transferFrom(creater, governance, creationFee)); + return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees); + } + + function _createProposal( + address _token, + uint24 _uniswapPoolSwappingFee, + uint256[] memory _denominations, + uint32[] memory _protocolFees + ) internal returns (address) { + require(_token.isContract(), "Token is not contract"); + require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); + require(_denominations.length > 0, "Empty denominations"); + require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); + + address proposal = address( + new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees) + ); + emit NewGovernanceProposalCreated(proposal); + + return proposal; + } + + function setCreationFee(uint256 _creationFee) external onlyGovernance { + creationFee = _creationFee; + emit NewCreationFeeSet(_creationFee); + } +} diff --git a/scripts/deployInstanceFactoryWithRegistry.js b/scripts/deployInstanceFactoryWithRegistry.js new file mode 100644 index 0000000..bc24b52 --- /dev/null +++ b/scripts/deployInstanceFactoryWithRegistry.js @@ -0,0 +1,28 @@ +const { ethers } = require('hardhat') +const config = require('../config') +const { generate } = require('../src/generateAddresses') + +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: config.deployGasLimit }) +} + +async function main() { + const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory) + const contracts = await generate() + await deploy({ ...contracts.factoryWithRegistryContract, singletonFactory }) + console.log( + `Instance factory with registry contract have been deployed on ${contracts.factoryWithRegistryContract.address} address`, + ) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/src/generateAddresses.js b/src/generateAddresses.js index 21ea249..50ece99 100644 --- a/src/generateAddresses.js +++ b/src/generateAddresses.js @@ -6,6 +6,19 @@ async function generate(config = defaultConfig) { const deploymentBytecodeFactory = FactoryFactory.bytecode + FactoryFactory.interface + .encodeDeploy([config.verifier, config.hasher, config.merkleTreeHeight, config.owner]) + .slice(2) + + const factoryAddress = ethers.utils.getCreate2Address( + config.singletonFactory, + config.salt, + ethers.utils.keccak256(deploymentBytecodeFactory), + ) + + const FactoryWithRegistryFactory = await ethers.getContractFactory('InstanceFactoryWithRegistry') + const deploymentBytecodeFactoryWithRegistry = + FactoryWithRegistryFactory.bytecode + + FactoryWithRegistryFactory.interface .encodeDeploy([ config.verifier, config.hasher, @@ -17,10 +30,10 @@ async function generate(config = defaultConfig) { ]) .slice(2) - const factoryAddress = ethers.utils.getCreate2Address( + const factoryWithRegistryAddress = ethers.utils.getCreate2Address( config.singletonFactory, config.salt, - ethers.utils.keccak256(deploymentBytecodeFactory), + ethers.utils.keccak256(deploymentBytecodeFactoryWithRegistry), ) const result = { @@ -29,6 +42,11 @@ async function generate(config = defaultConfig) { bytecode: deploymentBytecodeFactory, isProxy: false, }, + factoryWithRegistryContract: { + address: factoryWithRegistryAddress, + bytecode: deploymentBytecodeFactoryWithRegistry, + isProxy: false, + }, } return result @@ -37,6 +55,7 @@ async function generate(config = defaultConfig) { async function generateWithLog() { const contracts = await generate() console.log('Instance factory contract: ', contracts.factoryContract.address) + console.log('Instance factory with registry contract: ', contracts.factoryWithRegistryContract.address) return contracts } diff --git a/test/factory.test.js b/test/factory.test.js index fa0b9a7..c588ab8 100644 --- a/test/factory.test.js +++ b/test/factory.test.js @@ -4,45 +4,30 @@ const { loadFixture } = waffle const { expect } = require('chai') const { BigNumber } = require('@ethersproject/bignumber') const config = require('../config') -const { getSignerFromAddress, minewait } = require('./utils') -const { PermitSigner } = require('../src/permit.js') +const { getSignerFromAddress } = require('./utils') const { generate } = require('../src/generateAddresses') +const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') describe('Instance Factory Tests', () => { - const ProposalState = { - Pending: 0, - Active: 1, - Defeated: 2, - Timelocked: 3, - AwaitingExecution: 4, - Executed: 5, - Expired: 6, - } - const addressZero = ethers.constants.AddressZero async function fixture() { - const [sender, deployer, multisig] = await ethers.getSigners() + const [sender, deployer] = await ethers.getSigners() - const tornWhale = await getSignerFromAddress(config.tornWhale) + const owner = await getSignerFromAddress(config.owner) - const gov = await ethers.getContractAt('Governance', config.governance) + await sender.sendTransaction({ + to: config.owner, + value: ethers.utils.parseEther('1'), + }) - const tornToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', - config.TORN, - ) + const compWhale = await getSignerFromAddress(config.compWhale) const compToken = await ethers.getContractAt( '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', config.COMP, ) - const instanceRegistry = await ethers.getContractAt( - 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', - config.instanceRegistry, - ) - // deploy InstanceFactory with CREATE2 const singletonFactory = await ethers.getContractAt( 'SingletonFactory', @@ -59,328 +44,115 @@ describe('Instance Factory Tests', () => { return { sender, deployer, - multisig, - tornWhale, - gov, - tornToken, + owner, compToken, - instanceRegistry, + compWhale, instanceFactory, } } it('Should have initialized all successfully', async function () { - const { sender, gov, tornToken, instanceRegistry, instanceFactory } = await loadFixture(fixture) + const { sender, compToken, instanceFactory } = await loadFixture(fixture) expect(sender.address).to.exist - expect(gov.address).to.exist - expect(tornToken.address).to.exist - expect(instanceRegistry.address).to.exist + expect(compToken.address).to.exist expect(instanceFactory.address).to.exist }) it('Should set correct params for factory', async function () { const { instanceFactory } = await loadFixture(fixture) - expect(await instanceFactory.governance()).to.be.equal(config.governance) expect(await instanceFactory.verifier()).to.be.equal(config.verifier) expect(await instanceFactory.hasher()).to.be.equal(config.hasher) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) expect(await instanceFactory.implementation()).to.exist - expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) - expect(await instanceFactory.torn()).to.be.equal(config.TORN) }) it('Governance should be able to set factory params', async function () { - let { instanceFactory, gov } = await loadFixture(fixture) + let { instanceFactory, owner } = await loadFixture(fixture) await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted - const govSigner = await getSignerFromAddress(gov.address) - instanceFactory = await instanceFactory.connect(govSigner) + instanceFactory = await instanceFactory.connect(owner) await instanceFactory.setVerifier(addressZero) await instanceFactory.setHasher(addressZero) await instanceFactory.setMerkleTreeHeight(1) - await instanceFactory.setCreationFee(0) expect(await instanceFactory.verifier()).to.be.equal(addressZero) expect(await instanceFactory.hasher()).to.be.equal(addressZero) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1) - expect(await instanceFactory.creationFee()).to.be.equal(0) await instanceFactory.setVerifier(config.verifier) await instanceFactory.setHasher(config.hasher) await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) - await instanceFactory.setCreationFee(config.creationFee) expect(await instanceFactory.verifier()).to.be.equal(config.verifier) expect(await instanceFactory.hasher()).to.be.equal(config.hasher) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) - expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) }) - it('Should successfully deploy/propose/execute proposal - add instance', async function () { - let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + it('Should successfully add instance', async function () { + let { sender, instanceFactory } = await loadFixture(fixture) - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(instanceFactory.address, config.creationFee) + // deploy instance + await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP) - await expect(() => - instanceFactory - .connect(sender) - .createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]), - ).to.changeTokenBalances( - tornToken, - [sender, gov], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], + // check instance initialization + let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') + const instance = await ethers.getContractAt( + 'ERC20TornadoCloneable', + ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), ) - let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') - const proposal = await ethers.getContractAt( - 'AddInstanceProposal', - ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), - ) - - expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) - expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) - expect(await proposal.token()).to.be.equal(config.COMP) - expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) - expect(await proposal.numInstances()).to.be.equal(1) - expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) - expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) - - // propose proposal --------------------------------------------- - let response, id, state - gov = await gov.connect(tornWhale) - await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) - await gov.lockWithApproval(ethers.utils.parseEther('26000')) - - response = await gov.propose(proposal.address, 'COMP token instance proposal') - id = await gov.latestProposalIds(tornWhale.address) - state = await gov.state(id) - - const { events } = await response.wait() - const args = events.find(({ event }) => event == 'ProposalCreated').args - expect(args.id).to.be.equal(id) - expect(args.proposer).to.be.equal(tornWhale.address) - expect(args.target).to.be.equal(proposal.address) - expect(args.description).to.be.equal('COMP token instance proposal') - expect(state).to.be.equal(ProposalState.Pending) - - // execute proposal --------------------------------------------- - await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) - await expect(gov.castVote(id, true)).to.not.be.reverted - expect(await gov.state(id)).to.be.equal(ProposalState.Active) - await minewait( - ( - await gov.VOTING_PERIOD() - ) - .add(await gov.EXECUTION_DELAY()) - .add(96400) - .toNumber(), - ) - expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) - - let tx = await gov.execute(id) - - expect(await gov.state(id)).to.be.equal(ProposalState.Executed) - - // check instance initialization -------------------------------- - let receipt = await tx.wait() - const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40) - const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) - - expect(await instance.token()).to.be.equal(config.COMP) - expect(await instance.verifier()).to.be.equal(config.verifier) - expect(await instance.hasher()).to.be.equal(config.hasher) - expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) - expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) - - const instanceData = await instanceRegistry.instances(instance.address) - expect(instanceData.isERC20).to.be.equal(true) - expect(instanceData.token).to.be.equal(config.COMP) - expect(instanceData.state).to.be.equal(1) - expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) - expect(instanceData.protocolFeePercentage).to.be.equal(30) - }) - - it('Should successfully deploy/propose/execute proposal - add instances', async function () { - let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(instanceFactory.address, config.creationFee) - - await expect(() => - instanceFactory - .connect(sender) - .createProposalApprove( - config.COMP, - 3000, - [ethers.utils.parseEther('100'), ethers.utils.parseEther('1000')], - [30, 30], - ), - ).to.changeTokenBalances( - tornToken, - [sender, gov], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], - ) - - let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') - const proposal = await ethers.getContractAt( - 'AddInstanceProposal', - ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), - ) - - expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) - expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) - expect(await proposal.token()).to.be.equal(config.COMP) - expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) - expect(await proposal.numInstances()).to.be.equal(2) - expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) - expect(await proposal.protocolFeeByIndex(1)).to.be.equal(30) - expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) - expect(await proposal.denominationByIndex(1)).to.be.equal(ethers.utils.parseEther('1000')) - - // propose proposal --------------------------------------------- - let response, id, state - gov = await gov.connect(tornWhale) - await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) - await gov.lockWithApproval(ethers.utils.parseEther('26000')) - - response = await gov.propose(proposal.address, 'COMP token instances proposal') - id = await gov.latestProposalIds(tornWhale.address) - state = await gov.state(id) - - const { events } = await response.wait() - const args = events.find(({ event }) => event == 'ProposalCreated').args - expect(args.id).to.be.equal(id) - expect(args.proposer).to.be.equal(tornWhale.address) - expect(args.target).to.be.equal(proposal.address) - expect(args.description).to.be.equal('COMP token instances proposal') - expect(state).to.be.equal(ProposalState.Pending) - - // execute proposal --------------------------------------------- - await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) - await expect(gov.castVote(id, true)).to.not.be.reverted - expect(await gov.state(id)).to.be.equal(ProposalState.Active) - await minewait( - ( - await gov.VOTING_PERIOD() - ) - .add(await gov.EXECUTION_DELAY()) - .add(96400) - .toNumber(), - ) - expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) - - await gov.execute(id) - - expect(await gov.state(id)).to.be.equal(ProposalState.Executed) - - // check instances initialization ------------------------------- - logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - let instanceAddr = '0x' + logs[0].topics[1].slice(-40) - let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) - - expect(await instance.token()).to.be.equal(config.COMP) - expect(await instance.verifier()).to.be.equal(config.verifier) - expect(await instance.hasher()).to.be.equal(config.hasher) - expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) - expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) - - let instanceData = await instanceRegistry.instances(instance.address) - expect(instanceData.isERC20).to.be.equal(true) - expect(instanceData.token).to.be.equal(config.COMP) - expect(instanceData.state).to.be.equal(1) - expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) - expect(instanceData.protocolFeePercentage).to.be.equal(30) - - instanceAddr = '0x' + logs[1].topics[1].slice(-40) - instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) - expect(await instance.token()).to.be.equal(config.COMP) expect(await instance.verifier()).to.be.equal(config.verifier) expect(await instance.hasher()).to.be.equal(config.hasher) expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000')) - - instanceData = await instanceRegistry.instances(instance.address) - expect(instanceData.isERC20).to.be.equal(true) - expect(instanceData.token).to.be.equal(config.COMP) - expect(instanceData.state).to.be.equal(1) - expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) - expect(instanceData.protocolFeePercentage).to.be.equal(30) }) - it('Should successfully deploy proposal with permit', async function () { - let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + it('Should deposit and withdraw into the new instance', async function () { + let { sender, instanceFactory, compToken, compWhale } = await loadFixture(fixture) - const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3' - const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex')) - const sender = await ethers.getSigner(publicKey.slice(2)) + // deploy instance + await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('100'), config.COMP) - await expect(() => - tornToken.connect(tornWhale).transfer(sender.address, config.creationFee), - ).to.changeTokenBalances( - tornToken, - [tornWhale, sender], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], + let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') + const instance = await ethers.getContractAt( + 'ERC20TornadoCloneable', + ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), ) - // prepare permit data - const domain = { - name: await tornToken.name(), - version: '1', - chainId: 1, - verifyingContract: tornToken.address, - } + // check instance work ------------------------------------------ + const depo = createDeposit({ + nullifier: rbigint(31), + secret: rbigint(31), + }) - const curTimestamp = Math.trunc(new Date().getTime() / 1000) - const args = { - owner: sender, - spender: instanceFactory.address, - value: config.creationFee, - nonce: 0, - deadline: curTimestamp + 1000, - } + const value = ethers.utils.parseEther('100') - const permitSigner = new PermitSigner(domain, args) - const signature = await permitSigner.getSignature(privateKey) - const signer = await permitSigner.getSignerAddress(args, signature.hex) - expect(signer).to.equal(sender.address) + await compToken.connect(compWhale).transfer(sender.address, value) + await compToken.connect(sender).approve(instance.address, value) - await expect(() => - instanceFactory.createProposalPermit( - config.COMP, - 3000, - [ethers.utils.parseEther('100')], - [30], - sender.address, - args.deadline.toString(), - signature.v, - signature.r, - signature.s, - ), - ).to.changeTokenBalances( - tornToken, - [sender, gov], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], + await expect(() => instance.deposit(toHex(depo.commitment), [])).to.changeTokenBalances( + compToken, + [sender, instance], + [BigNumber.from(0).sub(value), value], ) - let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') - const proposal = await ethers.getContractAt( - 'AddInstanceProposal', - ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), - ) + let pevents = await instance.queryFilter('Deposit') + await initialize({ merkleTreeHeight: 20 }) - expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) - expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) - expect(await proposal.token()).to.be.equal(config.COMP) - expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) - expect(await proposal.numInstances()).to.be.equal(1) - expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) - expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + const { proof, args } = await generateProof({ + deposit: depo, + recipient: sender.address, + events: pevents, + }) + + await expect(() => instance.withdraw(proof, ...args)).to.changeTokenBalances( + compToken, + [instance, sender], + [BigNumber.from(0).sub(value), value], + ) }) }) diff --git a/test/factory.with.registry.test.js b/test/factory.with.registry.test.js new file mode 100644 index 0000000..271c0ef --- /dev/null +++ b/test/factory.with.registry.test.js @@ -0,0 +1,482 @@ +const hre = require('hardhat') +const { ethers, waffle } = hre +const { loadFixture } = waffle +const { expect } = require('chai') +const { BigNumber } = require('@ethersproject/bignumber') +const config = require('../config') +const { getSignerFromAddress, minewait } = require('./utils') +const { PermitSigner } = require('../src/permit.js') +const { generate } = require('../src/generateAddresses') +const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') + +describe('Instance Factory With Registry Tests', () => { + const ProposalState = { + Pending: 0, + Active: 1, + Defeated: 2, + Timelocked: 3, + AwaitingExecution: 4, + Executed: 5, + Expired: 6, + } + + const addressZero = ethers.constants.AddressZero + + async function fixture() { + const [sender, deployer, multisig] = await ethers.getSigners() + + const tornWhale = await getSignerFromAddress(config.tornWhale) + + const compWhale = await getSignerFromAddress(config.compWhale) + + const gov = await ethers.getContractAt('Governance', config.governance) + + const router = await ethers.getContractAt( + 'tornado-relayer-registry/contracts/tornado-proxy/TornadoRouter.sol:TornadoRouter', + config.router, + ) + + const tornToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + config.TORN, + ) + + const compToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', + config.COMP, + ) + + const instanceRegistry = await ethers.getContractAt( + 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', + config.instanceRegistry, + ) + + // deploy InstanceFactoryWithRegistry with CREATE2 + const singletonFactory = await ethers.getContractAt( + 'SingletonFactory', + config.singletonFactoryVerboseWrapper, + ) + const contracts = await generate() + if ((await ethers.provider.getCode(contracts.factoryWithRegistryContract.address)) == '0x') { + await singletonFactory.deploy(contracts.factoryWithRegistryContract.bytecode, config.salt, { + gasLimit: config.deployGasLimit, + }) + } + const instanceFactory = await ethers.getContractAt( + 'InstanceFactoryWithRegistry', + contracts.factoryWithRegistryContract.address, + ) + + return { + sender, + deployer, + multisig, + tornWhale, + compWhale, + router, + gov, + tornToken, + compToken, + instanceRegistry, + instanceFactory, + } + } + + it('Should have initialized all successfully', async function () { + const { sender, gov, tornToken, instanceRegistry, instanceFactory } = await loadFixture(fixture) + expect(sender.address).to.exist + expect(gov.address).to.exist + expect(tornToken.address).to.exist + expect(instanceRegistry.address).to.exist + expect(instanceFactory.address).to.exist + }) + + it('Should set correct params for factory', async function () { + const { instanceFactory } = await loadFixture(fixture) + + expect(await instanceFactory.governance()).to.be.equal(config.governance) + expect(await instanceFactory.verifier()).to.be.equal(config.verifier) + expect(await instanceFactory.hasher()).to.be.equal(config.hasher) + expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) + expect(await instanceFactory.implementation()).to.exist + expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) + expect(await instanceFactory.torn()).to.be.equal(config.TORN) + }) + + it('Governance should be able to set factory params', async function () { + let { instanceFactory, gov } = await loadFixture(fixture) + + await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted + + const govSigner = await getSignerFromAddress(gov.address) + instanceFactory = await instanceFactory.connect(govSigner) + + await instanceFactory.setVerifier(addressZero) + await instanceFactory.setHasher(addressZero) + await instanceFactory.setMerkleTreeHeight(1) + await instanceFactory.setCreationFee(0) + + expect(await instanceFactory.verifier()).to.be.equal(addressZero) + expect(await instanceFactory.hasher()).to.be.equal(addressZero) + expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1) + expect(await instanceFactory.creationFee()).to.be.equal(0) + + await instanceFactory.setVerifier(config.verifier) + await instanceFactory.setHasher(config.hasher) + await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) + await instanceFactory.setCreationFee(config.creationFee) + + expect(await instanceFactory.verifier()).to.be.equal(config.verifier) + expect(await instanceFactory.hasher()).to.be.equal(config.hasher) + expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) + expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) + }) + + it('Should successfully deploy/propose/execute proposal - add instance', async function () { + let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + + // deploy proposal ---------------------------------------------- + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) + + await expect(() => + instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]), + ).to.changeTokenBalances( + tornToken, + [sender, gov], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), + ) + + expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) + expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) + expect(await proposal.token()).to.be.equal(config.COMP) + expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) + expect(await proposal.numInstances()).to.be.equal(1) + expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) + expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + + // propose proposal --------------------------------------------- + let response, id, state + gov = await gov.connect(tornWhale) + await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) + await gov.lockWithApproval(ethers.utils.parseEther('26000')) + + response = await gov.propose(proposal.address, 'COMP token instance proposal') + id = await gov.latestProposalIds(tornWhale.address) + state = await gov.state(id) + + const { events } = await response.wait() + const args = events.find(({ event }) => event == 'ProposalCreated').args + expect(args.id).to.be.equal(id) + expect(args.proposer).to.be.equal(tornWhale.address) + expect(args.target).to.be.equal(proposal.address) + expect(args.description).to.be.equal('COMP token instance proposal') + expect(state).to.be.equal(ProposalState.Pending) + + // execute proposal --------------------------------------------- + await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) + await expect(gov.castVote(id, true)).to.not.be.reverted + expect(await gov.state(id)).to.be.equal(ProposalState.Active) + await minewait( + ( + await gov.VOTING_PERIOD() + ) + .add(await gov.EXECUTION_DELAY()) + .add(96400) + .toNumber(), + ) + expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) + + let tx = await gov.execute(id) + + expect(await gov.state(id)).to.be.equal(ProposalState.Executed) + + // check instance initialization -------------------------------- + let receipt = await tx.wait() + const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40) + const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) + + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) + + const instanceData = await instanceRegistry.instances(instance.address) + expect(instanceData.isERC20).to.be.equal(true) + expect(instanceData.token).to.be.equal(config.COMP) + expect(instanceData.state).to.be.equal(1) + expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) + expect(instanceData.protocolFeePercentage).to.be.equal(30) + }) + + it('Should successfully deploy/propose/execute proposal - add instances', async function () { + let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + + // deploy proposal ---------------------------------------------- + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) + + await expect(() => + instanceFactory + .connect(sender) + .createProposalApprove( + config.COMP, + 3000, + [ethers.utils.parseEther('100'), ethers.utils.parseEther('1000')], + [30, 30], + ), + ).to.changeTokenBalances( + tornToken, + [sender, gov], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), + ) + + expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) + expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) + expect(await proposal.token()).to.be.equal(config.COMP) + expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) + expect(await proposal.numInstances()).to.be.equal(2) + expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) + expect(await proposal.protocolFeeByIndex(1)).to.be.equal(30) + expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + expect(await proposal.denominationByIndex(1)).to.be.equal(ethers.utils.parseEther('1000')) + + // propose proposal --------------------------------------------- + let response, id, state + gov = await gov.connect(tornWhale) + await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) + await gov.lockWithApproval(ethers.utils.parseEther('26000')) + + response = await gov.propose(proposal.address, 'COMP token instances proposal') + id = await gov.latestProposalIds(tornWhale.address) + state = await gov.state(id) + + const { events } = await response.wait() + const args = events.find(({ event }) => event == 'ProposalCreated').args + expect(args.id).to.be.equal(id) + expect(args.proposer).to.be.equal(tornWhale.address) + expect(args.target).to.be.equal(proposal.address) + expect(args.description).to.be.equal('COMP token instances proposal') + expect(state).to.be.equal(ProposalState.Pending) + + // execute proposal --------------------------------------------- + await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) + await expect(gov.castVote(id, true)).to.not.be.reverted + expect(await gov.state(id)).to.be.equal(ProposalState.Active) + await minewait( + ( + await gov.VOTING_PERIOD() + ) + .add(await gov.EXECUTION_DELAY()) + .add(96400) + .toNumber(), + ) + expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) + + await gov.execute(id) + + expect(await gov.state(id)).to.be.equal(ProposalState.Executed) + + // check instances initialization ------------------------------- + logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') + let instanceAddr = '0x' + logs[logs.length - 2].topics[1].slice(-40) + let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) + + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) + + let instanceData = await instanceRegistry.instances(instance.address) + expect(instanceData.isERC20).to.be.equal(true) + expect(instanceData.token).to.be.equal(config.COMP) + expect(instanceData.state).to.be.equal(1) + expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) + expect(instanceData.protocolFeePercentage).to.be.equal(30) + + instanceAddr = '0x' + logs[logs.length - 1].topics[1].slice(-40) + instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) + + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000')) + + instanceData = await instanceRegistry.instances(instance.address) + expect(instanceData.isERC20).to.be.equal(true) + expect(instanceData.token).to.be.equal(config.COMP) + expect(instanceData.state).to.be.equal(1) + expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) + expect(instanceData.protocolFeePercentage).to.be.equal(30) + }) + + it('Should successfully deploy proposal with permit', async function () { + let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + + const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3' + const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex')) + const sender = await ethers.getSigner(publicKey.slice(2)) + + await expect(() => + tornToken.connect(tornWhale).transfer(sender.address, config.creationFee), + ).to.changeTokenBalances( + tornToken, + [tornWhale, sender], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + // prepare permit data + const domain = { + name: await tornToken.name(), + version: '1', + chainId: 1, + verifyingContract: tornToken.address, + } + + const curTimestamp = Math.trunc(new Date().getTime() / 1000) + const args = { + owner: sender, + spender: instanceFactory.address, + value: config.creationFee, + nonce: 0, + deadline: curTimestamp + 1000, + } + + const permitSigner = new PermitSigner(domain, args) + const signature = await permitSigner.getSignature(privateKey) + const signer = await permitSigner.getSignerAddress(args, signature.hex) + expect(signer).to.equal(sender.address) + + await expect(() => + instanceFactory.createProposalPermit( + config.COMP, + 3000, + [ethers.utils.parseEther('100')], + [30], + sender.address, + args.deadline.toString(), + signature.v, + signature.r, + signature.s, + ), + ).to.changeTokenBalances( + tornToken, + [sender, gov], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), + ) + + expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address) + expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) + expect(await proposal.token()).to.be.equal(config.COMP) + expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) + expect(await proposal.numInstances()).to.be.equal(1) + expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) + expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) + }) + + it('Should deposit and withdraw into the new instance', async function () { + let { sender, instanceFactory, gov, tornWhale, tornToken, router, compToken, compWhale } = + await loadFixture(fixture) + + // deploy proposal ---------------------------------------------- + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) + + await expect(() => + instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]), + ).to.changeTokenBalances( + tornToken, + [sender, gov], + [BigNumber.from(0).sub(config.creationFee), config.creationFee], + ) + + let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') + const proposal = await ethers.getContractAt( + 'AddInstanceProposal', + ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), + ) + + // propose proposal --------------------------------------------- + let id + gov = await gov.connect(tornWhale) + await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) + await gov.lockWithApproval(ethers.utils.parseEther('26000')) + + await gov.propose(proposal.address, 'COMP token instance proposal') + id = await gov.latestProposalIds(tornWhale.address) + + // execute proposal --------------------------------------------- + await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) + await expect(gov.castVote(id, true)).to.not.be.reverted + await minewait( + ( + await gov.VOTING_PERIOD() + ) + .add(await gov.EXECUTION_DELAY()) + .add(96400) + .toNumber(), + ) + + let tx = await gov.execute(id) + let receipt = await tx.wait() + const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40) + const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) + + // check instance work ------------------------------------------ + const depo = createDeposit({ + nullifier: rbigint(31), + secret: rbigint(31), + }) + + const value = ethers.utils.parseEther('100') + + await compToken.connect(compWhale).transfer(sender.address, value) + await compToken.connect(sender).approve(router.address, value) + + await expect(() => router.deposit(instance.address, toHex(depo.commitment), [])).to.changeTokenBalances( + compToken, + [sender, instance], + [BigNumber.from(0).sub(value), value], + ) + + let pevents = await instance.queryFilter('Deposit') + await initialize({ merkleTreeHeight: 20 }) + + const { proof, args } = await generateProof({ + deposit: depo, + recipient: sender.address, + events: pevents, + }) + + await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeTokenBalances( + compToken, + [instance, sender], + [BigNumber.from(0).sub(value), value], + ) + }) +}) diff --git a/test/instance.tests.js b/test/instance.tests.js deleted file mode 100644 index a85af3b..0000000 --- a/test/instance.tests.js +++ /dev/null @@ -1,166 +0,0 @@ -const hre = require('hardhat') -const { ethers, waffle } = hre -const { loadFixture } = waffle -const { expect } = require('chai') -const { BigNumber } = require('@ethersproject/bignumber') -const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') -const config = require('../config') -const { getSignerFromAddress, minewait } = require('./utils') -const { generate } = require('../src/generateAddresses') - -describe('Instance Factory Tests', () => { - const ProposalState = { - Pending: 0, - Active: 1, - Defeated: 2, - Timelocked: 3, - AwaitingExecution: 4, - Executed: 5, - Expired: 6, - } - - async function fixture() { - const [sender, deployer, multisig] = await ethers.getSigners() - - const tornWhale = await getSignerFromAddress(config.tornWhale) - const compWhale = await getSignerFromAddress(config.compWhale) - - let gov = await ethers.getContractAt('Governance', config.governance) - - const tornToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', - config.TORN, - ) - - const compToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', - config.COMP, - ) - - const instanceRegistry = await ethers.getContractAt( - 'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry', - config.instanceRegistry, - ) - - const router = await ethers.getContractAt( - 'tornado-relayer-registry/contracts/tornado-proxy/TornadoRouter.sol:TornadoRouter', - config.router, - ) - - // deploy InstanceFactory with CREATE2 - const singletonFactory = await ethers.getContractAt( - 'SingletonFactory', - config.singletonFactoryVerboseWrapper, - ) - const contracts = await generate() - if ((await ethers.provider.getCode(contracts.factoryContract.address)) == '0x') { - await singletonFactory.deploy(contracts.factoryContract.bytecode, config.salt, { - gasLimit: config.deployGasLimit, - }) - } - const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factoryContract.address) - - // deploy proposal - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(instanceFactory.address, config.creationFee) - - await instanceFactory - .connect(sender) - .createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]) - - let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated') - const proposal = await ethers.getContractAt( - 'AddInstanceProposal', - ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), - ) - - // propose proposal - gov = await gov.connect(tornWhale) - await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000')) - await gov.lockWithApproval(ethers.utils.parseEther('26000')) - await gov.propose(proposal.address, 'COMP token instance proposal') - const id = await gov.latestProposalIds(tornWhale.address) - - // execute proposal - await minewait((await gov.VOTING_DELAY()).add(1).toNumber()) - await expect(gov.castVote(id, true)).to.not.be.reverted - expect(await gov.state(id)).to.be.equal(ProposalState.Active) - await minewait( - ( - await gov.VOTING_PERIOD() - ) - .add(await gov.EXECUTION_DELAY()) - .add(96400) - .toNumber(), - ) - expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution) - await gov.execute(id) - expect(await gov.state(id)).to.be.equal(ProposalState.Executed) - - logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - const instance = await ethers.getContractAt( - 'ERC20TornadoCloneable', - ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)), - ) - - return { - sender, - deployer, - multisig, - tornWhale, - compWhale, - gov, - tornToken, - compToken, - instanceRegistry, - router, - instanceFactory, - instance, - } - } - - it('Should set correct params for factory', async function () { - const { instance } = await loadFixture(fixture) - - expect(await instance.token()).to.be.equal(config.COMP) - expect(await instance.verifier()).to.be.equal(config.verifier) - expect(await instance.hasher()).to.be.equal(config.hasher) - expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) - expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) - }) - - it('Should deposit and withdraw into the new instance', async function () { - const { sender, instance, compToken, compWhale, router } = await loadFixture(fixture) - - const depo = createDeposit({ - nullifier: rbigint(31), - secret: rbigint(31), - }) - - const value = ethers.utils.parseEther('100') - - await compToken.connect(compWhale).transfer(sender.address, value) - await compToken.connect(sender).approve(router.address, value) - - await expect(() => router.deposit(instance.address, toHex(depo.commitment), [])).to.changeTokenBalances( - compToken, - [sender, instance], - [BigNumber.from(0).sub(value), value], - ) - - let pevents = await instance.queryFilter('Deposit') - await initialize({ merkleTreeHeight: 20 }) - - const { proof, args } = await generateProof({ - deposit: depo, - recipient: sender.address, - events: pevents, - }) - - await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeTokenBalances( - compToken, - [instance, sender], - [BigNumber.from(0).sub(value), value], - ) - }) -}) From 65a01c3ce844ccd98dde5b02d24cf6852afbbbe4 Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 15 Mar 2022 18:20:36 +0300 Subject: [PATCH 17/18] max 4 instances --- README.md | 2 +- contracts/AddInstanceProposal.sol | 13 +++-- contracts/InstanceFactory.sol | 26 +++------- test/factory.test.js | 8 ++- test/factory.with.registry.test.js | 81 +++++++++++++----------------- 5 files changed, 54 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 3dc2b65..69b7584 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b ### InstanceFactoryWithRegistry -1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 3 instances at once. +1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 4 instances at once. 2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`. ## Warnings diff --git a/contracts/AddInstanceProposal.sol b/contracts/AddInstanceProposal.sol index acfb386..876f4f0 100644 --- a/contracts/AddInstanceProposal.sol +++ b/contracts/AddInstanceProposal.sol @@ -16,9 +16,11 @@ contract AddInstanceProposal { uint256 internal immutable denomination0; uint256 internal immutable denomination1; uint256 internal immutable denomination2; + uint256 internal immutable denomination3; uint32 internal immutable protocolFee0; uint32 internal immutable protocolFee1; uint32 internal immutable protocolFee2; + uint32 internal immutable protocolFee3; event AddInstanceForRegistry(address instance, address token, uint256 denomination); @@ -35,18 +37,19 @@ contract AddInstanceProposal { token = _token; uniswapPoolSwappingFee = _uniswapPoolSwappingFee; - require(_denominations.length == _protocolFees.length); + require(_denominations.length == _protocolFees.length, "denominations length != protocolFees length"); uint256 _numInstances = _denominations.length; - require(_numInstances > 0); - require(_numInstances < 4); + require(_numInstances > 0 && _numInstances <= 4, "incorrect instances number"); numInstances = _numInstances; denomination0 = _numInstances > 0 ? _denominations[0] : 0; denomination1 = _numInstances > 1 ? _denominations[1] : 0; denomination2 = _numInstances > 2 ? _denominations[2] : 0; + denomination3 = _numInstances > 3 ? _denominations[3] : 0; protocolFee0 = _numInstances > 0 ? _protocolFees[0] : 0; protocolFee1 = _numInstances > 1 ? _protocolFees[1] : 0; protocolFee2 = _numInstances > 2 ? _protocolFees[2] : 0; + protocolFee3 = _numInstances > 3 ? _protocolFees[3] : 0; } function executeProposal() external { @@ -76,6 +79,8 @@ contract AddInstanceProposal { return denomination1; } else if (_index == 2) { return denomination2; + } else if (_index == 3) { + return denomination3; } else { revert("Invalid instance index"); } @@ -88,6 +93,8 @@ contract AddInstanceProposal { return protocolFee1; } else if (_index == 2) { return protocolFee2; + } else if (_index == 3) { + return protocolFee3; } else { revert("Invalid instance index"); } diff --git a/contracts/InstanceFactory.sol b/contracts/InstanceFactory.sol index 4c4331a..f3e5a80 100644 --- a/contracts/InstanceFactory.sol +++ b/contracts/InstanceFactory.sol @@ -17,10 +17,8 @@ contract InstanceFactory is Ownable { address public hasher; uint32 public merkleTreeHeight; - event NewVerifierSet(address indexed newVerifier); - event NewHasherSet(address indexed newHasher); event NewTreeHeightSet(uint32 indexed newTreeHeight); - event NewImplementationSet(address indexed newImplemenentation); + event NewImplementationSet(address indexed newImplemenentation, address verifier, address hasher); event NewInstanceCloneCreated(address indexed clone); constructor( @@ -61,27 +59,15 @@ contract InstanceFactory is Ownable { return implementation.predictDeterministicAddress(salt); } - function setVerifier(address _verifier) external onlyOwner { - verifier = _verifier; - emit NewVerifierSet(verifier); - } - - function setHasher(address _hasher) external onlyOwner { - hasher = _hasher; - emit NewHasherSet(hasher); - } - function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyOwner { merkleTreeHeight = _merkleTreeHeight; emit NewTreeHeightSet(merkleTreeHeight); } - function setImplementation(address _newImplementation) external onlyOwner { - implementation = _newImplementation; - emit NewImplementationSet(implementation); - } - - function generateNewImplementation() external onlyOwner { - implementation = address(new ERC20TornadoCloneable(verifier, hasher)); + function generateNewImplementation(address _verifier, address _hasher) external onlyOwner { + verifier = _verifier; + hasher = _hasher; + implementation = address(new ERC20TornadoCloneable(_verifier, _hasher)); + emit NewImplementationSet(implementation, _verifier, _hasher); } } diff --git a/test/factory.test.js b/test/factory.test.js index c588ab8..0201c63 100644 --- a/test/factory.test.js +++ b/test/factory.test.js @@ -70,20 +70,18 @@ describe('Instance Factory Tests', () => { it('Governance should be able to set factory params', async function () { let { instanceFactory, owner } = await loadFixture(fixture) - await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted + await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted instanceFactory = await instanceFactory.connect(owner) - await instanceFactory.setVerifier(addressZero) - await instanceFactory.setHasher(addressZero) + await instanceFactory.generateNewImplementation(addressZero, addressZero) await instanceFactory.setMerkleTreeHeight(1) expect(await instanceFactory.verifier()).to.be.equal(addressZero) expect(await instanceFactory.hasher()).to.be.equal(addressZero) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1) - await instanceFactory.setVerifier(config.verifier) - await instanceFactory.setHasher(config.hasher) + await instanceFactory.generateNewImplementation(config.verifier, config.hasher) await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) expect(await instanceFactory.verifier()).to.be.equal(config.verifier) diff --git a/test/factory.with.registry.test.js b/test/factory.with.registry.test.js index 271c0ef..8927dc2 100644 --- a/test/factory.with.registry.test.js +++ b/test/factory.with.registry.test.js @@ -106,13 +106,12 @@ describe('Instance Factory With Registry Tests', () => { it('Governance should be able to set factory params', async function () { let { instanceFactory, gov } = await loadFixture(fixture) - await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted + await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted const govSigner = await getSignerFromAddress(gov.address) instanceFactory = await instanceFactory.connect(govSigner) - await instanceFactory.setVerifier(addressZero) - await instanceFactory.setHasher(addressZero) + await instanceFactory.generateNewImplementation(addressZero, addressZero) await instanceFactory.setMerkleTreeHeight(1) await instanceFactory.setCreationFee(0) @@ -121,8 +120,7 @@ describe('Instance Factory With Registry Tests', () => { expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1) expect(await instanceFactory.creationFee()).to.be.equal(0) - await instanceFactory.setVerifier(config.verifier) - await instanceFactory.setHasher(config.hasher) + await instanceFactory.generateNewImplementation(config.verifier, config.hasher) await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) await instanceFactory.setCreationFee(config.creationFee) @@ -221,19 +219,22 @@ describe('Instance Factory With Registry Tests', () => { it('Should successfully deploy/propose/execute proposal - add instances', async function () { let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture) + const denominations = [ + ethers.utils.parseEther('1'), + ethers.utils.parseEther('10'), + ethers.utils.parseEther('100'), + ethers.utils.parseEther('1000'), + ] + const numInstances = denominations.length + + const protocolFees = [30, 30, 30, 30] + // deploy proposal ---------------------------------------------- await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) await tornToken.approve(instanceFactory.address, config.creationFee) await expect(() => - instanceFactory - .connect(sender) - .createProposalApprove( - config.COMP, - 3000, - [ethers.utils.parseEther('100'), ethers.utils.parseEther('1000')], - [30, 30], - ), + instanceFactory.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees), ).to.changeTokenBalances( tornToken, [sender, gov], @@ -250,11 +251,11 @@ describe('Instance Factory With Registry Tests', () => { expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address) expect(await proposal.token()).to.be.equal(config.COMP) expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000) - expect(await proposal.numInstances()).to.be.equal(2) - expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30) - expect(await proposal.protocolFeeByIndex(1)).to.be.equal(30) - expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100')) - expect(await proposal.denominationByIndex(1)).to.be.equal(ethers.utils.parseEther('1000')) + expect(await proposal.numInstances()).to.be.equal(numInstances) + for (let i = 0; i < numInstances; i++) { + expect(await proposal.protocolFeeByIndex(i)).to.be.equal(protocolFees[i]) + expect(await proposal.denominationByIndex(i)).to.be.equal(denominations[i]) + } // propose proposal --------------------------------------------- let response, id, state @@ -294,37 +295,23 @@ describe('Instance Factory With Registry Tests', () => { // check instances initialization ------------------------------- logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - let instanceAddr = '0x' + logs[logs.length - 2].topics[1].slice(-40) - let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) + for (let i = 0; i < numInstances; i++) { + let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40) + let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) - expect(await instance.token()).to.be.equal(config.COMP) - expect(await instance.verifier()).to.be.equal(config.verifier) - expect(await instance.hasher()).to.be.equal(config.hasher) - expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) - expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100')) + expect(await instance.token()).to.be.equal(config.COMP) + expect(await instance.verifier()).to.be.equal(config.verifier) + expect(await instance.hasher()).to.be.equal(config.hasher) + expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) + expect(await instance.denomination()).to.equal(denominations[i]) - let instanceData = await instanceRegistry.instances(instance.address) - expect(instanceData.isERC20).to.be.equal(true) - expect(instanceData.token).to.be.equal(config.COMP) - expect(instanceData.state).to.be.equal(1) - expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) - expect(instanceData.protocolFeePercentage).to.be.equal(30) - - instanceAddr = '0x' + logs[logs.length - 1].topics[1].slice(-40) - instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr) - - expect(await instance.token()).to.be.equal(config.COMP) - expect(await instance.verifier()).to.be.equal(config.verifier) - expect(await instance.hasher()).to.be.equal(config.hasher) - expect(await instance.levels()).to.be.equal(config.merkleTreeHeight) - expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000')) - - instanceData = await instanceRegistry.instances(instance.address) - expect(instanceData.isERC20).to.be.equal(true) - expect(instanceData.token).to.be.equal(config.COMP) - expect(instanceData.state).to.be.equal(1) - expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) - expect(instanceData.protocolFeePercentage).to.be.equal(30) + let instanceData = await instanceRegistry.instances(instance.address) + expect(instanceData.isERC20).to.be.equal(true) + expect(instanceData.token).to.be.equal(config.COMP) + expect(instanceData.state).to.be.equal(1) + expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000) + expect(instanceData.protocolFeePercentage).to.be.equal(protocolFees[i]) + } }) it('Should successfully deploy proposal with permit', async function () { From 9bfbc01d8ebbb569c3f7c39167f079845cafee1e Mon Sep 17 00:00:00 2001 From: Drygin Date: Tue, 15 Mar 2022 20:55:01 +0300 Subject: [PATCH 18/18] add uniswap pool check --- README.md | 5 ++-- config.js | 3 +++ contracts/AddInstanceProposal.sol | 2 +- contracts/InstanceFactoryWithRegistry.sol | 33 +++++++++++++++++++++-- src/generateAddresses.js | 3 +++ test/factory.with.registry.test.js | 27 +++++++++++++++++++ 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 69b7584..0f7c102 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b 1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 4 instances at once. 2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`. +3. `TWAPSlotsMin` - minimum number of TWAP slots for Uniswap pool that is chosen during `createProposalApprove/createProposalPermit` factory method call. It can be changed by governance. Default value is stored in `config.js`. ## Warnings @@ -60,8 +61,8 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be: -1. `InstanceFactory` - `0x9A04e3F1091A69CB53D163abE7ad9bbc86C23823` -1. `InstanceFactoryWithRegistry` - `0xee994E045B9Ec5a37f3f85d34f9fD087A0c69236` +1. `InstanceFactory` - `0x09110e04d5AEF747bcf7A3585D8FFC892Ab9D1Cf` +2. `InstanceFactoryWithRegistry` - `0xC01D57d83E9Fe35E0Fb900F9D384EFcA679DF4bD` Check addresses with current config: diff --git a/config.js b/config.js index 5c9c57e..58fed34 100644 --- a/config.js +++ b/config.js @@ -15,4 +15,7 @@ module.exports = { creationFee: '200000000000000000000', // 200 TORN deployGasLimit: 7000000, owner: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', + WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', + TWAPSlotsMin: 50, } diff --git a/contracts/AddInstanceProposal.sol b/contracts/AddInstanceProposal.sol index 876f4f0..96cf6de 100644 --- a/contracts/AddInstanceProposal.sol +++ b/contracts/AddInstanceProposal.sol @@ -37,7 +37,7 @@ contract AddInstanceProposal { token = _token; uniswapPoolSwappingFee = _uniswapPoolSwappingFee; - require(_denominations.length == _protocolFees.length, "denominations length != protocolFees length"); + require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); uint256 _numInstances = _denominations.length; require(_numInstances > 0 && _numInstances <= 4, "incorrect instances number"); numInstances = _numInstances; diff --git a/contracts/InstanceFactoryWithRegistry.sol b/contracts/InstanceFactoryWithRegistry.sol index 9046df7..c9e2b9e 100644 --- a/contracts/InstanceFactoryWithRegistry.sol +++ b/contracts/InstanceFactoryWithRegistry.sol @@ -8,6 +8,8 @@ import "./AddInstanceProposal.sol"; import "./InstanceFactory.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol"; +import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; +import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol"; contract InstanceFactoryWithRegistry is InstanceFactory { using Address for address; @@ -15,9 +17,13 @@ contract InstanceFactoryWithRegistry is InstanceFactory { address public immutable governance; address public immutable torn; address public immutable instanceRegistry; + IUniswapV3Factory public immutable UniswapV3Factory; + address public immutable WETH; + uint16 public TWAPSlotsMin; uint256 public creationFee; - event NewCreationFeeSet(uint256 indexed newCreationFee); + event NewCreationFeeSet(uint256 newCreationFee); + event NewTWAPSlotsMinSet(uint256 newTWAPSlotsMin); event NewGovernanceProposalCreated(address indexed proposal); /** @@ -35,11 +41,17 @@ contract InstanceFactoryWithRegistry is InstanceFactory { address _governance, address _instanceRegistry, address _torn, + address _UniswapV3Factory, + address _WETH, + uint16 _TWAPSlotsMin, uint256 _creationFee ) InstanceFactory(_verifier, _hasher, _merkleTreeHeight, _governance) { governance = _governance; instanceRegistry = _instanceRegistry; torn = _torn; + UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory); + WETH = _WETH; + TWAPSlotsMin = _TWAPSlotsMin; creationFee = _creationFee; } @@ -103,10 +115,22 @@ contract InstanceFactoryWithRegistry is InstanceFactory { uint32[] memory _protocolFees ) internal returns (address) { require(_token.isContract(), "Token is not contract"); - require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); require(_denominations.length > 0, "Empty denominations"); require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); + // check Uniswap Pool + for (uint8 i = 0; i < _protocolFees.length; i++) { + if (_protocolFees[i] > 0) { + // pool exists + address poolAddr = UniswapV3Factory.getPool(_token, WETH, _uniswapPoolSwappingFee); + require(poolAddr != address(0), "Uniswap pool is not exist"); + // TWAP slots + (, , , , uint16 observationCardinalityNext, , ) = IUniswapV3PoolState(poolAddr).slot0(); + require(observationCardinalityNext >= TWAPSlotsMin, "Uniswap pool TWAP slots number is low"); + break; + } + } + address proposal = address( new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees) ); @@ -119,4 +143,9 @@ contract InstanceFactoryWithRegistry is InstanceFactory { creationFee = _creationFee; emit NewCreationFeeSet(_creationFee); } + + function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance { + TWAPSlotsMin = _TWAPSlotsMin; + emit NewTWAPSlotsMinSet(_TWAPSlotsMin); + } } diff --git a/src/generateAddresses.js b/src/generateAddresses.js index 50ece99..c6a57c7 100644 --- a/src/generateAddresses.js +++ b/src/generateAddresses.js @@ -26,6 +26,9 @@ async function generate(config = defaultConfig) { config.governance, config.instanceRegistry, config.TORN, + config.UniswapV3Factory, + config.WETH, + config.TWAPSlotsMin, config.creationFee, ]) .slice(2) diff --git a/test/factory.with.registry.test.js b/test/factory.with.registry.test.js index 8927dc2..6fce033 100644 --- a/test/factory.with.registry.test.js +++ b/test/factory.with.registry.test.js @@ -101,6 +101,9 @@ describe('Instance Factory With Registry Tests', () => { expect(await instanceFactory.implementation()).to.exist expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) expect(await instanceFactory.torn()).to.be.equal(config.TORN) + expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin) + expect(await instanceFactory.WETH()).to.be.equal(config.WETH) + expect(await instanceFactory.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory) }) it('Governance should be able to set factory params', async function () { @@ -114,20 +117,24 @@ describe('Instance Factory With Registry Tests', () => { await instanceFactory.generateNewImplementation(addressZero, addressZero) await instanceFactory.setMerkleTreeHeight(1) await instanceFactory.setCreationFee(0) + await instanceFactory.setTWAPSlotsMin(0) expect(await instanceFactory.verifier()).to.be.equal(addressZero) expect(await instanceFactory.hasher()).to.be.equal(addressZero) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1) expect(await instanceFactory.creationFee()).to.be.equal(0) + expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(0) await instanceFactory.generateNewImplementation(config.verifier, config.hasher) await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) await instanceFactory.setCreationFee(config.creationFee) + await instanceFactory.setTWAPSlotsMin(config.TWAPSlotsMin) expect(await instanceFactory.verifier()).to.be.equal(config.verifier) expect(await instanceFactory.hasher()).to.be.equal(config.hasher) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) + expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin) }) it('Should successfully deploy/propose/execute proposal - add instance', async function () { @@ -466,4 +473,24 @@ describe('Instance Factory With Registry Tests', () => { [BigNumber.from(0).sub(value), value], ) }) + + it('Should not deploy proposal with incorrect Uniswap pool', async function () { + let { sender, instanceFactory, tornWhale, tornToken } = await loadFixture(fixture) + + // deploy proposal ---------------------------------------------- + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) + + await expect( + instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]), + ).to.be.revertedWith('Uniswap pool is not exist') + + await expect( + instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]), + ).to.be.revertedWith('Uniswap pool TWAP slots number is low') + }) })