mirror of
https://github.com/tornadocash/tornado-pool-factory
synced 2024-02-02 15:04:08 +01:00
add InstanceFactory version without registry
This commit is contained in:
parent
39659876fc
commit
ce1f8f43de
13
.solcover.js
13
.solcover.js
@ -1,14 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
skipFiles: [
|
skipFiles: [],
|
||||||
'tornado_proxy/TornadoProxy.sol',
|
|
||||||
'tornado_proxy/ITornadoTrees.sol',
|
|
||||||
'tornado_proxy/ITornadoInstance.sol',
|
|
||||||
'ERC20TornadoVirtual.sol',
|
|
||||||
'denomination_templates/Add1Instance.sol',
|
|
||||||
'denomination_templates/Add2Instances.sol',
|
|
||||||
'denomination_templates/Add3Instances.sol',
|
|
||||||
'denomination_templates/Add4Instances.sol',
|
|
||||||
'denomination_templates/Add5Instances.sol',
|
|
||||||
'denomination_templates/Add6Instances.sol',
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
33
README.md
33
README.md
@ -2,7 +2,19 @@
|
|||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
This repository contains governance proposal factory for the addition of new Tornado ERC20 instances to the Tornado router.
|
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
|
||||||
|
|
||||||
|
### InstanceFactory
|
||||||
|
|
||||||
|
Anyone can create a new ERC20 instance by calling `createInstanceClone` method of the factory with parameters:
|
||||||
|
|
||||||
|
1. `address token` - address of ERC20 token for a new instance
|
||||||
|
2. `uint256 denomination` - denomination for new instance (tokens can only be deposited in certain denominations into instances)
|
||||||
|
|
||||||
|
### InstanceFactoryWithRegistry
|
||||||
|
|
||||||
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 ERC20 instance by calling `createProposalApprove/createProposalPermit` method of the factory with parameters (proposal creation fee in TORN is charged from sender):
|
||||||
|
|
||||||
@ -13,13 +25,15 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b
|
|||||||
|
|
||||||
## Factory parameters
|
## Factory parameters
|
||||||
|
|
||||||
|
### InstanceFactoryWithRegistry
|
||||||
|
|
||||||
1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 3 instances at once.
|
1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 3 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`.
|
||||||
|
|
||||||
## 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. 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 `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.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
@ -46,7 +60,8 @@ Check config.js for actual values.
|
|||||||
|
|
||||||
With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be:
|
With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be:
|
||||||
|
|
||||||
1. `InstanceFactory` - `0xBb3bd4849F88E709Ea6e5dC8F2C4cDc5293a12d5`
|
1. `InstanceFactory` - `0x9A04e3F1091A69CB53D163abE7ad9bbc86C23823`
|
||||||
|
1. `InstanceFactoryWithRegistry` - `0xee994E045B9Ec5a37f3f85d34f9fD087A0c69236`
|
||||||
|
|
||||||
Check addresses with current config:
|
Check addresses with current config:
|
||||||
|
|
||||||
@ -61,14 +76,14 @@ Deploy InstanceFactory:
|
|||||||
yarn hardhat run scripts/deployInstanceFactory.js --network mainnet
|
yarn hardhat run scripts/deployInstanceFactory.js --network mainnet
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Deploy InstanceFactoryWithRegistry:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn hardhat run scripts/deployInstanceFactoryWithRegistry.js --network mainnet
|
||||||
|
```
|
||||||
|
|
||||||
Verify InstanceFactory on Etherscan:
|
Verify InstanceFactory on Etherscan:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn hardhat verify --network <network-name> <contract-address> <constructor-arguments>
|
yarn hardhat verify --network <network-name> <contract-address> <constructor-arguments>
|
||||||
```
|
```
|
||||||
|
|
||||||
With current config:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn hardhat verify --network mainnet 0x7a6e627DC6F66617b4A74Be097A8f56c622fa24c 0xce172ce1F20EC0B3728c9965470eaf994A03557A 0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe 20 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce 0xB20c66C4DE72433F3cE747b58B86830c459CA911 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C 200000000000000000000
|
|
||||||
```
|
|
||||||
|
@ -14,4 +14,5 @@ module.exports = {
|
|||||||
compWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC',
|
compWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC',
|
||||||
creationFee: '200000000000000000000', // 200 TORN
|
creationFee: '200000000000000000000', // 200 TORN
|
||||||
deployGasLimit: 7000000,
|
deployGasLimit: 7000000,
|
||||||
|
owner: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f',
|
||||||
}
|
}
|
||||||
|
@ -7,68 +7,44 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
|||||||
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||||
import "@openzeppelin/contracts/proxy/Clones.sol";
|
import "@openzeppelin/contracts/proxy/Clones.sol";
|
||||||
import "./ERC20TornadoCloneable.sol";
|
import "./ERC20TornadoCloneable.sol";
|
||||||
import "./AddInstanceProposal.sol";
|
|
||||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
||||||
import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol";
|
|
||||||
|
|
||||||
contract InstanceFactory is Ownable {
|
contract InstanceFactory is Ownable {
|
||||||
using Clones for address;
|
using Clones for address;
|
||||||
using Address for address;
|
using Address for address;
|
||||||
|
|
||||||
address public immutable governance;
|
|
||||||
address public immutable torn;
|
|
||||||
address public immutable instanceRegistry;
|
|
||||||
address public implementation;
|
address public implementation;
|
||||||
address public verifier;
|
address public verifier;
|
||||||
address public hasher;
|
address public hasher;
|
||||||
uint32 public merkleTreeHeight;
|
uint32 public merkleTreeHeight;
|
||||||
uint256 public creationFee;
|
|
||||||
|
|
||||||
event NewVerifierSet(address indexed newVerifier);
|
event NewVerifierSet(address indexed newVerifier);
|
||||||
event NewHasherSet(address indexed newHasher);
|
event NewHasherSet(address indexed newHasher);
|
||||||
event NewTreeHeightSet(uint32 indexed newTreeHeight);
|
event NewTreeHeightSet(uint32 indexed newTreeHeight);
|
||||||
event NewCreationFeeSet(uint256 indexed newCreationFee);
|
|
||||||
event NewImplementationSet(address indexed newImplemenentation);
|
event NewImplementationSet(address indexed newImplemenentation);
|
||||||
event NewInstanceCloneCreated(address indexed clone);
|
event NewInstanceCloneCreated(address indexed clone);
|
||||||
event NewGovernanceProposalCreated(address indexed proposal);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Throws if called by any account other than the Governance.
|
|
||||||
*/
|
|
||||||
modifier onlyGovernance() {
|
|
||||||
require(owner() == _msgSender(), "Caller is not the Governance");
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
address _verifier,
|
address _verifier,
|
||||||
address _hasher,
|
address _hasher,
|
||||||
uint32 _merkleTreeHeight,
|
uint32 _merkleTreeHeight,
|
||||||
address _governance,
|
address _owner
|
||||||
address _instanceRegistry,
|
|
||||||
address _torn,
|
|
||||||
uint256 _creationFee
|
|
||||||
) {
|
) {
|
||||||
verifier = _verifier;
|
verifier = _verifier;
|
||||||
hasher = _hasher;
|
hasher = _hasher;
|
||||||
merkleTreeHeight = _merkleTreeHeight;
|
merkleTreeHeight = _merkleTreeHeight;
|
||||||
governance = _governance;
|
|
||||||
instanceRegistry = _instanceRegistry;
|
|
||||||
torn = _torn;
|
|
||||||
creationFee = _creationFee;
|
|
||||||
|
|
||||||
ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher);
|
ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher);
|
||||||
implementation = address(implContract);
|
implementation = address(implContract);
|
||||||
|
|
||||||
transferOwnership(_governance);
|
transferOwnership(_owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Throws if called by any account other than the Governance.
|
* @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
|
||||||
*/
|
*/
|
||||||
function createInstanceClone(uint256 _denomination, address _token) external onlyGovernance returns (address) {
|
function createInstanceClone(uint256 _denomination, address _token) public virtual returns (address) {
|
||||||
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
||||||
|
|
||||||
require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance already exists");
|
require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance already exists");
|
||||||
@ -85,95 +61,27 @@ contract InstanceFactory is Ownable {
|
|||||||
return implementation.predictDeterministicAddress(salt);
|
return implementation.predictDeterministicAddress(salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function setVerifier(address _verifier) external onlyOwner {
|
||||||
* @dev Creates AddInstanceProposal with approve.
|
|
||||||
* @param _token address of ERC20 token for a new instance
|
|
||||||
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
|
|
||||||
* `3000` means 0.3% fee Uniswap pool.
|
|
||||||
* @param _denominations list of denominations for each new instance
|
|
||||||
* @param _protocolFees list of protocol fees for each new instance.
|
|
||||||
* `100` means that instance withdrawal fee is 1% of denomination.
|
|
||||||
*/
|
|
||||||
function createProposalApprove(
|
|
||||||
address _token,
|
|
||||||
uint24 _uniswapPoolSwappingFee,
|
|
||||||
uint256[] memory _denominations,
|
|
||||||
uint32[] memory _protocolFees
|
|
||||||
) external returns (address) {
|
|
||||||
require(IERC20(torn).transferFrom(msg.sender, governance, creationFee));
|
|
||||||
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Creates AddInstanceProposal with approve.
|
|
||||||
* @param _token address of ERC20 token for a new instance
|
|
||||||
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
|
|
||||||
* `3000` means 0.3% fee Uniswap pool.
|
|
||||||
* @param _denominations list of denominations for each new instance
|
|
||||||
* @param _protocolFees list of protocol fees for each new instance.
|
|
||||||
* `100` means that instance withdrawal fee is 1% of denomination.
|
|
||||||
*/
|
|
||||||
function createProposalPermit(
|
|
||||||
address _token,
|
|
||||||
uint24 _uniswapPoolSwappingFee,
|
|
||||||
uint256[] memory _denominations,
|
|
||||||
uint32[] memory _protocolFees,
|
|
||||||
address creater,
|
|
||||||
uint256 deadline,
|
|
||||||
uint8 v,
|
|
||||||
bytes32 r,
|
|
||||||
bytes32 s
|
|
||||||
) external returns (address) {
|
|
||||||
IERC20Permit(torn).permit(creater, address(this), creationFee, deadline, v, r, s);
|
|
||||||
require(IERC20(torn).transferFrom(creater, governance, creationFee));
|
|
||||||
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createProposal(
|
|
||||||
address _token,
|
|
||||||
uint24 _uniswapPoolSwappingFee,
|
|
||||||
uint256[] memory _denominations,
|
|
||||||
uint32[] memory _protocolFees
|
|
||||||
) internal returns (address) {
|
|
||||||
require(_token.isContract(), "Token is not contract");
|
|
||||||
require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero");
|
|
||||||
require(_denominations.length > 0, "Empty denominations");
|
|
||||||
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
|
||||||
|
|
||||||
address proposal = address(
|
|
||||||
new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees)
|
|
||||||
);
|
|
||||||
emit NewGovernanceProposalCreated(proposal);
|
|
||||||
|
|
||||||
return proposal;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVerifier(address _verifier) external onlyGovernance {
|
|
||||||
verifier = _verifier;
|
verifier = _verifier;
|
||||||
emit NewVerifierSet(verifier);
|
emit NewVerifierSet(verifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHasher(address _hasher) external onlyGovernance {
|
function setHasher(address _hasher) external onlyOwner {
|
||||||
hasher = _hasher;
|
hasher = _hasher;
|
||||||
emit NewHasherSet(hasher);
|
emit NewHasherSet(hasher);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyGovernance {
|
function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyOwner {
|
||||||
merkleTreeHeight = _merkleTreeHeight;
|
merkleTreeHeight = _merkleTreeHeight;
|
||||||
emit NewTreeHeightSet(merkleTreeHeight);
|
emit NewTreeHeightSet(merkleTreeHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCreationFee(uint256 _creationFee) external onlyGovernance {
|
function setImplementation(address _newImplementation) external onlyOwner {
|
||||||
creationFee = _creationFee;
|
|
||||||
emit NewCreationFeeSet(_creationFee);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setImplementation(address _newImplementation) external onlyGovernance {
|
|
||||||
implementation = _newImplementation;
|
implementation = _newImplementation;
|
||||||
emit NewImplementationSet(implementation);
|
emit NewImplementationSet(implementation);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateNewImplementation() external onlyGovernance {
|
function generateNewImplementation() external onlyOwner {
|
||||||
implementation = address(new ERC20TornadoCloneable(verifier, hasher));
|
implementation = address(new ERC20TornadoCloneable(verifier, hasher));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
122
contracts/InstanceFactoryWithRegistry.sol
Normal file
122
contracts/InstanceFactoryWithRegistry.sol
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity 0.7.6;
|
||||||
|
pragma abicoder v2;
|
||||||
|
|
||||||
|
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||||
|
import "./AddInstanceProposal.sol";
|
||||||
|
import "./InstanceFactory.sol";
|
||||||
|
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol";
|
||||||
|
|
||||||
|
contract InstanceFactoryWithRegistry is InstanceFactory {
|
||||||
|
using Address for address;
|
||||||
|
|
||||||
|
address public immutable governance;
|
||||||
|
address public immutable torn;
|
||||||
|
address public immutable instanceRegistry;
|
||||||
|
uint256 public creationFee;
|
||||||
|
|
||||||
|
event NewCreationFeeSet(uint256 indexed newCreationFee);
|
||||||
|
event NewGovernanceProposalCreated(address indexed proposal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Throws if called by any account other than the Governance.
|
||||||
|
*/
|
||||||
|
modifier onlyGovernance() {
|
||||||
|
require(owner() == _msgSender(), "Caller is not the Governance");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address _verifier,
|
||||||
|
address _hasher,
|
||||||
|
uint32 _merkleTreeHeight,
|
||||||
|
address _governance,
|
||||||
|
address _instanceRegistry,
|
||||||
|
address _torn,
|
||||||
|
uint256 _creationFee
|
||||||
|
) InstanceFactory(_verifier, _hasher, _merkleTreeHeight, _governance) {
|
||||||
|
governance = _governance;
|
||||||
|
instanceRegistry = _instanceRegistry;
|
||||||
|
torn = _torn;
|
||||||
|
creationFee = _creationFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev 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
|
||||||
|
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
|
||||||
|
* `3000` means 0.3% fee Uniswap pool.
|
||||||
|
* @param _denominations list of denominations for each new instance
|
||||||
|
* @param _protocolFees list of protocol fees for each new instance.
|
||||||
|
* `100` means that instance withdrawal fee is 1% of denomination.
|
||||||
|
*/
|
||||||
|
function createProposalApprove(
|
||||||
|
address _token,
|
||||||
|
uint24 _uniswapPoolSwappingFee,
|
||||||
|
uint256[] memory _denominations,
|
||||||
|
uint32[] memory _protocolFees
|
||||||
|
) external returns (address) {
|
||||||
|
require(IERC20(torn).transferFrom(msg.sender, governance, creationFee));
|
||||||
|
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Creates AddInstanceProposal with permit.
|
||||||
|
* @param _token address of ERC20 token for a new instance
|
||||||
|
* @param _uniswapPoolSwappingFee fee value of Uniswap instance which will be used for `TORN/token` price determination.
|
||||||
|
* `3000` means 0.3% fee Uniswap pool.
|
||||||
|
* @param _denominations list of denominations for each new instance
|
||||||
|
* @param _protocolFees list of protocol fees for each new instance.
|
||||||
|
* `100` means that instance withdrawal fee is 1% of denomination.
|
||||||
|
*/
|
||||||
|
function createProposalPermit(
|
||||||
|
address _token,
|
||||||
|
uint24 _uniswapPoolSwappingFee,
|
||||||
|
uint256[] memory _denominations,
|
||||||
|
uint32[] memory _protocolFees,
|
||||||
|
address creater,
|
||||||
|
uint256 deadline,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s
|
||||||
|
) external returns (address) {
|
||||||
|
IERC20Permit(torn).permit(creater, address(this), creationFee, deadline, v, r, s);
|
||||||
|
require(IERC20(torn).transferFrom(creater, governance, creationFee));
|
||||||
|
return _createProposal(_token, _uniswapPoolSwappingFee, _denominations, _protocolFees);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createProposal(
|
||||||
|
address _token,
|
||||||
|
uint24 _uniswapPoolSwappingFee,
|
||||||
|
uint256[] memory _denominations,
|
||||||
|
uint32[] memory _protocolFees
|
||||||
|
) internal returns (address) {
|
||||||
|
require(_token.isContract(), "Token is not contract");
|
||||||
|
require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero");
|
||||||
|
require(_denominations.length > 0, "Empty denominations");
|
||||||
|
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
||||||
|
|
||||||
|
address proposal = address(
|
||||||
|
new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees)
|
||||||
|
);
|
||||||
|
emit NewGovernanceProposalCreated(proposal);
|
||||||
|
|
||||||
|
return proposal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCreationFee(uint256 _creationFee) external onlyGovernance {
|
||||||
|
creationFee = _creationFee;
|
||||||
|
emit NewCreationFeeSet(_creationFee);
|
||||||
|
}
|
||||||
|
}
|
28
scripts/deployInstanceFactoryWithRegistry.js
Normal file
28
scripts/deployInstanceFactoryWithRegistry.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const { ethers } = require('hardhat')
|
||||||
|
const config = require('../config')
|
||||||
|
const { generate } = require('../src/generateAddresses')
|
||||||
|
|
||||||
|
async function deploy({ address, bytecode, singletonFactory }) {
|
||||||
|
const contractCode = await ethers.provider.getCode(address)
|
||||||
|
if (contractCode !== '0x') {
|
||||||
|
console.log(`Contract ${address} already deployed. Skipping...`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await singletonFactory.deploy(bytecode, config.salt, { gasLimit: config.deployGasLimit })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
|
||||||
|
const contracts = await generate()
|
||||||
|
await deploy({ ...contracts.factoryWithRegistryContract, singletonFactory })
|
||||||
|
console.log(
|
||||||
|
`Instance factory with registry contract have been deployed on ${contracts.factoryWithRegistryContract.address} address`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
@ -6,6 +6,19 @@ async function generate(config = defaultConfig) {
|
|||||||
const deploymentBytecodeFactory =
|
const deploymentBytecodeFactory =
|
||||||
FactoryFactory.bytecode +
|
FactoryFactory.bytecode +
|
||||||
FactoryFactory.interface
|
FactoryFactory.interface
|
||||||
|
.encodeDeploy([config.verifier, config.hasher, config.merkleTreeHeight, config.owner])
|
||||||
|
.slice(2)
|
||||||
|
|
||||||
|
const factoryAddress = ethers.utils.getCreate2Address(
|
||||||
|
config.singletonFactory,
|
||||||
|
config.salt,
|
||||||
|
ethers.utils.keccak256(deploymentBytecodeFactory),
|
||||||
|
)
|
||||||
|
|
||||||
|
const FactoryWithRegistryFactory = await ethers.getContractFactory('InstanceFactoryWithRegistry')
|
||||||
|
const deploymentBytecodeFactoryWithRegistry =
|
||||||
|
FactoryWithRegistryFactory.bytecode +
|
||||||
|
FactoryWithRegistryFactory.interface
|
||||||
.encodeDeploy([
|
.encodeDeploy([
|
||||||
config.verifier,
|
config.verifier,
|
||||||
config.hasher,
|
config.hasher,
|
||||||
@ -17,10 +30,10 @@ async function generate(config = defaultConfig) {
|
|||||||
])
|
])
|
||||||
.slice(2)
|
.slice(2)
|
||||||
|
|
||||||
const factoryAddress = ethers.utils.getCreate2Address(
|
const factoryWithRegistryAddress = ethers.utils.getCreate2Address(
|
||||||
config.singletonFactory,
|
config.singletonFactory,
|
||||||
config.salt,
|
config.salt,
|
||||||
ethers.utils.keccak256(deploymentBytecodeFactory),
|
ethers.utils.keccak256(deploymentBytecodeFactoryWithRegistry),
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
@ -29,6 +42,11 @@ async function generate(config = defaultConfig) {
|
|||||||
bytecode: deploymentBytecodeFactory,
|
bytecode: deploymentBytecodeFactory,
|
||||||
isProxy: false,
|
isProxy: false,
|
||||||
},
|
},
|
||||||
|
factoryWithRegistryContract: {
|
||||||
|
address: factoryWithRegistryAddress,
|
||||||
|
bytecode: deploymentBytecodeFactoryWithRegistry,
|
||||||
|
isProxy: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -37,6 +55,7 @@ async function generate(config = defaultConfig) {
|
|||||||
async function generateWithLog() {
|
async function generateWithLog() {
|
||||||
const contracts = await generate()
|
const contracts = await generate()
|
||||||
console.log('Instance factory contract: ', contracts.factoryContract.address)
|
console.log('Instance factory contract: ', contracts.factoryContract.address)
|
||||||
|
console.log('Instance factory with registry contract: ', contracts.factoryWithRegistryContract.address)
|
||||||
return contracts
|
return contracts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,45 +4,30 @@ const { loadFixture } = waffle
|
|||||||
const { expect } = require('chai')
|
const { expect } = require('chai')
|
||||||
const { BigNumber } = require('@ethersproject/bignumber')
|
const { BigNumber } = require('@ethersproject/bignumber')
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
const { getSignerFromAddress, minewait } = require('./utils')
|
const { getSignerFromAddress } = require('./utils')
|
||||||
const { PermitSigner } = require('../src/permit.js')
|
|
||||||
const { generate } = require('../src/generateAddresses')
|
const { generate } = require('../src/generateAddresses')
|
||||||
|
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
||||||
|
|
||||||
describe('Instance Factory Tests', () => {
|
describe('Instance Factory Tests', () => {
|
||||||
const ProposalState = {
|
|
||||||
Pending: 0,
|
|
||||||
Active: 1,
|
|
||||||
Defeated: 2,
|
|
||||||
Timelocked: 3,
|
|
||||||
AwaitingExecution: 4,
|
|
||||||
Executed: 5,
|
|
||||||
Expired: 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
const addressZero = ethers.constants.AddressZero
|
const addressZero = ethers.constants.AddressZero
|
||||||
|
|
||||||
async function fixture() {
|
async function fixture() {
|
||||||
const [sender, deployer, multisig] = await ethers.getSigners()
|
const [sender, deployer] = await ethers.getSigners()
|
||||||
|
|
||||||
const tornWhale = await getSignerFromAddress(config.tornWhale)
|
const owner = await getSignerFromAddress(config.owner)
|
||||||
|
|
||||||
const gov = await ethers.getContractAt('Governance', config.governance)
|
await sender.sendTransaction({
|
||||||
|
to: config.owner,
|
||||||
|
value: ethers.utils.parseEther('1'),
|
||||||
|
})
|
||||||
|
|
||||||
const tornToken = await ethers.getContractAt(
|
const compWhale = await getSignerFromAddress(config.compWhale)
|
||||||
'@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
|
|
||||||
config.TORN,
|
|
||||||
)
|
|
||||||
|
|
||||||
const compToken = await ethers.getContractAt(
|
const compToken = await ethers.getContractAt(
|
||||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||||
config.COMP,
|
config.COMP,
|
||||||
)
|
)
|
||||||
|
|
||||||
const instanceRegistry = await ethers.getContractAt(
|
|
||||||
'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry',
|
|
||||||
config.instanceRegistry,
|
|
||||||
)
|
|
||||||
|
|
||||||
// deploy InstanceFactory with CREATE2
|
// deploy InstanceFactory with CREATE2
|
||||||
const singletonFactory = await ethers.getContractAt(
|
const singletonFactory = await ethers.getContractAt(
|
||||||
'SingletonFactory',
|
'SingletonFactory',
|
||||||
@ -59,328 +44,115 @@ describe('Instance Factory Tests', () => {
|
|||||||
return {
|
return {
|
||||||
sender,
|
sender,
|
||||||
deployer,
|
deployer,
|
||||||
multisig,
|
owner,
|
||||||
tornWhale,
|
|
||||||
gov,
|
|
||||||
tornToken,
|
|
||||||
compToken,
|
compToken,
|
||||||
instanceRegistry,
|
compWhale,
|
||||||
instanceFactory,
|
instanceFactory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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, compToken, instanceFactory } = await loadFixture(fixture)
|
||||||
expect(sender.address).to.exist
|
expect(sender.address).to.exist
|
||||||
expect(gov.address).to.exist
|
expect(compToken.address).to.exist
|
||||||
expect(tornToken.address).to.exist
|
|
||||||
expect(instanceRegistry.address).to.exist
|
|
||||||
expect(instanceFactory.address).to.exist
|
expect(instanceFactory.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 } = await loadFixture(fixture)
|
||||||
|
|
||||||
expect(await instanceFactory.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.implementation()).to.exist
|
||||||
expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee)
|
|
||||||
expect(await instanceFactory.torn()).to.be.equal(config.TORN)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Governance should be able to set factory params', async function () {
|
it('Governance should be able to set factory params', async function () {
|
||||||
let { instanceFactory, gov } = await loadFixture(fixture)
|
let { instanceFactory, owner } = await loadFixture(fixture)
|
||||||
|
|
||||||
await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted
|
await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted
|
||||||
|
|
||||||
const govSigner = await getSignerFromAddress(gov.address)
|
instanceFactory = await instanceFactory.connect(owner)
|
||||||
instanceFactory = await instanceFactory.connect(govSigner)
|
|
||||||
|
|
||||||
await instanceFactory.setVerifier(addressZero)
|
await instanceFactory.setVerifier(addressZero)
|
||||||
await instanceFactory.setHasher(addressZero)
|
await instanceFactory.setHasher(addressZero)
|
||||||
await instanceFactory.setMerkleTreeHeight(1)
|
await instanceFactory.setMerkleTreeHeight(1)
|
||||||
await instanceFactory.setCreationFee(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)
|
|
||||||
|
|
||||||
await instanceFactory.setVerifier(config.verifier)
|
await instanceFactory.setVerifier(config.verifier)
|
||||||
await instanceFactory.setHasher(config.hasher)
|
await instanceFactory.setHasher(config.hasher)
|
||||||
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
||||||
await instanceFactory.setCreationFee(config.creationFee)
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should successfully deploy/propose/execute proposal - add instance', async function () {
|
it('Should successfully add instance', async function () {
|
||||||
let { sender, instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture)
|
let { sender, instanceFactory } = await loadFixture(fixture)
|
||||||
|
|
||||||
// deploy proposal ----------------------------------------------
|
// deploy instance
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP)
|
||||||
await tornToken.approve(instanceFactory.address, config.creationFee)
|
|
||||||
|
|
||||||
await expect(() =>
|
// check instance initialization
|
||||||
instanceFactory
|
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
||||||
.connect(sender)
|
const instance = await ethers.getContractAt(
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
'ERC20TornadoCloneable',
|
||||||
).to.changeTokenBalances(
|
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(1)
|
|
||||||
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
|
||||||
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let response, id, state
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
response = await gov.propose(proposal.address, 'COMP token instance proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
state = await gov.state(id)
|
|
||||||
|
|
||||||
const { events } = await response.wait()
|
|
||||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
|
||||||
expect(args.id).to.be.equal(id)
|
|
||||||
expect(args.proposer).to.be.equal(tornWhale.address)
|
|
||||||
expect(args.target).to.be.equal(proposal.address)
|
|
||||||
expect(args.description).to.be.equal('COMP token instance proposal')
|
|
||||||
expect(state).to.be.equal(ProposalState.Pending)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
|
||||||
|
|
||||||
let tx = await gov.execute(id)
|
|
||||||
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
|
||||||
|
|
||||||
// check instance initialization --------------------------------
|
|
||||||
let receipt = await tx.wait()
|
|
||||||
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
|
|
||||||
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100'))
|
|
||||||
|
|
||||||
const instanceData = await instanceRegistry.instances(instance.address)
|
|
||||||
expect(instanceData.isERC20).to.be.equal(true)
|
|
||||||
expect(instanceData.token).to.be.equal(config.COMP)
|
|
||||||
expect(instanceData.state).to.be.equal(1)
|
|
||||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
|
||||||
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should successfully deploy/propose/execute proposal - add instances', async function () {
|
|
||||||
let { sender, instanceFactory, 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 expect(() =>
|
|
||||||
instanceFactory
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(
|
|
||||||
config.COMP,
|
|
||||||
3000,
|
|
||||||
[ethers.utils.parseEther('100'), ethers.utils.parseEther('1000')],
|
|
||||||
[30, 30],
|
|
||||||
),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
|
||||||
|
|
||||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
|
||||||
expect(await proposal.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
|
||||||
expect(await proposal.numInstances()).to.be.equal(2)
|
|
||||||
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
|
||||||
expect(await proposal.protocolFeeByIndex(1)).to.be.equal(30)
|
|
||||||
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
|
||||||
expect(await proposal.denominationByIndex(1)).to.be.equal(ethers.utils.parseEther('1000'))
|
|
||||||
|
|
||||||
// propose proposal ---------------------------------------------
|
|
||||||
let response, id, state
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
|
|
||||||
response = await gov.propose(proposal.address, 'COMP token instances proposal')
|
|
||||||
id = await gov.latestProposalIds(tornWhale.address)
|
|
||||||
state = await gov.state(id)
|
|
||||||
|
|
||||||
const { events } = await response.wait()
|
|
||||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
|
||||||
expect(args.id).to.be.equal(id)
|
|
||||||
expect(args.proposer).to.be.equal(tornWhale.address)
|
|
||||||
expect(args.target).to.be.equal(proposal.address)
|
|
||||||
expect(args.description).to.be.equal('COMP token instances proposal')
|
|
||||||
expect(state).to.be.equal(ProposalState.Pending)
|
|
||||||
|
|
||||||
// execute proposal ---------------------------------------------
|
|
||||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
|
||||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
|
||||||
await minewait(
|
|
||||||
(
|
|
||||||
await gov.VOTING_PERIOD()
|
|
||||||
)
|
|
||||||
.add(await gov.EXECUTION_DELAY())
|
|
||||||
.add(96400)
|
|
||||||
.toNumber(),
|
|
||||||
)
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
|
||||||
|
|
||||||
await gov.execute(id)
|
|
||||||
|
|
||||||
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
|
||||||
|
|
||||||
// check instances initialization -------------------------------
|
|
||||||
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
let instanceAddr = '0x' + logs[0].topics[1].slice(-40)
|
|
||||||
let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100'))
|
|
||||||
|
|
||||||
let instanceData = await instanceRegistry.instances(instance.address)
|
|
||||||
expect(instanceData.isERC20).to.be.equal(true)
|
|
||||||
expect(instanceData.token).to.be.equal(config.COMP)
|
|
||||||
expect(instanceData.state).to.be.equal(1)
|
|
||||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
|
||||||
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
|
||||||
|
|
||||||
instanceAddr = '0x' + logs[1].topics[1].slice(-40)
|
|
||||||
instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
expect(await instance.token()).to.be.equal(config.COMP)
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
expect(await instance.verifier()).to.be.equal(config.verifier)
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
expect(await instance.hasher()).to.be.equal(config.hasher)
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
||||||
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000'))
|
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000'))
|
||||||
|
|
||||||
instanceData = await instanceRegistry.instances(instance.address)
|
|
||||||
expect(instanceData.isERC20).to.be.equal(true)
|
|
||||||
expect(instanceData.token).to.be.equal(config.COMP)
|
|
||||||
expect(instanceData.state).to.be.equal(1)
|
|
||||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
|
||||||
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should successfully deploy proposal with permit', async function () {
|
it('Should deposit and withdraw into the new instance', async function () {
|
||||||
let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture)
|
let { sender, instanceFactory, compToken, compWhale } = await loadFixture(fixture)
|
||||||
|
|
||||||
const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
|
// deploy instance
|
||||||
const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex'))
|
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('100'), config.COMP)
|
||||||
const sender = await ethers.getSigner(publicKey.slice(2))
|
|
||||||
|
|
||||||
await expect(() =>
|
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
||||||
tornToken.connect(tornWhale).transfer(sender.address, config.creationFee),
|
const instance = await ethers.getContractAt(
|
||||||
).to.changeTokenBalances(
|
'ERC20TornadoCloneable',
|
||||||
tornToken,
|
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||||
[tornWhale, sender],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// prepare permit data
|
// check instance work ------------------------------------------
|
||||||
const domain = {
|
const depo = createDeposit({
|
||||||
name: await tornToken.name(),
|
nullifier: rbigint(31),
|
||||||
version: '1',
|
secret: rbigint(31),
|
||||||
chainId: 1,
|
})
|
||||||
verifyingContract: tornToken.address,
|
|
||||||
}
|
|
||||||
|
|
||||||
const curTimestamp = Math.trunc(new Date().getTime() / 1000)
|
const value = ethers.utils.parseEther('100')
|
||||||
const args = {
|
|
||||||
owner: sender,
|
|
||||||
spender: instanceFactory.address,
|
|
||||||
value: config.creationFee,
|
|
||||||
nonce: 0,
|
|
||||||
deadline: curTimestamp + 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
const permitSigner = new PermitSigner(domain, args)
|
await compToken.connect(compWhale).transfer(sender.address, value)
|
||||||
const signature = await permitSigner.getSignature(privateKey)
|
await compToken.connect(sender).approve(instance.address, value)
|
||||||
const signer = await permitSigner.getSignerAddress(args, signature.hex)
|
|
||||||
expect(signer).to.equal(sender.address)
|
|
||||||
|
|
||||||
await expect(() =>
|
await expect(() => instance.deposit(toHex(depo.commitment), [])).to.changeTokenBalances(
|
||||||
instanceFactory.createProposalPermit(
|
compToken,
|
||||||
config.COMP,
|
[sender, instance],
|
||||||
3000,
|
[BigNumber.from(0).sub(value), value],
|
||||||
[ethers.utils.parseEther('100')],
|
|
||||||
[30],
|
|
||||||
sender.address,
|
|
||||||
args.deadline.toString(),
|
|
||||||
signature.v,
|
|
||||||
signature.r,
|
|
||||||
signature.s,
|
|
||||||
),
|
|
||||||
).to.changeTokenBalances(
|
|
||||||
tornToken,
|
|
||||||
[sender, gov],
|
|
||||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
let pevents = await instance.queryFilter('Deposit')
|
||||||
const proposal = await ethers.getContractAt(
|
await initialize({ merkleTreeHeight: 20 })
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
const { proof, args } = await generateProof({
|
||||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
deposit: depo,
|
||||||
expect(await proposal.token()).to.be.equal(config.COMP)
|
recipient: sender.address,
|
||||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
events: pevents,
|
||||||
expect(await proposal.numInstances()).to.be.equal(1)
|
})
|
||||||
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
|
||||||
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
await expect(() => instance.withdraw(proof, ...args)).to.changeTokenBalances(
|
||||||
|
compToken,
|
||||||
|
[instance, sender],
|
||||||
|
[BigNumber.from(0).sub(value), value],
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
482
test/factory.with.registry.test.js
Normal file
482
test/factory.with.registry.test.js
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
const hre = require('hardhat')
|
||||||
|
const { ethers, waffle } = hre
|
||||||
|
const { loadFixture } = waffle
|
||||||
|
const { expect } = require('chai')
|
||||||
|
const { BigNumber } = require('@ethersproject/bignumber')
|
||||||
|
const config = require('../config')
|
||||||
|
const { getSignerFromAddress, minewait } = require('./utils')
|
||||||
|
const { PermitSigner } = require('../src/permit.js')
|
||||||
|
const { generate } = require('../src/generateAddresses')
|
||||||
|
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
||||||
|
|
||||||
|
describe('Instance Factory With Registry Tests', () => {
|
||||||
|
const ProposalState = {
|
||||||
|
Pending: 0,
|
||||||
|
Active: 1,
|
||||||
|
Defeated: 2,
|
||||||
|
Timelocked: 3,
|
||||||
|
AwaitingExecution: 4,
|
||||||
|
Executed: 5,
|
||||||
|
Expired: 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
const addressZero = ethers.constants.AddressZero
|
||||||
|
|
||||||
|
async function fixture() {
|
||||||
|
const [sender, deployer, multisig] = await ethers.getSigners()
|
||||||
|
|
||||||
|
const tornWhale = await getSignerFromAddress(config.tornWhale)
|
||||||
|
|
||||||
|
const compWhale = await getSignerFromAddress(config.compWhale)
|
||||||
|
|
||||||
|
const gov = await ethers.getContractAt('Governance', config.governance)
|
||||||
|
|
||||||
|
const router = await ethers.getContractAt(
|
||||||
|
'tornado-relayer-registry/contracts/tornado-proxy/TornadoRouter.sol:TornadoRouter',
|
||||||
|
config.router,
|
||||||
|
)
|
||||||
|
|
||||||
|
const tornToken = await ethers.getContractAt(
|
||||||
|
'@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
|
||||||
|
config.TORN,
|
||||||
|
)
|
||||||
|
|
||||||
|
const compToken = await ethers.getContractAt(
|
||||||
|
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||||
|
config.COMP,
|
||||||
|
)
|
||||||
|
|
||||||
|
const instanceRegistry = await ethers.getContractAt(
|
||||||
|
'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry',
|
||||||
|
config.instanceRegistry,
|
||||||
|
)
|
||||||
|
|
||||||
|
// deploy InstanceFactoryWithRegistry with CREATE2
|
||||||
|
const singletonFactory = await ethers.getContractAt(
|
||||||
|
'SingletonFactory',
|
||||||
|
config.singletonFactoryVerboseWrapper,
|
||||||
|
)
|
||||||
|
const contracts = await generate()
|
||||||
|
if ((await ethers.provider.getCode(contracts.factoryWithRegistryContract.address)) == '0x') {
|
||||||
|
await singletonFactory.deploy(contracts.factoryWithRegistryContract.bytecode, config.salt, {
|
||||||
|
gasLimit: config.deployGasLimit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const instanceFactory = await ethers.getContractAt(
|
||||||
|
'InstanceFactoryWithRegistry',
|
||||||
|
contracts.factoryWithRegistryContract.address,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sender,
|
||||||
|
deployer,
|
||||||
|
multisig,
|
||||||
|
tornWhale,
|
||||||
|
compWhale,
|
||||||
|
router,
|
||||||
|
gov,
|
||||||
|
tornToken,
|
||||||
|
compToken,
|
||||||
|
instanceRegistry,
|
||||||
|
instanceFactory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should have initialized all successfully', async function () {
|
||||||
|
const { sender, gov, tornToken, instanceRegistry, instanceFactory } = 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
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should set correct params for factory', async function () {
|
||||||
|
const { instanceFactory } = await loadFixture(fixture)
|
||||||
|
|
||||||
|
expect(await instanceFactory.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)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Governance should be able to set factory params', async function () {
|
||||||
|
let { instanceFactory, gov } = await loadFixture(fixture)
|
||||||
|
|
||||||
|
await expect(instanceFactory.setVerifier(addressZero)).to.be.reverted
|
||||||
|
|
||||||
|
const govSigner = await getSignerFromAddress(gov.address)
|
||||||
|
instanceFactory = await instanceFactory.connect(govSigner)
|
||||||
|
|
||||||
|
await instanceFactory.setVerifier(addressZero)
|
||||||
|
await instanceFactory.setHasher(addressZero)
|
||||||
|
await instanceFactory.setMerkleTreeHeight(1)
|
||||||
|
await instanceFactory.setCreationFee(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)
|
||||||
|
|
||||||
|
await instanceFactory.setVerifier(config.verifier)
|
||||||
|
await instanceFactory.setHasher(config.hasher)
|
||||||
|
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
||||||
|
await instanceFactory.setCreationFee(config.creationFee)
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should successfully deploy/propose/execute proposal - add instance', async function () {
|
||||||
|
let { sender, instanceFactory, 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 expect(() =>
|
||||||
|
instanceFactory
|
||||||
|
.connect(sender)
|
||||||
|
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
||||||
|
).to.changeTokenBalances(
|
||||||
|
tornToken,
|
||||||
|
[sender, gov],
|
||||||
|
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||||
|
)
|
||||||
|
|
||||||
|
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||||
|
const proposal = await ethers.getContractAt(
|
||||||
|
'AddInstanceProposal',
|
||||||
|
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
||||||
|
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
||||||
|
expect(await proposal.token()).to.be.equal(config.COMP)
|
||||||
|
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
||||||
|
expect(await proposal.numInstances()).to.be.equal(1)
|
||||||
|
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
||||||
|
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
||||||
|
|
||||||
|
// propose proposal ---------------------------------------------
|
||||||
|
let response, id, state
|
||||||
|
gov = await gov.connect(tornWhale)
|
||||||
|
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
||||||
|
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
||||||
|
|
||||||
|
response = await gov.propose(proposal.address, 'COMP token instance proposal')
|
||||||
|
id = await gov.latestProposalIds(tornWhale.address)
|
||||||
|
state = await gov.state(id)
|
||||||
|
|
||||||
|
const { events } = await response.wait()
|
||||||
|
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||||
|
expect(args.id).to.be.equal(id)
|
||||||
|
expect(args.proposer).to.be.equal(tornWhale.address)
|
||||||
|
expect(args.target).to.be.equal(proposal.address)
|
||||||
|
expect(args.description).to.be.equal('COMP token instance proposal')
|
||||||
|
expect(state).to.be.equal(ProposalState.Pending)
|
||||||
|
|
||||||
|
// execute proposal ---------------------------------------------
|
||||||
|
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
||||||
|
await expect(gov.castVote(id, true)).to.not.be.reverted
|
||||||
|
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
||||||
|
await minewait(
|
||||||
|
(
|
||||||
|
await gov.VOTING_PERIOD()
|
||||||
|
)
|
||||||
|
.add(await gov.EXECUTION_DELAY())
|
||||||
|
.add(96400)
|
||||||
|
.toNumber(),
|
||||||
|
)
|
||||||
|
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
||||||
|
|
||||||
|
let tx = await gov.execute(id)
|
||||||
|
|
||||||
|
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
||||||
|
|
||||||
|
// check instance initialization --------------------------------
|
||||||
|
let receipt = await tx.wait()
|
||||||
|
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
|
||||||
|
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
||||||
|
|
||||||
|
expect(await instance.token()).to.be.equal(config.COMP)
|
||||||
|
expect(await instance.verifier()).to.be.equal(config.verifier)
|
||||||
|
expect(await instance.hasher()).to.be.equal(config.hasher)
|
||||||
|
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
||||||
|
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100'))
|
||||||
|
|
||||||
|
const instanceData = await instanceRegistry.instances(instance.address)
|
||||||
|
expect(instanceData.isERC20).to.be.equal(true)
|
||||||
|
expect(instanceData.token).to.be.equal(config.COMP)
|
||||||
|
expect(instanceData.state).to.be.equal(1)
|
||||||
|
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
||||||
|
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should successfully deploy/propose/execute proposal - add instances', async function () {
|
||||||
|
let { sender, instanceFactory, 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 expect(() =>
|
||||||
|
instanceFactory
|
||||||
|
.connect(sender)
|
||||||
|
.createProposalApprove(
|
||||||
|
config.COMP,
|
||||||
|
3000,
|
||||||
|
[ethers.utils.parseEther('100'), ethers.utils.parseEther('1000')],
|
||||||
|
[30, 30],
|
||||||
|
),
|
||||||
|
).to.changeTokenBalances(
|
||||||
|
tornToken,
|
||||||
|
[sender, gov],
|
||||||
|
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||||
|
)
|
||||||
|
|
||||||
|
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||||
|
const proposal = await ethers.getContractAt(
|
||||||
|
'AddInstanceProposal',
|
||||||
|
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
||||||
|
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
||||||
|
expect(await proposal.token()).to.be.equal(config.COMP)
|
||||||
|
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
||||||
|
expect(await proposal.numInstances()).to.be.equal(2)
|
||||||
|
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
||||||
|
expect(await proposal.protocolFeeByIndex(1)).to.be.equal(30)
|
||||||
|
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
||||||
|
expect(await proposal.denominationByIndex(1)).to.be.equal(ethers.utils.parseEther('1000'))
|
||||||
|
|
||||||
|
// propose proposal ---------------------------------------------
|
||||||
|
let response, id, state
|
||||||
|
gov = await gov.connect(tornWhale)
|
||||||
|
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
||||||
|
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
||||||
|
|
||||||
|
response = await gov.propose(proposal.address, 'COMP token instances proposal')
|
||||||
|
id = await gov.latestProposalIds(tornWhale.address)
|
||||||
|
state = await gov.state(id)
|
||||||
|
|
||||||
|
const { events } = await response.wait()
|
||||||
|
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||||
|
expect(args.id).to.be.equal(id)
|
||||||
|
expect(args.proposer).to.be.equal(tornWhale.address)
|
||||||
|
expect(args.target).to.be.equal(proposal.address)
|
||||||
|
expect(args.description).to.be.equal('COMP token instances proposal')
|
||||||
|
expect(state).to.be.equal(ProposalState.Pending)
|
||||||
|
|
||||||
|
// execute proposal ---------------------------------------------
|
||||||
|
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
||||||
|
await expect(gov.castVote(id, true)).to.not.be.reverted
|
||||||
|
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
||||||
|
await minewait(
|
||||||
|
(
|
||||||
|
await gov.VOTING_PERIOD()
|
||||||
|
)
|
||||||
|
.add(await gov.EXECUTION_DELAY())
|
||||||
|
.add(96400)
|
||||||
|
.toNumber(),
|
||||||
|
)
|
||||||
|
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
||||||
|
|
||||||
|
await gov.execute(id)
|
||||||
|
|
||||||
|
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
||||||
|
|
||||||
|
// check instances initialization -------------------------------
|
||||||
|
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
||||||
|
let instanceAddr = '0x' + logs[logs.length - 2].topics[1].slice(-40)
|
||||||
|
let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
||||||
|
|
||||||
|
expect(await instance.token()).to.be.equal(config.COMP)
|
||||||
|
expect(await instance.verifier()).to.be.equal(config.verifier)
|
||||||
|
expect(await instance.hasher()).to.be.equal(config.hasher)
|
||||||
|
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
||||||
|
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100'))
|
||||||
|
|
||||||
|
let instanceData = await instanceRegistry.instances(instance.address)
|
||||||
|
expect(instanceData.isERC20).to.be.equal(true)
|
||||||
|
expect(instanceData.token).to.be.equal(config.COMP)
|
||||||
|
expect(instanceData.state).to.be.equal(1)
|
||||||
|
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
||||||
|
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
||||||
|
|
||||||
|
instanceAddr = '0x' + logs[logs.length - 1].topics[1].slice(-40)
|
||||||
|
instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
||||||
|
|
||||||
|
expect(await instance.token()).to.be.equal(config.COMP)
|
||||||
|
expect(await instance.verifier()).to.be.equal(config.verifier)
|
||||||
|
expect(await instance.hasher()).to.be.equal(config.hasher)
|
||||||
|
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
||||||
|
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000'))
|
||||||
|
|
||||||
|
instanceData = await instanceRegistry.instances(instance.address)
|
||||||
|
expect(instanceData.isERC20).to.be.equal(true)
|
||||||
|
expect(instanceData.token).to.be.equal(config.COMP)
|
||||||
|
expect(instanceData.state).to.be.equal(1)
|
||||||
|
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
||||||
|
expect(instanceData.protocolFeePercentage).to.be.equal(30)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should successfully deploy proposal with permit', async function () {
|
||||||
|
let { instanceFactory, gov, instanceRegistry, tornWhale, tornToken } = await loadFixture(fixture)
|
||||||
|
|
||||||
|
const privateKey = '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
|
||||||
|
const publicKey = '0x' + ethers.utils.computeAddress(Buffer.from(privateKey.slice(2), 'hex'))
|
||||||
|
const sender = await ethers.getSigner(publicKey.slice(2))
|
||||||
|
|
||||||
|
await expect(() =>
|
||||||
|
tornToken.connect(tornWhale).transfer(sender.address, config.creationFee),
|
||||||
|
).to.changeTokenBalances(
|
||||||
|
tornToken,
|
||||||
|
[tornWhale, sender],
|
||||||
|
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||||
|
)
|
||||||
|
|
||||||
|
// prepare permit data
|
||||||
|
const domain = {
|
||||||
|
name: await tornToken.name(),
|
||||||
|
version: '1',
|
||||||
|
chainId: 1,
|
||||||
|
verifyingContract: tornToken.address,
|
||||||
|
}
|
||||||
|
|
||||||
|
const curTimestamp = Math.trunc(new Date().getTime() / 1000)
|
||||||
|
const args = {
|
||||||
|
owner: sender,
|
||||||
|
spender: instanceFactory.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(() =>
|
||||||
|
instanceFactory.createProposalPermit(
|
||||||
|
config.COMP,
|
||||||
|
3000,
|
||||||
|
[ethers.utils.parseEther('100')],
|
||||||
|
[30],
|
||||||
|
sender.address,
|
||||||
|
args.deadline.toString(),
|
||||||
|
signature.v,
|
||||||
|
signature.r,
|
||||||
|
signature.s,
|
||||||
|
),
|
||||||
|
).to.changeTokenBalances(
|
||||||
|
tornToken,
|
||||||
|
[sender, gov],
|
||||||
|
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||||
|
)
|
||||||
|
|
||||||
|
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||||
|
const proposal = await ethers.getContractAt(
|
||||||
|
'AddInstanceProposal',
|
||||||
|
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
||||||
|
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
||||||
|
expect(await proposal.token()).to.be.equal(config.COMP)
|
||||||
|
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
||||||
|
expect(await proposal.numInstances()).to.be.equal(1)
|
||||||
|
expect(await proposal.protocolFeeByIndex(0)).to.be.equal(30)
|
||||||
|
expect(await proposal.denominationByIndex(0)).to.be.equal(ethers.utils.parseEther('100'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should deposit and withdraw into the new instance', async function () {
|
||||||
|
let { sender, instanceFactory, 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 expect(() =>
|
||||||
|
instanceFactory
|
||||||
|
.connect(sender)
|
||||||
|
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30]),
|
||||||
|
).to.changeTokenBalances(
|
||||||
|
tornToken,
|
||||||
|
[sender, gov],
|
||||||
|
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||||
|
)
|
||||||
|
|
||||||
|
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||||
|
const proposal = await ethers.getContractAt(
|
||||||
|
'AddInstanceProposal',
|
||||||
|
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// propose proposal ---------------------------------------------
|
||||||
|
let id
|
||||||
|
gov = await gov.connect(tornWhale)
|
||||||
|
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
||||||
|
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
||||||
|
|
||||||
|
await gov.propose(proposal.address, 'COMP token instance proposal')
|
||||||
|
id = await gov.latestProposalIds(tornWhale.address)
|
||||||
|
|
||||||
|
// execute proposal ---------------------------------------------
|
||||||
|
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
||||||
|
await expect(gov.castVote(id, true)).to.not.be.reverted
|
||||||
|
await minewait(
|
||||||
|
(
|
||||||
|
await gov.VOTING_PERIOD()
|
||||||
|
)
|
||||||
|
.add(await gov.EXECUTION_DELAY())
|
||||||
|
.add(96400)
|
||||||
|
.toNumber(),
|
||||||
|
)
|
||||||
|
|
||||||
|
let tx = await gov.execute(id)
|
||||||
|
let receipt = await tx.wait()
|
||||||
|
const instanceAddr = '0x' + receipt.events[0].topics[1].toString().slice(-40)
|
||||||
|
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
||||||
|
|
||||||
|
// check instance work ------------------------------------------
|
||||||
|
const depo = createDeposit({
|
||||||
|
nullifier: rbigint(31),
|
||||||
|
secret: rbigint(31),
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = ethers.utils.parseEther('100')
|
||||||
|
|
||||||
|
await compToken.connect(compWhale).transfer(sender.address, value)
|
||||||
|
await compToken.connect(sender).approve(router.address, value)
|
||||||
|
|
||||||
|
await expect(() => router.deposit(instance.address, toHex(depo.commitment), [])).to.changeTokenBalances(
|
||||||
|
compToken,
|
||||||
|
[sender, instance],
|
||||||
|
[BigNumber.from(0).sub(value), value],
|
||||||
|
)
|
||||||
|
|
||||||
|
let pevents = await instance.queryFilter('Deposit')
|
||||||
|
await initialize({ merkleTreeHeight: 20 })
|
||||||
|
|
||||||
|
const { proof, args } = await generateProof({
|
||||||
|
deposit: depo,
|
||||||
|
recipient: sender.address,
|
||||||
|
events: pevents,
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeTokenBalances(
|
||||||
|
compToken,
|
||||||
|
[instance, sender],
|
||||||
|
[BigNumber.from(0).sub(value), value],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,166 +0,0 @@
|
|||||||
const hre = require('hardhat')
|
|
||||||
const { ethers, waffle } = hre
|
|
||||||
const { loadFixture } = waffle
|
|
||||||
const { expect } = require('chai')
|
|
||||||
const { BigNumber } = require('@ethersproject/bignumber')
|
|
||||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
|
||||||
const config = require('../config')
|
|
||||||
const { getSignerFromAddress, minewait } = require('./utils')
|
|
||||||
const { generate } = require('../src/generateAddresses')
|
|
||||||
|
|
||||||
describe('Instance Factory Tests', () => {
|
|
||||||
const ProposalState = {
|
|
||||||
Pending: 0,
|
|
||||||
Active: 1,
|
|
||||||
Defeated: 2,
|
|
||||||
Timelocked: 3,
|
|
||||||
AwaitingExecution: 4,
|
|
||||||
Executed: 5,
|
|
||||||
Expired: 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fixture() {
|
|
||||||
const [sender, deployer, multisig] = await ethers.getSigners()
|
|
||||||
|
|
||||||
const tornWhale = await getSignerFromAddress(config.tornWhale)
|
|
||||||
const compWhale = await getSignerFromAddress(config.compWhale)
|
|
||||||
|
|
||||||
let gov = await ethers.getContractAt('Governance', config.governance)
|
|
||||||
|
|
||||||
const tornToken = await ethers.getContractAt(
|
|
||||||
'@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
|
|
||||||
config.TORN,
|
|
||||||
)
|
|
||||||
|
|
||||||
const compToken = await ethers.getContractAt(
|
|
||||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
|
||||||
config.COMP,
|
|
||||||
)
|
|
||||||
|
|
||||||
const instanceRegistry = await ethers.getContractAt(
|
|
||||||
'tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol:InstanceRegistry',
|
|
||||||
config.instanceRegistry,
|
|
||||||
)
|
|
||||||
|
|
||||||
const router = await ethers.getContractAt(
|
|
||||||
'tornado-relayer-registry/contracts/tornado-proxy/TornadoRouter.sol:TornadoRouter',
|
|
||||||
config.router,
|
|
||||||
)
|
|
||||||
|
|
||||||
// deploy InstanceFactory with CREATE2
|
|
||||||
const singletonFactory = await ethers.getContractAt(
|
|
||||||
'SingletonFactory',
|
|
||||||
config.singletonFactoryVerboseWrapper,
|
|
||||||
)
|
|
||||||
const contracts = await generate()
|
|
||||||
if ((await ethers.provider.getCode(contracts.factoryContract.address)) == '0x') {
|
|
||||||
await singletonFactory.deploy(contracts.factoryContract.bytecode, config.salt, {
|
|
||||||
gasLimit: config.deployGasLimit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factoryContract.address)
|
|
||||||
|
|
||||||
// deploy proposal
|
|
||||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
|
||||||
await tornToken.approve(instanceFactory.address, config.creationFee)
|
|
||||||
|
|
||||||
await instanceFactory
|
|
||||||
.connect(sender)
|
|
||||||
.createProposalApprove(config.COMP, 3000, [ethers.utils.parseEther('100')], [30])
|
|
||||||
|
|
||||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
|
||||||
const proposal = await ethers.getContractAt(
|
|
||||||
'AddInstanceProposal',
|
|
||||||
ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// propose proposal
|
|
||||||
gov = await gov.connect(tornWhale)
|
|
||||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
|
||||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
|
||||||
await gov.propose(proposal.address, 'COMP token instance proposal')
|
|
||||||
const 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
|
|
||||||
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)
|
|
||||||
|
|
||||||
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
|
||||||
const instance = await ethers.getContractAt(
|
|
||||||
'ERC20TornadoCloneable',
|
|
||||||
ethers.utils.getAddress('0x' + logs[0].topics[1].slice(-40)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
sender,
|
|
||||||
deployer,
|
|
||||||
multisig,
|
|
||||||
tornWhale,
|
|
||||||
compWhale,
|
|
||||||
gov,
|
|
||||||
tornToken,
|
|
||||||
compToken,
|
|
||||||
instanceRegistry,
|
|
||||||
router,
|
|
||||||
instanceFactory,
|
|
||||||
instance,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it('Should set correct params for factory', async function () {
|
|
||||||
const { instance } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
expect(await instance.token()).to.be.equal(config.COMP)
|
|
||||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
|
||||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
|
||||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
|
||||||
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('100'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should deposit and withdraw into the new instance', async function () {
|
|
||||||
const { sender, instance, compToken, compWhale, router } = await loadFixture(fixture)
|
|
||||||
|
|
||||||
const depo = createDeposit({
|
|
||||||
nullifier: rbigint(31),
|
|
||||||
secret: rbigint(31),
|
|
||||||
})
|
|
||||||
|
|
||||||
const value = ethers.utils.parseEther('100')
|
|
||||||
|
|
||||||
await compToken.connect(compWhale).transfer(sender.address, value)
|
|
||||||
await compToken.connect(sender).approve(router.address, value)
|
|
||||||
|
|
||||||
await expect(() => router.deposit(instance.address, toHex(depo.commitment), [])).to.changeTokenBalances(
|
|
||||||
compToken,
|
|
||||||
[sender, instance],
|
|
||||||
[BigNumber.from(0).sub(value), value],
|
|
||||||
)
|
|
||||||
|
|
||||||
let pevents = await instance.queryFilter('Deposit')
|
|
||||||
await initialize({ merkleTreeHeight: 20 })
|
|
||||||
|
|
||||||
const { proof, args } = await generateProof({
|
|
||||||
deposit: depo,
|
|
||||||
recipient: sender.address,
|
|
||||||
events: pevents,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(() => router.withdraw(instance.address, proof, ...args)).to.changeTokenBalances(
|
|
||||||
compToken,
|
|
||||||
[instance, sender],
|
|
||||||
[BigNumber.from(0).sub(value), value],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
Loading…
Reference in New Issue
Block a user