mirror of
https://github.com/tornadocash/tornado-pool-factory
synced 2024-02-02 15:04:08 +01:00
TC-128 native currency pools
This commit is contained in:
parent
9d168730f2
commit
466e546734
40
README.md
40
README.md
@ -4,28 +4,28 @@
|
||||
|
||||
This repository contains:
|
||||
|
||||
1. `InstanceFactory` - instance factory for the creation new Tornado ERC20 pools
|
||||
2. `InstanceFactoryWithRegistry` - governance proposal factory for the addition of new Tornado ERC20 instances to the Tornado router
|
||||
1. `InstanceFactory` - instance factory for the creation new Tornado ERC20/native pools
|
||||
2. `InstanceProposalCreator` - governance proposal factory for the addition of new Tornado instances to the Tornado router
|
||||
|
||||
### InstanceFactory
|
||||
|
||||
Anyone can create a new ERC20 instance by calling `createInstanceClone` method of the factory with parameters:
|
||||
Anyone can create a new instance by calling `createInstanceClone` method of the factory with parameters:
|
||||
|
||||
1. `address token` - address of ERC20 token for a new instance
|
||||
1. `address token` - address of ERC20 token for a new instance, zero address for the native instance
|
||||
2. `uint256 denomination` - denomination for new instance (tokens can only be deposited in certain denominations into instances)
|
||||
|
||||
### InstanceFactoryWithRegistry
|
||||
### InstanceProposalCreator
|
||||
|
||||
Anyone can create governance proposal for the addition of a new ERC20 instance by calling `createProposalApprove/createProposalPermit` method of the factory with parameters (proposal creation fee in TORN is charged from sender):
|
||||
Anyone can create governance proposal for the addition of a new instance by calling `createProposalApprove/createProposalPermit` method of the factory with parameters (proposal creation fee in TORN is charged from sender):
|
||||
|
||||
1. `address token` - address of ERC20 token for a new instance
|
||||
2. `uint24 uniswapPoolSwappingFee` - fee value of Uniswap instance which will be used for `TORN/token` price determination. `3000` means 0.3% fee Uniswap pool.
|
||||
1. `address token` - address of ERC20 token for a new instance, zero address for the native instance
|
||||
2. `uint24 uniswapPoolSwappingFee` - fee value of Uniswap instance which will be used for `TORN/token` price determination. `3000` means 0.3% fee Uniswap pool. Zero value for the native instance.
|
||||
3. `uint256[] denominations` - list of denominations for each new instance (tokens can only be deposited in certain denominations into instances).
|
||||
4. `uint32[] protocolFees` - list of protocol fees for each new instance (this fee is only charged from registrated relayer during withdrawal process). `100` means 1% of instance denomination fee for withdrawal throw registrated relayer.
|
||||
|
||||
## Factory parameters
|
||||
|
||||
### InstanceFactoryWithRegistry
|
||||
### InstanceProposalCreator
|
||||
|
||||
1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 4 instances at once.
|
||||
2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`.
|
||||
@ -34,7 +34,7 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b
|
||||
## Warnings
|
||||
|
||||
1. This version of the factory creates a proposal for **immutable** Tornado instance initialization.
|
||||
2. For `InstanceFactoryWithRegistry` users should manually propose a proposal after its creation using the factory (in governance UI for example). As `propose()` method caller must have 1000 TORN locked in the governance. Moreover, the proposer can't propose more than one proposal simultaneously.
|
||||
2. For `InstanceProposalCreator` users should manually propose a proposal after its creation using the factory (in governance UI for example). As `propose()` method caller must have 1000 TORN locked in the governance. Moreover, the proposer can't propose more than one proposal simultaneously.
|
||||
|
||||
## Tests
|
||||
|
||||
@ -61,10 +61,12 @@ Check config.js for actual values.
|
||||
|
||||
With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be:
|
||||
|
||||
1. `MultipleInstanceFactory` - `0x8a1BCFc608DdF6c4715277a24310B9E8ecc4c110`
|
||||
2. `MultipleInstanceFactory proxy` - `0x0F9AE1d1ABbDd441B813ada0B038df130b2a6A86`
|
||||
3. `InstanceFactoryWithRegistry` - `0x557fB18B66088728Ea975136de46714983aa4f2E`
|
||||
4. `InstanceFactoryWithRegistry proxy` - `0x4D1b1c294dA4D14aC0e0Eed7BcD4Db3fe2bDe4C3`
|
||||
1. `SidechainInstanceFactory` - `0x0a0601c2E952aC53bb95b1A81764Fc730C9bBDd5`
|
||||
2. `SidechainInstanceFactory proxy` - `0xb4838f15185E4A7E2ED8534c89b7AC92aC927C9b`
|
||||
3. `InstanceFactory` - `0xa7610A8292850f516603bA0066A731109Dfe854E`
|
||||
4. `InstanceFactory proxy` - `0x0f9B6646815f118d671084cd73Ea8713b18be354`
|
||||
5. `InstanceProposalCreator` - `0x39F00b914f8DBD28082D1f1ae9b3210d7F7B404f`
|
||||
6. `InstanceProposalCreator proxy` - `0xA2980DAfcdAf61C6d39FE63a6360907b69e840c8`
|
||||
|
||||
Check addresses with current config:
|
||||
|
||||
@ -73,19 +75,19 @@ Check addresses with current config:
|
||||
node -e 'require("./src/generateAddresses").generateWithLog()'
|
||||
```
|
||||
|
||||
Deploy MultipleInstanceFactory:
|
||||
Deploy SidechainInstanceFactory:
|
||||
|
||||
```shell
|
||||
yarn hardhat run scripts/deployMultipleInstanceFactory.js --network mainnet
|
||||
yarn hardhat run scripts/deploySidechainInstanceFactory.js --network mainnet
|
||||
```
|
||||
|
||||
Deploy InstanceFactoryWithRegistry:
|
||||
Deploy InstanceProposalCreator:
|
||||
|
||||
```shell
|
||||
yarn hardhat run scripts/deployInstanceFactoryWithRegistry.js --network mainnet
|
||||
yarn hardhat run scripts/deployInstanceProposalCreator.js --network mainnet
|
||||
```
|
||||
|
||||
Verify InstanceFactory on Etherscan:
|
||||
Verify on Etherscan:
|
||||
|
||||
```
|
||||
yarn hardhat verify --network <network-name> <contract-address> <constructor-arguments>
|
||||
|
@ -4,10 +4,10 @@ pragma solidity 0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "./interfaces/IInstanceRegistry.sol";
|
||||
import "./InstanceFactory.sol";
|
||||
import "./interfaces/IInstanceFactory.sol";
|
||||
|
||||
contract AddInstanceProposal {
|
||||
InstanceFactory public immutable instanceFactory;
|
||||
IInstanceFactory public immutable instanceFactory;
|
||||
IInstanceRegistry public immutable instanceRegistry;
|
||||
address public immutable token;
|
||||
uint24 public immutable uniswapPoolSwappingFee;
|
||||
@ -32,7 +32,7 @@ contract AddInstanceProposal {
|
||||
uint256[] memory _denominations,
|
||||
uint32[] memory _protocolFees
|
||||
) {
|
||||
instanceFactory = InstanceFactory(_instanceFactory);
|
||||
instanceFactory = IInstanceFactory(_instanceFactory);
|
||||
instanceRegistry = IInstanceRegistry(_instanceRegistry);
|
||||
token = _token;
|
||||
uniswapPoolSwappingFee = _uniswapPoolSwappingFee;
|
||||
@ -57,7 +57,7 @@ contract AddInstanceProposal {
|
||||
address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token);
|
||||
|
||||
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
||||
true,
|
||||
token != address(0),
|
||||
IERC20(token),
|
||||
IInstanceRegistry.InstanceState.ENABLED,
|
||||
uniswapPoolSwappingFee,
|
||||
|
26
contracts/ETHTornadoCloneable.sol
Normal file
26
contracts/ETHTornadoCloneable.sol
Normal file
@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "tornado-core/contracts/ETHTornado.sol";
|
||||
|
||||
contract ETHTornadoCloneable is ETHTornado {
|
||||
constructor(address verifier, address hasher) ETHTornado(IVerifier(verifier), IHasher(hasher), 1, 1) {}
|
||||
|
||||
function init(uint256 _denomination, uint32 _merkleTreeHeight) external {
|
||||
require(denomination == 0 && levels == 0, "already initialized");
|
||||
|
||||
require(_denomination > 0, "denomination should be greater than 0");
|
||||
denomination = _denomination;
|
||||
require(_merkleTreeHeight > 0, "_levels should be greater than zero");
|
||||
require(_merkleTreeHeight < 32, "_levels should be less than 32");
|
||||
levels = _merkleTreeHeight;
|
||||
|
||||
for (uint32 i = 0; i < _merkleTreeHeight; i++) {
|
||||
filledSubtrees[i] = zeros(i);
|
||||
}
|
||||
|
||||
roots[0] = zeros(_merkleTreeHeight - 1);
|
||||
}
|
||||
}
|
@ -7,19 +7,21 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
|
||||
import "@openzeppelin/contracts/proxy/Clones.sol";
|
||||
import "./ERC20TornadoCloneable.sol";
|
||||
import "./ETHTornadoCloneable.sol";
|
||||
|
||||
contract InstanceFactory is Initializable {
|
||||
using Clones for address;
|
||||
using Address for address;
|
||||
|
||||
address public admin;
|
||||
address public implementation;
|
||||
address public ERC20Impl;
|
||||
address public nativeCurImpl;
|
||||
address public verifier;
|
||||
address public hasher;
|
||||
uint32 public merkleTreeHeight;
|
||||
|
||||
event NewTreeHeightSet(uint32 indexed newTreeHeight);
|
||||
event NewImplementationSet(address indexed newImplemenentation, address verifier, address hasher);
|
||||
event NewImplementationSet(address indexed ERC20Impl, address indexed nativeCurImpl, address verifier, address hasher);
|
||||
event NewInstanceCloneCreated(address indexed clone);
|
||||
|
||||
modifier onlyAdmin() {
|
||||
@ -43,30 +45,49 @@ contract InstanceFactory is Initializable {
|
||||
merkleTreeHeight = _merkleTreeHeight;
|
||||
admin = _admin;
|
||||
|
||||
ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher);
|
||||
implementation = address(implContract);
|
||||
ERC20TornadoCloneable ERC20ImplContract = new ERC20TornadoCloneable(_verifier, _hasher);
|
||||
ERC20Impl = address(ERC20ImplContract);
|
||||
ETHTornadoCloneable nativeCurImplContract = new ETHTornadoCloneable(_verifier, _hasher);
|
||||
nativeCurImpl = address(nativeCurImplContract);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates new Tornado instance.
|
||||
* @param _denomination denomination of new Tornado instance
|
||||
* @param _token address of ERC20 token for a new instance
|
||||
* @param _token address of ERC20 token for a new instance, if zero address, then it will be ETH
|
||||
*/
|
||||
function createInstanceClone(uint256 _denomination, address _token) public virtual returns (address) {
|
||||
function createInstanceClone(uint256 _denomination, address _token) public virtual onlyAdmin returns (address clone) {
|
||||
return _createInstanceClone(_denomination, _token);
|
||||
}
|
||||
|
||||
function _createInstanceClone(uint256 _denomination, address _token) internal returns (address clone) {
|
||||
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
||||
|
||||
address newClone = implementation.predictDeterministicAddress(salt);
|
||||
if (!newClone.isContract()) {
|
||||
implementation.cloneDeterministic(salt);
|
||||
emit NewInstanceCloneCreated(newClone);
|
||||
ERC20TornadoCloneable(newClone).init(_denomination, merkleTreeHeight, _token);
|
||||
if (_token == address(0)) {
|
||||
clone = nativeCurImpl.predictDeterministicAddress(salt);
|
||||
if (!clone.isContract()) {
|
||||
nativeCurImpl.cloneDeterministic(salt);
|
||||
emit NewInstanceCloneCreated(clone);
|
||||
ETHTornadoCloneable(clone).init(_denomination, merkleTreeHeight);
|
||||
}
|
||||
} else {
|
||||
clone = ERC20Impl.predictDeterministicAddress(salt);
|
||||
if (!clone.isContract()) {
|
||||
ERC20Impl.cloneDeterministic(salt);
|
||||
emit NewInstanceCloneCreated(clone);
|
||||
ERC20TornadoCloneable(clone).init(_denomination, merkleTreeHeight, _token);
|
||||
}
|
||||
}
|
||||
return newClone;
|
||||
return clone;
|
||||
}
|
||||
|
||||
function getInstanceAddress(uint256 _denomination, address _token) public view returns (address) {
|
||||
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
||||
return implementation.predictDeterministicAddress(salt);
|
||||
if (_token == address(0)) {
|
||||
return nativeCurImpl.predictDeterministicAddress(salt);
|
||||
} else {
|
||||
return ERC20Impl.predictDeterministicAddress(salt);
|
||||
}
|
||||
}
|
||||
|
||||
function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyAdmin {
|
||||
@ -77,7 +98,8 @@ contract InstanceFactory is Initializable {
|
||||
function generateNewImplementation(address _verifier, address _hasher) external onlyAdmin {
|
||||
verifier = _verifier;
|
||||
hasher = _hasher;
|
||||
implementation = address(new ERC20TornadoCloneable(_verifier, _hasher));
|
||||
emit NewImplementationSet(implementation, _verifier, _hasher);
|
||||
ERC20Impl = address(new ERC20TornadoCloneable(_verifier, _hasher));
|
||||
nativeCurImpl = address(new ETHTornadoCloneable(_verifier, _hasher));
|
||||
emit NewImplementationSet(ERC20Impl, nativeCurImpl, _verifier, _hasher);
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,19 @@ pragma abicoder v2;
|
||||
|
||||
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||
import "./AddInstanceProposal.sol";
|
||||
import "./InstanceFactory.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 InstanceFactoryWithRegistry is InstanceFactory {
|
||||
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;
|
||||
@ -36,12 +38,14 @@ contract InstanceFactoryWithRegistry is InstanceFactory {
|
||||
|
||||
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);
|
||||
@ -53,28 +57,11 @@ contract InstanceFactoryWithRegistry is InstanceFactory {
|
||||
* @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(
|
||||
address _verifier,
|
||||
address _hasher,
|
||||
uint32 _merkleTreeHeight,
|
||||
address _governance,
|
||||
uint16 _TWAPSlotsMin,
|
||||
uint256 _creationFee
|
||||
) external initializer {
|
||||
initialize(_verifier, _hasher, _merkleTreeHeight, _governance);
|
||||
function initialize(uint16 _TWAPSlotsMin, uint256 _creationFee) external initializer {
|
||||
TWAPSlotsMin = _TWAPSlotsMin;
|
||||
creationFee = _creationFee;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates new Tornado instances. Throws if called by any account other than the Governance.
|
||||
* @param _denomination denomination of new Tornado instance
|
||||
* @param _token address of ERC20 token for a new instance
|
||||
*/
|
||||
function createInstanceClone(uint256 _denomination, address _token) public override onlyGovernance returns (address) {
|
||||
return super.createInstanceClone(_denomination, _token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates AddInstanceProposal with approve.
|
||||
* @param _token address of ERC20 token for a new instance
|
||||
@ -125,14 +112,14 @@ contract InstanceFactoryWithRegistry is InstanceFactory {
|
||||
uint256[] memory _denominations,
|
||||
uint32[] memory _protocolFees
|
||||
) internal returns (address) {
|
||||
require(_token.isContract(), "Token is not contract");
|
||||
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++) {
|
||||
if (_protocolFees[i] > 0) {
|
||||
require(_protocolFees[i] <= 10000, "Protocol fee is more than 100%");
|
||||
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");
|
||||
@ -144,19 +131,26 @@ contract InstanceFactoryWithRegistry is InstanceFactory {
|
||||
}
|
||||
|
||||
address proposal = address(
|
||||
new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees)
|
||||
new AddInstanceProposal(
|
||||
address(instanceFactory),
|
||||
instanceRegistry,
|
||||
_token,
|
||||
_uniswapPoolSwappingFee,
|
||||
_denominations,
|
||||
_protocolFees
|
||||
)
|
||||
);
|
||||
emit NewGovernanceProposalCreated(proposal);
|
||||
|
||||
return proposal;
|
||||
}
|
||||
|
||||
function setCreationFee(uint256 _creationFee) external onlyAdmin {
|
||||
function setCreationFee(uint256 _creationFee) external onlyGovernance {
|
||||
creationFee = _creationFee;
|
||||
emit NewCreationFeeSet(_creationFee);
|
||||
}
|
||||
|
||||
function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyAdmin {
|
||||
function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance {
|
||||
TWAPSlotsMin = _TWAPSlotsMin;
|
||||
emit NewTWAPSlotsMinSet(_TWAPSlotsMin);
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
|
||||
import "./InstanceFactory.sol";
|
||||
|
||||
contract MultipleInstanceFactory is InstanceFactory {
|
||||
/**
|
||||
* @dev Creates new Tornado instances.
|
||||
* @param _token address of ERC20 token for a new instance
|
||||
* @param _denominations list of denominations for each new instance
|
||||
*/
|
||||
function createInstanceClones(address _token, uint256[] memory _denominations) external returns (address[] memory) {
|
||||
address[] memory newClones = new address[](_denominations.length);
|
||||
for (uint256 i = 0; i < _denominations.length; i++) {
|
||||
newClones[i] = createInstanceClone(_denominations[i], _token);
|
||||
}
|
||||
return newClones;
|
||||
}
|
||||
}
|
29
contracts/SidechainInstanceFactory.sol
Normal file
29
contracts/SidechainInstanceFactory.sol
Normal file
@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
|
||||
import "./InstanceFactory.sol";
|
||||
|
||||
contract SidechainInstanceFactory is InstanceFactory {
|
||||
/**
|
||||
* @dev Creates new Tornado instance. Overriding to move onlyAdmin check for sidechains.
|
||||
* @param _denomination denomination of new Tornado instance
|
||||
* @param _token address of ERC20 token for a new instance, if zero address, then it will be ETH
|
||||
*/
|
||||
function createInstanceClone(uint256 _denomination, address _token) public override returns (address clone) {
|
||||
return _createInstanceClone(_denomination, _token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates new Tornado instances.
|
||||
* @param _token address of ERC20 token for a new instance
|
||||
* @param _denominations list of denominations for each new instance
|
||||
*/
|
||||
function createInstanceClones(address _token, uint256[] memory _denominations) external returns (address[] memory) {
|
||||
address[] memory newClones = new address[](_denominations.length);
|
||||
for (uint256 i = 0; i < _denominations.length; i++) {
|
||||
newClones[i] = _createInstanceClone(_denominations[i], _token);
|
||||
}
|
||||
return newClones;
|
||||
}
|
||||
}
|
8
contracts/interfaces/IInstanceFactory.sol
Normal file
8
contracts/interfaces/IInstanceFactory.sol
Normal file
@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
interface IInstanceFactory {
|
||||
function createInstanceClone(uint256 denomination, address token) external returns (address);
|
||||
}
|
@ -3,6 +3,7 @@ require('@nomiclabs/hardhat-waffle')
|
||||
require('@nomiclabs/hardhat-etherscan')
|
||||
require('hardhat-log-remover')
|
||||
require('solidity-coverage')
|
||||
require('hardhat-contract-sizer')
|
||||
|
||||
/**
|
||||
* @type import('hardhat/config').HardhatUserConfig
|
||||
|
@ -34,6 +34,7 @@
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"ethereum-waffle": "^3.4.0",
|
||||
"hardhat": "^2.4.3",
|
||||
"hardhat-contract-sizer": "^2.6.1",
|
||||
"hardhat-log-remover": "^2.0.2",
|
||||
"mocha-lcov-reporter": "^1.3.0",
|
||||
"prettier": "^2.3.2",
|
||||
|
@ -14,14 +14,14 @@ async function deploy({ address, bytecode, singletonFactory }) {
|
||||
async function main() {
|
||||
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
||||
const contracts = await generate()
|
||||
await deploy({ ...contracts.factoryWithRegistryContract.implementation, singletonFactory })
|
||||
await deploy({ ...contracts.factoryWithRegistryContract.proxy, singletonFactory })
|
||||
console.log(
|
||||
`Instance factory with registry contract have been deployed on ${contracts.factoryWithRegistryContract.implementation.address} address`,
|
||||
)
|
||||
console.log(
|
||||
`Instance factory with registry proxy contract have been deployed on ${contracts.factoryWithRegistryContract.proxy.address} address`,
|
||||
)
|
||||
await deploy({ ...contracts.factory.implementation, singletonFactory })
|
||||
console.log(`Instance factory contract have been deployed on ${contracts.factory.implementation.address}`)
|
||||
await deploy({ ...contracts.factory.proxy, singletonFactory })
|
||||
console.log(`Instance factory proxy contract have been deployed on ${contracts.factory.proxy.address}`)
|
||||
await deploy({ ...contracts.proposalCreator.implementation, singletonFactory })
|
||||
console.log(`Proposal creator have been deployed on ${contracts.proposalCreator.implementation.address}`)
|
||||
await deploy({ ...contracts.proposalCreator.proxy, singletonFactory })
|
||||
console.log(`Proposal creator proxy have been deployed on ${contracts.proposalCreator.proxy.address}`)
|
||||
}
|
||||
|
||||
main()
|
@ -14,13 +14,13 @@ async function deploy({ address, bytecode, singletonFactory }) {
|
||||
async function main() {
|
||||
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
||||
const contracts = await generate()
|
||||
await deploy({ ...contracts.factoryContract.implementation, singletonFactory })
|
||||
await deploy({ ...contracts.factoryContract.proxy, singletonFactory })
|
||||
await deploy({ ...contracts.sidechainFactory.implementation, singletonFactory })
|
||||
await deploy({ ...contracts.sidechainFactory.proxy, singletonFactory })
|
||||
console.log(
|
||||
`MultipleInstanceFactory contract have been deployed on ${contracts.factoryContract.implementation.address} address`,
|
||||
`SidechainInstanceFactory contract have been deployed on ${contracts.sidechainFactory.implementation.address} address`,
|
||||
)
|
||||
console.log(
|
||||
`MultipleInstanceFactory proxy contract have been deployed on ${contracts.factoryContract.proxy.address} address`,
|
||||
`SidechainInstanceFactory proxy contract have been deployed on ${contracts.sidechainFactory.proxy.address} address`,
|
||||
)
|
||||
}
|
||||
|
@ -36,52 +36,62 @@ async function upgradableContract({ contractName, implConstructorArgs, proxyCons
|
||||
}
|
||||
|
||||
async function generate(config = defaultConfig) {
|
||||
// factory contract -----------------------------------------------
|
||||
const FactoryFactory = await ethers.getContractFactory('MultipleInstanceFactory')
|
||||
const FactoryInitData = FactoryFactory.interface.encodeFunctionData('initialize', [
|
||||
// sidechain factory contract -------------------------------------
|
||||
const SidechainFactory = await ethers.getContractFactory('SidechainInstanceFactory')
|
||||
const SidechainFactoryInitData = SidechainFactory.interface.encodeFunctionData('initialize', [
|
||||
config.verifier,
|
||||
config.hasher,
|
||||
config.merkleTreeHeight,
|
||||
config.admin,
|
||||
])
|
||||
|
||||
const factoryContract = await upgradableContract({
|
||||
contractName: 'MultipleInstanceFactory',
|
||||
const sidechainFactory = await upgradableContract({
|
||||
contractName: 'SidechainInstanceFactory',
|
||||
implConstructorArgs: [],
|
||||
proxyConstructorArgs: [config.admin, FactoryInitData],
|
||||
proxyConstructorArgs: [config.admin, SidechainFactoryInitData],
|
||||
salt: config.salt,
|
||||
})
|
||||
|
||||
// factory with registry contract ---------------------------------
|
||||
const FactoryWithRegistryFactory = await ethers.getContractFactory('InstanceFactoryWithRegistry')
|
||||
const FactoryWithRegistryInitData = FactoryWithRegistryFactory.interface.encodeFunctionData(
|
||||
'initialize(address,address,uint32,address,uint16,uint256)',
|
||||
[
|
||||
config.verifier,
|
||||
config.hasher,
|
||||
config.merkleTreeHeight,
|
||||
config.governance,
|
||||
config.TWAPSlotsMin,
|
||||
config.creationFee,
|
||||
],
|
||||
)
|
||||
const Factory = await ethers.getContractFactory('InstanceFactory')
|
||||
const FactoryInitData = Factory.interface.encodeFunctionData('initialize', [
|
||||
config.verifier,
|
||||
config.hasher,
|
||||
config.merkleTreeHeight,
|
||||
config.governance,
|
||||
])
|
||||
|
||||
const factoryWithRegistryContract = await upgradableContract({
|
||||
contractName: 'InstanceFactoryWithRegistry',
|
||||
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, FactoryWithRegistryInitData],
|
||||
proxyConstructorArgs: [config.governance, ProposalCreatorInitData],
|
||||
salt: config.salt,
|
||||
})
|
||||
|
||||
const result = {
|
||||
factoryContract,
|
||||
factoryWithRegistryContract,
|
||||
sidechainFactory,
|
||||
factory,
|
||||
proposalCreator,
|
||||
}
|
||||
|
||||
return result
|
||||
@ -89,16 +99,12 @@ async function generate(config = defaultConfig) {
|
||||
|
||||
async function generateWithLog() {
|
||||
const contracts = await generate()
|
||||
console.log('MultipleInstanceFactory contract: ', contracts.factoryContract.implementation.address)
|
||||
console.log('MultipleInstanceFactory proxy contract: ', contracts.factoryContract.proxy.address)
|
||||
console.log(
|
||||
'Instance factory with registry contract: ',
|
||||
contracts.factoryWithRegistryContract.implementation.address,
|
||||
)
|
||||
console.log(
|
||||
'Instance factory with registry proxy contract: ',
|
||||
contracts.factoryWithRegistryContract.proxy.address,
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -51,31 +51,37 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
config.instanceRegistry,
|
||||
)
|
||||
|
||||
// deploy InstanceFactoryWithRegistry with CREATE2
|
||||
// deploy InstanceProposalCreator with CREATE2
|
||||
const singletonFactory = await ethers.getContractAt(
|
||||
'SingletonFactory',
|
||||
config.singletonFactoryVerboseWrapper,
|
||||
)
|
||||
const contracts = await generate()
|
||||
if (
|
||||
(await ethers.provider.getCode(contracts.factoryWithRegistryContract.implementation.address)) == '0x'
|
||||
) {
|
||||
await singletonFactory.deploy(
|
||||
contracts.factoryWithRegistryContract.implementation.bytecode,
|
||||
config.salt,
|
||||
{
|
||||
gasLimit: config.deployGasLimit,
|
||||
},
|
||||
)
|
||||
}
|
||||
if ((await ethers.provider.getCode(contracts.factoryWithRegistryContract.proxy.address)) == '0x') {
|
||||
await singletonFactory.deploy(contracts.factoryWithRegistryContract.proxy.bytecode, config.salt, {
|
||||
if ((await ethers.provider.getCode(contracts.factory.implementation.address)) == '0x') {
|
||||
await singletonFactory.deploy(contracts.factory.implementation.bytecode, config.salt, {
|
||||
gasLimit: config.deployGasLimit,
|
||||
})
|
||||
}
|
||||
const instanceFactory = await ethers.getContractAt(
|
||||
'InstanceFactoryWithRegistry',
|
||||
contracts.factoryWithRegistryContract.proxy.address,
|
||||
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 {
|
||||
@ -90,73 +96,80 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
compToken,
|
||||
instanceRegistry,
|
||||
instanceFactory,
|
||||
proposalCreator,
|
||||
}
|
||||
}
|
||||
|
||||
it('Should have initialized all successfully', async function () {
|
||||
const { sender, gov, tornToken, instanceRegistry, instanceFactory } = await loadFixture(fixture)
|
||||
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 } = await loadFixture(fixture)
|
||||
const { instanceFactory, proposalCreator } = await loadFixture(fixture)
|
||||
|
||||
expect(await instanceFactory.governance()).to.be.equal(config.governance)
|
||||
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.implementation()).to.exist
|
||||
expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee)
|
||||
expect(await instanceFactory.torn()).to.be.equal(config.TORN)
|
||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
||||
expect(await instanceFactory.WETH()).to.be.equal(config.WETH)
|
||||
expect(await instanceFactory.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory)
|
||||
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 params', async function () {
|
||||
let { instanceFactory, gov } = await loadFixture(fixture)
|
||||
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 instanceFactory.setCreationFee(0)
|
||||
await instanceFactory.setTWAPSlotsMin(0)
|
||||
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 instanceFactory.creationFee()).to.be.equal(0)
|
||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(0)
|
||||
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 instanceFactory.setCreationFee(config.creationFee)
|
||||
await instanceFactory.setTWAPSlotsMin(config.TWAPSlotsMin)
|
||||
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 instanceFactory.creationFee()).to.be.equal(config.creationFee)
|
||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
||||
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, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture)
|
||||
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(instanceFactory.address, config.creationFee)
|
||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
||||
|
||||
await expect(() =>
|
||||
instanceFactory
|
||||
proposalCreator
|
||||
.connect(sender)
|
||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
||||
).to.changeTokenBalances(
|
||||
@ -165,7 +178,7 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||
)
|
||||
|
||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
||||
const proposal = await ethers.getContractAt(
|
||||
'AddInstanceProposal',
|
||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||
@ -235,7 +248,8 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
})
|
||||
|
||||
it('Should successfully deploy/propose/execute proposal - add instances', async function () {
|
||||
let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture)
|
||||
let { sender, instanceFactory, proposalCreator, gov, instanceRegistry, tornWhale, tornToken } =
|
||||
await loadFixture(fixture)
|
||||
|
||||
const denominations = [
|
||||
ethers.utils.parseEther('1'),
|
||||
@ -249,17 +263,17 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
|
||||
// deploy proposal ----------------------------------------------
|
||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
||||
await tornToken.approve(instanceFactory.address, config.creationFee)
|
||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
||||
|
||||
await expect(() =>
|
||||
instanceFactory.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees),
|
||||
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 instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
||||
const proposal = await ethers.getContractAt(
|
||||
'AddInstanceProposal',
|
||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||
@ -333,7 +347,9 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
})
|
||||
|
||||
it('Should successfully deploy proposal with permit', async function () {
|
||||
let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture)
|
||||
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'))
|
||||
@ -358,7 +374,7 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
const curTimestamp = Math.trunc(new Date().getTime() / 1000)
|
||||
const args = {
|
||||
owner: sender,
|
||||
spender: instanceFactory.address,
|
||||
spender: proposalCreator.address,
|
||||
value: config.creationFee,
|
||||
nonce: 0,
|
||||
deadline: curTimestamp + 1000,
|
||||
@ -370,7 +386,7 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
expect(signer).to.equal(sender.address)
|
||||
|
||||
await expect(() =>
|
||||
instanceFactory.createProposalPermit(
|
||||
proposalCreator.createProposalPermit(
|
||||
config.COMP,
|
||||
3000,
|
||||
[ethers.utils.parseEther('100')],
|
||||
@ -387,7 +403,7 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||
)
|
||||
|
||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
||||
const proposal = await ethers.getContractAt(
|
||||
'AddInstanceProposal',
|
||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||
@ -403,15 +419,15 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
})
|
||||
|
||||
it('Should deposit and withdraw into the new instance', async function () {
|
||||
let { sender, instanceFactory, gov, tornWhale, tornToken, router, compToken, compWhale } =
|
||||
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(instanceFactory.address, config.creationFee)
|
||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
||||
|
||||
await expect(() =>
|
||||
instanceFactory
|
||||
proposalCreator
|
||||
.connect(sender)
|
||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
||||
).to.changeTokenBalances(
|
||||
@ -420,7 +436,7 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||
)
|
||||
|
||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||
let logs = await proposalCreator.queryFilter('NewGovernanceProposalCreated')
|
||||
const proposal = await ethers.getContractAt(
|
||||
'AddInstanceProposal',
|
||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||
@ -486,36 +502,382 @@ describe('Instance Factory With Registry Tests', () => {
|
||||
})
|
||||
|
||||
it('Should not deploy proposal with incorrect Uniswap pool', async function () {
|
||||
let { sender, instanceFactory, tornWhale, tornToken } = await loadFixture(fixture)
|
||||
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
|
||||
|
||||
// deploy proposal ----------------------------------------------
|
||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
||||
await tornToken.approve(instanceFactory.address, config.creationFee)
|
||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
||||
|
||||
await expect(
|
||||
instanceFactory
|
||||
proposalCreator
|
||||
.connect(sender)
|
||||
.createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]),
|
||||
).to.be.revertedWith('Uniswap pool is not exist')
|
||||
|
||||
await expect(
|
||||
instanceFactory
|
||||
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, instanceFactory, tornWhale, tornToken } = await loadFixture(fixture)
|
||||
let { sender, proposalCreator, tornWhale, tornToken } = await loadFixture(fixture)
|
||||
|
||||
// deploy proposal ----------------------------------------------
|
||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
||||
await tornToken.approve(instanceFactory.address, config.creationFee)
|
||||
await tornToken.approve(proposalCreator.address, config.creationFee)
|
||||
|
||||
await expect(
|
||||
instanceFactory
|
||||
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%')
|
||||
})
|
||||
})
|
||||
|
@ -8,7 +8,7 @@ const { getSignerFromAddress } = require('./utils')
|
||||
const { generate } = require('../src/generateAddresses')
|
||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
||||
|
||||
describe('Multiple Instance Factory Tests', () => {
|
||||
describe('Sidechain Instance Factory Tests', () => {
|
||||
const addressZero = ethers.constants.AddressZero
|
||||
|
||||
async function fixture() {
|
||||
@ -34,19 +34,19 @@ describe('Multiple Instance Factory Tests', () => {
|
||||
config.singletonFactoryVerboseWrapper,
|
||||
)
|
||||
const contracts = await generate()
|
||||
if ((await ethers.provider.getCode(contracts. |