8f085566cc
Signed-off-by: T-Hax <>
884 lines
35 KiB
JavaScript
884 lines
35 KiB
JavaScript
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%')
|
|
})
|
|
})
|