require('dotenv').config() const fs = require('fs') const ethers = require('ethers') const { namehash } = ethers.utils const config = require('../torn-token/config') const get = require('get-value') const { deploy, getContractData, zeroMerkleRoot } = require('./utils') const { DEPLOYER, SALT } = process.env const deployer = getContractData('../deployer/build/contracts/Deployer.json') const torn = getContractData('../torn-token/build/contracts/TORN.json') const vesting = getContractData('../torn-token/build/contracts/Vesting.json') const voucher = getContractData('../torn-token/build/contracts/Voucher.json') const governance = getContractData('../tornado-governance/build/contracts/Governance.json') const governanceProxy = getContractData('../tornado-governance/build/contracts/LoopbackProxy.json') const miner = getContractData('../tornado-anonymity-mining/build/contracts/Miner.json') const rewardSwap = getContractData('../tornado-anonymity-mining/build/contracts/RewardSwap.json') const tornadoTrees = getContractData('../tornado-anonymity-mining/build/contracts/TornadoTrees.json') const tornadoProxy = getContractData('../tornado-anonymity-mining/build/contracts/TornadoProxy.json') const poseidonHasher2 = getContractData('../tornado-anonymity-mining/build/contracts/Hasher2.json') const poseidonHasher3 = getContractData('../tornado-anonymity-mining/build/contracts/Hasher3.json') const rewardVerifier = getContractData('../tornado-anonymity-mining/build/contracts/RewardVerifier.json') const withdrawVerifier = getContractData('../tornado-anonymity-mining/build/contracts/WithdrawVerifier.json') const treeUpdateVerifier = getContractData( '../tornado-anonymity-mining/build/contracts/TreeUpdateVerifier.json', ) const airdrop = require('../airdrop.json') const actions = [] actions.push( deploy({ domain: config.deployer.address, contract: deployer, args: ['0x0000000000000000000000000000000000000000'], dependsOn: [], title: 'Deployment proxy', description: 'This a required contract to initialize all other contracts. It is simple wrapper around EIP-2470 Singleton Factory that emits an event of contract deployment. The wrapper also validates if the deployment was successful.', }), ) // Deploy TORN const distribution = Object.values(config.torn.distribution).map(({ to, amount }) => ({ to: namehash(get(config, to).address), amount, })) console.log(distribution) actions.push( deploy({ domain: config.torn.address, contract: torn, args: [namehash(config.governance.address), config.torn.pausePeriod, distribution], title: 'TORN token', description: 'Tornado.cash governance token', }), ) // Deploy Governance implementation actions.push( deploy({ domain: config.governanceImpl.address, contract: governance, title: 'Governance implementation', description: 'Initial implementation of upgradable governance contract', }), ) // Deploy Governance proxy const governanceContract = new ethers.utils.Interface(governance.abi) const initData = governanceContract.encodeFunctionData('initialize', [namehash(config.torn.address)]) actions.push( deploy({ domain: config.governance.address, contract: governanceProxy, args: [namehash(config.governanceImpl.address), initData], dependsOn: [config.deployer.address, config.governanceImpl.address], title: 'Governance Upgradable Proxy', description: 'EIP-1167 Upgradable Proxy for Governance. It can only be upgraded through a proposal by TORN holders', }), ) // Deploy Verifiers actions.push( deploy({ domain: config.rewardVerifier.address, contract: rewardVerifier, title: 'Reward Verifier', description: 'ZkSnark verifier smart contract for mining rewards', }), ) actions.push( deploy({ domain: config.withdrawVerifier.address, contract: withdrawVerifier, title: 'Withdraw Verifier', description: 'ZkSnark verifier smart contract for reward withdrawals', }), ) actions.push( deploy({ domain: config.treeUpdateVerifier.address, contract: treeUpdateVerifier, title: 'Tree Update Verifier', description: 'ZkSnark verifier smart contract for validation for account merkle tree updates', }), ) // Deploy RewardSwap actions.push( deploy({ domain: config.rewardSwap.address, contract: rewardSwap, args: [ namehash(config.torn.address), namehash(config.miningV2.address), config.torn.distribution.miningV2.amount, config.miningV2.initialBalance, config.rewardSwap.poolWeight, ], title: 'Reward Swap', description: 'AMM that allows to swap Anonymity Points to TORN', }), ) // Deploy PoseidonHasher2 actions.push( deploy({ domain: config.poseidonHasher2.address, contract: poseidonHasher2, title: 'Poseidon hasher 2', description: 'Poseidon hash function for 2 arguments', }), ) // Deploy PoseidonHasher3 actions.push( deploy({ domain: config.poseidonHasher3.address, contract: poseidonHasher3, title: 'Poseidon hasher 3', description: 'Poseidon hash function for 3 arguments', }), ) // Deploy TornadoProxy const instances = config.miningV2.rates.map((rate) => namehash(rate.instance)) actions.push( deploy({ domain: config.tornadoProxy.address, contract: tornadoProxy, args: [namehash(config.tornadoTrees.address), namehash(config.governance.address), instances], title: 'TornadoCash Proxy', description: 'Proxy contract for tornado.cash deposits and withdrawals that records block numbers for mining', }), ) // Deploy TornadoTrees actions.push( deploy({ domain: config.tornadoTrees.address, contract: tornadoTrees, args: [ namehash(config.tornadoProxy.address), namehash(config.poseidonHasher2.address), namehash(config.poseidonHasher3.address), config.tornadoTrees.levels, ], title: 'TornadoTrees', description: 'Merkle tree with information about tornado cash deposits and withdrawals', }), ) // Deploy Miner const rates = config.miningV2.rates.map((rate) => ({ instance: namehash(rate.instance), value: rate.value, })) actions.push( deploy({ domain: config.miningV2.address, contract: miner, args: [ namehash(config.rewardSwap.address), namehash(config.governance.address), namehash(config.tornadoTrees.address), [ namehash(config.rewardVerifier.address), namehash(config.withdrawVerifier.address), namehash(config.treeUpdateVerifier.address), ], zeroMerkleRoot, rates, ], title: 'Miner', description: 'Mining contract for Anonymity Points', }), ) // Deploy Voucher const airdrops = airdrop.actions.map((a) => ({ to: a.expectedAddress, amount: a.amount })) actions.push( deploy({ domain: config.voucher.address, contract: voucher, args: [ namehash(config.torn.address), namehash(config.governance.address), config.voucher.duration * 2592000, // 60 * 60 * 24 * 30 airdrops, ], title: 'Voucher', description: 'TornadoCash voucher contract for early adopters', }), ) // Deploy Vestings config.vesting.governance.beneficiary = actions.find( (a) => a.domain === 'governance.contract.tornadocash.eth', ).expectedAddress const vestings = Object.values(config.vesting) for (const [i, vest] of vestings.entries()) { actions.push( deploy({ domain: vest.address, contract: vesting, args: [namehash(config.torn.address), vest.beneficiary, 0, vest.cliff, vest.duration], title: `Vesting ${i + 1} / ${vestings.length}`, description: `Vesting contract for ${vest.address}`, }), ) } // Write output const result = { deployer: DEPLOYER, salt: SALT, actions: actions.concat(airdrop.actions), } fs.writeFileSync('actions.json', JSON.stringify(result, null, ' ')) console.log('Created actions.json')