add instance factory tests

This commit is contained in:
Drygin 2022-02-17 23:15:19 +03:00
parent 35004bc184
commit 41ddd8023d
17 changed files with 1121 additions and 886 deletions

View File

@ -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
PRIVATE_KEY=

View File

@ -1,8 +0,0 @@
use_latest_block=false
PROXY=0x722122dF12D4e14e13Ac3b6895a86e84145b6967
DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80
HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe
VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A
SALT=0x0000000000000000000000000000000000000000000000000000000047941987
COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888

12
config.js Normal file
View File

@ -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',
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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 {}

View File

@ -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}`,
},
}

View File

@ -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",

View File

@ -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

View File

@ -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)
},
)

View File

@ -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)
})

View File

@ -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)
})

View File

@ -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,
},
},
])
})
})

View File

@ -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(),
// )
// }
// })
})

View File

@ -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,
},
},
])
})
})

43
test/utils.js Normal file
View File

@ -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,
}

740
yarn.lock

File diff suppressed because it is too large Load Diff