start writing deployment script, work on reintroducing tests

Signed-off-by: T-Hax <>
This commit is contained in:
T-Hax 2023-04-13 20:03:12 +00:00
parent 197d593504
commit a8495d76a4
13 changed files with 125 additions and 1660 deletions

@ -1,5 +1,6 @@
ETHERSCAN_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 ETHERSCAN_KEY=
ALCHEMY_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 PRIVATE_KEY=
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 MAINNET_RPC_URL=
INFURA_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 RINKEBY_RPC_URL=
FORKNET_RPC_URL=https://link-to.fork GOERLI_RPC_URL=
SEPOLIA_RPC_URL=

@ -17,5 +17,5 @@ module.exports = {
admin: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', // TODO admin: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', // TODO
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',
TWAPSlotsMin: 50, TWAPSlotsMin: 80,
} }

@ -1,102 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
pragma abicoder v2;
import "./interfaces/IInstanceRegistry.sol";
import "./interfaces/IInstanceFactory.sol";
contract AddInstanceProposal {
IInstanceFactory public immutable instanceFactory;
IInstanceRegistry public immutable instanceRegistry;
uint24 public immutable uniswapPoolSwappingFee;
address public immutable token;
uint256 public immutable numInstances;
uint256 internal immutable denomination0;
uint256 internal immutable denomination1;
uint256 internal immutable denomination2;
uint256 internal immutable denomination3;
uint32 internal immutable protocolFee0;
uint32 internal immutable protocolFee1;
uint32 internal immutable protocolFee2;
uint32 internal immutable protocolFee3;
event AddInstanceForRegistry(address instance, address token, uint256 denomination);
constructor(
address _instanceFactory,
address _instanceRegistry,
address _token,
uint24 _uniswapPoolSwappingFee,
uint256[] memory _denominations,
uint32[] memory _protocolFees
) {
instanceFactory = IInstanceFactory(_instanceFactory);
instanceRegistry = IInstanceRegistry(_instanceRegistry);
token = _token;
uniswapPoolSwappingFee = _uniswapPoolSwappingFee;
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
uint256 _numInstances = _denominations.length;
require(_numInstances > 0 && _numInstances <= 4, "incorrect instances number");
numInstances = _numInstances;
denomination0 = _numInstances > 0 ? _denominations[0] : 0;
denomination1 = _numInstances > 1 ? _denominations[1] : 0;
denomination2 = _numInstances > 2 ? _denominations[2] : 0;
denomination3 = _numInstances > 3 ? _denominations[3] : 0;
protocolFee0 = _numInstances > 0 ? _protocolFees[0] : 0;
protocolFee1 = _numInstances > 1 ? _protocolFees[1] : 0;
protocolFee2 = _numInstances > 2 ? _protocolFees[2] : 0;
protocolFee3 = _numInstances > 3 ? _protocolFees[3] : 0;
}
function executeProposal() external {
for (uint256 i = 0; i < numInstances; i++) {
address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token);
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
token != address(0),
IERC20(token),
IInstanceRegistry.InstanceState.ENABLED,
uniswapPoolSwappingFee,
protocolFeeByIndex(i)
);
IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData);
instanceRegistry.updateInstance(tornadoForUpdate);
emit AddInstanceForRegistry(address(instance), token, denominationByIndex(i));
}
}
function denominationByIndex(uint256 _index) public view returns (uint256) {
if (_index == 0) {
return denomination0;
} else if (_index == 1) {
return denomination1;
} else if (_index == 2) {
return denomination2;
} else if (_index == 3) {
return denomination3;
} else {
revert("Invalid instance index");
}
}
function protocolFeeByIndex(uint256 _index) public view returns (uint32) {
if (_index == 0) {
return protocolFee0;
} else if (_index == 1) {
return protocolFee1;
} else if (_index == 2) {
return protocolFee2;
} else if (_index == 3) {
return protocolFee3;
} else {
revert("Invalid instance index");
}
}
}

@ -40,12 +40,16 @@ contract InstanceAdditionProposal {
uint256 howMany = toAdd.length; uint256 howMany = toAdd.length;
for (uint256 i = 0; i < howMany; i++) { for (uint256 i = 0; i < howMany; i++) {
// Read out the data for the new instance
InstanceAdditionData memory data = toAdd[i]; InstanceAdditionData memory data = toAdd[i];
// Safely calculate the denomination
uint256 denomination = uint256(data.smallDenomination).mul(10 ** uint256(data.base10Exponent)); uint256 denomination = uint256(data.smallDenomination).mul(10 ** uint256(data.base10Exponent));
// Create the instance
address instance = instanceFactory.createInstanceClone(denomination, data.tokenAddress); address instance = instanceFactory.createInstanceClone(denomination, data.tokenAddress);
// Prepare the data for the registry.
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance( IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
data.tokenAddress != address(0), data.tokenAddress != address(0),
IERC20(data.tokenAddress), IERC20(data.tokenAddress),
@ -54,8 +58,10 @@ contract InstanceAdditionProposal {
data.protocolFee data.protocolFee
); );
// Convert the data.
IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData); IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData);
// Update the instance data in the registry.
instanceRegistry.updateInstance(tornadoForUpdate); instanceRegistry.updateInstance(tornadoForUpdate);
emit AddInstanceForRegistry(address(instance), data.tokenAddress, denomination); emit AddInstanceForRegistry(address(instance), data.tokenAddress, denomination);

@ -1,157 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
pragma abicoder v2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import "./AddInstanceProposal.sol";
import "./interfaces/IInstanceFactory.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol";
import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
contract InstanceProposalCreator is Initializable {
using Address for address;
address public immutable governance;
address public immutable torn;
IInstanceFactory public immutable instanceFactory;
address public immutable instanceRegistry;
IUniswapV3Factory public immutable UniswapV3Factory;
address public immutable WETH;
uint16 public TWAPSlotsMin;
uint256 public creationFee;
event NewCreationFeeSet(uint256 newCreationFee);
event NewTWAPSlotsMinSet(uint256 newTWAPSlotsMin);
event NewGovernanceProposalCreated(address indexed proposal);
/**
* @dev Throws if called by any account other than the Governance.
*/
modifier onlyGovernance() {
require(governance == msg.sender, "IF: caller is not the Governance");
_;
}
constructor(
address _governance,
address _instanceFactory,
address _instanceRegistry,
address _torn,
address _UniswapV3Factory,
address _WETH
) {
governance = _governance;
instanceFactory = IInstanceFactory(_instanceFactory);
instanceRegistry = _instanceRegistry;
torn = _torn;
UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory);
WETH = _WETH;
}
/**
* @notice initialize function for upgradeability
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
* params left out because self explainable
* */
function initialize(uint16 _TWAPSlotsMin, uint256 _creationFee) external initializer {
TWAPSlotsMin = _TWAPSlotsMin;
creationFee = _creationFee;
}
/**
* @dev Creates AddInstanceProposal with approve.
* @param _token address of ERC20 token for a new instance
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
* `3000` means 0.3% fee Uniswap pool.
* @param _denominations list of denominations for each new instance
* @param _protocolFees list of protocol fees for each new instance.
* `100` means that instance withdrawal fee is 1% of denomination.
*/
function createProposalApprove(
address _token,
uint24 _uniswapPoolSwappingFee,
uint256[] memory _denominations,
uint32[] memory _protocolFees
) external returns (address) {
require(IERC20(torn).transferFrom(msg.sender, governance, creationFee));
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
}
/**
* @dev Creates AddInstanceProposal with permit.
* @param _token address of ERC20 token for a new instance
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
* `3000` means 0.3% fee Uniswap pool.
* @param _denominations list of denominations for each new instance
* @param _protocolFees list of protocol fees for each new instance.
* `100` means that instance withdrawal fee is 1% of denomination.
*/
function createProposalPermit(
address _token,
uint24 _uniswapPoolSwappingFee,
uint256[] memory _denominations,
uint32[] memory _protocolFees,
address creater,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (address) {
IERC20Permit(torn).permit(creater, address(this), creationFee, deadline, v, r, s);
require(IERC20(torn).transferFrom(creater, governance, creationFee));
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
}
function _createProposal(
address _token,
uint24 _uniswapPoolSwappingFee,
uint256[] memory _denominations,
uint32[] memory _protocolFees
) internal returns (address) {
require(_token == address(0) || _token.isContract(), "Token is not contract");
require(_denominations.length > 0, "Empty denominations");
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
// check Uniswap Pool
for (uint8 i = 0; i < _protocolFees.length; i++) {
require(_protocolFees[i] <= 10000, "Protocol fee is more than 100%");
if (_protocolFees[i] > 0 && _token != address(0)) {
// pool exists
address poolAddr = UniswapV3Factory.getPool(_token, WETH, _uniswapPoolSwappingFee);
require(poolAddr != address(0), "Uniswap pool is not exist");
// TWAP slots
(, , , , uint16 observationCardinalityNext, , ) = IUniswapV3PoolState(poolAddr).slot0();
require(observationCardinalityNext >= TWAPSlotsMin, "Uniswap pool TWAP slots number is low");
break;
}
}
address proposal = address(
new AddInstanceProposal(
address(instanceFactory),
instanceRegistry,
_token,
_uniswapPoolSwappingFee,
_denominations,
_protocolFees
)
);
emit NewGovernanceProposalCreated(proposal);
return proposal;
}
function setCreationFee(uint256 _creationFee) external onlyGovernance {
creationFee = _creationFee;
emit NewCreationFeeSet(_creationFee);
}
function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance {
TWAPSlotsMin = _TWAPSlotsMin;
emit NewTWAPSlotsMinSet(_TWAPSlotsMin);
}
}

@ -105,7 +105,7 @@ contract InstanceProposalFactory {
uint24 swappingFee = _uniswapPoolSwappingFees[t]; uint24 swappingFee = _uniswapPoolSwappingFees[t];
// 3 Fails if the token is not a contract or if there is no pool. // 3 If not eth the token is a contract, the swapping fee must give a good pool, the cardinality must be good.
if (token != address(0)) { if (token != address(0)) {
require(token.isContract(), "Token must be a contract if not addr(0)"); require(token.isContract(), "Token must be a contract if not addr(0)");
@ -119,7 +119,7 @@ contract InstanceProposalFactory {
require(minObservationCardinality <= observationCardinalityNext, "Uniswap Pool observation cardinality is low."); require(minObservationCardinality <= observationCardinalityNext, "Uniswap Pool observation cardinality is low.");
} }
// 4 Denominations, div exponent and protocol fees must have an element at t(oken). // 4 Denominations, div exponent and protocol fees must have an element at t(oken). The exponent must be good.
uint256[] memory denominations = _denominations[t]; uint256[] memory denominations = _denominations[t];

@ -43,8 +43,9 @@ module.exports = {
networks: { networks: {
hardhat: { hardhat: {
forking: { forking: {
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, 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, chainId: 1,
initialBaseFeePerGas: 5, initialBaseFeePerGas: 5,
@ -53,33 +54,46 @@ module.exports = {
blockGasLimit: 50000000, blockGasLimit: 50000000,
}, },
rinkeby: { rinkeby: {
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, url: process.env.RINKEBY_RPC_URL,
accounts: process.env.PRIVATE_KEY accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY] ? [process.env.PRIVATE_KEY]
: { mnemonic: 'test test test test test junk' }, : { mnemonic: 'test test test test test junk' },
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
}, },
goerli: { goerli: {
url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`, url: process.env.GOERLI_RPC_URL,
accounts: process.env.PRIVATE_KEY accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY] ? [process.env.PRIVATE_KEY]
: { mnemonic: 'test test test test test junk' }, : { mnemonic: 'test test test test test junk' },
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY]
: { mnemonic: 'test test test test test junk' },
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
}, },
mainnet: { mainnet: {
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, url: process.env.MAINNET_RPC_URL,
accounts: process.env.PRIVATE_KEY accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY] ? [process.env.PRIVATE_KEY]
: { mnemonic: 'test test test test test junk' }, : { mnemonic: 'test test test test test junk' },
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
}, },
...(typeof process.env.FORKNET_RPC_URL === 'string' && { // What the fuck is this
forknet: { // ...(typeof process.env.FORKNET_RPC_URL === 'string' && {
url: process.env.FORKNET_RPC_URL, // forknet: {
accounts: process.env.PRIVATE_KEY // url: process.env.FORKNET_RPC_URL,
? [process.env.PRIVATE_KEY] // accounts: process.env.PRIVATE_KEY
: { mnemonic: 'test test test test test junk' }, // ? [process.env.PRIVATE_KEY]
// : { mnemonic: 'test test test test test junk' },
// },
// }),
}, },
}), mocha: {
timeout: 9999999999 /* The following simply does not work: ignore: ["factory.with.registry.test.js", "sidechain.instance.factory.test.js"] */,
}, },
mocha: { timeout: 9999999999 },
spdxLicenseIdentifier: { spdxLicenseIdentifier: {
overwrite: true, overwrite: true,
runOnCompile: true, runOnCompile: true,

83
scripts/deploy.js Normal file

@ -0,0 +1,83 @@
require('dotenv').config()
const { ethers } = require('hardhat')
const config = require('../config')
const { createInterface } = require('readline')
function idToNetwork(id) {
switch (id) {
case 1:
return 'Mainnet'
case 3:
return 'Ropsten'
case 4:
return 'Rinkeby'
case 5:
return 'Goerli'
case 11155111:
return 'Sepolia'
default:
throw Error('\nChain id could not be recognized. What network are you using?\n')
}
}
const prompter = createInterface({ input: process.stdin, output: process.stdout })
function _prompt(prompt, resolve) {
prompter.question(prompt, (answer) => {
if (answer == 'y') {
userInput.close()
resolve(true)
} else if (answer == 'n') {
userInput.close()
resolve(false)
} else wipeCachePrompt('', resolve)
})
}
function prompt(prompt) {
return new Promise((resolve) => _prompt(prompt, resolve))
}
function happyDeployedMessage(name, chainId, address) {
return `\n${name} successfully deployed on ${idToNetwork(chainId)} at ${address} 🥳\n`
}
function happyVerifiedMessage(name) {
return `\n${name} successfully verified on Etherscan!`
}
const promptMessageBase = (middle) => `\n${middle}\n\nAre you sure you would like to continue? 🧐 (y/n): `
async function main() {
const minFacFac = await ethers.getContractFactory('MinimalInstanceFactory')
const proposalFacFac = await ethers.getContractFactory('InstanceProposalFactory')
const minFac = await minFacFac.deploy(config.verifier, config.hasher, config.merkleTreeHeight)
console.log(happyDeployedMessage('MinimalInstanceFactory', minFac.address))
if (await prompt(promptMessageBase('Continuing to InstanceProposalFactory deployment.'))) {
const proposalFac = await proposalFacFac.deploy(
config.governance,
minFac.address,
config.instanceRegistry,
config.UniswapV3Factory,
config.WETH,
config.TWAPSlotsMin,
)
console.log(happyDeployedMessage('InstanceProposalFactory', proposalFac.address))
} else {
return '\nDecided to stop at InstanceProposalFactory deployment.\n'
}
if (await prompt(promptMessageBase('Continuing to contract verification.'))) {
} else {
return '\nDecided to stop at contract verification.\n'
}
}
main().then((res) => {
console.log(res ?? '\nScript succesfully finished.')
})

@ -1,114 +0,0 @@
const { ethers } = require('hardhat')
const defaultConfig = require('../config')
async function upgradableContract({ contractName, implConstructorArgs, proxyConstructorArgs, salt }) {
const Implementation = await ethers.getContractFactory(contractName)
const implementationBytecode =
Implementation.bytecode + Implementation.interface.encodeDeploy(implConstructorArgs).slice(2)
const implementationAddress = ethers.utils.getCreate2Address(
defaultConfig.singletonFactory,
salt,
ethers.utils.keccak256(implementationBytecode),
)
const AdminUpgradeableProxy = await ethers.getContractFactory('AdminUpgradeableProxy')
const proxyConst = [implementationAddress, ...proxyConstructorArgs]
const proxyBytecode =
AdminUpgradeableProxy.bytecode + AdminUpgradeableProxy.interface.encodeDeploy(proxyConst).slice(2)
const proxyAddress = ethers.utils.getCreate2Address(
defaultConfig.singletonFactory,
salt,
ethers.utils.keccak256(proxyBytecode),
)
return {
implementation: {
address: implementationAddress,
bytecode: implementationBytecode,
args: implConstructorArgs,
},
proxy: { address: proxyAddress, bytecode: proxyBytecode, args: proxyConst },
isProxy: true,
}
}
async function generate(config = defaultConfig) {
// sidechain factory contract -------------------------------------
const SidechainFactory = await ethers.getContractFactory('SidechainInstanceFactory')
const SidechainFactoryInitData = SidechainFactory.interface.encodeFunctionData('initialize', [
config.verifier,
config.hasher,
config.merkleTreeHeight,
config.admin,
])
const sidechainFactory = await upgradableContract({
contractName: 'SidechainInstanceFactory',
implConstructorArgs: [],
proxyConstructorArgs: [config.admin, SidechainFactoryInitData],
salt: config.salt,
})
// factory with registry contract ---------------------------------
const Factory = await ethers.getContractFactory('InstanceFactory')
const FactoryInitData = Factory.interface.encodeFunctionData('initialize', [
config.verifier,
config.hasher,
config.merkleTreeHeight,
config.governance,
])
const factory = await upgradableContract({
contractName: 'InstanceFactory',
implConstructorArgs: [],
proxyConstructorArgs: [config.governance, FactoryInitData],
salt: config.salt,
})
const ProposalCreator = await ethers.getContractFactory('InstanceProposalCreator')
const ProposalCreatorInitData = ProposalCreator.interface.encodeFunctionData('initialize', [
config.TWAPSlotsMin,
config.creationFee,
])
const proposalCreator = await upgradableContract({
contractName: 'InstanceProposalCreator',
implConstructorArgs: [
config.governance,
factory.proxy.address,
config.instanceRegistry,
config.TORN,
config.UniswapV3Factory,
config.WETH,
],
proxyConstructorArgs: [config.governance, ProposalCreatorInitData],
salt: config.salt,
})
const result = {
sidechainFactory,
factory,
proposalCreator,
}
return result
}
async function generateWithLog() {
const contracts = await generate()
console.log('SidechainInstanceFactory contract: ', contracts.sidechainFactory.implementation.address)
console.log('SidechainInstanceFactory proxy contract: ', contracts.sidechainFactory.proxy.address)
console.log('Instance factory contract: ', contracts.factory.implementation.address)
console.log('Instance factory proxy contract: ', contracts.factory.proxy.address)
console.log('Proposal creator contract: ', contracts.proposalCreator.implementation.address)
console.log('Proposal creator proxy contract: ', contracts.proposalCreator.proxy.address)
return contracts
}
module.exports = {
generate,
generateWithLog,
}

@ -1,44 +0,0 @@
const { EIP712Signer } = require('@ticket721/e712')
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
]
class PermitSigner extends EIP712Signer {
constructor(_domain, _permitArgs) {
super(_domain, ['Permit', Permit])
this.permitArgs = _permitArgs
}
// Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)
setPermitInfo(_permitArgs) {
this.permitArgs = _permitArgs
}
getPayload() {
return this.generatePayload(this.permitArgs, 'Permit')
}
async getSignature(privateKey) {
let payload = this.getPayload()
payload.message.owner = payload.message.owner.address
const { hex, v, r, s } = await this.sign(privateKey, payload)
return {
hex,
v,
r: '0x' + r,
s: '0x' + s,
}
}
getSignerAddress(permitArgs, signature) {
const original_payload = this.generatePayload(permitArgs, 'Permit')
return this.verify(original_payload, signature)
}
}
module.exports = { PermitSigner }

@ -1,883 +0,0 @@
const hre = require('hardhat')
const { ethers, waffle } = hre
const { loadFixture } = waffle
const { expect } = require('chai')
const { BigNumber } = require('@ethersproject/bignumber')
const config = require('../config')
const { getSignerFromAddress, minewait } = require('./utils')
const { PermitSigner } = require('../src/permit.js')
const { generate } = require('../src/generateAddresses')
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
describe('Instance Factory With Registry Tests', () => {
const ProposalState = {
Pending: 0,
Active: 1,
Defeated: 2,
Timelocked: 3,
AwaitingExecution: 4,
Executed: 5,
Expired: 6,
}
const addressZero = ethers.constants.AddressZero
async function fixture() {
const [sender, deployer, multisig] = await ethers.getSigners()
const tornWhale = await getSignerFromAddress(config.tornWhale)
const compWhale = await getSignerFromAddress(config.compWhale)
const gov = await ethers.getContractAt('Governance', config.governance)
const router = await ethers.getContractAt(
'tornado-relayer-registry/contracts/tornado-proxy/TornadoRouter.sol:TornadoRouter',
config.router,
)
const tornToken = await ethers.getContractAt(
'@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
config.TORN,
)
const compToken = await ethers.getContractAt(
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
config.COMP,
)
const instanceRegistry = await ethers.getContractAt(
'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry',
config.instanceRegistry,
)
// deploy InstanceProposalCreator with CREATE2
const singletonFactory = await ethers.getContractAt(
'SingletonFactory',
config.singletonFactoryVerboseWrapper,
)
const contracts = await generate()
if ((await ethers.provider.getCode(contracts.factory.implementation.address)) == '0x') {
await singletonFactory.deploy(contracts.factory.implementation.bytecode, config.salt, {
gasLimit: config.deployGasLimit,
})
}
if ((await ethers.provider.getCode(contracts.factory.proxy.address)) == '0x') {
await singletonFactory.deploy(contracts.factory.proxy.bytecode, config.salt, {
gasLimit: config.deployGasLimit,
})
}
const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factory.proxy.address)
if ((await ethers.provider.getCode(contracts.proposalCreator.implementation.address)) == '0x') {
await singletonFactory.deploy(contracts.proposalCreator.implementation.bytecode, config.salt, {
gasLimit: config.deployGasLimit,
})
}
if ((await ethers.provider.getCode(contracts.proposalCreator.proxy.address)) == '0x') {
await singletonFactory.deploy(contracts.proposalCreator.proxy.bytecode, config.salt, {
gasLimit: config.deployGasLimit,
})
}
const proposalCreator = await ethers.getContractAt(
'InstanceProposalCreator',
contracts.proposalCreator.proxy.address,
)
return {
sender,
deployer,
multisig,
tornWhale,
compWhale,
router,
gov,
tornToken,
compToken,
instanceRegistry,
instanceFactory,
proposalCreator,
}
}
it('Should have initialized all successfully', async function () {
const { sender, gov, tornToken, instanceRegistry, instanceFactory, proposalCreator } = await loadFixture(
fixture,
)
expect(sender.address).to.exist
expect(gov.address).to.exist
expect(tornToken.address).to.exist
expect(instanceRegistry.address).to.exist
expect(instanceFactory.address).to.exist
expect(proposalCreator.address).to.exist
})
it('Should set correct params for factory', async function () {
const { instanceFactory, proposalCreator } = await loadFixture(fixture)
expect(await proposalCreator.governance()).to.be.equal(config.governance)
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
expect(await instanceFactory.ERC20Impl()).to.exist
expect(await instanceFactory.nativeCurImpl()).to.exist
expect(await proposalCreator.creationFee()).to.be.equal(config.creationFee)
expect(await proposalCreator.torn()).to.be.equal(config.TORN)
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
expect(await proposalCreator.WETH()).to.be.equal(config.WETH)
expect(await proposalCreator.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory)
})
it('Governance should be able to set factory/proposalCreator params', async function () {
let { instanceFactory, proposalCreator, gov } = await loadFixture(fixture)
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
const govSigner = await getSignerFromAddress(gov.address)
instanceFactory = await instanceFactory.connect(govSigner)
proposalCreator = await proposalCreator.connect(govSigner)
await instanceFactory.generateNewImplementation(addressZero, addressZero)
await instanceFactory.setMerkleTreeHeight(1)
await proposalCreator.setCreationFee(0)
await proposalCreator.setTWAPSlotsMin(0)
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
expect(await proposalCreator.creationFee()).to.be.equal(0)
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(0)
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
await proposalCreator.setCreationFee(config.creationFee)
await proposalCreator.setTWAPSlotsMin(config.TWAPSlotsMin)
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
expect(await proposalCreator.creationFee()).to.be.equal(config.creationFee)
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
})
it('Should successfully deploy/propose/execute proposal - add instance', async function () {
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
await loadFixture(fixture)
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(() =>
proposalCreator
.connect(sender)
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
expect(await proposal.token()).to.be.equal(config.COMP)
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
expect(await proposal.numInstances()).to.be.equal(1)
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
// propose proposal ---------------------------------------------
let response, id, state
gov = await gov.connect(tornWhale)
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
response = await gov.propose(proposal.address, 'COMP token instance proposal')
id = await gov.latestProposalIds(tornWhale.address)
state = await gov.state(id)
const { events } = await response.wait()
const args = events.find(({ event }) => event == 'ProposalCreated').args
expect(args.id).to.be.equal(id)
expect(args.proposer).to.be.equal(tornWhale.address)
expect(args.target).to.be.equal(proposal.address)
expect(args.description).to.be.equal('COMP token instance proposal')
expect(state).to.be.equal(ProposalState.Pending)
// execute proposal ---------------------------------------------
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
await expect(gov.castVote(id, true)).to.not.be.reverted
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
await minewait(
(
await gov.VOTING_PERIOD()
)
.add(await gov.EXECUTION_DELAY())
.add(96400)
.toNumber(),
)
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
let tx = await gov.execute(id)
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
// check instance initialization --------------------------------
let receipt = await tx.wait()
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
expect(await instance.token()).to.be.equal(config.COMP)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100'))
const instanceData = await instanceRegistry.instances(instance.address)
expect(instanceData.isERC20).to.be.equal(true)
expect(instanceData.token).to.be.equal(config.COMP)
expect(instanceData.state).to.be.equal(1)
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
expect(instanceData.protocolFeePercentage).to.be.equal(30)
})
it('Should successfully deploy/propose/execute proposal - add instances', async function () {
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
await loadFixture(fixture)
const denominations = [
ethers.utils.parseEther('1'),
ethers.utils.parseEther('10'),
ethers.utils.parseEther('100'),
ethers.utils.parseEther('1000'),
]
const numInstances = denominations.length
const protocolFees = [30, 30, 30, 30]
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(() =>
proposalCreator.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
expect(await proposal.token()).to.be.equal(config.COMP)
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
expect(await proposal.numInstances()).to.be.equal(numInstances)
for (let i = 0; i < numInstances; i++) {
expect(await proposal.protocolFeeByIndex(i)).to.be.equal(protocolFees[i])
expect(await proposal.denominationByIndex(i)).to.be.equal(denominations[i])
}
// propose proposal ---------------------------------------------
let response, id, state
gov = await gov.connect(tornWhale)
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
response = await gov.propose(proposal.address, 'COMP token instances proposal')
id = await gov.latestProposalIds(tornWhale.address)
state = await gov.state(id)
const { events } = await response.wait()
const args = events.find(({ event }) => event == 'ProposalCreated').args
expect(args.id).to.be.equal(id)
expect(args.proposer).to.be.equal(tornWhale.address)
expect(args.target).to.be.equal(proposal.address)
expect(args.description).to.be.equal('COMP token instances proposal')
expect(state).to.be.equal(ProposalState.Pending)
// execute proposal ---------------------------------------------
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
await expect(gov.castVote(id, true)).to.not.be.reverted
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
await minewait(
(
await gov.VOTING_PERIOD()
)
.add(await gov.EXECUTION_DELAY())
.add(96400)
.toNumber(),
)
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
await gov.execute(id)
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
// check instances initialization -------------------------------
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
for (let i = 0; i < numInstances; i++) {
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
expect(await instance.token()).to.be.equal(config.COMP)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(denominations[i])
let instanceData = await instanceRegistry.instances(instance.address)
expect(instanceData.isERC20).to.be.equal(true)
expect(instanceData.token).to.be.equal(config.COMP)
expect(instanceData.state).to.be.equal(1)
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
expect(instanceData.protocolFeePercentage).to.be.equal(protocolFees[i])
}
})
it('Should successfully deploy proposal with permit', async function () {
let { instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(
fixture,
)
const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex'))
const sender = await ethers.getSigner(publicKey.slice(2))
await expect(() =>
tornToken.connect(tornWhale).transfer(sender.address, config.creationFee),
).to.changeTokenBalances(
tornToken,
[tornWhale, sender],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
// prepare permit data
const domain = {
name: await tornToken.name(),
version: '1',
chainId: 1,
verifyingContract: tornToken.address,
}
const curTimestamp = Math.trunc(new Date().getTime() / 1000)
const args = {
owner: sender,
spender: proposalCreator.address,
value: config.creationFee,
nonce: 0,
deadline: curTimestamp + 1000,
}
const permitSigner = new PermitSigner(domain, args)
const signature = await permitSigner.getSignature(privateKey)
const signer = await permitSigner.getSignerAddress(args, signature.hex)
expect(signer).to.equal(sender.address)
await expect(() =>
proposalCreator.createProposalPermit(
config.COMP,
3000,
[ethers.utils.parseEther('100')],
[30],
sender.address,
args.deadline.toString(),
signature.v,
signature.r,
signature.s,
),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
expect(await proposal.token()).to.be.equal(config.COMP)
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
expect(await proposal.numInstances()).to.be.equal(1)
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
})
it('Should deposit and withdraw into the new instance', async function () {
let { sender, proposalCreator, gov, tornWhale, tornToken, router, compToken, compWhale } =
await loadFixture(fixture)
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(() =>
proposalCreator
.connect(sender)
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
// propose proposal ---------------------------------------------
let id
gov = await gov.connect(tornWhale)
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
await gov.propose(proposal.address, 'COMP token instance proposal')
id = await gov.latestProposalIds(tornWhale.address)
// execute proposal ---------------------------------------------
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
await expect(gov.castVote(id, true)).to.not.be.reverted
await minewait(
(
await gov.VOTING_PERIOD()
)
.add(await gov.EXECUTION_DELAY())
.add(96400)
.toNumber(),
)
let tx = await gov.execute(id)
let receipt = await tx.wait()
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
// check instance work ------------------------------------------
const depo = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31),
})
const value = ethers.utils.parseEther('100')
await compToken.connect(compWhale).transfer(sender.address, value)
await compToken.connect(sender).approve(router.address, value)
await expect(() => router.deposit(instance.address, toHex(depo.commitment), [])).to.changeTokenBalances(
compToken,
[sender, instance],
[BigNumber.from(0).sub(value), value],
)
let pevents = await instance.queryFilter('Deposit')
await initialize({ merkleTreeHeight: 20 })
const { proof, args } = await generateProof({
deposit: depo,
recipient: sender.address,
events: pevents,
})
await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeTokenBalances(
compToken,
[instance, sender],
[BigNumber.from(0).sub(value), value],
)
})
it('Should not deploy proposal with incorrect Uniswap pool', async function () {
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(
proposalCreator
.connect(sender)
.createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]),
).to.be.revertedWith('Uniswap pool is not exist')
await expect(
proposalCreator
.connect(sender)
.createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]),
).to.be.revertedWith('Uniswap pool TWAP slots number is low')
})
it('Should not deploy proposal with incorrect protocol fee', async function () {
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(
proposalCreator
.connect(sender)
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [10300]),
).to.be.revertedWith('Protocol fee is more than 100%')
})
it('Should successfully deploy/propose/execute proposal - add native instance', async function () {
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
await loadFixture(fixture)
const denomination = ethers.utils.parseEther('1.5')
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(() =>
proposalCreator.connect(sender).createProposalApprove(addressZero, 0, [denomination], [30]),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
expect(await proposal.token()).to.be.equal(addressZero)
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0)
expect(await proposal.numInstances()).to.be.equal(1)
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
expect(await proposal.denominationByIndex(0)).to.be.equal(denomination)
// propose proposal ---------------------------------------------
let response, id, state
gov = await gov.connect(tornWhale)
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
response = await gov.propose(proposal.address, 'ETH 1.5 instance proposal')
id = await gov.latestProposalIds(tornWhale.address)
state = await gov.state(id)
const { events } = await response.wait()
const args = events.find(({ event }) => event == 'ProposalCreated').args
expect(args.id).to.be.equal(id)
expect(args.proposer).to.be.equal(tornWhale.address)
expect(args.target).to.be.equal(proposal.address)
expect(args.description).to.be.equal('ETH 1.5 instance proposal')
expect(state).to.be.equal(ProposalState.Pending)
// execute proposal ---------------------------------------------
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
await expect(gov.castVote(id, true)).to.not.be.reverted
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
await minewait(
(
await gov.VOTING_PERIOD()
)
.add(await gov.EXECUTION_DELAY())
.add(96400)
.toNumber(),
)
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
let tx = await gov.execute(id)
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
// check instance initialization --------------------------------
let receipt = await tx.wait()
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
const instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(denomination)
const instanceData = await instanceRegistry.instances(instance.address)
expect(instanceData.isERC20).to.be.equal(false)
expect(instanceData.token).to.be.equal(addressZero)
expect(instanceData.state).to.be.equal(1)
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(0)
expect(instanceData.protocolFeePercentage).to.be.equal(30)
})
it('Should successfully deploy/propose/execute proposal - add native instances', async function () {
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
await loadFixture(fixture)
const denominations = [
ethers.utils.parseEther('1.5'),
ethers.utils.parseEther('10.5'),
ethers.utils.parseEther('100.5'),
ethers.utils.parseEther('1000.5'),
]
const numInstances = denominations.length
const protocolFees = [30, 30, 30, 30]
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(() =>
proposalCreator.connect(sender).createProposalApprove(addressZero, 0, denominations, protocolFees),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
expect(await proposal.token()).to.be.equal(addressZero)
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0)
expect(await proposal.numInstances()).to.be.equal(numInstances)
for (let i = 0; i < numInstances; i++) {
expect(await proposal.protocolFeeByIndex(i)).to.be.equal(protocolFees[i])
expect(await proposal.denominationByIndex(i)).to.be.equal(denominations[i])
}
// propose proposal ---------------------------------------------
let response, id, state
gov = await gov.connect(tornWhale)
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
response = await gov.propose(proposal.address, 'ETH instances proposal')
id = await gov.latestProposalIds(tornWhale.address)
state = await gov.state(id)
const { events } = await response.wait()
const args = events.find(({ event }) => event == 'ProposalCreated').args
expect(args.id).to.be.equal(id)
expect(args.proposer).to.be.equal(tornWhale.address)
expect(args.target).to.be.equal(proposal.address)
expect(args.description).to.be.equal('ETH instances proposal')
expect(state).to.be.equal(ProposalState.Pending)
// execute proposal ---------------------------------------------
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
await expect(gov.castVote(id, true)).to.not.be.reverted
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
await minewait(
(
await gov.VOTING_PERIOD()
)
.add(await gov.EXECUTION_DELAY())
.add(96400)
.toNumber(),
)
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
await gov.execute(id)
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
// check instances initialization -------------------------------
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
for (let i = 0; i < numInstances; i++) {
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
let instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(denominations[i])
let instanceData = await instanceRegistry.instances(instance.address)
expect(instanceData.isERC20).to.be.equal(false)
expect(instanceData.token).to.be.equal(addressZero)
expect(instanceData.state).to.be.equal(1)
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(0)
expect(instanceData.protocolFeePercentage).to.be.equal(protocolFees[i])
}
})
it('Should successfully deploy proposal with permit for native', async function () {
let { instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(
fixture,
)
const denomination = ethers.utils.parseEther('1.5')
const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex'))
const sender = await ethers.getSigner(publicKey.slice(2))
await expect(() =>
tornToken.connect(tornWhale).transfer(sender.address, config.creationFee),
).to.changeTokenBalances(
tornToken,
[tornWhale, sender],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
// prepare permit data
const domain = {
name: await tornToken.name(),
version: '1',
chainId: 1,
verifyingContract: tornToken.address,
}
const curTimestamp = Math.trunc(new Date().getTime() / 1000)
const args = {
owner: sender,
spender: proposalCreator.address,
value: config.creationFee,
nonce: 0,
deadline: curTimestamp + 1000,
}
const permitSigner = new PermitSigner(domain, args)
const signature = await permitSigner.getSignature(privateKey)
const signer = await permitSigner.getSignerAddress(args, signature.hex)
expect(signer).to.equal(sender.address)
await expect(() =>
proposalCreator.createProposalPermit(
addressZero,
0,
[denomination],
[30],
sender.address,
args.deadline.toString(),
signature.v,
signature.r,
signature.s,
),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
expect(await proposal.token()).to.be.equal(addressZero)
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(0)
expect(await proposal.numInstances()).to.be.equal(1)
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
expect(await proposal.denominationByIndex(0)).to.be.equal(denomination)
})
it('Should deposit and withdraw into the new native instance', async function () {
let { sender, proposalCreator, gov, tornWhale, tornToken, router } = await loadFixture(fixture)
const denomination = ethers.utils.parseEther('1.5')
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(() =>
proposalCreator.connect(sender).createProposalApprove(addressZero, 0, [denomination], [30]),
).to.changeTokenBalances(
tornToken,
[sender, gov],
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
)
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
const proposal = await ethers.getContractAt(
'AddInstanceProposal',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
// propose proposal ---------------------------------------------
let id
gov = await gov.connect(tornWhale)
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
await gov.propose(proposal.address, 'ETH instance proposal')
id = await gov.latestProposalIds(tornWhale.address)
// execute proposal ---------------------------------------------
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
await expect(gov.castVote(id, true)).to.not.be.reverted
await minewait(
(
await gov.VOTING_PERIOD()
)
.add(await gov.EXECUTION_DELAY())
.add(96400)
.toNumber(),
)
let tx = await gov.execute(id)
let receipt = await tx.wait()
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
const instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
// check instance work ------------------------------------------
const depo = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31),
})
await expect(() =>
router.deposit(instance.address, toHex(depo.commitment), [], { value: denomination }),
).to.changeEtherBalances([sender, instance], [BigNumber.from(0).sub(denomination), denomination])
let pevents = await instance.queryFilter('Deposit')
await initialize({ merkleTreeHeight: 20 })
const { proof, args } = await generateProof({
deposit: depo,
recipient: sender.address,
events: pevents,
})
await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeEtherBalances(
[instance, sender],
[BigNumber.from(0).sub(denomination), denomination],
)
})
it('Should not deploy native currency proposal with incorrect protocol fee', async function () {
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
// deploy proposal ----------------------------------------------
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
await tornToken.approve(proposalCreator.address, config.creationFee)
await expect(
proposalCreator
.connect(sender)
.createProposalApprove(addressZero, 0, [ethers.utils.parseEther('1.5')], [10300]),
).to.be.revertedWith('Protocol fee is more than 100%')
})
})

@ -1,296 +0,0 @@
const hre = require('hardhat')
const { ethers, waffle } = hre
const { loadFixture } = waffle
const { expect } = require('chai')
const { BigNumber } = require('@ethersproject/bignumber')
const config = require('../config')
const { getSignerFromAddress } = require('./utils')
const { generate } = require('../src/generateAddresses')
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
describe('Sidechain Instance Factory Tests', () => {
const addressZero = ethers.constants.AddressZero
async function fixture() {
const [sender, deployer] = await ethers.getSigners()
const owner = await getSignerFromAddress(config.admin)
await sender.sendTransaction({
to: config.admin,
value: ethers.utils.parseEther('1'),
})
const compWhale = await getSignerFromAddress(config.compWhale)
const compToken = await ethers.getContractAt(
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
config.COMP,
)
// deploy InstanceFactory with CREATE2
const singletonFactory = await ethers.getContractAt(
'SingletonFactory',
config.singletonFactoryVerboseWrapper,
)
const contracts = await generate()
if ((await ethers.provider.getCode(contracts.sidechainFactory.implementation.address)) == '0x') {
await singletonFactory.deploy(contracts.sidechainFactory.implementation.bytecode, config.salt, {
gasLimit: config.deployGasLimit,
})
}
if ((await ethers.provider.getCode(contracts.sidechainFactory.proxy.address)) == '0x') {
await singletonFactory.deploy(contracts.sidechainFactory.proxy.bytecode, config.salt, {
gasLimit: config.deployGasLimit,
})
}
const instanceFactory = await ethers.getContractAt(
'SidechainInstanceFactory',
contracts.sidechainFactory.proxy.address,
)
return {
sender,
deployer,
owner,
compToken,
compWhale,
instanceFactory,
}
}
it('Should have initialized all successfully', async function () {
const { sender, compToken, instanceFactory } = await loadFixture(fixture)
expect(sender.address).to.exist
expect(compToken.address).to.exist
expect(instanceFactory.address).to.exist
})
it('Should set correct params for factory', async function () {
const { instanceFactory } = await loadFixture(fixture)
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
expect(await instanceFactory.ERC20Impl()).to.exist
expect(await instanceFactory.nativeCurImpl()).to.exist
})
it('Admin should be able to set factory params', async function () {
let { instanceFactory, owner } = await loadFixture(fixture)
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
instanceFactory = await instanceFactory.connect(owner)
await instanceFactory.generateNewImplementation(addressZero, addressZero)
await instanceFactory.setMerkleTreeHeight(1)
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
})
it('Should successfully add instance', async function () {
let { sender, instanceFactory } = await loadFixture(fixture)
// deploy instance
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP)
// check instance initialization
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
const instance = await ethers.getContractAt(
'ERC20TornadoCloneable',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await instance.token()).to.be.equal(config.COMP)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000'))
// try to deploy the same instance again
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP)
// check that instance has not been created - no new NewInstanceCloneCreated event
let curLogs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
expect(curLogs.length).to.be.equal(logs.length)
})
it('Should successfully add instances', async function () {
let { sender, instanceFactory } = await loadFixture(fixture)
const denominations = [
ethers.utils.parseEther('1'),
ethers.utils.parseEther('10'),
ethers.utils.parseEther('100'),
ethers.utils.parseEther('1000'),
]
const numInstances = denominations.length
// deploy instances
await instanceFactory.connect(sender).createInstanceClones(config.COMP, denominations)
// check instance initialization
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
for (let i = 0; i < numInstances; i++) {
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
expect(await instance.token()).to.be.equal(config.COMP)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(denominations[i])
}
})
it('Should deposit and withdraw into the new instance', async function () {
let { sender, instanceFactory, compToken, compWhale } = await loadFixture(fixture)
// deploy instance
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('100'), config.COMP)
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
const instance = await ethers.getContractAt(
'ERC20TornadoCloneable',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
// check instance work ------------------------------------------
const depo = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31),
})
const value = ethers.utils.parseEther('100')
await compToken.connect(compWhale).transfer(sender.address, value)
await compToken.connect(sender).approve(instance.address, value)
await expect(() => instance.deposit(toHex(depo.commitment), [])).to.changeTokenBalances(
compToken,
[sender, instance],
[BigNumber.from(0).sub(value), value],
)
let pevents = await instance.queryFilter('Deposit')
await initialize({ merkleTreeHeight: 20 })
const { proof, args } = await generateProof({
deposit: depo,
recipient: sender.address,
events: pevents,
})
await expect(() => instance.withdraw(proof, ...args)).to.changeTokenBalances(
compToken,
[instance, sender],
[BigNumber.from(0).sub(value), value],
)
})
it('Should successfully add native currency instance', async function () {
let { sender, instanceFactory } = await loadFixture(fixture)
const denomination = ethers.utils.parseEther('1')
// deploy instance
await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero)
// check instance initialization
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
const instance = await ethers.getContractAt(
'ETHTornadoCloneable',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(denomination)
// try to deploy the same instance again
await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero)
// check that instance has not been created - no new NewInstanceCloneCreated event
let curLogs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
expect(curLogs.length).to.be.equal(logs.length)
})
it('Should successfully add native currency instances', async function () {
let { sender, instanceFactory } = await loadFixture(fixture)
const denominations = [
ethers.utils.parseEther('1'),
ethers.utils.parseEther('10'),
ethers.utils.parseEther('100'),
ethers.utils.parseEther('1000'),
]
const numInstances = denominations.length
// deploy instances
await instanceFactory.connect(sender).createInstanceClones(addressZero, denominations)
// check instance initialization
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
for (let i = 0; i < numInstances; i++) {
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
let instance = await ethers.getContractAt('ETHTornadoCloneable', instanceAddr)
expect(await instance.verifier()).to.be.equal(config.verifier)
expect(await instance.hasher()).to.be.equal(config.hasher)
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
expect(await instance.denomination()).to.equal(denominations[i])
}
})
it('Should deposit and withdraw into the new native currency instance', async function () {
let { sender, instanceFactory } = await loadFixture(fixture)
const denomination = ethers.utils.parseEther('1.5')
// deploy instance
await instanceFactory.connect(sender).createInstanceClone(denomination, addressZero)
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
const instance = await ethers.getContractAt(
'ETHTornadoCloneable',
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
)
// check instance work ------------------------------------------
const depo = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31),
})
await expect(() =>
instance.connect(sender).deposit(toHex(depo.commitment), {
value: denomination,
}),
).to.changeEtherBalances([sender, instance], [BigNumber.from(0).sub(denomination), denomination])
let pevents = await instance.queryFilter('Deposit')
await initialize({ merkleTreeHeight: 20 })
const { proof, args } = await generateProof({
deposit: depo,
recipient: sender.address,
events: pevents,
})
await expect(() => instance.withdraw(proof, ...args)).to.changeEtherBalances(
[instance, sender],
[BigNumber.from(0).sub(denomination), denomination],
)
})
})

@ -1,43 +0,0 @@
/* global ethers, network */
async function setTime(timestamp) {
await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp])
}
async function takeSnapshot() {
return await ethers.provider.send('evm_snapshot', [])
}
async function revertSnapshot(id) {
await ethers.provider.send('evm_revert', [id])
}
async function advanceTime(sec) {
const now = (await ethers.provider.getBlock('latest')).timestamp
await setTime(now + sec)
}
async function getSignerFromAddress(address) {
await network.provider.request({
method: 'hardhat_impersonateAccount',
params: [address],
})
let signer = await ethers.provider.getSigner(address)
signer.address = signer._address
return signer
}
async function minewait(time) {
await ethers.provider.send('evm_increaseTime', [time])
await ethers.provider.send('evm_mine', [])
}
module.exports = {
setTime,
advanceTime,
takeSnapshot,
revertSnapshot,
getSignerFromAddress,
minewait,
}