From a8495d76a4a6535042aae9acce2f0e1e677aa4db Mon Sep 17 00:00:00 2001 From: T-Hax <> Date: Thu, 13 Apr 2023 20:03:12 +0000 Subject: [PATCH] start writing deployment script, work on reintroducing tests Signed-off-by: T-Hax <> --- .env.example | 11 +- config.js | 2 +- contracts/AddInstanceProposal.sol | 102 --- contracts/InstanceAdditionProposal.sol | 6 + contracts/InstanceProposalCreator.sol | 157 ----- contracts/InstanceProposalFactory.sol | 4 +- hardhat.config.js | 40 +- scripts/deploy.js | 83 +++ src/generateAddresses.js | 114 --- src/permit.js | 44 -- test/factory.with.registry.test.js | 883 ------------------------ test/sidechain.instance.factory.test.js | 296 -------- test/utils.js | 43 -- 13 files changed, 125 insertions(+), 1660 deletions(-) delete mode 100644 contracts/AddInstanceProposal.sol delete mode 100644 contracts/InstanceProposalCreator.sol create mode 100644 scripts/deploy.js delete mode 100644 src/generateAddresses.js delete mode 100644 src/permit.js delete mode 100644 test/factory.with.registry.test.js delete mode 100644 test/sidechain.instance.factory.test.js delete mode 100644 test/utils.js diff --git a/.env.example b/.env.example index fdd1984..2118e2e 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ -ETHERSCAN_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 -ALCHEMY_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 -PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 -INFURA_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 -FORKNET_RPC_URL=https://link-to.fork +ETHERSCAN_KEY= +PRIVATE_KEY= +MAINNET_RPC_URL= +RINKEBY_RPC_URL= +GOERLI_RPC_URL= +SEPOLIA_RPC_URL= \ No newline at end of file diff --git a/config.js b/config.js index 3b274e4..1a35b18 100644 --- a/config.js +++ b/config.js @@ -17,5 +17,5 @@ module.exports = { admin: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', // TODO WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', - TWAPSlotsMin: 50, + TWAPSlotsMin: 80, } diff --git a/contracts/AddInstanceProposal.sol b/contracts/AddInstanceProposal.sol deleted file mode 100644 index 3f72665..0000000 --- a/contracts/AddInstanceProposal.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.7.6; -pragma abicoder v2; - -import "./interfaces/IInstanceRegistry.sol"; -import "./interfaces/IInstanceFactory.sol"; - -contract AddInstanceProposal { - IInstanceFactory public immutable instanceFactory; - IInstanceRegistry public immutable instanceRegistry; - uint24 public immutable uniswapPoolSwappingFee; - address public immutable token; - - uint256 public immutable numInstances; - 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); - - constructor( - address _instanceFactory, - address _instanceRegistry, - address _token, - uint24 _uniswapPoolSwappingFee, - uint256[] memory _denominations, - uint32[] memory _protocolFees - ) { - instanceFactory = IInstanceFactory(_instanceFactory); - instanceRegistry = IInstanceRegistry(_instanceRegistry); - token = _token; - uniswapPoolSwappingFee = _uniswapPoolSwappingFee; - - require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); - uint256 _numInstances = _denominations.length; - 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 { - for (uint256 i = 0; i < numInstances; i++) { - address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token); - - IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance( - token != address(0), - 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 if (_index == 3) { - return denomination3; - } 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 == 3) { - return protocolFee3; - } else { - revert("Invalid instance index"); - } - } -} diff --git a/contracts/InstanceAdditionProposal.sol b/contracts/InstanceAdditionProposal.sol index 0c6d756..18ef464 100644 --- a/contracts/InstanceAdditionProposal.sol +++ b/contracts/InstanceAdditionProposal.sol @@ -40,12 +40,16 @@ contract InstanceAdditionProposal { uint256 howMany = toAdd.length; for (uint256 i = 0; i < howMany; i++) { + // Read out the data for the new instance InstanceAdditionData memory data = toAdd[i]; + // Safely calculate the denomination uint256 denomination = uint256(data.smallDenomination).mul(10 ** uint256(data.base10Exponent)); + // Create the instance address instance = instanceFactory.createInstanceClone(denomination, data.tokenAddress); + // Prepare the data for the registry. IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance( data.tokenAddress != address(0), IERC20(data.tokenAddress), @@ -54,8 +58,10 @@ contract InstanceAdditionProposal { data.protocolFee ); + // Convert the data. IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData); + // Update the instance data in the registry. instanceRegistry.updateInstance(tornadoForUpdate); emit AddInstanceForRegistry(address(instance), data.tokenAddress, denomination); diff --git a/contracts/InstanceProposalCreator.sol b/contracts/InstanceProposalCreator.sol deleted file mode 100644 index 9ff39d3..0000000 --- a/contracts/InstanceProposalCreator.sol +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.7.6; -pragma abicoder v2; - -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; -import "./AddInstanceProposal.sol"; -import "./interfaces/IInstanceFactory.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"; -import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol"; - -contract InstanceProposalCreator is Initializable { - using Address for address; - - address public immutable governance; - address public immutable torn; - IInstanceFactory public immutable instanceFactory; - address public immutable instanceRegistry; - IUniswapV3Factory public immutable UniswapV3Factory; - address public immutable WETH; - uint16 public TWAPSlotsMin; - uint256 public creationFee; - - event NewCreationFeeSet(uint256 newCreationFee); - event NewTWAPSlotsMinSet(uint256 newTWAPSlotsMin); - event NewGovernanceProposalCreated(address indexed proposal); - - /** - * @dev Throws if called by any account other than the Governance. - */ - modifier onlyGovernance() { - require(governance == msg.sender, "IF: caller is not the Governance"); - _; - } - - constructor( - address _governance, - address _instanceFactory, - address _instanceRegistry, - address _torn, - address _UniswapV3Factory, - address _WETH - ) { - governance = _governance; - instanceFactory = IInstanceFactory(_instanceFactory); - instanceRegistry = _instanceRegistry; - torn = _torn; - UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory); - WETH = _WETH; - } - - /** - * @notice initialize function for upgradeability - * @dev this contract will be deployed behind a proxy and should not assign values at logic address, - * params left out because self explainable - * */ - function initialize(uint16 _TWAPSlotsMin, uint256 _creationFee) external initializer { - TWAPSlotsMin = _TWAPSlotsMin; - creationFee = _creationFee; - } - - /** - * @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 == address(0) || _token.isContract(), "Token is not contract"); - 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++) { - require(_protocolFees[i] <= 10000, "Protocol fee is more than 100%"); - if (_protocolFees[i] > 0 && _token != address(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(instanceFactory), - instanceRegistry, - _token, - _uniswapPoolSwappingFee, - _denominations, - _protocolFees - ) - ); - emit NewGovernanceProposalCreated(proposal); - - return proposal; - } - - function setCreationFee(uint256 _creationFee) external onlyGovernance { - creationFee = _creationFee; - emit NewCreationFeeSet(_creationFee); - } - - function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance { - TWAPSlotsMin = _TWAPSlotsMin; - emit NewTWAPSlotsMinSet(_TWAPSlotsMin); - } -} diff --git a/contracts/InstanceProposalFactory.sol b/contracts/InstanceProposalFactory.sol index 72bb0df..2a796db 100644 --- a/contracts/InstanceProposalFactory.sol +++ b/contracts/InstanceProposalFactory.sol @@ -105,7 +105,7 @@ contract InstanceProposalFactory { uint24 swappingFee = _uniswapPoolSwappingFees[t]; - // 3️⃣ Fails if the token is not a contract or if there is no pool. + // 3️⃣ If not eth the token is a contract, the swapping fee must give a good pool, the cardinality must be good. if (token != address(0)) { require(token.isContract(), "Token must be a contract if not addr(0)"); @@ -119,7 +119,7 @@ contract InstanceProposalFactory { require(minObservationCardinality <= observationCardinalityNext, "Uniswap Pool observation cardinality is low."); } - // 4️⃣ Denominations, div exponent and protocol fees must have an element at t(oken). + // 4️⃣ Denominations, div exponent and protocol fees must have an element at t(oken). The exponent must be good. uint256[] memory denominations = _denominations[t]; diff --git a/hardhat.config.js b/hardhat.config.js index cda536f..fa0085a 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -43,8 +43,9 @@ module.exports = { networks: { hardhat: { forking: { - url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, + url: process.env.MAINNET_RPC_URL, blockNumber: 14250000, + httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' }, }, chainId: 1, initialBaseFeePerGas: 5, @@ -53,33 +54,46 @@ module.exports = { blockGasLimit: 50000000, }, rinkeby: { - url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, + url: process.env.RINKEBY_RPC_URL, accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : { mnemonic: 'test test test test test junk' }, + httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' }, }, goerli: { - url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`, + url: process.env.GOERLI_RPC_URL, accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : { mnemonic: 'test test test test test junk' }, + httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' }, + }, + sepolia: { + url: process.env.SEPOLIA_RPC_URL, + accounts: process.env.PRIVATE_KEY + ? [process.env.PRIVATE_KEY] + : { mnemonic: 'test test test test test junk' }, + httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' }, }, mainnet: { - url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, + url: process.env.MAINNET_RPC_URL, accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : { mnemonic: 'test test test test test junk' }, + httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' }, }, - ...(typeof process.env.FORKNET_RPC_URL === 'string' && { - forknet: { - url: process.env.FORKNET_RPC_URL, - accounts: process.env.PRIVATE_KEY - ? [process.env.PRIVATE_KEY] - : { mnemonic: 'test test test test test junk' }, - }, - }), + // What the fuck is this + // ...(typeof process.env.FORKNET_RPC_URL === 'string' && { + // forknet: { + // url: process.env.FORKNET_RPC_URL, + // accounts: process.env.PRIVATE_KEY + // ? [process.env.PRIVATE_KEY] + // : { mnemonic: 'test test test test test junk' }, + // }, + // }), + }, + mocha: { + timeout: 9999999999 /* The following simply does not work: ignore: ["factory.with.registry.test.js", "sidechain.instance.factory.test.js"] */, }, - mocha: { timeout: 9999999999 }, spdxLicenseIdentifier: { overwrite: true, runOnCompile: true, diff --git a/scripts/deploy.js b/scripts/deploy.js new file mode 100644 index 0000000..b2d3d5b --- /dev/null +++ b/scripts/deploy.js @@ -0,0 +1,83 @@ +require('dotenv').config() + +const { ethers } = require('hardhat') +const config = require('../config') +const { createInterface } = require('readline') + +function idToNetwork(id) { + switch (id) { + case 1: + return 'Mainnet' + case 3: + return 'Ropsten' + case 4: + return 'Rinkeby' + case 5: + return 'Goerli' + case 11155111: + return 'Sepolia' + default: + throw Error('\nChain id could not be recognized. What network are you using?\n') + } +} + +const prompter = createInterface({ input: process.stdin, output: process.stdout }) + +function _prompt(prompt, resolve) { + prompter.question(prompt, (answer) => { + if (answer == 'y') { + userInput.close() + resolve(true) + } else if (answer == 'n') { + userInput.close() + resolve(false) + } else wipeCachePrompt('', resolve) + }) +} + +function prompt(prompt) { + return new Promise((resolve) => _prompt(prompt, resolve)) +} + +function happyDeployedMessage(name, chainId, address) { + return `\n${name} successfully deployed on ${idToNetwork(chainId)} at ${address} 🥳\n` +} + +function happyVerifiedMessage(name) { + return `\n${name} successfully verified on Etherscan!` +} + +const promptMessageBase = (middle) => `\n${middle}\n\nAre you sure you would like to continue? 🧐 (y/n): ` + +async function main() { + const minFacFac = await ethers.getContractFactory('MinimalInstanceFactory') + const proposalFacFac = await ethers.getContractFactory('InstanceProposalFactory') + + const minFac = await minFacFac.deploy(config.verifier, config.hasher, config.merkleTreeHeight) + + console.log(happyDeployedMessage('MinimalInstanceFactory', minFac.address)) + + if (await prompt(promptMessageBase('Continuing to InstanceProposalFactory deployment.'))) { + const proposalFac = await proposalFacFac.deploy( + config.governance, + minFac.address, + config.instanceRegistry, + config.UniswapV3Factory, + config.WETH, + config.TWAPSlotsMin, + ) + + console.log(happyDeployedMessage('InstanceProposalFactory', proposalFac.address)) + } else { + return '\nDecided to stop at InstanceProposalFactory deployment.\n' + } + + if (await prompt(promptMessageBase('Continuing to contract verification.'))) { + } else { + return '\nDecided to stop at contract verification.\n' + } +} + +main().then((res) => { + console.log(res ?? '\nScript succesfully finished.') +}) diff --git a/src/generateAddresses.js b/src/generateAddresses.js deleted file mode 100644 index ce5872a..0000000 --- a/src/generateAddresses.js +++ /dev/null @@ -1,114 +0,0 @@ -const { ethers } = require('hardhat') -const defaultConfig = require('../config') - -async function upgradableContract({ contractName, implConstructorArgs, proxyConstructorArgs, salt }) { - const Implementation = await ethers.getContractFactory(contractName) - - const implementationBytecode = - Implementation.bytecode + Implementation.interface.encodeDeploy(implConstructorArgs).slice(2) - - const implementationAddress = ethers.utils.getCreate2Address( - defaultConfig.singletonFactory, - salt, - ethers.utils.keccak256(implementationBytecode), - ) - - const AdminUpgradeableProxy = await ethers.getContractFactory('AdminUpgradeableProxy') - const proxyConst = [implementationAddress, ...proxyConstructorArgs] - const proxyBytecode = - AdminUpgradeableProxy.bytecode + AdminUpgradeableProxy.interface.encodeDeploy(proxyConst).slice(2) - - const proxyAddress = ethers.utils.getCreate2Address( - defaultConfig.singletonFactory, - salt, - ethers.utils.keccak256(proxyBytecode), - ) - - return { - implementation: { - address: implementationAddress, - bytecode: implementationBytecode, - args: implConstructorArgs, - }, - proxy: { address: proxyAddress, bytecode: proxyBytecode, args: proxyConst }, - isProxy: true, - } -} - -async function generate(config = defaultConfig) { - // sidechain factory contract ------------------------------------- - const SidechainFactory = await ethers.getContractFactory('SidechainInstanceFactory') - const SidechainFactoryInitData = SidechainFactory.interface.encodeFunctionData('initialize', [ - config.verifier, - config.hasher, - config.merkleTreeHeight, - config.admin, - ]) - - const sidechainFactory = await upgradableContract({ - contractName: 'SidechainInstanceFactory', - implConstructorArgs: [], - proxyConstructorArgs: [config.admin, SidechainFactoryInitData], - salt: config.salt, - }) - - // factory with registry contract --------------------------------- - const Factory = await ethers.getContractFactory('InstanceFactory') - const FactoryInitData = Factory.interface.encodeFunctionData('initialize', [ - config.verifier, - config.hasher, - config.merkleTreeHeight, - config.governance, - ]) - - const factory = await upgradableContract({ - contractName: 'InstanceFactory', - implConstructorArgs: [], - proxyConstructorArgs: [config.governance, FactoryInitData], - salt: config.salt, - }) - - const ProposalCreator = await ethers.getContractFactory('InstanceProposalCreator') - const ProposalCreatorInitData = ProposalCreator.interface.encodeFunctionData('initialize', [ - config.TWAPSlotsMin, - config.creationFee, - ]) - - const proposalCreator = await upgradableContract({ - contractName: 'InstanceProposalCreator', - implConstructorArgs: [ - config.governance, - factory.proxy.address, - config.instanceRegistry, - config.TORN, - config.UniswapV3Factory, - config.WETH, - ], - proxyConstructorArgs: [config.governance, ProposalCreatorInitData], - salt: config.salt, - }) - - const result = { - sidechainFactory, - factory, - proposalCreator, - } - - return result -} - -async function generateWithLog() { - const contracts = await generate() - console.log('SidechainInstanceFactory contract: ', contracts.sidechainFactory.implementation.address) - console.log('SidechainInstanceFactory proxy contract: ', contracts.sidechainFactory.proxy.address) - console.log('Instance factory contract: ', contracts.factory.implementation.address) - console.log('Instance factory proxy contract: ', contracts.factory.proxy.address) - console.log('Proposal creator contract: ', contracts.proposalCreator.implementation.address) - console.log('Proposal creator proxy contract: ', contracts.proposalCreator.proxy.address) - return contracts -} - -module.exports = { - generate, - generateWithLog, -} diff --git a/src/permit.js b/src/permit.js deleted file mode 100644 index 36ce381..0000000 --- a/src/permit.js +++ /dev/null @@ -1,44 +0,0 @@ -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/factory.with.registry.test.js b/test/factory.with.registry.test.js deleted file mode 100644 index ac8bbcc..0000000 --- a/test/factory.with.registry.test.js +++ /dev/null @@ -1,883 +0,0 @@ -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 InstanceProposalCreator with CREATE2 - const singletonFactory = await ethers.getContractAt( - 'SingletonFactory', - config.singletonFactoryVerboseWrapper, - ) - const contracts = await generate() - if ((await ethers.provider.getCode(contracts.factory.implementation.address)) == '0x') { - await singletonFactory.deploy(contracts.factory.implementation.bytecode, config.salt, { - gasLimit: config.deployGasLimit, - }) - } - if ((await ethers.provider.getCode(contracts.factory.proxy.address)) == '0x') { - await singletonFactory.deploy(contracts.factory.proxy.bytecode, config.salt, { - gasLimit: config.deployGasLimit, - }) - } - const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factory.proxy.address) - - if ((await ethers.provider.getCode(contracts.proposalCreator.implementation.address)) == '0x') { - await singletonFactory.deploy(contracts.proposalCreator.implementation.bytecode, config.salt, { - gasLimit: config.deployGasLimit, - }) - } - if ((await ethers.provider.getCode(contracts.proposalCreator.proxy.address)) == '0x') { - await singletonFactory.deploy(contracts.proposalCreator.proxy.bytecode, config.salt, { - gasLimit: config.deployGasLimit, - }) - } - const proposalCreator = await ethers.getContractAt( - 'InstanceProposalCreator', - contracts.proposalCreator.proxy.address, - ) - - return { - sender, - deployer, - multisig, - tornWhale, - compWhale, - router, - gov, - tornToken, - compToken, - instanceRegistry, - instanceFactory, - proposalCreator, - } - } - - it('Should have initialized all successfully', async function () { - const { sender, gov, tornToken, instanceRegistry, instanceFactory, proposalCreator } = 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 - expect(proposalCreator.address).to.exist - }) - - it('Should set correct params for factory', async function () { - const { instanceFactory, proposalCreator } = await loadFixture(fixture) - - expect(await proposalCreator.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.ERC20Impl()).to.exist - expect(await instanceFactory.nativeCurImpl()).to.exist - expect(await proposalCreator.creationFee()).to.be.equal(config.creationFee) - expect(await proposalCreator.torn()).to.be.equal(config.TORN) - expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin) - expect(await proposalCreator.WETH()).to.be.equal(config.WETH) - expect(await proposalCreator.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory) - }) - - it('Governance should be able to set factory/proposalCreator params', async function () { - let { instanceFactory, proposalCreator, gov } = await loadFixture(fixture) - - await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted - - const govSigner = await getSignerFromAddress(gov.address) - instanceFactory = await instanceFactory.connect(govSigner) - proposalCreator = await proposalCreator.connect(govSigner) - - await instanceFactory.generateNewImplementation(addressZero, addressZero) - await instanceFactory.setMerkleTreeHeight(1) - await proposalCreator.setCreationFee(0) - await proposalCreator.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 proposalCreator.creationFee()).to.be.equal(0) - expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(0) - - await instanceFactory.generateNewImplementation(config.verifier, config.hasher) - await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) - await proposalCreator.setCreationFee(config.creationFee) - await proposalCreator.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 proposalCreator.creationFee()).to.be.equal(config.creationFee) - expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin) - }) - - it('Should successfully deploy/propose/execute proposal - add instance', async function () { - let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = - await loadFixture(fixture) - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect(() => - proposalCreator - .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 proposalCreator.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, proposalCreator, 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(proposalCreator.address, config.creationFee) - - await expect(() => - proposalCreator.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees), - ).to.changeTokenBalances( - tornToken, - [sender, gov], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], - ) - - let logs = await proposalCreator.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(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 - 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') - 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(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(protocolFees[i]) - } - }) - - it('Should successfully deploy proposal with permit', async function () { - let { instanceFactory, proposalCreator, 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: proposalCreator.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(() => - proposalCreator.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 proposalCreator.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, proposalCreator, gov, tornWhale, tornToken, router, compToken, compWhale } = - await loadFixture(fixture) - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect(() => - proposalCreator - .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 proposalCreator.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], - ) - }) - - it('Should not deploy proposal with incorrect Uniswap pool', async function () { - let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture) - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect( - proposalCreator - .connect(sender) - .createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]), - ).to.be.revertedWith('Uniswap pool is not exist') - - await expect( - proposalCreator - .connect(sender) - .createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]), - ).to.be.revertedWith('Uniswap pool TWAP slots number is low') - }) - - it('Should not deploy proposal with incorrect protocol fee', async function () { - let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture) - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect( - proposalCreator - .connect(sender) - .createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [10300]), - ).to.be.revertedWith('Protocol fee is more than 100%') - }) - - it('Should successfully deploy/propose/execute proposal - add native instance', async function () { - let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = - await loadFixture(fixture) - - const denomination = ethers.utils.parseEther('1.5') - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect(() => - proposalCreator.connect(sender).createProposalApprove(addressZero, 0, [denomination], [30]), - ).to.changeTokenBalances( - tornToken, - [sender, gov], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], - ) - - let logs = await proposalCreator.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(addressZero) - expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0) - 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(denomination) - - // 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, 'ETH 1.5 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('ETH 1.5 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('ETHTornadoCloneable', instanceAddr) - - 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(denomination) - - const instanceData = await instanceRegistry.instances(instance.address) - expect(instanceData.isERC20).to.be.equal(false) - expect(instanceData.token).to.be.equal(addressZero) - expect(instanceData.state).to.be.equal(1) - expect(instanceData.uniswapPoolSwappingFee).to.be.equal(0) - expect(instanceData.protocolFeePercentage).to.be.equal(30) - }) - - it('Should successfully deploy/propose/execute proposal - add native instances', async function () { - let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = - await loadFixture(fixture) - - const denominations = [ - ethers.utils.parseEther('1.5'), - ethers.utils.parseEther('10.5'), - ethers.utils.parseEther('100.5'), - ethers.utils.parseEther('1000.5'), - ] - const numInstances = denominations.length - - const protocolFees = [30, 30, 30, 30] - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect(() => - proposalCreator.connect(sender).createProposalApprove(addressZero, 0, denominations, protocolFees), - ).to.changeTokenBalances( - tornToken, - [sender, gov], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], - ) - - let logs = await proposalCreator.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(addressZero) - expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0) - 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 - 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, 'ETH 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('ETH 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') - for (let i = 0; i < numInstances; i++) { - let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40) - let instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr) - - 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(false) - expect(instanceData.token).to.be.equal(addressZero) - expect(instanceData.state).to.be.equal(1) - expect(instanceData.uniswapPoolSwappingFee).to.be.equal(0) - expect(instanceData.protocolFeePercentage).to.be.equal(protocolFees[i]) - } - }) - - it('Should successfully deploy proposal with permit for native', async function () { - let { instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture( - fixture, - ) - - const denomination = ethers.utils.parseEther('1.5') - - 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: proposalCreator.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(() => - proposalCreator.createProposalPermit( - addressZero, - 0, - [denomination], - [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 proposalCreator.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(addressZero) - expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0) - 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(denomination) - }) - - it('Should deposit and withdraw into the new native instance', async function () { - let { sender, proposalCreator, gov, tornWhale, tornToken, router } = await loadFixture(fixture) - - const denomination = ethers.utils.parseEther('1.5') - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect(() => - proposalCreator.connect(sender).createProposalApprove(addressZero, 0, [denomination], [30]), - ).to.changeTokenBalances( - tornToken, - [sender, gov], - [BigNumber.from(0).sub(config.creationFee), config.creationFee], - ) - - let logs = await proposalCreator.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, 'ETH 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('ETHTornadoCloneable', instanceAddr) - - // check instance work ------------------------------------------ - const depo = createDeposit({ - nullifier: rbigint(31), - secret: rbigint(31), - }) - - await expect(() => - router.deposit(instance.address, toHex(depo.commitment), [], { value: denomination }), - ).to.changeEtherBalances([sender, instance], [BigNumber.from(0).sub(denomination), denomination]) - - 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.changeEtherBalances( - [instance, sender], - [BigNumber.from(0).sub(denomination), denomination], - ) - }) - - it('Should not deploy native currency proposal with incorrect protocol fee', async function () { - let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture) - - // deploy proposal ---------------------------------------------- - await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) - await tornToken.approve(proposalCreator.address, config.creationFee) - - await expect( - proposalCreator - .connect(sender) - .createProposalApprove(addressZero, 0, [ethers.utils.parseEther('1.5')], [10300]), - ).to.be.revertedWith('Protocol fee is more than 100%') - }) -}) diff --git a/test/sidechain.instance.factory.test.js b/test/sidechain.instance.factory.test.js deleted file mode 100644 index 1a7f37a..0000000 --- a/test/sidechain.instance.factory.test.js +++ /dev/null @@ -1,296 +0,0 @@ -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 } = require('./utils') -const { generate } = require('../src/generateAddresses') -const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli') - -describe('Sidechain Instance Factory Tests', () => { - const addressZero = ethers.constants.AddressZero - - async function fixture() { - const [sender, deployer] = await ethers.getSigners() - - const owner = await getSignerFromAddress(config.admin) - - await sender.sendTransaction({ - to: config.admin, - value: ethers.utils.parseEther('1'), - }) - - const compWhale = await getSignerFromAddress(config.compWhale) - - const compToken = await ethers.getContractAt( - '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', - config.COMP, - ) - - // deploy InstanceFactory with CREATE2 - const singletonFactory = await ethers.getContractAt( - 'SingletonFactory', - config.singletonFactoryVerboseWrapper, - ) - const contracts = await generate() - if ((await ethers.provider.getCode(contracts.sidechainFactory.implementation.address)) == '0x') { - await singletonFactory.deploy(contracts.sidechainFactory.implementation.bytecode, config.salt, { - gasLimit: config.deployGasLimit, - }) - } - if ((await ethers.provider.getCode(contracts.sidechainFactory.proxy.address)) == '0x') { - await singletonFactory.deploy(contracts.sidechainFactory.proxy.bytecode, config.salt, { - gasLimit: config.deployGasLimit, - }) - } - const instanceFactory = await ethers.getContractAt( - 'SidechainInstanceFactory', - contracts.sidechainFactory.proxy.address, - ) - - return { - sender, - deployer, - owner, - compToken, - compWhale, - instanceFactory, - } - } - - it('Should have initialized all successfully', async function () { - const { sender, compToken, instanceFactory } = await loadFixture(fixture) - expect(sender.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.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.ERC20Impl()).to.exist - expect(await instanceFactory.nativeCurImpl()).to.exist - }) - - it('Admin should be able to set factory params', async function () { - let { instanceFactory, owner } = await loadFixture(fixture) - - await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted - - instanceFactory = await instanceFactory.connect(owner) - - 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.generateNewImplementation(config.verifier, 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 add instance', async function () { - let { sender, instanceFactory } = await loadFixture(fixture) - - // deploy instance - await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP) - - // 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)), - ) - - 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')) - - // try to deploy the same instance again - await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP) - - // check that instance has not been created - no new NewInstanceCloneCreated event - let curLogs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - expect(curLogs.length).to.be.equal(logs.length) - }) - - it('Should successfully add instances', async function () { - let { sender, instanceFactory } = 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 - - // deploy instances - await instanceFactory.connect(sender).createInstanceClones(config.COMP, denominations) - - // check instance initialization - let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - 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(denominations[i]) - } - }) - - it('Should deposit and withdraw into the new instance', async function () { - let { sender, instanceFactory, compToken, compWhale } = await loadFixture(fixture) - - // deploy instance - await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('100'), config.COMP) - - let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - const instance = await ethers.getContractAt( - 'ERC20TornadoCloneable', - ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), - ) - - // 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(instance.address, value) - - await expect(() => instance.deposit(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(() => instance.withdraw(proof, ...args)).to.changeTokenBalances( - compToken, - [instance, sender], - [BigNumber.from(0).sub(value), value], - ) - }) - - it('Should successfully add native currency instance', async function () { - let { sender, instanceFactory } = await loadFixture(fixture) - - const denomination = ethers.utils.parseEther('1') - - // deploy instance - await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero) - - // check instance initialization - let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - const instance = await ethers.getContractAt( - 'ETHTornadoCloneable', - ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), - ) - - 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(denomination) - - // try to deploy the same instance again - await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero) - - // check that instance has not been created - no new NewInstanceCloneCreated event - let curLogs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - expect(curLogs.length).to.be.equal(logs.length) - }) - - it('Should successfully add native currency instances', async function () { - let { sender, instanceFactory } = 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 - - // deploy instances - await instanceFactory.connect(sender).createInstanceClones(addressZero, denominations) - - // check instance initialization - let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - for (let i = 0; i < numInstances; i++) { - let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40) - let instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr) - - 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]) - } - }) - - it('Should deposit and withdraw into the new native currency instance', async function () { - let { sender, instanceFactory } = await loadFixture(fixture) - - const denomination = ethers.utils.parseEther('1.5') - - // deploy instance - await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero) - - let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated') - const instance = await ethers.getContractAt( - 'ETHTornadoCloneable', - ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)), - ) - - // check instance work ------------------------------------------ - const depo = createDeposit({ - nullifier: rbigint(31), - secret: rbigint(31), - }) - - await expect(() => - instance.connect(sender).deposit(toHex(depo.commitment), { - value: denomination, - }), - ).to.changeEtherBalances([sender, instance], [BigNumber.from(0).sub(denomination), denomination]) - - let pevents = await instance.queryFilter('Deposit') - await initialize({ merkleTreeHeight: 20 }) - - const { proof, args } = await generateProof({ - deposit: depo, - recipient: sender.address, - events: pevents, - }) - - await expect(() => instance.withdraw(proof, ...args)).to.changeEtherBalances( - [instance, sender], - [BigNumber.from(0).sub(denomination), denomination], - ) - }) -}) diff --git a/test/utils.js b/test/utils.js deleted file mode 100644 index 7745595..0000000 --- a/test/utils.js +++ /dev/null @@ -1,43 +0,0 @@ -/* 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, -}