From 3863e951d8cf5c9e756b80859596c6ee050ad1c7 Mon Sep 17 00:00:00 2001 From: T-Hax <> Date: Fri, 14 Apr 2023 19:16:26 +0000 Subject: [PATCH] fork tested Signed-off-by: T-Hax <> --- config.js | 5 +- contracts/InstanceAdditionProposal.sol | 54 ++++--- contracts/InstanceProposalFactory.sol | 8 +- hardhat.config.js | 6 +- package.json | 2 +- scripts/deploy.js | 79 +++++++++-- test/all.test.js | 188 +++++++++++++++++++++++++ test/utils.js | 43 ++++++ 8 files changed, 344 insertions(+), 41 deletions(-) create mode 100644 test/all.test.js create mode 100644 test/utils.js diff --git a/config.js b/config.js index 1a35b18..80cc129 100644 --- a/config.js +++ b/config.js @@ -8,8 +8,11 @@ module.exports = { singletonFactory: '0xce0042B868300000d44A59004Da54A005ffdcf9f', singletonFactoryVerboseWrapper: '0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80', salt: '0x0000000000000000000000000000000000000000000000000000000047941987', - COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', TORN: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', + COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', + LUSD: '0x5f98805a4e8be255a32880fdec7f6728c6568ba0', + RETH: '0xae78736cd615f374d3085123a210448e74fc6393', + FRXETH: '0x5E8422345238F34275888049021821E8E08CAa1f', tornWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', compWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC', creationFee: '200000000000000000000', // 200 TORN diff --git a/contracts/InstanceAdditionProposal.sol b/contracts/InstanceAdditionProposal.sol index 18ef464..47c2bb9 100644 --- a/contracts/InstanceAdditionProposal.sol +++ b/contracts/InstanceAdditionProposal.sol @@ -8,40 +8,51 @@ import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; import "./interfaces/IInstanceRegistry.sol"; import "./interfaces/IInstanceFactory.sol"; +struct InstanceAdditionData { + address tokenAddress; + uint40 smallDenomination; + uint16 base10Exponent; + uint24 uniPoolSwappingFee; + uint16 protocolFee; +} + +contract InstanceAdditionDataProvider { + InstanceAdditionData[] public toAddData; + + constructor(InstanceAdditionData[] memory _toAdd) { + for (uint256 i = 0; i < _toAdd.length; i++) { + toAddData.push(_toAdd[i]); + } + } + + function getAllDataToAdd() external view returns (InstanceAdditionData[] memory) { + return toAddData; + } +} + contract InstanceAdditionProposal { using SafeMath for uint256; + InstanceAdditionDataProvider public immutable provider; IInstanceFactory public immutable instanceFactory; IInstanceRegistry public immutable instanceRegistry; - struct InstanceAdditionData { - address tokenAddress; - uint40 smallDenomination; - uint16 base10Exponent; - uint24 uniPoolSwappingFee; - uint16 protocolFee; - } - - InstanceAdditionData[] public toAdd; - event AddInstanceForRegistry(address instance, address token, uint256 denomination); constructor(address _instanceFactory, address _instanceRegistry, InstanceAdditionData[] memory _toAdd) { + provider = new InstanceAdditionDataProvider(_toAdd); instanceFactory = IInstanceFactory(_instanceFactory); instanceRegistry = IInstanceRegistry(_instanceRegistry); - - // Copying structs is not implemented - for (uint256 i = 0; i < _toAdd.length; i++) { - toAdd.push(_toAdd[i]); - } } function executeProposal() external { - uint256 howMany = toAdd.length; + InstanceAdditionData[] memory _toAddData = provider.getAllDataToAdd(); + + uint256 howMany = _toAddData.length; for (uint256 i = 0; i < howMany; i++) { // Read out the data for the new instance - InstanceAdditionData memory data = toAdd[i]; + InstanceAdditionData memory data = _toAddData[i]; // Safely calculate the denomination uint256 denomination = uint256(data.smallDenomination).mul(10 ** uint256(data.base10Exponent)); @@ -67,4 +78,13 @@ contract InstanceAdditionProposal { emit AddInstanceForRegistry(address(instance), data.tokenAddress, denomination); } } + + function getDataToAddByIndex(uint256 index) external view returns (InstanceAdditionData memory data) { + // The compiler is behaving weird here. Something to do with struct type inference. + return (provider.getAllDataToAdd())[index]; + } + + function getAllDataToAdd() external view returns (InstanceAdditionData[] memory) { + return provider.getAllDataToAdd(); + } } diff --git a/contracts/InstanceProposalFactory.sol b/contracts/InstanceProposalFactory.sol index 2a796db..e705298 100644 --- a/contracts/InstanceProposalFactory.sol +++ b/contracts/InstanceProposalFactory.sol @@ -9,7 +9,7 @@ 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 { InstanceAdditionProposal } from "./InstanceAdditionProposal.sol"; +import { InstanceAdditionProposal, InstanceAdditionData } from "./InstanceAdditionProposal.sol"; /** * @notice Contract which creates instance addition proposals. @@ -87,9 +87,7 @@ contract InstanceProposalFactory { uint16[][] calldata _protocolFees, uint256 _totalNumberDenominations ) external returns (address) { - InstanceAdditionProposal.InstanceAdditionData[] memory toAdd = new InstanceAdditionProposal.InstanceAdditionData[]( - _totalNumberDenominations - ); + InstanceAdditionData[] memory toAdd = new InstanceAdditionData[](_totalNumberDenominations); uint256 toAddSize = 0; @@ -153,7 +151,7 @@ contract InstanceProposalFactory { // 9️⃣ If all of the above are fine, add the packed struct to the memory array. - toAdd[toAddSize] = InstanceAdditionProposal.InstanceAdditionData({ + toAdd[toAddSize] = InstanceAdditionData({ tokenAddress: token, smallDenomination: smallDenomination, base10Exponent: exponent, diff --git a/hardhat.config.js b/hardhat.config.js index fa0085a..ee5a15b 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -44,14 +44,14 @@ module.exports = { hardhat: { forking: { url: process.env.MAINNET_RPC_URL, - blockNumber: 14250000, + //blockNumber: 14250000, httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' }, }, chainId: 1, - initialBaseFeePerGas: 5, + //initialBaseFeePerGas: 5, loggingEnabled: false, allowUnlimitedContractSize: false, - blockGasLimit: 50000000, + //blockGasLimit: 50000000, }, rinkeby: { url: process.env.RINKEBY_RPC_URL, diff --git a/package.json b/package.json index 855effd..eddf35e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.4.0", "ethereum-waffle": "^3.4.0", - "hardhat": "^2.4.3", + "hardhat": ">=2.4.3", "hardhat-contract-sizer": "^2.6.1", "hardhat-log-remover": "^2.0.2", "mocha-lcov-reporter": "^1.3.0", diff --git a/scripts/deploy.js b/scripts/deploy.js index b2d3d5b..99ec1b7 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -1,6 +1,7 @@ require('dotenv').config() -const { ethers } = require('hardhat') +const hre = require('hardhat') +const { ethers } = hre const config = require('../config') const { createInterface } = require('readline') @@ -17,7 +18,7 @@ function idToNetwork(id) { case 11155111: return 'Sepolia' default: - throw Error('\nChain id could not be recognized. What network are you using?\n') + throw Error('\nChain Id could not be recognized. What network are you using?\n') } } @@ -31,7 +32,7 @@ function _prompt(prompt, resolve) { } else if (answer == 'n') { userInput.close() resolve(false) - } else wipeCachePrompt('', resolve) + } else _prompt('', resolve) }) } @@ -40,44 +41,94 @@ function prompt(prompt) { } function happyDeployedMessage(name, chainId, address) { - return `\n${name} successfully deployed on ${idToNetwork(chainId)} at ${address} 🥳\n` + return `\n${name} successfully deployed on ${idToNetwork(chainId)} @ ${address} 🥳\n` } -function happyVerifiedMessage(name) { - return `\n${name} successfully verified on Etherscan!` +function happyVerifiedMessage(name, address) { + return `\n${name} @ ${address} successfully verified on Etherscan! 🥳\n` } 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 minimalFactoryContractFactory = await ethers.getContractFactory('MinimalInstanceFactory') + const proposalFactoryContractFactory = await ethers.getContractFactory('InstanceProposalFactory') - const minFac = await minFacFac.deploy(config.verifier, config.hasher, config.merkleTreeHeight) + let minimalFactory, proposalFactory, nativeCloneableImplAddr, erc20CloneableImplAddr - console.log(happyDeployedMessage('MinimalInstanceFactory', minFac.address)) + if (await prompt(promptMessageBase('Continuing to MinimalInstanceFactory deployment.'))) { + minimalFactory = await minimalFactoryContractFactory.deploy( + config.verifier, + config.hasher, + config.merkleTreeHeight, + ) + } else { + return '\nDecided to stop at InstanceProposalFactory deployment.\n' + } + + console.log(happyDeployedMessage('MinimalInstanceFactory', minimalFactory.address)) + + nativeCloneableImplAddr = await minimalFactory.nativeCurImpl() + erc20CloneableImplAddr = await minimalFactory.ERC20Impl() + + console.log(happyDeployedMessage('ETHTornadoCloneable', nativeCloneableImplAddr)) + console.log(happyDeployedMessage('ERC20TornadoCloneable', erc20CloneableImplAddr)) if (await prompt(promptMessageBase('Continuing to InstanceProposalFactory deployment.'))) { - const proposalFac = await proposalFacFac.deploy( + proposalFactory = await proposalFactoryContractFactory.deploy( config.governance, - minFac.address, + minimalFactory.address, config.instanceRegistry, config.UniswapV3Factory, config.WETH, config.TWAPSlotsMin, ) - console.log(happyDeployedMessage('InstanceProposalFactory', proposalFac.address)) + console.log(happyDeployedMessage('InstanceProposalFactory', proposalFactory.address)) } else { return '\nDecided to stop at InstanceProposalFactory deployment.\n' } if (await prompt(promptMessageBase('Continuing to contract verification.'))) { + await hre.run('verify:verify', { + address: minimalFactory.address, + constructorArguments: [config.verifier, config.hasher, config.merkleTreeHeight], + }) + + console.log(happyVerifiedMessage('MinimalInstanceFactory', minimalFactory.address)) + + await hre.run('verify:verify', { + address: proposalFactory.address, + constructorArguments: [ + config.governance, + minimalFactory.address, + config.instanceRegistry, + config.UniswapV3Factory, + config.WETH, + config.TWAPSlotsMin, + ], + }) + + console.log(happyVerifiedMessage('InstanceProposalFactory', proposalFactory.address)) + + await hre.run('verify:verify', { + address: nativeCloneableImplAddr, + constructorArguments: [config.verifier, config.hasher], + }) + + console.log(happyVerifiedMessage('ETHTornadoCloneable', nativeCloneableImplAddr)) + + await hre.run('verify:verify', { + address: erc20CloneableImplAddr, + constructorArguments: [config.verifier, config.hasher], + }) + + console.log(happyVerifiedMessage('ERC20TornadoCloneable', erc20CloneableImplAddr)) } else { return '\nDecided to stop at contract verification.\n' } } main().then((res) => { - console.log(res ?? '\nScript succesfully finished.') + console.log(res ?? '\nScript succesfully finished.\n') }) diff --git a/test/all.test.js b/test/all.test.js new file mode 100644 index 0000000..e7c8f26 --- /dev/null +++ b/test/all.test.js @@ -0,0 +1,188 @@ +const hre = require('hardhat') +const config = require('../config') + +const { ethers, waffle } = hre +const { loadFixture } = waffle +const { expect } = require('chai') +const { minewait } = require('./utils') + +const { BigNumber } = require('@ethersproject/bignumber') + +describe('Tests', () => { + let governanceSigner, proposer + let minimalFactory, proposalFactory, governance, torn + + before(async () => { + minimalFactory = await ( + await ethers.getContractFactory('MinimalInstanceFactory') + ).deploy(config.verifier, config.hasher, config.merkleTreeHeight) + + let gasUsed = await ethers.provider.estimateGas(minimalFactory.deployTransaction.data.toString()) + + console.log(`\nManaged to deploy the MinimalInstanceFactory. Gas used: ${gasUsed} 🏭\n`) + + proposalFactory = await ( + await ethers.getContractFactory('InstanceProposalFactory') + ).deploy( + config.governance, + minimalFactory.address, + config.instanceRegistry, + config.UniswapV3Factory, + config.WETH, + config.TWAPSlotsMin, + ) + + gasUsed = await ethers.provider.estimateGas(minimalFactory.deployTransaction.data.toString()) + + console.log(`\nManaged to deploy the InstanceProposalFactory. Gas used: ${gasUsed} 🏭\n`) + + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [config.governance], + }) + + await hre.network.provider.request({ + method: 'hardhat_setBalance', + params: [config.governance, ethers.utils.parseUnits('10').toHexString()], + }) + + governanceSigner = await ethers.getSigner(config.governance) + + governance = await ethers.getContractAt( + 'tornado-governance/contracts/v1/Governance.sol:Governance', + config.governance, + governanceSigner, + ) + + console.log('\nManaged to setup self-signing governance (just like IRL). 🏛️\n') + + torn = await ethers.getContractAt('torn-token/contracts/TORN.sol:TORN', config.TORN, governanceSigner) + + proposer = (await ethers.getSigners())[0] + + const fundAmount = (await torn.balanceOf(governance.address)).div(2) + + await expect(() => torn.transfer(proposer.address, fundAmount)).to.changeTokenBalance( + torn, + proposer, + fundAmount, + ) + + console.log(`\nFunded proposer with ${fundAmount.div(BigNumber.from(10).pow(18))} TORN 🌪️\n`) + }) + + it('Test the entire instance creation process.', async () => { + let response = await proposalFactory.createProposalContract( + [config.LUSD], + // This should work fine + [3000, 100000], + // This should work fine + [18, 3921], + [[ethers.utils.parseUnits('10000'), ethers.utils.parseUnits('1000'), ethers.utils.parseUnits('100')]], + // This should work fine + [[100, 100, 100, 312]], + 3, + ) + + console.log('\nManaged to deploy an LUSD proposal factory. 🏭\n') + + let receipt = await response.wait() + + const proposalContractLUSD = await ethers.getContractAt( + 'InstanceAdditionProposal', + receipt.events[0].args[0], + ) + + expect((await proposalContractLUSD.getAllDataToAdd()).length).to.equal(3) + + let denominations = [ + ethers.utils.parseUnits('1000'), + ethers.utils.parseUnits('100'), + ethers.utils.parseUnits('10'), + ] + + response = await proposalFactory.createProposalContract( + [config.RETH, config.LUSD], + // This should work fine + [500, 3000, 100000], + // This too + [18, 18, 3921], + [denominations, denominations], + // This should work fine + [ + [100, 100, 100, 312], + [300, 300, 300, 312], + ], + 6, + ) + + console.log('\nManaged to deploy a RETH + LUSD proposal factory. 🏭\n') + + receipt = await response.wait() + + const proposalContractRETHLUSD = await ethers.getContractAt( + 'InstanceAdditionProposal', + receipt.events[0].args[0], + ) + + const addedData = await proposalContractRETHLUSD.getAllDataToAdd() + + expect(addedData.length).to.equal(6) + + await expect( + proposalFactory.createProposalContract( + [config.RETH, config.FRXETH], + // Should be reverted because of frxeth + [500, 3000], + [18, 18, 3921], + [denominations, denominations], + [ + [100, 100, 100, 312], + [100, 100, 100, 312], + ], + 6, + ), + ).to.be.reverted + + console.log('\nInsufficient cardinality frxETH reverted. 🦄\n') + + console.log('Starting proposal process on former proposal...\n') + + governance = governance.connect(proposer) + torn = torn.connect(proposer) + + const proposerBalance = await torn.balanceOf(proposer.address) + + await torn.approve(governance.address, proposerBalance) + + await expect(() => governance.lockWithApproval(proposerBalance)).to.changeTokenBalance( + torn, + proposer, + proposerBalance.mul('-1'), + ) + + await expect(governance.propose(proposalContractRETHLUSD.address, 'Add some random tokens.')).to.not.be + .reverted + + const proposalId = await governance.latestProposalIds(proposer.address) + + console.log(`\nSuccessfully proposed proposal with id ${proposalId}. 📜\n`) + + const votingDelay = await governance.VOTING_DELAY() + + // Mine the time necessary for the proposal to finish + await minewait(votingDelay.toNumber()) + + await expect(governance.castVote(proposalId, true)).to.not.be.reverted + + const delay = await governance.EXECUTION_DELAY() + const period = await governance.VOTING_PERIOD() + + // Mine the time necessary for the proposal to finish + await minewait(delay.add(period.add('43200')).toNumber()) + + await expect(governance.execute(proposalId)).to.not.be.reverted + + console.log('\n Successfully executed the proposal! 🥳\n') + }) +}) 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, +}