start writing deployment script, work on reintroducing tests
Signed-off-by: T-Hax <>
This commit is contained in:
parent
197d593504
commit
a8495d76a4
11
.env.example
11
.env.example
@ -1,5 +1,6 @@
|
||||
ETHERSCAN_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
|
||||
ALCHEMY_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
|
||||
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
|
||||
INFURA_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
|
||||
FORKNET_RPC_URL=https://link-to.fork
|
||||
ETHERSCAN_KEY=
|
||||
PRIVATE_KEY=
|
||||
MAINNET_RPC_URL=
|
||||
RINKEBY_RPC_URL=
|
||||
GOERLI_RPC_URL=
|
||||
SEPOLIA_RPC_URL=
|
@ -17,5 +17,5 @@ module.exports = {
|
||||
admin: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', // TODO
|
||||
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
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;
|
||||
|
||||
for (uint256 i = 0; i < howMany; i++) {
|
||||
// Read out the data for the new instance
|
||||
InstanceAdditionData memory data = toAdd[i];
|
||||
|
||||
// Safely calculate the denomination
|
||||
uint256 denomination = uint256(data.smallDenomination).mul(10 ** uint256(data.base10Exponent));
|
||||
|
||||
// Create the instance
|
||||
address instance = instanceFactory.createInstanceClone(denomination, data.tokenAddress);
|
||||
|
||||
// Prepare the data for the registry.
|
||||
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
||||
data.tokenAddress != address(0),
|
||||
IERC20(data.tokenAddress),
|
||||
@ -54,8 +58,10 @@ contract InstanceAdditionProposal {
|
||||
data.protocolFee
|
||||
);
|
||||
|
||||
// Convert the data.
|
||||
IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData);
|
||||
|
||||
// Update the instance data in the registry.
|
||||
instanceRegistry.updateInstance(tornadoForUpdate);
|
||||
|
||||
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];
|
||||
|
||||
// 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)) {
|
||||
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.");
|
||||
}
|
||||
|
||||
// 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];
|
||||
|
||||
|
@ -43,8 +43,9 @@ module.exports = {
|
||||
networks: {
|
||||
hardhat: {
|
||||
forking: {
|
||||
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
url: process.env.MAINNET_RPC_URL,
|
||||
blockNumber: 14250000,
|
||||
httpHeaders: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0' },
|
||||
},
|
||||
chainId: 1,
|
||||
initialBaseFeePerGas: 5,
|
||||
@ -53,33 +54,46 @@ module.exports = {
|
||||
blockGasLimit: 50000000,
|
||||
},
|
||||
rinkeby: {
|
||||
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
|
||||
url: process.env.RINKEBY_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' },
|
||||
},
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
|
||||
url: process.env.GOERLI_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' },
|
||||
},
|
||||
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: {
|
||||
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
url: process.env.MAINNET_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' },
|
||||
},
|
||||
...(typeof process.env.FORKNET_RPC_URL === 'string' && {
|
||||
forknet: {
|
||||
url: process.env.FORKNET_RPC_URL,
|
||||
accounts: process.env.PRIVATE_KEY
|
||||
? [process.env.PRIVATE_KEY]
|
||||
: { mnemonic: 'test test test test test junk' },
|
||||
// What the fuck is this
|
||||
// ...(typeof process.env.FORKNET_RPC_URL === 'string' && {
|
||||
// forknet: {
|
||||
// url: process.env.FORKNET_RPC_URL,
|
||||
// accounts: process.env.PRIVATE_KEY
|
||||
// ? [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: {
|
||||
overwrite: true,
|
||||
runOnCompile: true,
|
||||
|
83
scripts/deploy.js
Normal file
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,
|
||||
}
|
Loading…
Reference in New Issue
Block a user