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:
|
This repository contains:
|
||||||
|
|
||||||
1. `InstanceFactory` - instance factory for the creation new Tornado ERC20 pools
|
1. `InstanceFactory` - instance factory for the creation new Tornado ERC20/native pools
|
||||||
2. `InstanceFactoryWithRegistry` - governance proposal factory for the addition of new Tornado ERC20 instances to the Tornado router
|
2. `InstanceProposalCreator` - governance proposal factory for the addition of new Tornado instances to the Tornado router
|
||||||
|
|
||||||
### InstanceFactory
|
### 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)
|
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
|
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.
|
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).
|
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.
|
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
|
## 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.
|
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`.
|
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
|
## Warnings
|
||||||
|
|
||||||
1. This version of the factory creates a proposal for **immutable** Tornado instance initialization.
|
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
|
## Tests
|
||||||
|
|
||||||
@ -61,10 +61,12 @@ Check config.js for actual values.
|
|||||||
|
|
||||||
With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be:
|
With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be:
|
||||||
|
|
||||||
1. `MultipleInstanceFactory` - `0x8a1BCFc608DdF6c4715277a24310B9E8ecc4c110`
|
1. `SidechainInstanceFactory` - `0x0a0601c2E952aC53bb95b1A81764Fc730C9bBDd5`
|
||||||
2. `MultipleInstanceFactory proxy` - `0x0F9AE1d1ABbDd441B813ada0B038df130b2a6A86`
|
2. `SidechainInstanceFactory proxy` - `0xb4838f15185E4A7E2ED8534c89b7AC92aC927C9b`
|
||||||
3. `InstanceFactoryWithRegistry` - `0x557fB18B66088728Ea975136de46714983aa4f2E`
|
3. `InstanceFactory` - `0xa7610A8292850f516603bA0066A731109Dfe854E`
|
||||||
4. `InstanceFactoryWithRegistry proxy` - `0x4D1b1c294dA4D14aC0e0Eed7BcD4Db3fe2bDe4C3`
|
4. `InstanceFactory proxy` - `0x0f9B6646815f118d671084cd73Ea8713b18be354`
|
||||||
|
5. `InstanceProposalCreator` - `0x39F00b914f8DBD28082D1f1ae9b3210d7F7B404f`
|
||||||
|
6. `InstanceProposalCreator proxy` - `0xA2980DAfcdAf61C6d39FE63a6360907b69e840c8`
|
||||||
|
|
||||||
Check addresses with current config:
|
Check addresses with current config:
|
||||||
|
|
||||||
@ -73,19 +75,19 @@ Check addresses with current config:
|
|||||||
node -e 'require("./src/generateAddresses").generateWithLog()'
|
node -e 'require("./src/generateAddresses").generateWithLog()'
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploy MultipleInstanceFactory:
|
Deploy SidechainInstanceFactory:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
yarn hardhat run scripts/deployMultipleInstanceFactory.js --network mainnet
|
yarn hardhat run scripts/deploySidechainInstanceFactory.js --network mainnet
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploy InstanceFactoryWithRegistry:
|
Deploy InstanceProposalCreator:
|
||||||
|
|
||||||
```shell
|
```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>
|
yarn hardhat verify --network <network-name> <contract-address> <constructor-arguments>
|
||||||
|
@ -4,10 +4,10 @@ pragma solidity 0.7.6;
|
|||||||
pragma abicoder v2;
|
pragma abicoder v2;
|
||||||
|
|
||||||
import "./interfaces/IInstanceRegistry.sol";
|
import "./interfaces/IInstanceRegistry.sol";
|
||||||
import "./InstanceFactory.sol";
|
import "./interfaces/IInstanceFactory.sol";
|
||||||
|
|
||||||
contract AddInstanceProposal {
|
contract AddInstanceProposal {
|
||||||
InstanceFactory public immutable instanceFactory;
|
IInstanceFactory public immutable instanceFactory;
|
||||||
IInstanceRegistry public immutable instanceRegistry;
|
IInstanceRegistry public immutable instanceRegistry;
|
||||||
address public immutable token;
|
address public immutable token;
|
||||||
uint24 public immutable uniswapPoolSwappingFee;
|
uint24 public immutable uniswapPoolSwappingFee;
|
||||||
@ -32,7 +32,7 @@ contract AddInstanceProposal {
|
|||||||
uint256[] memory _denominations,
|
uint256[] memory _denominations,
|
||||||
uint32[] memory _protocolFees
|
uint32[] memory _protocolFees
|
||||||
) {
|
) {
|
||||||
instanceFactory = InstanceFactory(_instanceFactory);
|
instanceFactory = IInstanceFactory(_instanceFactory);
|
||||||
instanceRegistry = IInstanceRegistry(_instanceRegistry);
|
instanceRegistry = IInstanceRegistry(_instanceRegistry);
|
||||||
token = _token;
|
token = _token;
|
||||||
uniswapPoolSwappingFee = _uniswapPoolSwappingFee;
|
uniswapPoolSwappingFee = _uniswapPoolSwappingFee;
|
||||||
@ -57,7 +57,7 @@ contract AddInstanceProposal {
|
|||||||
address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token);
|
address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token);
|
||||||
|
|
||||||
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
||||||
true,
|
token != address(0),
|
||||||
IERC20(token),
|
IERC20(token),
|
||||||
IInstanceRegistry.InstanceState.ENABLED,
|
IInstanceRegistry.InstanceState.ENABLED,
|
||||||
uniswapPoolSwappingFee,
|
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 { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
|
||||||
import "@openzeppelin/contracts/proxy/Clones.sol";
|
import "@openzeppelin/contracts/proxy/Clones.sol";
|
||||||
import "./ERC20TornadoCloneable.sol";
|
import "./ERC20TornadoCloneable.sol";
|
||||||
|
import "./ETHTornadoCloneable.sol";
|
||||||
|
|
||||||
contract InstanceFactory is Initializable {
|
contract InstanceFactory is Initializable {
|
||||||
using Clones for address;
|
using Clones for address;
|
||||||
using Address for address;
|
using Address for address;
|
||||||
|
|
||||||
address public admin;
|
address public admin;
|
||||||
address public implementation;
|
address public ERC20Impl;
|
||||||
|
address public nativeCurImpl;
|
||||||
address public verifier;
|
address public verifier;
|
||||||
address public hasher;
|
address public hasher;
|
||||||
uint32 public merkleTreeHeight;
|
uint32 public merkleTreeHeight;
|
||||||
|
|
||||||
event NewTreeHeightSet(uint32 indexed newTreeHeight);
|
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);
|
event NewInstanceCloneCreated(address indexed clone);
|
||||||
|
|
||||||
modifier onlyAdmin() {
|
modifier onlyAdmin() {
|
||||||
@ -43,30 +45,49 @@ contract InstanceFactory is Initializable {
|
|||||||
merkleTreeHeight = _merkleTreeHeight;
|
merkleTreeHeight = _merkleTreeHeight;
|
||||||
admin = _admin;
|
admin = _admin;
|
||||||
|
|
||||||
ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher);
|
ERC20TornadoCloneable ERC20ImplContract = new ERC20TornadoCloneable(_verifier, _hasher);
|
||||||
implementation = address(implContract);
|
ERC20Impl = address(ERC20ImplContract);
|
||||||
|
ETHTornadoCloneable nativeCurImplContract = new ETHTornadoCloneable(_verifier, _hasher);
|
||||||
|
nativeCurImpl = address(nativeCurImplContract);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Creates new Tornado instance.
|
* @dev Creates new Tornado instance.
|
||||||
* @param _denomination denomination of 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));
|
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
||||||
|
|
||||||
address newClone = implementation.predictDeterministicAddress(salt);
|
if (_token == address(0)) {
|
||||||
if (!newClone.isContract()) {
|
clone = nativeCurImpl.predictDeterministicAddress(salt);
|
||||||
implementation.cloneDeterministic(salt);
|
if (!clone.isContract()) {
|
||||||
emit NewInstanceCloneCreated(newClone);
|
nativeCurImpl.cloneDeterministic(salt);
|
||||||
ERC20TornadoCloneable(newClone).init(_denomination, merkleTreeHeight, _token);
|
emit NewInstanceCloneCreated(clone);
|
||||||
|
ETHTornadoCloneable(clone).init(_denomination, merkleTreeHeight);
|
||||||
}
|
}
|
||||||
return newClone;
|
} else {
|
||||||
|
clone = ERC20Impl.predictDeterministicAddress(salt);
|
||||||
|
if (!clone.isContract()) {
|
||||||
|
ERC20Impl.cloneDeterministic(salt);
|
||||||
|
emit NewInstanceCloneCreated(clone);
|
||||||
|
ERC20TornadoCloneable(clone).init(_denomination, merkleTreeHeight, _token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInstanceAddress(uint256 _denomination, address _token) public view returns (address) {
|
function getInstanceAddress(uint256 _denomination, address _token) public view returns (address) {
|
||||||
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
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 {
|
function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyAdmin {
|
||||||
@ -77,7 +98,8 @@ contract InstanceFactory is Initializable {
|
|||||||
function generateNewImplementation(address _verifier, address _hasher) external onlyAdmin {
|
function generateNewImplementation(address _verifier, address _hasher) external onlyAdmin {
|
||||||
verifier = _verifier;
|
verifier = _verifier;
|
||||||
hasher = _hasher;
|
hasher = _hasher;
|
||||||
implementation = address(new ERC20TornadoCloneable(_verifier, _hasher));
|
ERC20Impl = address(new ERC20TornadoCloneable(_verifier, _hasher));
|
||||||
emit NewImplementationSet(implementation, _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 { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||||
import "./AddInstanceProposal.sol";
|
import "./AddInstanceProposal.sol";
|
||||||
import "./InstanceFactory.sol";
|
import "./interfaces/IInstanceFactory.sol";
|
||||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol";
|
import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol";
|
||||||
import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
|
import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
|
||||||
import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.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;
|
using Address for address;
|
||||||
|
|
||||||
address public immutable governance;
|
address public immutable governance;
|
||||||
address public immutable torn;
|
address public immutable torn;
|
||||||
|
IInstanceFactory public immutable instanceFactory;
|
||||||
address public immutable instanceRegistry;
|
address public immutable instanceRegistry;
|
||||||
IUniswapV3Factory public immutable UniswapV3Factory;
|
IUniswapV3Factory public immutable UniswapV3Factory;
|
||||||
address public immutable WETH;
|
address public immutable WETH;
|
||||||
@ -36,12 +38,14 @@ contract InstanceFactoryWithRegistry is InstanceFactory {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
address _governance,
|
address _governance,
|
||||||
|
address _instanceFactory,
|
||||||
address _instanceRegistry,
|
address _instanceRegistry,
|
||||||
address _torn,
|
address _torn,
|
||||||
address _UniswapV3Factory,
|
address _UniswapV3Factory,
|
||||||
address _WETH
|
address _WETH
|
||||||
) {
|
) {
|
||||||
governance = _governance;
|
governance = _governance;
|
||||||
|
instanceFactory = IInstanceFactory(_instanceFactory);
|
||||||
instanceRegistry = _instanceRegistry;
|
instanceRegistry = _instanceRegistry;
|
||||||
torn = _torn;
|
torn = _torn;
|
||||||
UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory);
|
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,
|
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
|
||||||
* params left out because self explainable
|
* params left out because self explainable
|
||||||
* */
|
* */
|
||||||
function initialize(
|
function initialize(uint16 _TWAPSlotsMin, uint256 _creationFee) external initializer {
|
||||||
address _verifier,
|
|
||||||
address _hasher,
|
|
||||||
uint32 _merkleTreeHeight,
|
|
||||||
address _governance,
|
|
||||||
uint16 _TWAPSlotsMin,
|
|
||||||
uint256 _creationFee
|
|
||||||
) external initializer {
|
|
||||||
initialize(_verifier, _hasher, _merkleTreeHeight, _governance);
|
|
||||||
TWAPSlotsMin = _TWAPSlotsMin;
|
TWAPSlotsMin = _TWAPSlotsMin;
|
||||||
creationFee = _creationFee;
|
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.
|
* @dev Creates AddInstanceProposal with approve.
|
||||||
* @param _token address of ERC20 token for a new instance
|
* @param _token address of ERC20 token for a new instance
|
||||||
@ -125,14 +112,14 @@ contract InstanceFactoryWithRegistry is InstanceFactory {
|
|||||||
uint256[] memory _denominations,
|
uint256[] memory _denominations,
|
||||||
uint32[] memory _protocolFees
|
uint32[] memory _protocolFees
|
||||||
) internal returns (address) {
|
) 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 > 0, "Empty denominations");
|
||||||
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
||||||
|
|
||||||
// check Uniswap Pool
|
// check Uniswap Pool
|
||||||
for (uint8 i = 0; i < _protocolFees.length; i++) {
|
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
|
// pool exists
|
||||||
address poolAddr = UniswapV3Factory.getPool(_token, WETH, _uniswapPoolSwappingFee);
|
address poolAddr = UniswapV3Factory.getPool(_token, WETH, _uniswapPoolSwappingFee);
|
||||||
require(poolAddr != address(0), "Uniswap pool is not exist");
|
require(poolAddr != address(0), "Uniswap pool is not exist");
|
||||||
@ -144,19 +131,26 @@ contract InstanceFactoryWithRegistry is InstanceFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
address proposal = address(
|
address proposal = address(
|
||||||
new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees)
|
new AddInstanceProposal(
|
||||||
|
address(instanceFactory),
|
||||||
|
instanceRegistry,
|
||||||
|
_token,
|
||||||
|
_uniswapPoolSwappingFee,
|
||||||
|
_denominations,
|
||||||
|
_protocolFees
|
||||||
|
)
|
||||||
);
|
);
|
||||||
emit NewGovernanceProposalCreated(proposal);
|
emit NewGovernanceProposalCreated(proposal);
|
||||||
|
|
||||||
return proposal;
|
return proposal;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCreationFee(uint256 _creationFee) external onlyAdmin {
|
function setCreationFee(uint256 _creationFee) external onlyGovernance {
|
||||||
creationFee = _creationFee;
|
creationFee = _creationFee;
|
||||||
emit NewCreationFeeSet(_creationFee);
|
emit NewCreationFeeSet(_creationFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyAdmin {
|
function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance {
|
||||||
TWAPSlotsMin = _TWAPSlotsMin;
|
TWAPSlotsMin = _TWAPSlotsMin;
|
||||||
emit NewTWAPSlotsMinSet(_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('@nomiclabs/hardhat-etherscan')
|
||||||
require('hardhat-log-remover')
|
require('hardhat-log-remover')
|
||||||
require('solidity-coverage')
|
require('solidity-coverage')
|
||||||
|
require('hardhat-contract-sizer')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type import('hardhat/config').HardhatUserConfig
|
* @type import('hardhat/config').HardhatUserConfig
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"ethereum-waffle": "^3.4.0",
|
"ethereum-waffle": "^3.4.0",
|
||||||
"hardhat": "^2.4.3",
|
"hardhat": "^2.4.3",
|
||||||
|
"hardhat-contract-sizer": "^2.6.1",
|
||||||
"hardhat-log-remover": "^2.0.2",
|
"hardhat-log-remover": "^2.0.2",
|
||||||
"mocha-lcov-reporter": "^1.3.0",
|
"mocha-lcov-reporter": "^1.3.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
|
@ -14,14 +14,14 @@ async function deploy({ address, bytecode, singletonFactory }) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
||||||
const contracts = await generate()
|
const contracts = await generate()
|
||||||
await deploy({ ...contracts.factoryWithRegistryContract.implementation, singletonFactory })
|
await deploy({ ...contracts.factory.implementation, singletonFactory })
|
||||||
await deploy({ ...contracts.factoryWithRegistryContract.proxy, singletonFactory })
|
console.log(`Instance factory contract have been deployed on ${contracts.factory.implementation.address}`)
|
||||||
console.log(
|
await deploy({ ...contracts.factory.proxy, singletonFactory })
|
||||||
`Instance factory with registry contract have been deployed on ${contracts.factoryWithRegistryContract.implementation.address} address`,
|
console.log(`Instance factory proxy contract have been deployed on ${contracts.factory.proxy.address}`)
|
||||||
)
|
await deploy({ ...contracts.proposalCreator.implementation, singletonFactory })
|
||||||
console.log(
|
console.log(`Proposal creator have been deployed on ${contracts.proposalCreator.implementation.address}`)
|
||||||
`Instance factory with registry proxy contract have been deployed on ${contracts.factoryWithRegistryContract.proxy.address} address`,
|
await deploy({ ...contracts.proposalCreator.proxy, singletonFactory })
|
||||||
)
|
console.log(`Proposal creator proxy have been deployed on ${contracts.proposalCreator.proxy.address}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
@ -14,13 +14,13 @@ async function deploy({ address, bytecode, singletonFactory }) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
||||||
const contracts = await generate()
|
const contracts = await generate()
|
||||||
await deploy({ ...contracts.factoryContract.implementation, singletonFactory })
|
await deploy({ ...contracts.sidechainFactory.implementation, singletonFactory })
|
||||||
await deploy({ ...contracts.factoryContract.proxy, singletonFactory })
|
await deploy({ ...contracts.sidechainFactory.proxy, singletonFactory })
|
||||||
console.log(
|
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(
|
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) {
|
async function generate(config = defaultConfig) {
|
||||||
// factory contract -----------------------------------------------
|
// sidechain factory contract -------------------------------------
|
||||||
const FactoryFactory = await ethers.getContractFactory('MultipleInstanceFactory')
|
const SidechainFactory = await ethers.getContractFactory('SidechainInstanceFactory')
|
||||||
const FactoryInitData = FactoryFactory.interface.encodeFunctionData('initialize', [
|
const SidechainFactoryInitData = SidechainFactory.interface.encodeFunctionData('initialize', [
|
||||||
config.verifier,
|
config.verifier,
|
||||||
config.hasher,
|
config.hasher,
|
||||||
config.merkleTreeHeight,
|
config.merkleTreeHeight,
|
||||||
config.admin,
|
config.admin,
|
||||||
])
|
])
|
||||||
|
|
||||||
const factoryContract = await upgradableContract({
|
const sidechainFactory = await upgradableContract({
|
||||||
contractName: 'MultipleInstanceFactory',
|
contractName: 'SidechainInstanceFactory',
|
||||||
implConstructorArgs: [],
|
implConstructorArgs: [],
|
||||||
proxyConstructorArgs: [config.admin, FactoryInitData],
|
proxyConstructorArgs: [config.admin, SidechainFactoryInitData],
|
||||||
salt: config.salt,
|
salt: config.salt,
|
||||||
})
|
})
|
||||||
|
|
||||||
// factory with registry contract ---------------------------------
|
// factory with registry contract ---------------------------------
|
||||||
const FactoryWithRegistryFactory = await ethers.getContractFactory('InstanceFactoryWithRegistry')
|
const Factory = await ethers.getContractFactory('InstanceFactory')
|
||||||
const FactoryWithRegistryInitData = FactoryWithRegistryFactory.interface.encodeFunctionData(
|
const FactoryInitData = Factory.interface.encodeFunctionData('initialize', [
|
||||||
'initialize(address,address,uint32,address,uint16,uint256)',
|
|
||||||
[
|
|
||||||
config.verifier,
|
config.verifier,
|
||||||
config.hasher,
|
config.hasher,
|
||||||
config.merkleTreeHeight,
|
config.merkleTreeHeight,
|
||||||
config.governance,
|
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.TWAPSlotsMin,
|
||||||
config.creationFee,
|
config.creationFee,
|
||||||
],
|
])
|
||||||
)
|
|
||||||
|
|
||||||
const factoryWithRegistryContract = await upgradableContract({
|
const proposalCreator = await upgradableContract({
|
||||||
contractName: 'InstanceFactoryWithRegistry',
|
contractName: 'InstanceProposalCreator',
|
||||||
implConstructorArgs: [
|
implConstructorArgs: [
|
||||||
config.governance,
|
config.governance,
|
||||||
|
factory.proxy.address,
|
||||||
config.instanceRegistry,
|
config.instanceRegistry,
|
||||||
config.TORN,
|
config.TORN,
|
||||||
config.UniswapV3Factory,
|
config.UniswapV3Factory,
|
||||||
config.WETH,
|
config.WETH,
|
||||||
],
|
],
|
||||||
proxyConstructorArgs: [config.governance, FactoryWithRegistryInitData],
|
proxyConstructorArgs: [config.governance, ProposalCreatorInitData],
|
||||||
salt: config.salt,
|
salt: config.salt,
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
factoryContract,
|
sidechainFactory,
|
||||||
factoryWithRegistryContract,
|
factory,
|
||||||
|
proposalCreator,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -89,16 +99,12 @@ async function generate(config = defaultConfig) {
|
|||||||
|
|
||||||
async function generateWithLog() {
|
async function generateWithLog() {
|
||||||
const contracts = await generate()
|
const contracts = await generate()
|
||||||
console.log('MultipleInstanceFactory contract: ', contracts.factoryContract.implementation.address)
|
console.log('SidechainInstanceFactory contract: ', contracts.sidechainFactory.implementation.address)
|
||||||
console.log('MultipleInstanceFactory proxy contract: ', contracts.factoryContract.proxy.address)
|
console.log('SidechainInstanceFactory proxy contract: ', contracts.sidechainFactory.proxy.address)
|
||||||
console.log(
|
console.log('Instance factory contract: ', contracts.factory.implementation.address)
|
||||||
'Instance factory with registry contract: ',
|
console.log('Instance factory proxy contract: ', contracts.factory.proxy.address)
|
||||||
contracts.factoryWithRegistryContract.implementation.address,
|
console.log('Proposal creator contract: ', contracts.proposalCreator.implementation.address)
|
||||||
)
|
console.log('Proposal creator proxy contract: ', contracts.proposalCreator.proxy.address)
|
||||||
console.log(
|
|
||||||
'Instance factory with registry proxy contract: ',
|
|
||||||
contracts.factoryWithRegistryContract.proxy.address,
|
|
||||||
)
|
|
||||||
return contracts
|
return contracts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,31 +51,37 @@ describe('Instance Factory With Registry Tests', () => {
|
|||||||
config.instanceRegistry,
|
config.instanceRegistry,
|
||||||
)
|
)
|
||||||
|
|
||||||
// deploy InstanceFactoryWithRegistry with CREATE2
|
// deploy InstanceProposalCreator with CREATE2
|
||||||
const singletonFactory = await ethers.getContractAt(
|
const singletonFactory = await ethers.getContractAt(
|
||||||
'SingletonFactory',
|
'SingletonFactory',
|
||||||
config.singletonFactoryVerboseWrapper,
|
config.singletonFactoryVerboseWrapper,
|
||||||
)
|
)
|
||||||
const contracts = await generate()
|
const contracts = await generate()
|
||||||
if (
|
if ((await ethers.provider.getCode(contracts.factory.implementation.address)) == '0x') {
|
||||||
(await ethers.provider.getCode(contracts.factoryWithRegistryContract.implementation.address)) == '0x'
|
await singletonFactory.deploy(contracts.factory.implementation.bytecode, config.salt, {
|
||||||
) {
|
|
||||||
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, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
gasLimit: config.deployGasLimit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const instanceFactory = await ethers.getContractAt(
|
if ((await ethers.provider.getCode(contracts.factory.proxy.address)) == '0x') {
|
||||||
'InstanceFactoryWithRegistry',
|
await singletonFactory.deploy(contracts.factory.proxy.bytecode, config.salt, {
|
||||||
contracts.factoryWithRegistryContract.proxy.address,
|
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 {
|
return {
|
||||||
@ -90,73 +96,80 @@ describe('Instance Factory With Registry Tests', () => {
|
|||||||
compToken,
|
compToken,
|
||||||
instanceRegistry,
|
instanceRegistry,
|
||||||
instanceFactory,
|
instanceFactory,
|
||||||
|
proposalCreator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it('Should have initialized all successfully', async function () {
|
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(sender.address).to.exist
|
||||||
expect(gov.address).to.exist
|
expect(gov.address).to.exist
|
||||||
expect(tornToken.address).to.exist
|
expect(tornToken.address).to.exist
|
||||||
expect(instanceRegistry.address).to.exist
|
expect(instanceRegistry.address).to.exist
|
||||||
expect(instanceFactory.address).to.exist
|
expect(instanceFactory.address).to.exist
|
||||||
|
expect(proposalCreator.address).to.exist
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should set correct params for factory', async function () {
|
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.verifier()).to.be.equal(config.verifier)
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
||||||
expect(await instanceFactory.implementation()).to.exist
|
expect(await instanceFactory.ERC20Impl()).to.exist
|
||||||
expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee)
|
expect(await instanceFactory.nativeCurImpl()).to.exist
|
||||||
expect(await instanceFactory.torn()).to.be.equal(config.TORN)
|
expect(await proposalCreator.creationFee()).to.be.equal(config.creationFee)
|
||||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
expect(await proposalCreator.torn()).to.be.equal(config.TORN)
|
||||||
expect(await instanceFactory.WETH()).to.be.equal(config.WETH)
|
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
||||||
expect(await instanceFactory.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory)
|
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 () {
|
it('Governance should be able to set factory/proposalCreator params', async function () {
|
||||||
let { instanceFactory, gov } = await loadFixture(fixture)
|
let { instanceFactory, proposalCreator, gov } = await loadFixture(fixture)
|
||||||
|
|
||||||
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
||||||
|
|
||||||
const govSigner = await getSignerFromAddress(gov.address)
|
const govSigner = await getSignerFromAddress(gov.address)
|
||||||
instanceFactory = await instanceFactory.connect(govSigner)
|
instanceFactory = await instanceFactory.connect(govSigner)
|
||||||
|
proposalCreator = await proposalCreator.connect(govSigner)
|
||||||
|
|
||||||
await instanceFactory.generateNewImplementation(addressZero, addressZero)
|
await instanceFactory.generateNewImplementation(addressZero, addressZero)
|
||||||
await instanceFactory.setMerkleTreeHeight(1)
|
await instanceFactory.setMerkleTreeHeight(1)
|
||||||
await instanceFactory.setCreationFee(0)
|
await proposalCreator.setCreationFee(0)
|
||||||
await instanceFactory.setTWAPSlotsMin(0)
|
await proposalCreator.setTWAPSlotsMin(0)
|
||||||
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
|
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
|
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
|
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
|
||||||
expect(await instanceFactory.creationFee()).to.be.equal(0)
|
expect(await proposalCreator.creationFee()).to.be.equal(0)
|
||||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(0)
|
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(0)
|
||||||
|
|
||||||
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
|
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
|
||||||
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
||||||
await instanceFactory.setCreationFee(config.creationFee)
|
await proposalCreator.setCreationFee(config.creationFee)
|
||||||
await instanceFactory.setTWAPSlotsMin(config.TWAPSlotsMin)
|
await proposalCreator.setTWAPSlotsMin(config.TWAPSlotsMin)
|
||||||
|
|
||||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
||||||
expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee)
|
expect(await proposalCreator.creationFee()).to.be.equal(config.creationFee)
|
||||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
expect(await proposalCreator.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should successfully deploy/propose/execute proposal - add instance', async function () {
|
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 ----------------------------------------------
|
// deploy proposal ----------------------------------------------
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
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(() =>
|
await expect(() =>
|
||||||
instanceFactory
|
proposalCreator
|
||||||
.connect(sender)
|
.connect(sender)
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
||||||
).to.changeTokenBalances(
|
).to.changeTokenBalances(
|
||||||
@ -165,7 +178,7 @@ describe('Instance Factory With Registry Tests', () => {
|
|||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
[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(
|
const proposal = await ethers.getContractAt(
|
||||||
'AddInstanceProposal',
|
'AddInstanceProposal',
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
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 () {
|
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 = [
|
const denominations = [
|
||||||
ethers.utils.parseEther('1'),
|
ethers.utils.parseEther('1'),
|
||||||
@ -249,17 +263,17 @@ describe('Instance Factory With Registry Tests', () => {
|
|||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
// deploy proposal ----------------------------------------------
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
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(() =>
|
await expect(() =>
|
||||||
instanceFactory.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees),
|
proposalCreator.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees),
|
||||||
).to.changeTokenBalances(
|
).to.changeTokenBalances(
|
||||||
tornToken,
|
tornToken,
|
||||||
[sender, gov],
|
[sender, gov],
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
[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(
|
const proposal = await ethers.getContractAt(
|
||||||
'AddInstanceProposal',
|
'AddInstanceProposal',
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
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 () {
|
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 privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
|
||||||
const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex'))
|
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 curTimestamp = Math.trunc(new Date().getTime() / 1000)
|
||||||
const args = {
|
const args = {
|
||||||
owner: sender,
|
owner: sender,
|
||||||
spender: instanceFactory.address,
|
spender: proposalCreator.address,
|
||||||
value: config.creationFee,
|
value: config.creationFee,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
deadline: curTimestamp + 1000,
|
deadline: curTimestamp + 1000,
|
||||||
@ -370,7 +386,7 @@ describe('Instance Factory With Registry Tests', () => {
|
|||||||
expect(signer).to.equal(sender.address)
|
expect(signer).to.equal(sender.address)
|
||||||
|
|
||||||
await expect(() =>
|
await expect(() =>
|
||||||
instanceFactory.createProposalPermit(
|
proposalCreator.createProposalPermit(
|
||||||
config.COMP,
|
config.COMP,
|
||||||
3000,
|
3000,
|
||||||
[ethers.utils.parseEther('100')],
|
[ethers.utils.parseEther('100')],
|
||||||
@ -387,7 +403,7 @@ describe('Instance Factory With Registry Tests', () => {
|
|||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
[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(
|
const proposal = await ethers.getContractAt(
|
||||||
'AddInstanceProposal',
|
'AddInstanceProposal',
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
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 () {
|
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)
|
await loadFixture(fixture)
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
// deploy proposal ----------------------------------------------
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
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(() =>
|
await expect(() =>
|
||||||
instanceFactory
|
proposalCreator
|
||||||
.connect(sender)
|
.connect(sender)
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
||||||
).to.changeTokenBalances(
|
).to.changeTokenBalances(
|
||||||
@ -420,7 +436,7 @@ describe('Instance Factory With Registry Tests', () => {
|
|||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
[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(
|
const proposal = await ethers.getContractAt(
|
||||||
'AddInstanceProposal',
|
'AddInstanceProposal',
|
||||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
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 () {
|
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 ----------------------------------------------
|
// deploy proposal ----------------------------------------------
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
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(
|
await expect(
|
||||||
instanceFactory
|
proposalCreator
|
||||||
.connect(sender)
|
.connect(sender)
|
||||||
.createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]),
|
.createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]),
|
||||||
).to.be.revertedWith('Uniswap pool is not exist')
|
).to.be.revertedWith('Uniswap pool is not exist')
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
instanceFactory
|
proposalCreator
|
||||||
.connect(sender)
|
.connect(sender)
|
||||||
.createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]),
|
.createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]),
|
||||||
).to.be.revertedWith('Uniswap pool TWAP slots number is low')
|
).to.be.revertedWith('Uniswap pool TWAP slots number is low')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not deploy proposal with incorrect protocol fee', async function () {
|
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 ----------------------------------------------
|
// deploy proposal ----------------------------------------------
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
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(
|
await expect(
|
||||||
instanceFactory
|
proposalCreator
|
||||||
.connect(sender)
|
.connect(sender)
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [10300]),
|
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [10300]),
|
||||||
).to.be.revertedWith('Protocol fee is more than 100%')
|
).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 { generate } = require('../src/generateAddresses')
|
||||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
||||||
|
|
||||||
describe('Multiple Instance Factory Tests', () => {
|
describe('Sidechain Instance Factory Tests', () => {
|
||||||
const addressZero = ethers.constants.AddressZero
|
const addressZero = ethers.constants.AddressZero
|
||||||
|
|
||||||
async function fixture() {
|
async function fixture() {
|
||||||
@ -34,19 +34,19 @@ describe('Multiple Instance Factory Tests', () => {
|
|||||||
config.singletonFactoryVerboseWrapper,
|
config.singletonFactoryVerboseWrapper,
|
||||||
)
|
)
|
||||||
const contracts = await generate()
|
const contracts = await generate()
|
||||||
if ((await ethers.provider.getCode(contracts.factoryContract.implementation.address)) == '0x') {
|
if ((await ethers.provider.getCode(contracts.sidechainFactory.implementation.address)) == '0x') {
|
||||||
await singletonFactory.deploy(contracts.factoryContract.implementation.bytecode, config.salt, {
|
await singletonFactory.deploy(contracts.sidechainFactory.implementation.bytecode, config.salt, {
|
||||||
gasLimit: config.deployGasLimit,
|
gasLimit: config.deployGasLimit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if ((await ethers.provider.getCode(contracts.factoryContract.proxy.address)) == '0x') {
|
if ((await ethers.provider.getCode(contracts.sidechainFactory.proxy.address)) == '0x') {
|
||||||
await singletonFactory.deploy(contracts.factoryContract.proxy.bytecode, config.salt, {
|
await singletonFactory.deploy(contracts.sidechainFactory.proxy.bytecode, config.salt, {
|
||||||
gasLimit: config.deployGasLimit,
|
gasLimit: config.deployGasLimit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const instanceFactory = await ethers.getContractAt(
|
const instanceFactory = await ethers.getContractAt(
|
||||||
'MultipleInstanceFactory',
|
'SidechainInstanceFactory',
|
||||||
contracts.factoryContract.proxy.address,
|
contracts.sidechainFactory.proxy.address,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -72,10 +72,11 @@ describe('Multiple Instance Factory Tests', () => {
|
|||||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
||||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
||||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
||||||
expect(await instanceFactory.implementation()).to.exist
|
expect(await instanceFactory.ERC20Impl()).to.exist
|
||||||
|
expect(await instanceFactory.nativeCurImpl()).to.exist
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Governance should be able to set factory params', async function () {
|
it('Admin should be able to set factory params', async function () {
|
||||||
let { instanceFactory, owner } = await loadFixture(fixture)
|
let { instanceFactory, owner } = await loadFixture(fixture)
|
||||||
|
|
||||||
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
||||||
@ -196,4 +197,100 @@ describe('Multiple Instance Factory Tests', () => {
|
|||||||
[BigNumber.from(0).sub(value), value],
|
[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],
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
22
yarn.lock
22
yarn.lock
@ -617,6 +617,11 @@
|
|||||||
"@babel/helper-validator-identifier" "^7.14.9"
|
"@babel/helper-validator-identifier" "^7.14.9"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@colors/colors@1.5.0":
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||||
|
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||||
|
|
||||||
"@consento/sync-randombytes@^1.0.4", "@consento/sync-randombytes@^1.0.5":
|
"@consento/sync-randombytes@^1.0.4", "@consento/sync-randombytes@^1.0.5":
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@consento/sync-randombytes/-/sync-randombytes-1.0.5.tgz#5be6bc58c6a6fa6e09f04cc684d037e29e6c28d5"
|
resolved "https://registry.yarnpkg.com/@consento/sync-randombytes/-/sync-randombytes-1.0.5.tgz#5be6bc58c6a6fa6e09f04cc684d037e29e6c28d5"
|
||||||
@ -6311,6 +6316,15 @@ cli-spinners@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d"
|
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d"
|
||||||
integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==
|
integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==
|
||||||
|
|
||||||
|
cli-table3@^0.6.0:
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.2.tgz#aaf5df9d8b5bf12634dc8b3040806a0c07120d2a"
|
||||||
|
integrity sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==
|
||||||
|
dependencies:
|
||||||
|
string-width "^4.2.0"
|
||||||
|
optionalDependencies:
|
||||||
|
"@colors/colors" "1.5.0"
|
||||||
|
|
||||||
cli-width@^2.0.0:
|
cli-width@^2.0.0:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
|
||||||
@ -10216,6 +10230,14 @@ har-validator@~5.1.3:
|
|||||||
ajv "^6.12.3"
|
ajv "^6.12.3"
|
||||||
har-schema "^2.0.0"
|
har-schema "^2.0.0"
|
||||||
|
|
||||||
|
hardhat-contract-sizer@^2.6.1:
|
||||||
|
version "2.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/hardhat-contract-sizer/-/hardhat-contract-sizer-2.6.1.tgz#2b0046a55fa1ec96f19fdab7fde372377401c874"
|
||||||
|
integrity sha512-b8wS7DBvyo22kmVwpzstAQTdDCThpl/ySBqZh5ga9Yxjf61/uTL12TEg5nl7lDeWy73ntEUzxMwY6XxbQEc2wA==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.0.0"
|
||||||
|
cli-table3 "^0.6.0"
|
||||||
|
|
||||||
hardhat-log-remover@^2.0.2:
|
hardhat-log-remover@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/hardhat-log-remover/-/hardhat-log-remover-2.0.2.tgz#6014fe2c515ced1e0eaa7a4d854e37695aaac61a"
|
resolved "https://registry.yarnpkg.com/hardhat-log-remover/-/hardhat-log-remover-2.0.2.tgz#6014fe2c515ced1e0eaa7a4d854e37695aaac61a"
|
||||||
|
Loading…
Reference in New Issue
Block a user