mirror of
https://github.com/tornadocash/tornado-pool-factory
synced 2024-02-02 15:04:08 +01:00
commit
281c8c50ab
17
.env.example
17
.env.example
@ -1,13 +1,4 @@
|
||||
etherscan_api_key=
|
||||
goerli_rpc_key=
|
||||
mainnet_rpc_key=
|
||||
goerli_account_pk=
|
||||
mainnet_account_pk=
|
||||
use_latest_block=false
|
||||
|
||||
PROXY=0x722122dF12D4e14e13Ac3b6895a86e84145b6967
|
||||
DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80
|
||||
HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe
|
||||
VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A
|
||||
SALT=0x0000000000000000000000000000000000000000000000000000000047941987
|
||||
COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888
|
||||
ETHERSCAN_KEY=
|
||||
ALCHEMY_KEY=
|
||||
PRIVATE_KEY=
|
||||
INFURA_API_KEY=
|
||||
|
@ -1,8 +0,0 @@
|
||||
use_latest_block=false
|
||||
|
||||
PROXY=0x722122dF12D4e14e13Ac3b6895a86e84145b6967
|
||||
DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80
|
||||
HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe
|
||||
VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A
|
||||
SALT=0x0000000000000000000000000000000000000000000000000000000047941987
|
||||
COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888
|
28
.github/workflows/build.js.yml
vendored
28
.github/workflows/build.js.yml
vendored
@ -3,34 +3,22 @@ name: build
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
tags: ['v[0-9]+.[0-9]+.[0-9]+']
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: secrets
|
||||
env:
|
||||
etherscan_api_key: ${{ secrets.ETHERSCAN_API_KEY }}
|
||||
goerli_rpc_key: ${{ secrets.GOERLI_RPC_KEY }}
|
||||
mainnet_rpc_key: ${{ secrets.MAINNET_RPC_KEY }}
|
||||
goerli_account_pk: ${{ secrets.GOERLI_ACCOUNT_PK }}
|
||||
mainnet_account_pk: ${{ secrets.MAINNET_ACCOUNT_PK }}
|
||||
steps:
|
||||
- name: Tests and setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- run: yarn install
|
||||
- run: cp .env.example.workflow .env
|
||||
- run: yarn prettier:fix
|
||||
node-version: 14
|
||||
- run: yarn cache clean --all
|
||||
- run: yarn install --network-concurrency 1
|
||||
- run: yarn lint
|
||||
- run: yarn compile
|
||||
- run: yarn test
|
||||
|
||||
- name: Generate coverage
|
||||
run: yarn coverage
|
||||
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
env:
|
||||
ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }}
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -106,4 +106,6 @@ dist
|
||||
artifacts
|
||||
cache
|
||||
coverage
|
||||
coverage.json
|
||||
coverage.json
|
||||
|
||||
.vscode
|
13
.solcover.js
13
.solcover.js
@ -1,14 +1,3 @@
|
||||
module.exports = {
|
||||
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',
|
||||
],
|
||||
skipFiles: [],
|
||||
}
|
||||
|
109
README.md
109
README.md
@ -1,87 +1,90 @@
|
||||
# Tornado Instances
|
||||
|
||||
[![build](https://img.shields.io/github/workflow/status/mirru2532/tornado-instances/build)](https://github.com/h-ivor/tornado-instances/actions) [![Coveralls](https://img.shields.io/coveralls/github/mirru2532/tornado-instances)](https://coveralls.io/github/mirru2532/tornado-instances)
|
||||
# Tornado Instances Factory
|
||||
|
||||
## About
|
||||
|
||||
This repository serves as a general template repository for deploying proposals for governance for the addition of new tornado ERC20 instances to the tornado proxy.
|
||||
This repository contains:
|
||||
|
||||
The contracts offer a template for the addition of 4 new instances for an ERC20 token (the standard way to add new instances) and can also be slightly modified to add a custom amount. The scripts deploy the contracts.
|
||||
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
|
||||
|
||||
The resources folder contains data necessary for the instances to be deployed and must be filled out. They are initially filled out with the RAI instance data.
|
||||
### InstanceFactory
|
||||
|
||||
### How-To:
|
||||
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):
|
||||
|
||||
1. `address token` - address of ERC20 token for a new instance
|
||||
2. `uint24 uniswapPoolSwappingFee` - fee value of Uniswap instance which will be used for `TORN/token` price determination. `3000` means 0.3% fee Uniswap pool.
|
||||
3. `uint256[] denominations` - list of denominations for each new instance (tokens can only be deposited in certain denominations into instances).
|
||||
4. `uint32[] protocolFees` - list of protocol fees for each new instance (this fee is only charged from registrated relayer during withdrawal process). `100` means 1% of instance denomination fee for withdrawal throw registrated relayer.
|
||||
|
||||
## Factory parameters
|
||||
|
||||
### InstanceFactoryWithRegistry
|
||||
|
||||
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`.
|
||||
3. `TWAPSlotsMin` - minimum number of TWAP slots for Uniswap pool that is chosen during `createProposalApprove/createProposalPermit` factory method call. It can be changed by governance. Default value is stored in `config.js`.
|
||||
|
||||
## Warnings
|
||||
|
||||
1. This version of the factory creates a proposal for **immutable** Tornado instance initialization.
|
||||
2. For `InstanceFactoryWithRegistry` users should manually propose a proposal after its creation using the factory (in governance UI for example). As `propose()` method caller must have 1000 TORN locked in the governance. Moreover, the proposer can't propose more than one proposal simultaneously.
|
||||
|
||||
## Tests
|
||||
|
||||
Setting up the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mirru2532/tornado-instances.git
|
||||
cd tornado-instances
|
||||
yarn
|
||||
cp .env.example .env
|
||||
yarn
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Please fill out .env according to the template provided in it. Please ensure that all of the example values are set to the correct addresses.
|
||||
|
||||
### Testing and running scripts:
|
||||
|
||||
To run test scripts:
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
yarn test
|
||||
```
|
||||
|
||||
Test scripts cover instance factory deployment, proposal deployment and executing proposal (RAI instances).
|
||||
Test scripts cover instance factory deployment, proposal deployment and executing proposal.
|
||||
|
||||
Running **tasks:**
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
# a list of yarn scripts specifically for instance deployment
|
||||
"deploy:proposal": "yarn hardhat --network mainnet deploy_proposal --factory-address",
|
||||
"deploy:proposal:test": "yarn hardhat --network goerli deploy_proposal --factory-address",
|
||||
"deploy:proposal:factory": "yarn hardhat --network mainnet deploy_factory_proposal",
|
||||
"deploy:proposal:factory:test": "yarn hardhat --network goerli deploy_factory_proposal",
|
||||
"propose": "yarn hardhat --network mainnet propose_proposal --proposal-address",
|
||||
Check config.js for actual values.
|
||||
|
||||
# as an example
|
||||
yarn deploy:proposal:factory
|
||||
With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be:
|
||||
|
||||
# to call a specific task
|
||||
yarn hardhat --network <network> <task> <args>
|
||||
1. `InstanceFactory` - `0x09110e04d5AEF747bcf7A3585D8FFC892Ab9D1Cf`
|
||||
2. `InstanceFactoryWithRegistry` - `0xC01D57d83E9Fe35E0Fb900F9D384EFcA679DF4bD`
|
||||
|
||||
Check addresses with current config:
|
||||
|
||||
```shell
|
||||
yarn compile
|
||||
node -e 'require("./src/generateAddresses").generateWithLog()'
|
||||
```
|
||||
|
||||
## Deploying a proposal for an instance update
|
||||
Deploy InstanceFactory:
|
||||
|
||||
Open `resources/instances.js`, a single object which generates an instance contains the following fields (RAI as an example):
|
||||
|
||||
```js
|
||||
{
|
||||
tokenAddress: "0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919",
|
||||
denomination: "33333333333333333333",
|
||||
symbol: "RAI",
|
||||
decimals: 18
|
||||
}
|
||||
```shell
|
||||
yarn hardhat run scripts/deployInstanceFactory.js --network mainnet
|
||||
```
|
||||
|
||||
`denomination` - tokens can only be deposited in certain denominations into instances, the above considers this instance to have a 100$ denomination, assuming RAI at 3$.
|
||||
Fill out each of these fields for your own token in the `instance.js` file. This repo supports the addition of a maximum of 6 instances at once. It will automatically detect how many instances are to use.
|
||||
Deploy InstanceFactoryWithRegistry:
|
||||
|
||||
Now find the factory contract address:
|
||||
|
||||
```bash
|
||||
yarn deploy:proposal <factory address>
|
||||
```shell
|
||||
yarn hardhat run scripts/deployInstanceFactoryWithRegistry.js --network mainnet
|
||||
```
|
||||
|
||||
If testing:
|
||||
Verify InstanceFactory on Etherscan:
|
||||
|
||||
```bash
|
||||
yarn deploy:proposal:test <factory address>
|
||||
```
|
||||
|
||||
The last step, or first depending on if you are simply proposing the proposal, is taking the address of the deployed proposal and calling:
|
||||
|
||||
```bash
|
||||
yarn propose <proposal address>
|
||||
yarn hardhat verify --network <network-name> <contract-address> <constructor-arguments>
|
||||
```
|
||||
|
||||
There is not test implementation for this.
|
||||
|
21
config.js
Normal file
21
config.js
Normal file
@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
verifier: '0xce172ce1F20EC0B3728c9965470eaf994A03557A',
|
||||
hasher: '0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe',
|
||||
governance: '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
instanceRegistry: '0xB20c66C4DE72433F3cE747b58B86830c459CA911',
|
||||
router: '0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b',
|
||||
merkleTreeHeight: 20,
|
||||
singletonFactory: '0xce0042B868300000d44A59004Da54A005ffdcf9f',
|
||||
singletonFactoryVerboseWrapper: '0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80',
|
||||
salt: '0x0000000000000000000000000000000000000000000000000000000047941987',
|
||||
COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888',
|
||||
TORN: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
||||
tornWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC',
|
||||
compWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC',
|
||||
creationFee: '200000000000000000000', // 200 TORN
|
||||
deployGasLimit: 7000000,
|
||||
owner: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f',
|
||||
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',
|
||||
TWAPSlotsMin: 50,
|
||||
}
|
102
contracts/AddInstanceProposal.sol
Normal file
102
contracts/AddInstanceProposal.sol
Normal file
@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "./interfaces/IInstanceRegistry.sol";
|
||||
import "./InstanceFactory.sol";
|
||||
|
||||
contract AddInstanceProposal {
|
||||
InstanceFactory public immutable instanceFactory;
|
||||
IInstanceRegistry public immutable instanceRegistry;
|
||||
address public immutable token;
|
||||
uint24 public immutable uniswapPoolSwappingFee;
|
||||
|
||||
uint256 public immutable numInstances;
|
||||
uint256 internal immutable denomination0;
|
||||
uint256 internal immutable denomination1;
|
||||
uint256 internal immutable denomination2;
|
||||
uint256 internal immutable denomination3;
|
||||
uint32 internal immutable protocolFee0;
|
||||
uint32 internal immutable protocolFee1;
|
||||
uint32 internal immutable protocolFee2;
|
||||
uint32 internal immutable protocolFee3;
|
||||
|
||||
event AddInstanceForRegistry(address instance, address token, uint256 denomination);
|
||||
|
||||
constructor(
|
||||
address _instanceFactory,
|
||||
address _instanceRegistry,
|
||||
address _token,
|
||||
uint24 _uniswapPoolSwappingFee,
|
||||
uint256[] memory _denominations,
|
||||
uint32[] memory _protocolFees
|
||||
) {
|
||||
instanceFactory = InstanceFactory(_instanceFactory);
|
||||
instanceRegistry = IInstanceRegistry(_instanceRegistry);
|
||||
token = _token;
|
||||
uniswapPoolSwappingFee = _uniswapPoolSwappingFee;
|
||||
|
||||
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
||||
uint256 _numInstances = _denominations.length;
|
||||
require(_numInstances > 0 && _numInstances <= 4, "incorrect instances number");
|
||||
numInstances = _numInstances;
|
||||
|
||||
denomination0 = _numInstances > 0 ? _denominations[0] : 0;
|
||||
denomination1 = _numInstances > 1 ? _denominations[1] : 0;
|
||||
denomination2 = _numInstances > 2 ? _denominations[2] : 0;
|
||||
denomination3 = _numInstances > 3 ? _denominations[3] : 0;
|
||||
protocolFee0 = _numInstances > 0 ? _protocolFees[0] : 0;
|
||||
protocolFee1 = _numInstances > 1 ? _protocolFees[1] : 0;
|
||||
protocolFee2 = _numInstances > 2 ? _protocolFees[2] : 0;
|
||||
protocolFee3 = _numInstances > 3 ? _protocolFees[3] : 0;
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
for (uint256 i = 0; i < numInstances; i++) {
|
||||
address instance = instanceFactory.createInstanceClone(denominationByIndex(i), token);
|
||||
|
||||
IInstanceRegistry.Instance memory newInstanceData = IInstanceRegistry.Instance(
|
||||
true,
|
||||
IERC20(token),
|
||||
IInstanceRegistry.InstanceState.ENABLED,
|
||||
uniswapPoolSwappingFee,
|
||||
protocolFeeByIndex(i)
|
||||
);
|
||||
|
||||
IInstanceRegistry.Tornado memory tornadoForUpdate = IInstanceRegistry.Tornado(instance, newInstanceData);
|
||||
|
||||
instanceRegistry.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit AddInstanceForRegistry(address(instance), token, denominationByIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
function denominationByIndex(uint256 _index) public view returns (uint256) {
|
||||
if (_index == 0) {
|
||||
return denomination0;
|
||||
} else if (_index == 1) {
|
||||
return denomination1;
|
||||
} else if (_index == 2) {
|
||||
return denomination2;
|
||||
} else if (_index == 3) {
|
||||
return denomination3;
|
||||
} else {
|
||||
revert("Invalid instance index");
|
||||
}
|
||||
}
|
||||
|
||||
function protocolFeeByIndex(uint256 _index) public view returns (uint32) {
|
||||
if (_index == 0) {
|
||||
return protocolFee0;
|
||||
} else if (_index == 1) {
|
||||
return protocolFee1;
|
||||
} else if (_index == 2) {
|
||||
return protocolFee2;
|
||||
} else if (_index == 3) {
|
||||
return protocolFee3;
|
||||
} else {
|
||||
revert("Invalid instance index");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "./TornadoInstanceCloneFactory.sol";
|
||||
import "./tornado_proxy/TornadoProxy.sol";
|
||||
|
||||
contract CreateFactoryAndAddInstancesProposal {
|
||||
address public constant verifier = 0xce172ce1F20EC0B3728c9965470eaf994A03557A;
|
||||
address public constant hasher = 0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe;
|
||||
address public constant governance = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
|
||||
TornadoInstanceCloneFactory public immutable instanceFactory;
|
||||
address public immutable token;
|
||||
address public immutable proxyAddress;
|
||||
|
||||
uint256 public immutable denomination1;
|
||||
uint256 public immutable denomination2;
|
||||
uint256 public immutable denomination3;
|
||||
uint256 public immutable denomination4;
|
||||
|
||||
event UpdatedInstanceForProxy(address indexed instance, address indexed token, uint256 indexed denomination);
|
||||
|
||||
constructor(
|
||||
address _proxyAddress,
|
||||
uint256[4] memory _denominations,
|
||||
address _token
|
||||
) {
|
||||
TornadoInstanceCloneFactory cachedFactory = new TornadoInstanceCloneFactory(verifier, hasher, 20);
|
||||
cachedFactory.transferOwnership(governance);
|
||||
instanceFactory = cachedFactory;
|
||||
|
||||
token = _token;
|
||||
proxyAddress = _proxyAddress;
|
||||
|
||||
denomination1 = _denominations[0];
|
||||
denomination2 = _denominations[1];
|
||||
denomination3 = _denominations[2];
|
||||
denomination4 = _denominations[3];
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
TornadoProxy tornadoProxy = TornadoProxy(proxyAddress);
|
||||
|
||||
for (uint256 i = 0; i < 4; i++) {
|
||||
ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token));
|
||||
TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(
|
||||
true,
|
||||
IERC20(token),
|
||||
TornadoProxy.InstanceState.ENABLED
|
||||
);
|
||||
TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData);
|
||||
|
||||
tornadoProxy.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination());
|
||||
}
|
||||
}
|
||||
|
||||
function denominations(uint256 index) private view returns (uint256) {
|
||||
if (index > 2) {
|
||||
return denomination4;
|
||||
} else if (index > 1) {
|
||||
return denomination3;
|
||||
} else if (index > 0) {
|
||||
return denomination2;
|
||||
} else {
|
||||
return denomination1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "tornado-core/contracts/ERC20Tornado.sol";
|
||||
@ -13,12 +14,10 @@ contract ERC20TornadoCloneable is ERC20Tornado {
|
||||
address _token
|
||||
) external {
|
||||
require(denomination == 0 && levels == 0, "already initialized");
|
||||
/// In Constructor: ERC20Tornado
|
||||
|
||||
token = IERC20(_token);
|
||||
/// In Constructor: Tornado
|
||||
require(_denomination > 0, "denomination should be greater than 0");
|
||||
denomination = _denomination;
|
||||
/// In Constructor: MerkleTreeWithHistory
|
||||
require(_merkleTreeHeight > 0, "_levels should be greater than zero");
|
||||
require(_merkleTreeHeight < 32, "_levels should be less than 32");
|
||||
levels = _merkleTreeHeight;
|
||||
|
@ -1,337 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
// https://tornado.cash
|
||||
/*
|
||||
* d888888P dP a88888b. dP
|
||||
* 88 88 d8' `88 88
|
||||
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||
*/
|
||||
|
||||
interface IHasher {
|
||||
function MiMCSponge(uint256 in_xL, uint256 in_xR) external pure returns (uint256 xL, uint256 xR);
|
||||
}
|
||||
|
||||
contract MerkleTreeWithHistory {
|
||||
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE
|
||||
|
||||
IHasher public hasher;
|
||||
uint32 public levels;
|
||||
|
||||
// the following variables are made public for easier testing and debugging and
|
||||
// are not supposed to be accessed in regular code
|
||||
|
||||
// filledSubtrees and roots could be bytes32[size], but using mappings makes it cheaper because
|
||||
// it removes index range check on every interaction
|
||||
mapping(uint256 => bytes32) public filledSubtrees;
|
||||
mapping(uint256 => bytes32) public roots;
|
||||
uint32 public constant ROOT_HISTORY_SIZE = 30;
|
||||
uint32 public currentRootIndex = 0;
|
||||
uint32 public nextIndex = 0;
|
||||
|
||||
constructor(uint32 _levels, IHasher _hasher) {
|
||||
require(_levels > 0, "_levels should be greater than zero");
|
||||
require(_levels < 32, "_levels should be less than 32");
|
||||
levels = _levels;
|
||||
hasher = _hasher;
|
||||
|
||||
for (uint32 i = 0; i < _levels; i++) {
|
||||
filledSubtrees[i] = zeros(i);
|
||||
}
|
||||
|
||||
roots[0] = zeros(_levels - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Hash 2 tree leaves, returns MiMC(_left, _right)
|
||||
*/
|
||||
function hashLeftRight(
|
||||
IHasher _hasher,
|
||||
bytes32 _left,
|
||||
bytes32 _right
|
||||
) public pure returns (bytes32) {
|
||||
require(uint256(_left) < FIELD_SIZE, "_left should be inside the field");
|
||||
require(uint256(_right) < FIELD_SIZE, "_right should be inside the field");
|
||||
uint256 R = uint256(_left);
|
||||
uint256 C = 0;
|
||||
(R, C) = _hasher.MiMCSponge(R, C);
|
||||
R = addmod(R, uint256(_right), FIELD_SIZE);
|
||||
(R, C) = _hasher.MiMCSponge(R, C);
|
||||
return bytes32(R);
|
||||
}
|
||||
|
||||
function _insert(bytes32 _leaf) internal returns (uint32 index) {
|
||||
uint32 _nextIndex = nextIndex;
|
||||
require(_nextIndex != uint32(2)**levels, "Merkle tree is full. No more leaves can be added");
|
||||
uint32 currentIndex = _nextIndex;
|
||||
bytes32 currentLevelHash = _leaf;
|
||||
bytes32 left;
|
||||
bytes32 right;
|
||||
|
||||
for (uint32 i = 0; i < levels; i++) {
|
||||
if (currentIndex % 2 == 0) {
|
||||
left = currentLevelHash;
|
||||
right = zeros(i);
|
||||
filledSubtrees[i] = currentLevelHash;
|
||||
} else {
|
||||
left = filledSubtrees[i];
|
||||
right = currentLevelHash;
|
||||
}
|
||||
currentLevelHash = hashLeftRight(hasher, left, right);
|
||||
currentIndex /= 2;
|
||||
}
|
||||
|
||||
uint32 newRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
|
||||
currentRootIndex = newRootIndex;
|
||||
roots[newRootIndex] = currentLevelHash;
|
||||
nextIndex = _nextIndex + 1;
|
||||
return _nextIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Whether the root is present in the root history
|
||||
*/
|
||||
function isKnownRoot(bytes32 _root) public view returns (bool) {
|
||||
if (_root == 0) {
|
||||
return false;
|
||||
}
|
||||
uint32 _currentRootIndex = currentRootIndex;
|
||||
uint32 i = _currentRootIndex;
|
||||
do {
|
||||
if (_root == roots[i]) {
|
||||
return true;
|
||||
}
|
||||
if (i == 0) {
|
||||
i = ROOT_HISTORY_SIZE;
|
||||
}
|
||||
i--;
|
||||
} while (i != _currentRootIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Returns the last root
|
||||
*/
|
||||
function getLastRoot() public view returns (bytes32) {
|
||||
return roots[currentRootIndex];
|
||||
}
|
||||
|
||||
/// @dev provides Zero (Empty) elements for a MiMC MerkleTree. Up to 32 levels
|
||||
function zeros(uint256 i) public pure returns (bytes32) {
|
||||
if (i == 0) return bytes32(0x2fe54c60d3acabf3343a35b6eba15db4821b340f76e741e2249685ed4899af6c);
|
||||
else if (i == 1) return bytes32(0x256a6135777eee2fd26f54b8b7037a25439d5235caee224154186d2b8a52e31d);
|
||||
else if (i == 2) return bytes32(0x1151949895e82ab19924de92c40a3d6f7bcb60d92b00504b8199613683f0c200);
|
||||
else if (i == 3) return bytes32(0x20121ee811489ff8d61f09fb89e313f14959a0f28bb428a20dba6b0b068b3bdb);
|
||||
else if (i == 4) return bytes32(0x0a89ca6ffa14cc462cfedb842c30ed221a50a3d6bf022a6a57dc82ab24c157c9);
|
||||
else if (i == 5) return bytes32(0x24ca05c2b5cd42e890d6be94c68d0689f4f21c9cec9c0f13fe41d566dfb54959);
|
||||
else if (i == 6) return bytes32(0x1ccb97c932565a92c60156bdba2d08f3bf1377464e025cee765679e604a7315c);
|
||||
else if (i == 7) return bytes32(0x19156fbd7d1a8bf5cba8909367de1b624534ebab4f0f79e003bccdd1b182bdb4);
|
||||
else if (i == 8) return bytes32(0x261af8c1f0912e465744641409f622d466c3920ac6e5ff37e36604cb11dfff80);
|
||||
else if (i == 9) return bytes32(0x0058459724ff6ca5a1652fcbc3e82b93895cf08e975b19beab3f54c217d1c007);
|
||||
else if (i == 10) return bytes32(0x1f04ef20dee48d39984d8eabe768a70eafa6310ad20849d4573c3c40c2ad1e30);
|
||||
else if (i == 11) return bytes32(0x1bea3dec5dab51567ce7e200a30f7ba6d4276aeaa53e2686f962a46c66d511e5);
|
||||
else if (i == 12) return bytes32(0x0ee0f941e2da4b9e31c3ca97a40d8fa9ce68d97c084177071b3cb46cd3372f0f);
|
||||
else if (i == 13) return bytes32(0x1ca9503e8935884501bbaf20be14eb4c46b89772c97b96e3b2ebf3a36a948bbd);
|
||||
else if (i == 14) return bytes32(0x133a80e30697cd55d8f7d4b0965b7be24057ba5dc3da898ee2187232446cb108);
|
||||
else if (i == 15) return bytes32(0x13e6d8fc88839ed76e182c2a779af5b2c0da9dd18c90427a644f7e148a6253b6);
|
||||
else if (i == 16) return bytes32(0x1eb16b057a477f4bc8f572ea6bee39561098f78f15bfb3699dcbb7bd8db61854);
|
||||
else if (i == 17) return bytes32(0x0da2cb16a1ceaabf1c16b838f7a9e3f2a3a3088d9e0a6debaa748114620696ea);
|
||||
else if (i == 18) return bytes32(0x24a3b3d822420b14b5d8cb6c28a574f01e98ea9e940551d2ebd75cee12649f9d);
|
||||
else if (i == 19) return bytes32(0x198622acbd783d1b0d9064105b1fc8e4d8889de95c4c519b3f635809fe6afc05);
|
||||
else if (i == 20) return bytes32(0x29d7ed391256ccc3ea596c86e933b89ff339d25ea8ddced975ae2fe30b5296d4);
|
||||
else if (i == 21) return bytes32(0x19be59f2f0413ce78c0c3703a3a5451b1d7f39629fa33abd11548a76065b2967);
|
||||
else if (i == 22) return bytes32(0x1ff3f61797e538b70e619310d33f2a063e7eb59104e112e95738da1254dc3453);
|
||||
else if (i == 23) return bytes32(0x10c16ae9959cf8358980d9dd9616e48228737310a10e2b6b731c1a548f036c48);
|
||||
else if (i == 24) return bytes32(0x0ba433a63174a90ac20992e75e3095496812b652685b5e1a2eae0b1bf4e8fcd1);
|
||||
else if (i == 25) return bytes32(0x019ddb9df2bc98d987d0dfeca9d2b643deafab8f7036562e627c3667266a044c);
|
||||
else if (i == 26) return bytes32(0x2d3c88b23175c5a5565db928414c66d1912b11acf974b2e644caaac04739ce99);
|
||||
else if (i == 27) return bytes32(0x2eab55f6ae4e66e32c5189eed5c470840863445760f5ed7e7b69b2a62600f354);
|
||||
else if (i == 28) return bytes32(0x002df37a2642621802383cf952bf4dd1f32e05433beeb1fd41031fb7eace979d);
|
||||
else if (i == 29) return bytes32(0x104aeb41435db66c3e62feccc1d6f5d98d0a0ed75d1374db457cf462e3a1f427);
|
||||
else if (i == 30) return bytes32(0x1f3c6fd858e9a7d4b0d1f38e256a09d81d5a5e3c963987e2d4b814cfab7c6ebb);
|
||||
else if (i == 31) return bytes32(0x2c7a07d20dff79d01fecedc1134284a8d08436606c93693b67e333f671bf69cc);
|
||||
else revert("Index out of bounds");
|
||||
}
|
||||
}
|
||||
|
||||
// https://tornado.cash
|
||||
/*
|
||||
* d888888P dP a88888b. dP
|
||||
* 88 88 d8' `88 88
|
||||
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||
*/
|
||||
|
||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||
|
||||
interface IVerifier {
|
||||
function verifyProof(bytes memory _proof, uint256[6] memory _input) external returns (bool);
|
||||
}
|
||||
|
||||
abstract contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
||||
IVerifier public verifier;
|
||||
uint256 public denomination;
|
||||
|
||||
mapping(bytes32 => bool) public nullifierHashes;
|
||||
// we store all commitments just to prevent accidental deposits with the same commitment
|
||||
mapping(bytes32 => bool) public commitments;
|
||||
|
||||
event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp);
|
||||
event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee);
|
||||
|
||||
/**
|
||||
@dev The constructor
|
||||
@param _verifier the address of SNARK verifier for this contract
|
||||
@param _hasher the address of MiMC hash contract
|
||||
@param _denomination transfer amount for each deposit
|
||||
@param _merkleTreeHeight the height of deposits' Merkle Tree
|
||||
*/
|
||||
constructor(
|
||||
IVerifier _verifier,
|
||||
IHasher _hasher,
|
||||
uint256 _denomination,
|
||||
uint32 _merkleTreeHeight
|
||||
) MerkleTreeWithHistory(_merkleTreeHeight, _hasher) {
|
||||
require(_denomination > 0, "denomination should be greater than 0");
|
||||
verifier = _verifier;
|
||||
denomination = _denomination;
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Deposit funds into the contract. The caller must send (for ETH) or approve (for ERC20) value equal to or `denomination` of this instance.
|
||||
@param _commitment the note commitment, which is PedersenHash(nullifier + secret)
|
||||
*/
|
||||
function deposit(bytes32 _commitment) external payable nonReentrant {
|
||||
require(!commitments[_commitment], "The commitment has been submitted");
|
||||
|
||||
uint32 insertedIndex = _insert(_commitment);
|
||||
commitments[_commitment] = true;
|
||||
_processDeposit();
|
||||
|
||||
emit Deposit(_commitment, insertedIndex, block.timestamp);
|
||||
}
|
||||
|
||||
/** @dev this function is defined in a child contract */
|
||||
function _processDeposit() internal virtual;
|
||||
|
||||
/**
|
||||
@dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs
|
||||
`input` array consists of:
|
||||
- merkle root of all deposits in the contract
|
||||
- hash of unique deposit nullifier to prevent double spends
|
||||
- the recipient of funds
|
||||
- optional fee that goes to the transaction sender (usually a relay)
|
||||
*/
|
||||
function withdraw(
|
||||
bytes calldata _proof,
|
||||
bytes32 _root,
|
||||
bytes32 _nullifierHash,
|
||||
address payable _recipient,
|
||||
address payable _relayer,
|
||||
uint256 _fee,
|
||||
uint256 _refund
|
||||
) external payable nonReentrant {
|
||||
require(_fee <= denomination, "Fee exceeds transfer value");
|
||||
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
|
||||
require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one
|
||||
require(
|
||||
verifier.verifyProof(
|
||||
_proof,
|
||||
[uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _fee, _refund]
|
||||
),
|
||||
"Invalid withdraw proof"
|
||||
);
|
||||
|
||||
nullifierHashes[_nullifierHash] = true;
|
||||
_processWithdraw(_recipient, _relayer, _fee, _refund);
|
||||
emit Withdrawal(_recipient, _nullifierHash, _relayer, _fee);
|
||||
}
|
||||
|
||||
/** @dev this function is defined in a child contract */
|
||||
function _processWithdraw(
|
||||
address payable _recipient,
|
||||
address payable _relayer,
|
||||
uint256 _fee,
|
||||
uint256 _refund
|
||||
) internal virtual;
|
||||
|
||||
/** @dev whether a note is already spent */
|
||||
function isSpent(bytes32 _nullifierHash) public view returns (bool) {
|
||||
return nullifierHashes[_nullifierHash];
|
||||
}
|
||||
|
||||
/** @dev whether an array of notes is already spent */
|
||||
function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns (bool[] memory spent) {
|
||||
spent = new bool[](_nullifierHashes.length);
|
||||
for (uint256 i = 0; i < _nullifierHashes.length; i++) {
|
||||
if (isSpent(_nullifierHashes[i])) {
|
||||
spent[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://tornado.cash
|
||||
/*
|
||||
* d888888P dP a88888b. dP
|
||||
* 88 88 d8' `88 88
|
||||
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||
*/
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
|
||||
|
||||
contract ERC20Tornado is Tornado {
|
||||
using SafeERC20 for IERC20;
|
||||
IERC20 public token;
|
||||
|
||||
constructor(
|
||||
IVerifier _verifier,
|
||||
IHasher _hasher,
|
||||
uint256 _denomination,
|
||||
uint32 _merkleTreeHeight,
|
||||
IERC20 _token
|
||||
) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) {
|
||||
token = _token;
|
||||
}
|
||||
|
||||
function _processDeposit() internal override {
|
||||
require(msg.value == 0, "ETH value is supposed to be 0 for ERC20 instance");
|
||||
token.safeTransferFrom(msg.sender, address(this), denomination);
|
||||
}
|
||||
|
||||
function _processWithdraw(
|
||||
address payable _recipient,
|
||||
address payable _relayer,
|
||||
uint256 _fee,
|
||||
uint256 _refund
|
||||
) internal override {
|
||||
require(msg.value == _refund, "Incorrect refund amount received by the contract");
|
||||
|
||||
token.safeTransfer(_recipient, denomination - _fee);
|
||||
if (_fee > 0) {
|
||||
token.safeTransfer(_relayer, _fee);
|
||||
}
|
||||
|
||||
if (_refund > 0) {
|
||||
(bool success, ) = _recipient.call{ value: _refund }("");
|
||||
if (!success) {
|
||||
// let's return _refund back to the relayer
|
||||
_relayer.transfer(_refund);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
import "@openzeppelin/contracts/proxy/Clones.sol";
|
||||
import "./ERC20TornadoCloneable.sol";
|
||||
|
||||
contract TornadoInstanceCloneFactory is Ownable {
|
||||
contract InstanceFactory is Ownable {
|
||||
using Clones for address;
|
||||
using Address for address;
|
||||
|
||||
@ -17,52 +17,35 @@ contract TornadoInstanceCloneFactory is Ownable {
|
||||
address public hasher;
|
||||
uint32 public merkleTreeHeight;
|
||||
|
||||
event NewVerifierSet(address indexed newVerifier);
|
||||
event NewHasherSet(address indexed newHasher);
|
||||
event NewTreeHeightSet(uint32 indexed newTreeHeight);
|
||||
event NewImplementationSet(address indexed newImplemenentation);
|
||||
event NewImplementationSet(address indexed newImplemenentation, address verifier, address hasher);
|
||||
event NewInstanceCloneCreated(address indexed clone);
|
||||
|
||||
constructor(
|
||||
address _verifier,
|
||||
address _hasher,
|
||||
uint32 _merkleTreeHeight
|
||||
uint32 _merkleTreeHeight,
|
||||
address _owner
|
||||
) {
|
||||
verifier = _verifier;
|
||||
hasher = _hasher;
|
||||
merkleTreeHeight = _merkleTreeHeight;
|
||||
|
||||
ERC20TornadoCloneable implContract = new ERC20TornadoCloneable(_verifier, _hasher);
|
||||
implementation = address(implContract);
|
||||
|
||||
transferOwnership(_owner);
|
||||
}
|
||||
|
||||
function setVerifier(address _verifier) external onlyOwner {
|
||||
verifier = _verifier;
|
||||
emit NewVerifierSet(verifier);
|
||||
}
|
||||
|
||||
function setHasher(address _hasher) external onlyOwner {
|
||||
hasher = _hasher;
|
||||
emit NewHasherSet(hasher);
|
||||
}
|
||||
|
||||
function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyOwner {
|
||||
merkleTreeHeight = _merkleTreeHeight;
|
||||
emit NewTreeHeightSet(merkleTreeHeight);
|
||||
}
|
||||
|
||||
function setImplementation(address _newImplementation) external onlyOwner {
|
||||
implementation = _newImplementation;
|
||||
emit NewImplementationSet(implementation);
|
||||
}
|
||||
|
||||
function generateNewImplementation() external onlyOwner {
|
||||
implementation = address(new ERC20TornadoCloneable(verifier, hasher));
|
||||
}
|
||||
|
||||
function createInstanceClone(uint256 _denomination, address _token) external onlyOwner returns (address) {
|
||||
/**
|
||||
* @dev Creates new Tornado instance.
|
||||
* @param _denomination denomination of new Tornado instance
|
||||
* @param _token address of ERC20 token for a new instance
|
||||
*/
|
||||
function createInstanceClone(uint256 _denomination, address _token) public virtual returns (address) {
|
||||
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
||||
|
||||
require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance for this denomination already exists");
|
||||
require(!implementation.predictDeterministicAddress(salt).isContract(), "Instance already exists");
|
||||
|
||||
address newClone = implementation.cloneDeterministic(salt);
|
||||
|
||||
@ -75,4 +58,16 @@ contract TornadoInstanceCloneFactory is Ownable {
|
||||
bytes32 salt = keccak256(abi.encodePacked(_denomination, _token));
|
||||
return implementation.predictDeterministicAddress(salt);
|
||||
}
|
||||
|
||||
function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyOwner {
|
||||
merkleTreeHeight = _merkleTreeHeight;
|
||||
emit NewTreeHeightSet(merkleTreeHeight);
|
||||
}
|
||||
|
||||
function generateNewImplementation(address _verifier, address _hasher) external onlyOwner {
|
||||
verifier = _verifier;
|
||||
hasher = _hasher;
|
||||
implementation = address(new ERC20TornadoCloneable(_verifier, _hasher));
|
||||
emit NewImplementationSet(implementation, _verifier, _hasher);
|
||||
}
|
||||
}
|
151
contracts/InstanceFactoryWithRegistry.sol
Normal file
151
contracts/InstanceFactoryWithRegistry.sol
Normal file
@ -0,0 +1,151 @@
|
||||
// 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";
|
||||
import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
|
||||
import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol";
|
||||
|
||||
contract InstanceFactoryWithRegistry is InstanceFactory {
|
||||
using Address for address;
|
||||
|
||||
address public immutable governance;
|
||||
address public immutable torn;
|
||||
address public immutable instanceRegistry;
|
||||
IUniswapV3Factory public immutable UniswapV3Factory;
|
||||
address public immutable WETH;
|
||||
uint16 public TWAPSlotsMin;
|
||||
uint256 public creationFee;
|
||||
|
||||
event NewCreationFeeSet(uint256 newCreationFee);
|
||||
event NewTWAPSlotsMinSet(uint256 newTWAPSlotsMin);
|
||||
event NewGovernanceProposalCreated(address indexed proposal);
|
||||
|
||||
/**
|
||||
* @dev Throws if called by any account other than the Governance.
|
||||
*/
|
||||
modifier onlyGovernance() {
|
||||
require(owner() == _msgSender(), "Caller is not the Governance");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(
|
||||
address _verifier,
|
||||
address _hasher,
|
||||
uint32 _merkleTreeHeight,
|
||||
address _governance,
|
||||
address _instanceRegistry,
|
||||
address _torn,
|
||||
address _UniswapV3Factory,
|
||||
address _WETH,
|
||||
uint16 _TWAPSlotsMin,
|
||||
uint256 _creationFee
|
||||
) InstanceFactory(_verifier, _hasher, _merkleTreeHeight, _governance) {
|
||||
governance = _governance;
|
||||
instanceRegistry = _instanceRegistry;
|
||||
torn = _torn;
|
||||
UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory);
|
||||
WETH = _WETH;
|
||||
TWAPSlotsMin = _TWAPSlotsMin;
|
||||
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(_denominations.length > 0, "Empty denominations");
|
||||
require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length");
|
||||
|
||||
// check Uniswap Pool
|
||||
for (uint8 i = 0; i < _protocolFees.length; i++) {
|
||||
if (_protocolFees[i] > 0) {
|
||||
// pool exists
|
||||
address poolAddr = UniswapV3Factory.getPool(_token, WETH, _uniswapPoolSwappingFee);
|
||||
require(poolAddr != address(0), "Uniswap pool is not exist");
|
||||
// TWAP slots
|
||||
(, , , , uint16 observationCardinalityNext, , ) = IUniswapV3PoolState(poolAddr).slot0();
|
||||
require(observationCardinalityNext >= TWAPSlotsMin, "Uniswap pool TWAP slots number is low");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
address proposal = address(
|
||||
new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees)
|
||||
);
|
||||
emit NewGovernanceProposalCreated(proposal);
|
||||
|
||||
return proposal;
|
||||
}
|
||||
|
||||
function setCreationFee(uint256 _creationFee) external onlyGovernance {
|
||||
creationFee = _creationFee;
|
||||
emit NewCreationFeeSet(_creationFee);
|
||||
}
|
||||
|
||||
function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance {
|
||||
TWAPSlotsMin = _TWAPSlotsMin;
|
||||
emit NewTWAPSlotsMinSet(_TWAPSlotsMin);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "../TornadoInstanceCloneFactory.sol";
|
||||
import "../tornado_proxy/TornadoProxy.sol";
|
||||
|
||||
contract Add1Instance {
|
||||
TornadoInstanceCloneFactory public immutable instanceFactory;
|
||||
address public immutable token;
|
||||
address public immutable proxyAddress;
|
||||
uint256 public immutable denomination;
|
||||
|
||||
event UpdatedInstanceForProxy(address instance, address token, uint256 denomination);
|
||||
|
||||
constructor(
|
||||
address _proxyAddress,
|
||||
address _instanceFactory,
|
||||
uint256 _denomination,
|
||||
address _token
|
||||
) {
|
||||
instanceFactory = TornadoInstanceCloneFactory(_instanceFactory);
|
||||
token = _token;
|
||||
proxyAddress = _proxyAddress;
|
||||
denomination = _denomination;
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
TornadoProxy tornadoProxy = TornadoProxy(proxyAddress);
|
||||
|
||||
ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denomination, token));
|
||||
|
||||
TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(true, IERC20(token), TornadoProxy.InstanceState.ENABLED);
|
||||
|
||||
TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData);
|
||||
|
||||
tornadoProxy.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination());
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "../TornadoInstanceCloneFactory.sol";
|
||||
import "../tornado_proxy/TornadoProxy.sol";
|
||||
|
||||
contract Add2Instances {
|
||||
TornadoInstanceCloneFactory public immutable instanceFactory;
|
||||
address public immutable token;
|
||||
address public immutable proxyAddress;
|
||||
|
||||
uint256 public immutable denomination1;
|
||||
uint256 public immutable denomination2;
|
||||
|
||||
event UpdatedInstanceForProxy(address instance, address token, uint256 denomination);
|
||||
|
||||
constructor(
|
||||
address _proxyAddress,
|
||||
address _instanceFactory,
|
||||
uint256[2] memory _denominations,
|
||||
address _token
|
||||
) {
|
||||
instanceFactory = TornadoInstanceCloneFactory(_instanceFactory);
|
||||
token = _token;
|
||||
proxyAddress = _proxyAddress;
|
||||
|
||||
denomination1 = _denominations[0];
|
||||
denomination2 = _denominations[1];
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
TornadoProxy tornadoProxy = TornadoProxy(proxyAddress);
|
||||
|
||||
for (uint256 i = 0; i < 2; i++) {
|
||||
ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token));
|
||||
|
||||
TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(
|
||||
true,
|
||||
IERC20(token),
|
||||
TornadoProxy.InstanceState.ENABLED
|
||||
);
|
||||
|
||||
TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData);
|
||||
|
||||
tornadoProxy.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination());
|
||||
}
|
||||
}
|
||||
|
||||
function denominations(uint256 index) private view returns (uint256) {
|
||||
if (index > 0) {
|
||||
return denomination2;
|
||||
} else {
|
||||
return denomination1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "../TornadoInstanceCloneFactory.sol";
|
||||
import "../tornado_proxy/TornadoProxy.sol";
|
||||
|
||||
contract Add3Instances {
|
||||
TornadoInstanceCloneFactory public immutable instanceFactory;
|
||||
address public immutable token;
|
||||
address public immutable proxyAddress;
|
||||
|
||||
uint256 public immutable denomination1;
|
||||
uint256 public immutable denomination2;
|
||||
uint256 public immutable denomination3;
|
||||
|
||||
event UpdatedInstanceForProxy(address instance, address token, uint256 denomination);
|
||||
|
||||
constructor(
|
||||
address _proxyAddress,
|
||||
address _instanceFactory,
|
||||
uint256[3] memory _denominations,
|
||||
address _token
|
||||
) {
|
||||
instanceFactory = TornadoInstanceCloneFactory(_instanceFactory);
|
||||
token = _token;
|
||||
proxyAddress = _proxyAddress;
|
||||
|
||||
denomination1 = _denominations[0];
|
||||
denomination2 = _denominations[1];
|
||||
denomination3 = _denominations[2];
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
TornadoProxy tornadoProxy = TornadoProxy(proxyAddress);
|
||||
|
||||
for (uint256 i = 0; i < 3; i++) {
|
||||
ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token));
|
||||
|
||||
TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(
|
||||
true,
|
||||
IERC20(token),
|
||||
TornadoProxy.InstanceState.ENABLED
|
||||
);
|
||||
|
||||
TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData);
|
||||
|
||||
tornadoProxy.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination());
|
||||
}
|
||||
}
|
||||
|
||||
function denominations(uint256 index) private view returns (uint256) {
|
||||
if (index > 1) {
|
||||
return denomination3;
|
||||
} else if (index > 0) {
|
||||
return denomination2;
|
||||
} else {
|
||||
return denomination1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "../TornadoInstanceCloneFactory.sol";
|
||||
import "../tornado_proxy/TornadoProxy.sol";
|
||||
|
||||
contract Add4Instances {
|
||||
TornadoInstanceCloneFactory public immutable instanceFactory;
|
||||
address public immutable token;
|
||||
address public immutable proxyAddress;
|
||||
|
||||
uint256 public immutable denomination1;
|
||||
uint256 public immutable denomination2;
|
||||
uint256 public immutable denomination3;
|
||||
uint256 public immutable denomination4;
|
||||
|
||||
event UpdatedInstanceForProxy(address instance, address token, uint256 denomination);
|
||||
|
||||
constructor(
|
||||
address _proxyAddress,
|
||||
address _instanceFactory,
|
||||
uint256[4] memory _denominations,
|
||||
address _token
|
||||
) {
|
||||
instanceFactory = TornadoInstanceCloneFactory(_instanceFactory);
|
||||
token = _token;
|
||||
proxyAddress = _proxyAddress;
|
||||
|
||||
denomination1 = _denominations[0];
|
||||
denomination2 = _denominations[1];
|
||||
denomination3 = _denominations[2];
|
||||
denomination4 = _denominations[3];
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
TornadoProxy tornadoProxy = TornadoProxy(proxyAddress);
|
||||
|
||||
for (uint256 i = 0; i < 4; i++) {
|
||||
ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token));
|
||||
|
||||
TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(
|
||||
true,
|
||||
IERC20(token),
|
||||
TornadoProxy.InstanceState.ENABLED
|
||||
);
|
||||
|
||||
TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData);
|
||||
|
||||
tornadoProxy.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination());
|
||||
}
|
||||
}
|
||||
|
||||
function denominations(uint256 index) private view returns (uint256) {
|
||||
if (index > 2) {
|
||||
return denomination4;
|
||||
} else if (index > 1) {
|
||||
return denomination3;
|
||||
} else if (index > 0) {
|
||||
return denomination2;
|
||||
} else {
|
||||
return denomination1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "../TornadoInstanceCloneFactory.sol";
|
||||
import "../tornado_proxy/TornadoProxy.sol";
|
||||
|
||||
contract Add5Instances {
|
||||
TornadoInstanceCloneFactory public immutable instanceFactory;
|
||||
address public immutable token;
|
||||
address public immutable proxyAddress;
|
||||
|
||||
uint256 public immutable denomination1;
|
||||
uint256 public immutable denomination2;
|
||||
uint256 public immutable denomination3;
|
||||
uint256 public immutable denomination4;
|
||||
uint256 public immutable denomination5;
|
||||
|
||||
event UpdatedInstanceForProxy(address instance, address token, uint256 denomination);
|
||||
|
||||
constructor(
|
||||
address _proxyAddress,
|
||||
address _instanceFactory,
|
||||
uint256[5] memory _denominations,
|
||||
address _token
|
||||
) {
|
||||
instanceFactory = TornadoInstanceCloneFactory(_instanceFactory);
|
||||
token = _token;
|
||||
proxyAddress = _proxyAddress;
|
||||
|
||||
denomination1 = _denominations[0];
|
||||
denomination2 = _denominations[1];
|
||||
denomination3 = _denominations[2];
|
||||
denomination4 = _denominations[3];
|
||||
denomination5 = _denominations[4];
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
TornadoProxy tornadoProxy = TornadoProxy(proxyAddress);
|
||||
|
||||
for (uint256 i = 0; i < 5; i++) {
|
||||
ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token));
|
||||
|
||||
TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(
|
||||
true,
|
||||
IERC20(token),
|
||||
TornadoProxy.InstanceState.ENABLED
|
||||
);
|
||||
|
||||
TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData);
|
||||
|
||||
tornadoProxy.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination());
|
||||
}
|
||||
}
|
||||
|
||||
function denominations(uint256 index) private view returns (uint256) {
|
||||
if (index > 3) {
|
||||
return denomination5;
|
||||
} else if (index > 2) {
|
||||
return denomination4;
|
||||
} else if (index > 1) {
|
||||
return denomination3;
|
||||
} else if (index > 0) {
|
||||
return denomination2;
|
||||
} else {
|
||||
return denomination1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "../TornadoInstanceCloneFactory.sol";
|
||||
import "../tornado_proxy/TornadoProxy.sol";
|
||||
|
||||
contract Add6Instances {
|
||||
TornadoInstanceCloneFactory public immutable instanceFactory;
|
||||
address public immutable token;
|
||||
address public immutable proxyAddress;
|
||||
|
||||
uint256 public immutable denomination1;
|
||||
uint256 public immutable denomination2;
|
||||
uint256 public immutable denomination3;
|
||||
uint256 public immutable denomination4;
|
||||
uint256 public immutable denomination5;
|
||||
uint256 public immutable denomination6;
|
||||
|
||||
event UpdatedInstanceForProxy(address instance, address token, uint256 denomination);
|
||||
|
||||
constructor(
|
||||
address _proxyAddress,
|
||||
address _instanceFactory,
|
||||
uint256[6] memory _denominations,
|
||||
address _token
|
||||
) {
|
||||
instanceFactory = TornadoInstanceCloneFactory(_instanceFactory);
|
||||
token = _token;
|
||||
proxyAddress = _proxyAddress;
|
||||
|
||||
denomination1 = _denominations[0];
|
||||
denomination2 = _denominations[1];
|
||||
denomination3 = _denominations[2];
|
||||
denomination4 = _denominations[3];
|
||||
denomination5 = _denominations[4];
|
||||
denomination6 = _denominations[5];
|
||||
}
|
||||
|
||||
function executeProposal() external {
|
||||
TornadoProxy tornadoProxy = TornadoProxy(proxyAddress);
|
||||
|
||||
for (uint256 i = 0; i < 6; i++) {
|
||||
ITornadoInstance instance = ITornadoInstance(instanceFactory.createInstanceClone(denominations(i), token));
|
||||
|
||||
TornadoProxy.Instance memory newInstanceData = TornadoProxy.Instance(
|
||||
true,
|
||||
IERC20(token),
|
||||
TornadoProxy.InstanceState.ENABLED
|
||||
);
|
||||
|
||||
TornadoProxy.Tornado memory tornadoForUpdate = TornadoProxy.Tornado(instance, newInstanceData);
|
||||
|
||||
tornadoProxy.updateInstance(tornadoForUpdate);
|
||||
|
||||
emit UpdatedInstanceForProxy(address(instance), instance.token(), instance.denomination());
|
||||
}
|
||||
}
|
||||
|
||||
function denominations(uint256 index) private view returns (uint256) {
|
||||
if (index > 4) {
|
||||
return denomination6;
|
||||
} else if (index > 3) {
|
||||
return denomination5;
|
||||
} else if (index > 2) {
|
||||
return denomination4;
|
||||
} else if (index > 1) {
|
||||
return denomination3;
|
||||
} else if (index > 0) {
|
||||
return denomination2;
|
||||
} else {
|
||||
return denomination1;
|
||||
}
|
||||
}
|
||||
}
|
30
contracts/interfaces/IInstanceRegistry.sol
Normal file
30
contracts/interfaces/IInstanceRegistry.sol
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
interface IInstanceRegistry {
|
||||
enum InstanceState {
|
||||
DISABLED,
|
||||
ENABLED
|
||||
}
|
||||
|
||||
struct Instance {
|
||||
bool isERC20;
|
||||
IERC20 token;
|
||||
InstanceState state;
|
||||
// the fee of the uniswap pool which will be used to get a TWAP
|
||||
uint24 uniswapPoolSwappingFee;
|
||||
// the fee the protocol takes from relayer, it should be multiplied by PROTOCOL_FEE_DIVIDER from FeeManager.sol
|
||||
uint32 protocolFeePercentage;
|
||||
}
|
||||
|
||||
struct Tornado {
|
||||
address addr;
|
||||
Instance instance;
|
||||
}
|
||||
|
||||
function updateInstance(Tornado calldata _tornado) external;
|
||||
}
|
28
contracts/libraries/SingletonFactory.sol
Normal file
28
contracts/libraries/SingletonFactory.sol
Normal file
@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/**
|
||||
*Submitted for verification at Etherscan.io on 2020-03-30
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.2;
|
||||
|
||||
/**
|
||||
* @title Singleton Factory (EIP-2470)
|
||||
* @notice Exposes CREATE2 (EIP-1014) to deploy bytecode on deterministic addresses based on initialization code and salt.
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
*/
|
||||
contract SingletonFactory {
|
||||
/**
|
||||
* @notice Deploys `_initCode` using `_salt` for defining the deterministic address.
|
||||
* @param _initCode Initialization code.
|
||||
* @param _salt Arbitrary value to modify resulting address.
|
||||
* @return createdContract Created contract address.
|
||||
*/
|
||||
function deploy(bytes memory _initCode, bytes32 _salt) public returns (address payable createdContract) {
|
||||
assembly {
|
||||
createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt)
|
||||
}
|
||||
}
|
||||
}
|
||||
// IV is a value changed to generate the vanity address.
|
||||
// IV: 6583047
|
@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.6.12;
|
||||
|
||||
import { Governance } from "tornado-governance/contracts/Governance.sol";
|
||||
pragma solidity 0.6.12||0.7.6;
|
||||
|
||||
import { Governance } from "tornado-governance/contracts/v1/Governance.sol";
|
||||
import { InstanceRegistry } from "tornado-relayer-registry/contracts/tornado-proxy/InstanceRegistry.sol";
|
||||
|
||||
contract CompileDummy {}
|
||||
|
@ -1,21 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.7.6;
|
||||
|
||||
interface ITornadoInstance {
|
||||
function token() external view returns (address);
|
||||
|
||||
function denomination() external view returns (uint256);
|
||||
|
||||
function deposit(bytes32 commitment) external payable;
|
||||
|
||||
function withdraw(
|
||||
bytes calldata proof,
|
||||
bytes32 root,
|
||||
bytes32 nullifierHash,
|
||||
address payable recipient,
|
||||
address payable relayer,
|
||||
uint256 fee,
|
||||
uint256 refund
|
||||
) external payable;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.7.6;
|
||||
|
||||
interface ITornadoTrees {
|
||||
function registerDeposit(address instance, bytes32 commitment) external;
|
||||
|
||||
function registerWithdrawal(address instance, bytes32 nullifier) external;
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.7.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
|
||||
import "@openzeppelin/contracts/math/Math.sol";
|
||||
import "./ITornadoInstance.sol";
|
||||
import "./ITornadoTrees.sol";
|
||||
|
||||
contract TornadoProxy {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
event EncryptedNote(address indexed sender, bytes encryptedNote);
|
||||
event InstanceStateUpdated(ITornadoInstance indexed instance, InstanceState state);
|
||||
event TornadoTreesUpdated(ITornadoTrees addr);
|
||||
|
||||
enum InstanceState {
|
||||
DISABLED,
|
||||
ENABLED,
|
||||
MINEABLE
|
||||
}
|
||||
|
||||
struct Instance {
|
||||
bool isERC20;
|
||||
IERC20 token;
|
||||
InstanceState state;
|
||||
}
|
||||
|
||||
struct Tornado {
|
||||
ITornadoInstance addr;
|
||||
Instance instance;
|
||||
}
|
||||
|
||||
ITornadoTrees public tornadoTrees;
|
||||
address public immutable governance;
|
||||
mapping(ITornadoInstance => Instance) public instances;
|
||||
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == governance, "Not authorized");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(
|
||||
address _tornadoTrees,
|
||||
address _governance,
|
||||
Tornado[] memory _instances
|
||||
) public {
|
||||
tornadoTrees = ITornadoTrees(_tornadoTrees);
|
||||
governance = _governance;
|
||||
|
||||
for (uint256 i = 0; i < _instances.length; i++) {
|
||||
_updateInstance(_instances[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function deposit(
|
||||
ITornadoInstance _tornado,
|
||||
bytes32 _commitment,
|
||||
bytes calldata _encryptedNote
|
||||
) external payable {
|
||||
Instance memory instance = instances[_tornado];
|
||||
require(instance.state != InstanceState.DISABLED, "The instance is not supported");
|
||||
|
||||
if (instance.isERC20) {
|
||||
instance.token.safeTransferFrom(msg.sender, address(this), _tornado.denomination());
|
||||
}
|
||||
_tornado.deposit{ value: msg.value }(_commitment);
|
||||
|
||||
if (instance.state == InstanceState.MINEABLE) {
|
||||
tornadoTrees.registerDeposit(address(_tornado), _commitment);
|
||||
}
|
||||
emit EncryptedNote(msg.sender, _encryptedNote);
|
||||
}
|
||||
|
||||
function withdraw(
|
||||
ITornadoInstance _tornado,
|
||||
bytes calldata _proof,
|
||||
bytes32 _root,
|
||||
bytes32 _nullifierHash,
|
||||
address payable _recipient,
|
||||
address payable _relayer,
|
||||
uint256 _fee,
|
||||
uint256 _refund
|
||||
) external payable {
|
||||
Instance memory instance = instances[_tornado];
|
||||
require(instance.state != InstanceState.DISABLED, "The instance is not supported");
|
||||
|
||||
_tornado.withdraw{ value: msg.value }(_proof, _root, _nullifierHash, _recipient, _relayer, _fee, _refund);
|
||||
if (instance.state == InstanceState.MINEABLE) {
|
||||
tornadoTrees.registerWithdrawal(address(_tornado), _nullifierHash);
|
||||
}
|
||||
}
|
||||
|
||||
function backupNotes(bytes[] calldata _encryptedNotes) external {
|
||||
for (uint256 i = 0; i < _encryptedNotes.length; i++) {
|
||||
emit EncryptedNote(msg.sender, _encryptedNotes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function updateInstance(Tornado calldata _tornado) external onlyGovernance {
|
||||
_updateInstance(_tornado);
|
||||
}
|
||||
|
||||
function setTornadoTreesContract(ITornadoTrees _tornadoTrees) external onlyGovernance {
|
||||
tornadoTrees = _tornadoTrees;
|
||||
emit TornadoTreesUpdated(_tornadoTrees);
|
||||
}
|
||||
|
||||
/// @dev Method to claim junk and accidentally sent tokens
|
||||
function rescueTokens(
|
||||
IERC20 _token,
|
||||
address payable _to,
|
||||
uint256 _amount
|
||||
) external onlyGovernance {
|
||||
require(_to != address(0), "TORN: can not send to zero address");
|
||||
|
||||
if (_token == IERC20(0)) {
|
||||
// for Ether
|
||||
uint256 totalBalance = address(this).balance;
|
||||
uint256 balance = Math.min(totalBalance, _amount);
|
||||
_to.transfer(balance);
|
||||
} else {
|
||||
// any other erc20
|
||||
uint256 totalBalance = _token.balanceOf(address(this));
|
||||
uint256 balance = Math.min(totalBalance, _amount);
|
||||
require(balance > 0, "TORN: trying to send 0 balance");
|
||||
_token.safeTransfer(_to, balance);
|
||||
}
|
||||
}
|
||||
|
||||
function _updateInstance(Tornado memory _tornado) internal {
|
||||
instances[_tornado.addr] = _tornado.instance;
|
||||
if (_tornado.instance.isERC20) {
|
||||
IERC20 token = IERC20(_tornado.addr.token());
|
||||
require(token == _tornado.instance.token, "Incorrect token");
|
||||
uint256 allowance = token.allowance(address(this), address(_tornado.addr));
|
||||
|
||||
if (_tornado.instance.state != InstanceState.DISABLED && allowance == 0) {
|
||||
token.safeApprove(address(_tornado.addr), uint256(-1));
|
||||
} else if (_tornado.instance.state == InstanceState.DISABLED && allowance != 0) {
|
||||
token.safeApprove(address(_tornado.addr), 0);
|
||||
}
|
||||
}
|
||||
emit InstanceStateUpdated(_tornado.addr, _tornado.instance.state);
|
||||
}
|
||||
}
|
@ -1,25 +1,30 @@
|
||||
require('dotenv').config()
|
||||
require('@nomiclabs/hardhat-ethers')
|
||||
require('@nomiclabs/hardhat-etherscan')
|
||||
require('@nomiclabs/hardhat-waffle')
|
||||
require('@nomiclabs/hardhat-etherscan')
|
||||
require('hardhat-log-remover')
|
||||
require('solidity-coverage')
|
||||
|
||||
require('./tasks/deploy_proposal.js')
|
||||
require('./tasks/deploy_factory_proposal.js')
|
||||
require('./tasks/propose_proposal.js')
|
||||
/**
|
||||
* @type import('hardhat/config').HardhatUserConfig
|
||||
*/
|
||||
module.exports = {
|
||||
solidity: {
|
||||
compilers: [
|
||||
{
|
||||
version: '0.6.2',
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
version: '0.6.12',
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 2000,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -28,7 +33,7 @@ module.exports = {
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 2000,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -37,24 +42,26 @@ module.exports = {
|
||||
networks: {
|
||||
hardhat: {
|
||||
forking: {
|
||||
url: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`,
|
||||
blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436,
|
||||
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
blockNumber: 14250000,
|
||||
},
|
||||
chainId: 1,
|
||||
initialBaseFeePerGas: 5,
|
||||
loggingEnabled: false,
|
||||
allowUnlimitedContractSize: false,
|
||||
blockGasLimit: 50000000,
|
||||
},
|
||||
localhost: {
|
||||
url: 'http://localhost:8545',
|
||||
timeout: 120000,
|
||||
rinkeby: {
|
||||
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
|
||||
accounts: process.env.PRIVATE_KEY
|
||||
? [process.env.PRIVATE_KEY]
|
||||
: { mnemonic: 'test test test test test junk' },
|
||||
},
|
||||
mainnet: {
|
||||
url: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`,
|
||||
accounts: [`${process.env.mainnet_account_pk}`],
|
||||
timeout: 2147483647,
|
||||
},
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${process.env.goerli_rpc_key}`,
|
||||
accounts: [`${process.env.goerli_account_pk}`],
|
||||
timeout: 2147483647,
|
||||
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||
accounts: process.env.PRIVATE_KEY
|
||||
? [process.env.PRIVATE_KEY]
|
||||
: { mnemonic: 'test test test test test junk' },
|
||||
},
|
||||
},
|
||||
mocha: { timeout: 9999999999 },
|
||||
@ -63,6 +70,6 @@ module.exports = {
|
||||
runOnCompile: true,
|
||||
},
|
||||
etherscan: {
|
||||
apiKey: `${process.env.etherscan_api_key}`,
|
||||
apiKey: `${process.env.ETHERSCAN_KEY}`,
|
||||
},
|
||||
}
|
||||
|
26
package.json
26
package.json
@ -1,24 +1,26 @@
|
||||
{
|
||||
"name": "tornado-instances",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"eslint": "eslint --ext .js --ignore-path .gitignore .",
|
||||
"prettier:check": "prettier --check . --config .prettierrc",
|
||||
"prettier:fix": "prettier --write . --config .prettierrc",
|
||||
"lint": "yarn eslint && yarn prettier:check",
|
||||
"deploy:proposal": "yarn hardhat --network mainnet deploy_proposal --factory-address",
|
||||
"deploy:proposal:test": "yarn hardhat --network goerli deploy_proposal --factory-address",
|
||||
"deploy:proposal:factory": "yarn hardhat --network mainnet deploy_factory_proposal",
|
||||
"deploy:proposal:factory:test": "yarn hardhat --network goerli deploy_factory_proposal",
|
||||
"propose": "yarn hardhat --network mainnet propose_proposal --proposal-address",
|
||||
"compile": "yarn hardhat compile",
|
||||
"test": "yarn hardhat test",
|
||||
"f:test": "yarn test && yarn clean",
|
||||
"clean": "yarn prettier:fix && yarn lint",
|
||||
"coverage": "yarn hardhat coverage --testfiles \"test/*.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "3.4.2",
|
||||
"@openzeppelin/upgrades-core": "^1.0.1",
|
||||
"torn-token": "^1.0.4",
|
||||
"tornado-cli": "^0.0.1",
|
||||
"tornado-core": "https://github.com/tornadocash/tornado-core.git",
|
||||
"tornado-governance": "2.0.0",
|
||||
"tornado-relayer-registry": "https://github.com/Rezan-vm/tornado-relayer-registry.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||
"@nomiclabs/hardhat-etherscan": "^2.1.4",
|
||||
@ -39,13 +41,5 @@
|
||||
"solhint-plugin-prettier": "^0.0.5",
|
||||
"solidity-coverage": "^0.7.17",
|
||||
"websnark": "^0.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "3.4.2",
|
||||
"@openzeppelin/upgrades-core": "^1.0.1",
|
||||
"torn-token": "^1.0.4",
|
||||
"tornado-cli": "^0.0.1",
|
||||
"tornado-core": "https://github.com/tornadocash/tornado-core.git",
|
||||
"tornado-governance": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
module.exports = [
|
||||
{
|
||||
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||
denomination: '33333333333333333333',
|
||||
symbol: 'RAI',
|
||||
decimals: 18,
|
||||
},
|
||||
{
|
||||
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||
denomination: '333333333333333333333',
|
||||
symbol: 'RAI',
|
||||
decimals: 18,
|
||||
},
|
||||
{
|
||||
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||
denomination: '3333333333333333333333',
|
||||
symbol: 'RAI',
|
||||
decimals: 18,
|
||||
},
|
||||
{
|
||||
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||
denomination: '33333333333333333333333',
|
||||
symbol: 'RAI',
|
||||
decimals: 18,
|
||||
},
|
||||
]
|
26
scripts/deployInstanceFactory.js
Normal file
26
scripts/deployInstanceFactory.js
Normal file
@ -0,0 +1,26 @@
|
||||
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.factoryContract, singletonFactory })
|
||||
console.log(`Instance factory contract have been deployed on ${contracts.factoryContract.address} address`)
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
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)
|
||||
})
|
@ -1,22 +0,0 @@
|
||||
require('dotenv').config()
|
||||
const { ethers } = require('hardhat')
|
||||
|
||||
// THIS IS ASSUMING YOU HAVE ENOUGH LOCKED TORN TO PROPOSE
|
||||
async function propose(proposalArgs) {
|
||||
const proposer = proposalArgs[0]
|
||||
const ProposalContract = proposalArgs[1]
|
||||
|
||||
let GovernanceContract = await ethers.getContractAt(
|
||||
'../artifacts/tornado-governance/contracts/Governance.sol:Governance',
|
||||
'0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
)
|
||||
GovernanceContract = await GovernanceContract.connect(proposer)
|
||||
|
||||
const response = await GovernanceContract.propose(ProposalContract.address, proposalArgs[2])
|
||||
|
||||
const id = await GovernanceContract.latestProposalIds(proposer.address)
|
||||
const state = await GovernanceContract.state(id)
|
||||
|
||||
return [response, id, state]
|
||||
}
|
||||
module.exports.propose = propose
|
68
src/generateAddresses.js
Normal file
68
src/generateAddresses.js
Normal file
@ -0,0 +1,68 @@
|
||||
const { ethers } = require('hardhat')
|
||||
const defaultConfig = require('../config')
|
||||
|
||||
async function generate(config = defaultConfig) {
|
||||
const FactoryFactory = await ethers.getContractFactory('InstanceFactory')
|
||||
const deploymentBytecodeFactory =
|
||||
FactoryFactory.bytecode +
|
||||
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([
|
||||
config.verifier,
|
||||
config.hasher,
|
||||
config.merkleTreeHeight,
|
||||
config.governance,
|
||||
config.instanceRegistry,
|
||||
config.TORN,
|
||||
config.UniswapV3Factory,
|
||||
config.WETH,
|
||||
config.TWAPSlotsMin,
|
||||
config.creationFee,
|
||||
])
|
||||
.slice(2)
|
||||
|
||||
const factoryWithRegistryAddress = ethers.utils.getCreate2Address(
|
||||
config.singletonFactory,
|
||||
config.salt,
|
||||
ethers.utils.keccak256(deploymentBytecodeFactoryWithRegistry),
|
||||
)
|
||||
|
||||
const result = {
|
||||
factoryContract: {
|
||||
address: factoryAddress,
|
||||
bytecode: deploymentBytecodeFactory,
|
||||
isProxy: false,
|
||||
},
|
||||
factoryWithRegistryContract: {
|
||||
address: factoryWithRegistryAddress,
|
||||
bytecode: deploymentBytecodeFactoryWithRegistry,
|
||||
isProxy: false,
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function generateWithLog() {
|
||||
const contracts = await generate()
|
||||
console.log('Instance factory contract: ', contracts.factoryContract.address)
|
||||
console.log('Instance factory with registry contract: ', contracts.factoryWithRegistryContract.address)
|
||||
return contracts
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generate,
|
||||
generateWithLog,
|
||||
}
|
44
src/permit.js
Normal file
44
src/permit.js
Normal file
@ -0,0 +1,44 @@
|
||||
const { EIP712Signer } = require('@ticket721/e712')
|
||||
|
||||
const Permit = [
|
||||
{ name: 'owner', type: 'address' },
|
||||
{ name: 'spender', type: 'address' },
|
||||
{ name: 'value', type: 'uint256' },
|
||||
{ name: 'nonce', type: 'uint256' },
|
||||
{ name: 'deadline', type: 'uint256' },
|
||||
]
|
||||
|
||||
class PermitSigner extends EIP712Signer {
|
||||
constructor(_domain, _permitArgs) {
|
||||
super(_domain, ['Permit', Permit])
|
||||
this.permitArgs = _permitArgs
|
||||
}
|
||||
|
||||
// Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)
|
||||
setPermitInfo(_permitArgs) {
|
||||
this.permitArgs = _permitArgs
|
||||
}
|
||||
|
||||
getPayload() {
|
||||
return this.generatePayload(this.permitArgs, 'Permit')
|
||||
}
|
||||
|
||||
async getSignature(privateKey) {
|
||||
let payload = this.getPayload()
|
||||
payload.message.owner = payload.message.owner.address
|
||||
const { hex, v, r, s } = await this.sign(privateKey, payload)
|
||||
return {
|
||||
hex,
|
||||
v,
|
||||
r: '0x' + r,
|
||||
s: '0x' + s,
|
||||
}
|
||||
}
|
||||
|
||||
getSignerAddress(permitArgs, signature) {
|
||||
const original_payload = this.generatePayload(permitArgs, 'Permit')
|
||||
return this.verify(original_payload, signature)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { PermitSigner }
|
@ -1,27 +0,0 @@
|
||||
require('dotenv').config()
|
||||
const { task } = require('hardhat/config')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const instancesData = require('../resources/instances')
|
||||
|
||||
task('deploy_factory_proposal', 'deploy the proposal that creates the factory').setAction(
|
||||
async (taskArgs, hre) => {
|
||||
const ProposalFactory = await hre.ethers.getContractFactory('CreateFactoryAndAddInstancesProposal')
|
||||
|
||||
let denominations = []
|
||||
for (let i = 0; i < 4; i++) {
|
||||
denominations[i] = BigNumber.from(instancesData[i].denomination)
|
||||
}
|
||||
const tokenAddress = instancesData[0].tokenAddress
|
||||
|
||||
const ProposalContract = await ProposalFactory.deploy(`${process.env.PROXY}`, denominations, tokenAddress)
|
||||
|
||||
await ProposalContract.deployTransaction.wait(5)
|
||||
|
||||
await hre.run('verify:verify', {
|
||||
address: ProposalContract.address,
|
||||
constructorArguments: [`${process.env.PROXY}`, denominations, tokenAddress],
|
||||
})
|
||||
|
||||
console.log('Verified CreateFactoryAndAddInstancesProposal deployed at: ', ProposalContract.address)
|
||||
},
|
||||
)
|
@ -1,40 +0,0 @@
|
||||
require('dotenv').config()
|
||||
const { task } = require('hardhat/config')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const instancesData = require('../resources/instances')
|
||||
|
||||
task('deploy_proposal', 'deploy proposal that uses factory')
|
||||
.addParam('factoryAddress', 'address of factory')
|
||||
.setAction(async (taskArgs, hre) => {
|
||||
const contractName = `Add${instancesData.length}Instance${instancesData.length == 1 ? '' : 's'}`
|
||||
|
||||
const ProposalFactory = await hre.ethers.getContractFactory(contractName)
|
||||
|
||||
let denominations = []
|
||||
for (let i = 0; i < instancesData.length; i++) {
|
||||
denominations[i] = BigNumber.from(instancesData[i].denomination)
|
||||
}
|
||||
|
||||
const tokenAddress = instancesData[0].tokenAddress
|
||||
|
||||
const ProposalContract = await ProposalFactory.deploy(
|
||||
`${process.env.PROXY}`,
|
||||
taskArgs.factoryAddress,
|
||||
denominations.length == 1 ? denominations[0] : denominations,
|
||||
tokenAddress,
|
||||
)
|
||||
|
||||
await ProposalContract.deployTransaction.wait(5)
|
||||
|
||||
await hre.run('verify:verify', {
|
||||
address: ProposalContract.address,
|
||||
constructorArguments: [
|
||||
`${process.env.PROXY}`,
|
||||
taskArgs.factoryAddress,
|
||||
denominations.length == 1 ? denominations[0] : denominations,
|
||||
tokenAddress,
|
||||
],
|
||||
})
|
||||
|
||||
console.log(`Verified ${contractName} deployed at: `, ProposalContract.address)
|
||||
})
|
@ -1,20 +0,0 @@
|
||||
require('dotenv').config()
|
||||
const { task } = require('hardhat/config')
|
||||
const instancesData = require('../resources/instances')
|
||||
|
||||
task('propose_proposal', 'propose proposal that uses factory')
|
||||
.addParam('proposalAddress', 'address of proposal')
|
||||
.setAction(async (taskArgs, hre) => {
|
||||
const proposalName = `add-${instancesData[0].symbol}-instances`
|
||||
|
||||
const GovernanceContract = await hre.ethers.getContractAt(
|
||||
'../artifacts/tornado-governance/contracts/Governance.sol:Governance',
|
||||
'0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
)
|
||||
await GovernanceContract.propose(taskArgs.proposalAddress, proposalName)
|
||||
|
||||
const id = await GovernanceContract.latestProposalIds((await hre.ethers.getSigners())[0].address)
|
||||
const state = await GovernanceContract.state(id)
|
||||
|
||||
console.log('Proposal with name: ', proposalName, ' proposed with id: ', id, ', has state: ', state)
|
||||
})
|
156
test/factory.test.js
Normal file
156
test/factory.test.js
Normal file
@ -0,0 +1,156 @@
|
||||
const hre = require('hardhat')
|
||||
const { ethers, waffle } = hre
|
||||
const { loadFixture } = waffle
|
||||
const { expect } = require('chai')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const config = require('../config')
|
||||
const { getSignerFromAddress } = require('./utils')
|
||||
const { generate } = require('../src/generateAddresses')
|
||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
||||
|
||||
describe('Instance Factory Tests', () => {
|
||||
const addressZero = ethers.constants.AddressZero
|
||||
|
||||
async function fixture() {
|
||||
const [sender, deployer] = await ethers.getSigners()
|
||||
|
||||
const owner = await getSignerFromAddress(config.owner)
|
||||
|
||||
await sender.sendTransaction({
|
||||
to: config.owner,
|
||||
value: ethers.utils.parseEther('1'),
|
||||
})
|
||||
|
||||
const compWhale = await getSignerFromAddress(config.compWhale)
|
||||
|
||||
const compToken = await ethers.getContractAt(
|
||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||
config.COMP,
|
||||
)
|
||||
|
||||
// deploy InstanceFactory with CREATE2
|
||||
const singletonFactory = await ethers.getContractAt(
|
||||
'SingletonFactory',
|
||||
config.singletonFactoryVerboseWrapper,
|
||||
)
|
||||
const contracts = await generate()
|
||||
if ((await ethers.provider.getCode(contracts.factoryContract.address)) == '0x') {
|
||||
await singletonFactory.deploy(contracts.factoryContract.bytecode, config.salt, {
|
||||
gasLimit: config.deployGasLimit,
|
||||
})
|
||||
}
|
||||
const instanceFactory = await ethers.getContractAt('InstanceFactory', contracts.factoryContract.address)
|
||||
|
||||
return {
|
||||
sender,
|
||||
deployer,
|
||||
owner,
|
||||
compToken,
|
||||
compWhale,
|
||||
instanceFactory,
|
||||
}
|
||||
}
|
||||
|
||||
it('Should have initialized all successfully', async function () {
|
||||
const { sender, compToken, instanceFactory } = await loadFixture(fixture)
|
||||
expect(sender.address).to.exist
|
||||
expect(compToken.address).to.exist
|
||||
expect(instanceFactory.address).to.exist
|
||||
})
|
||||
|
||||
it('Should set correct params for factory', async function () {
|
||||
const { instanceFactory } = await loadFixture(fixture)
|
||||
|
||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
||||
expect(await instanceFactory.implementation()).to.exist
|
||||
})
|
||||
|
||||
it('Governance should be able to set factory params', async function () {
|
||||
let { instanceFactory, owner } = await loadFixture(fixture)
|
||||
|
||||
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
||||
|
||||
instanceFactory = await instanceFactory.connect(owner)
|
||||
|
||||
await instanceFactory.generateNewImplementation(addressZero, addressZero)
|
||||
await instanceFactory.setMerkleTreeHeight(1)
|
||||
|
||||
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
|
||||
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
|
||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
|
||||
|
||||
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
|
||||
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
||||
|
||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
||||
})
|
||||
|
||||
it('Should successfully add instance', async function () {
|
||||
let { sender, instanceFactory } = await loadFixture(fixture)
|
||||
|
||||
// deploy instance
|
||||
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('1000'), config.COMP)
|
||||
|
||||
// check instance initialization
|
||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
||||
const instance = await ethers.getContractAt(
|
||||
'ERC20TornadoCloneable',
|
||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||
)
|
||||
|
||||
expect(await instance.token()).to.be.equal(config.COMP)
|
||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
||||
expect(await instance.denomination()).to.equal(ethers.utils.parseEther('1000'))
|
||||
})
|
||||
|
||||
it('Should deposit and withdraw into the new instance', async function () {
|
||||
let { sender, instanceFactory, compToken, compWhale } = await loadFixture(fixture)
|
||||
|
||||
// deploy instance
|
||||
await instanceFactory.connect(sender).createInstanceClone(ethers.utils.parseEther('100'), config.COMP)
|
||||
|
||||
let logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
||||
const instance = await ethers.getContractAt(
|
||||
'ERC20TornadoCloneable',
|
||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||
)
|
||||
|
||||
// check instance work ------------------------------------------
|
||||
const depo = createDeposit({
|
||||
nullifier: rbigint(31),
|
||||
secret: rbigint(31),
|
||||
})
|
||||
|
||||
const value = ethers.utils.parseEther('100')
|
||||
|
||||
await compToken.connect(compWhale).transfer(sender.address, value)
|
||||
await compToken.connect(sender).approve(instance.address, value)
|
||||
|
||||
await expect(() => instance.deposit(toHex(depo.commitment), [])).to.changeTokenBalances(
|
||||
compToken,
|
||||
[sender, instance],
|
||||
[BigNumber.from(0).sub(value), value],
|
||||
)
|
||||
|
||||
let pevents = await instance.queryFilter('Deposit')
|
||||
await initialize({ merkleTreeHeight: 20 })
|
||||
|
||||
const { proof, args } = await generateProof({
|
||||
deposit: depo,
|
||||
recipient: sender.address,
|
||||
events: pevents,
|
||||
})
|
||||
|
||||
await expect(() => instance.withdraw(proof, ...args)).to.changeTokenBalances(
|
||||
compToken,
|
||||
[instance, sender],
|
||||
[BigNumber.from(0).sub(value), value],
|
||||
)
|
||||
})
|
||||
})
|
496
test/factory.with.registry.test.js
Normal file
496
test/factory.with.registry.test.js
Normal file
@ -0,0 +1,496 @@
|
||||
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)
|
||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
||||
expect(await instanceFactory.WETH()).to.be.equal(config.WETH)
|
||||
expect(await instanceFactory.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory)
|
||||
})
|
||||
|
||||
it('Governance should be able to set factory params', async function () {
|
||||
let { instanceFactory, gov } = await loadFixture(fixture)
|
||||
|
||||
await expect(instanceFactory.setMerkleTreeHeight(1)).to.be.reverted
|
||||
|
||||
const govSigner = await getSignerFromAddress(gov.address)
|
||||
instanceFactory = await instanceFactory.connect(govSigner)
|
||||
|
||||
await instanceFactory.generateNewImplementation(addressZero, addressZero)
|
||||
await instanceFactory.setMerkleTreeHeight(1)
|
||||
await instanceFactory.setCreationFee(0)
|
||||
await instanceFactory.setTWAPSlotsMin(0)
|
||||
|
||||
expect(await instanceFactory.verifier()).to.be.equal(addressZero)
|
||||
expect(await instanceFactory.hasher()).to.be.equal(addressZero)
|
||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1)
|
||||
expect(await instanceFactory.creationFee()).to.be.equal(0)
|
||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(0)
|
||||
|
||||
await instanceFactory.generateNewImplementation(config.verifier, config.hasher)
|
||||
await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight)
|
||||
await instanceFactory.setCreationFee(config.creationFee)
|
||||
await instanceFactory.setTWAPSlotsMin(config.TWAPSlotsMin)
|
||||
|
||||
expect(await instanceFactory.verifier()).to.be.equal(config.verifier)
|
||||
expect(await instanceFactory.hasher()).to.be.equal(config.hasher)
|
||||
expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight)
|
||||
expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee)
|
||||
expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin)
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
const denominations = [
|
||||
ethers.utils.parseEther('1'),
|
||||
ethers.utils.parseEther('10'),
|
||||
ethers.utils.parseEther('100'),
|
||||
ethers.utils.parseEther('1000'),
|
||||
]
|
||||
const numInstances = denominations.length
|
||||
|
||||
const protocolFees = [30, 30, 30, 30]
|
||||
|
||||
// deploy proposal ----------------------------------------------
|
||||
await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee)
|
||||
await tornToken.approve(instanceFactory.address, config.creationFee)
|
||||
|
||||
await expect(() =>
|
||||
instanceFactory.connect(sender).createProposalApprove(config.COMP, 3000, denominations, protocolFees),
|
||||
).to.changeTokenBalances(
|
||||
tornToken,
|
||||
[sender, gov],
|
||||
[BigNumber.from(0).sub(config.creationFee), config.creationFee],
|
||||
)
|
||||
|
||||
let logs = await instanceFactory.queryFilter('NewGovernanceProposalCreated')
|
||||
const proposal = await ethers.getContractAt(
|
||||
'AddInstanceProposal',
|
||||
ethers.utils.getAddress('0x' + logs[logs.length - 1].topics[1].slice(-40)),
|
||||
)
|
||||
|
||||
expect(await proposal.instanceFactory()).to.be.equal(instanceFactory.address)
|
||||
expect(await proposal.instanceRegistry()).to.be.equal(instanceRegistry.address)
|
||||
expect(await proposal.token()).to.be.equal(config.COMP)
|
||||
expect(await proposal.uniswapPoolSwappingFee()).to.be.equal(3000)
|
||||
expect(await proposal.numInstances()).to.be.equal(numInstances)
|
||||
for (let i = 0; i < numInstances; i++) {
|
||||
expect(await proposal.protocolFeeByIndex(i)).to.be.equal(protocolFees[i])
|
||||
expect(await proposal.denominationByIndex(i)).to.be.equal(denominations[i])
|
||||
}
|
||||
|
||||
// propose proposal ---------------------------------------------
|
||||
let response, id, state
|
||||
gov = await gov.connect(tornWhale)
|
||||
await tornToken.connect(tornWhale).approve(gov.address, ethers.utils.parseEther('26000'))
|
||||
await gov.lockWithApproval(ethers.utils.parseEther('26000'))
|
||||
|
||||
response = await gov.propose(proposal.address, 'COMP token instances proposal')
|
||||
id = await gov.latestProposalIds(tornWhale.address)
|
||||
state = await gov.state(id)
|
||||
|
||||
const { events } = await response.wait()
|
||||
const args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||
expect(args.id).to.be.equal(id)
|
||||
expect(args.proposer).to.be.equal(tornWhale.address)
|
||||
expect(args.target).to.be.equal(proposal.address)
|
||||
expect(args.description).to.be.equal('COMP token instances proposal')
|
||||
expect(state).to.be.equal(ProposalState.Pending)
|
||||
|
||||
// execute proposal ---------------------------------------------
|
||||
await minewait((await gov.VOTING_DELAY()).add(1).toNumber())
|
||||
await expect(gov.castVote(id, true)).to.not.be.reverted
|
||||
expect(await gov.state(id)).to.be.equal(ProposalState.Active)
|
||||
await minewait(
|
||||
(
|
||||
await gov.VOTING_PERIOD()
|
||||
)
|
||||
.add(await gov.EXECUTION_DELAY())
|
||||
.add(96400)
|
||||
.toNumber(),
|
||||
)
|
||||
expect(await gov.state(id)).to.be.equal(ProposalState.AwaitingExecution)
|
||||
|
||||
await gov.execute(id)
|
||||
|
||||
expect(await gov.state(id)).to.be.equal(ProposalState.Executed)
|
||||
|
||||
// check instances initialization -------------------------------
|
||||
logs = await instanceFactory.queryFilter('NewInstanceCloneCreated')
|
||||
for (let i = 0; i < numInstances; i++) {
|
||||
let instanceAddr = '0x' + logs[logs.length - numInstances + i].topics[1].slice(-40)
|
||||
let instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddr)
|
||||
|
||||
expect(await instance.token()).to.be.equal(config.COMP)
|
||||
expect(await instance.verifier()).to.be.equal(config.verifier)
|
||||
expect(await instance.hasher()).to.be.equal(config.hasher)
|
||||
expect(await instance.levels()).to.be.equal(config.merkleTreeHeight)
|
||||
expect(await instance.denomination()).to.equal(denominations[i])
|
||||
|
||||
let instanceData = await instanceRegistry.instances(instance.address)
|
||||
expect(instanceData.isERC20).to.be.equal(true)
|
||||
expect(instanceData.token).to.be.equal(config.COMP)
|
||||
expect(instanceData.state).to.be.equal(1)
|
||||
expect(instanceData.uniswapPoolSwappingFee).to.be.equal(3000)
|
||||
expect(instanceData.protocolFeePercentage).to.be.equal(protocolFees[i])
|
||||
}
|
||||
})
|
||||
|
||||
it('Should successfully deploy proposal with permit', async function () {
|
||||
let { instanceFactory, 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],
|
||||
)
|
||||
})
|
||||
|
||||
it('Should not deploy proposal with incorrect Uniswap pool', async function () {
|
||||
let { sender, instanceFactory, 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, 4000, [ethers.utils.parseEther('100')], [30]),
|
||||
).to.be.revertedWith('Uniswap pool is not exist')
|
||||
|
||||
await expect(
|
||||
instanceFactory
|
||||
.connect(sender)
|
||||
.createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]),
|
||||
).to.be.revertedWith('Uniswap pool TWAP slots number is low')
|
||||
})
|
||||
})
|
@ -1,346 +0,0 @@
|
||||
require('dotenv').config()
|
||||
const { ethers } = require('hardhat')
|
||||
const { expect } = require('chai')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
||||
|
||||
const { propose } = require('../scripts/helper/propose_proposal.js')
|
||||
|
||||
const MixerContractABI = require('tornado-cli/build/contracts/Mixer.abi.json')
|
||||
|
||||
describe('Deployments test setup', () => {
|
||||
const Verifier = `${process.env.VERIFIER}`
|
||||
const Hasher = `${process.env.HASHER}`
|
||||
const Proxy = `${process.env.PROXY}`
|
||||
|
||||
//// IMPERSONATED ACCOUNTS
|
||||
let accounts
|
||||
let whale
|
||||
let impGov
|
||||
|
||||
//// CONTRACTS / FACTORIES
|
||||
let ProposalFactory
|
||||
let ProposalContract
|
||||
|
||||
let GovernanceContract
|
||||
let TornToken
|
||||
let RAIToken
|
||||
let TornadoProxy
|
||||
|
||||
let TornadoInstanceFactoryContract
|
||||
|
||||
/// HARDCODED
|
||||
let denominations = [
|
||||
'33333333333333333333',
|
||||
'333333333333333333333',
|
||||
'3333333333333333333333',
|
||||
'33333333333333333333333',
|
||||
]
|
||||
let tokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919'
|
||||
|
||||
let minewait = async (time) => {
|
||||
await ethers.provider.send('evm_increaseTime', [time])
|
||||
await ethers.provider.send('evm_mine', [])
|
||||
}
|
||||
|
||||
let sendr = async (method, params) => {
|
||||
return await ethers.provider.send(method, params)
|
||||
}
|
||||
|
||||
let clog = (...x) => {
|
||||
console.log(x)
|
||||
}
|
||||
|
||||
let pE = (x) => {
|
||||
return ethers.utils.parseEther(`${x}`)
|
||||
}
|
||||
|
||||
const ProposalState = {
|
||||
Pending: 0,
|
||||
Active: 1,
|
||||
Defeated: 2,
|
||||
Timelocked: 3,
|
||||
AwaitingExecution: 4,
|
||||
Executed: 5,
|
||||
Expired: 6,
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
accounts = await ethers.getSigners()
|
||||
ProposalFactory = await ethers.getContractFactory('CreateFactoryAndAddInstancesProposal')
|
||||
GovernanceContract = await ethers.getContractAt(
|
||||
'Governance',
|
||||
'0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
)
|
||||
TornToken = await ethers.getContractAt(
|
||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||
'0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
||||
)
|
||||
TornadoProxy = await ethers.getContractAt('TornadoProxy', '0x722122dF12D4e14e13Ac3b6895a86e84145b6967')
|
||||
})
|
||||
|
||||
describe('Test instance deployment', () => {
|
||||
let snapshotId
|
||||
|
||||
it('Should have initialized all successfully', () => {
|
||||
expect(accounts[0].address).to.exist
|
||||
expect(GovernanceContract.address).to.exist
|
||||
expect(TornToken.address).to.exist
|
||||
expect(TornadoProxy.address).to.exist
|
||||
})
|
||||
|
||||
it('Should successfully imitate whale', async () => {
|
||||
await sendr('hardhat_impersonateAccount', ['0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3'])
|
||||
whale = await ethers.getSigner('0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3')
|
||||
GovernanceContract = await GovernanceContract.connect(whale)
|
||||
|
||||
let balance = await TornToken.balanceOf(whale.address)
|
||||
TornToken = await TornToken.connect(whale)
|
||||
|
||||
await TornToken.approve(GovernanceContract.address, ethers.utils.parseEther('8000000000'))
|
||||
await expect(GovernanceContract.lockWithApproval(balance)).to.not.be.reverted
|
||||
|
||||
expect((await GovernanceContract.lockedBalance(whale.address)).toString()).to.equal(balance.toString())
|
||||
})
|
||||
|
||||
it('Should successfully deploy proposal', async () => {
|
||||
ProposalContract = await ProposalFactory.deploy(Proxy, denominations, tokenAddress)
|
||||
expect(await ProposalContract.token()).to.equal(tokenAddress)
|
||||
expect(await ProposalContract.denomination1()).to.equal(denominations[0])
|
||||
expect(await ProposalContract.denomination2()).to.equal(denominations[1])
|
||||
expect(await ProposalContract.denomination3()).to.equal(denominations[2])
|
||||
expect(await ProposalContract.denomination4()).to.equal(denominations[3])
|
||||
|
||||
TornadoInstanceFactoryContract = await ethers.getContractAt(
|
||||
'TornadoInstanceCloneFactory',
|
||||
await ProposalContract.instanceFactory(),
|
||||
)
|
||||
})
|
||||
|
||||
it('Should successfully pass the proposal', async () => {
|
||||
let response, id, state
|
||||
;[response, id, state] = await propose([whale, ProposalContract, 'Instances'])
|
||||
|
||||
let { events } = await response.wait()
|
||||
let args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||
expect(args.id).to.be.equal(id)
|
||||
expect(args.proposer).to.be.equal(whale.address)
|
||||
expect(args.target).to.be.equal(ProposalContract.address)
|
||||
expect(args.description).to.be.equal('Instances')
|
||||
expect(state).to.be.equal(ProposalState.Pending)
|
||||
|
||||
await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber())
|
||||
await expect(GovernanceContract.castVote(id, true)).to.not.be.reverted
|
||||
state = await GovernanceContract.state(id)
|
||||
expect(state).to.be.equal(ProposalState.Active)
|
||||
await minewait(
|
||||
(
|
||||
await GovernanceContract.VOTING_PERIOD()
|
||||
)
|
||||
.add(await GovernanceContract.EXECUTION_DELAY())
|
||||
.add(86400)
|
||||
.toNumber(),
|
||||
)
|
||||
const overrides = {
|
||||
gasLimit: BigNumber.from('30000000'),
|
||||
}
|
||||
await GovernanceContract.execute(id, overrides)
|
||||
})
|
||||
|
||||
it('Should set correct params for factory', async () => {
|
||||
expect(await TornadoInstanceFactoryContract.verifier()).to.equal(Verifier)
|
||||
expect(await TornadoInstanceFactoryContract.hasher()).to.equal(Hasher)
|
||||
expect(await TornadoInstanceFactoryContract.merkleTreeHeight()).to.equal(20)
|
||||
clog(await TornadoInstanceFactoryContract.implementation())
|
||||
})
|
||||
|
||||
it('Factory should be able to generate an instance without reverting', async () => {
|
||||
const OHMAddress = '0x383518188C0C6d7730D91b2c03a03C837814a899'
|
||||
|
||||
await sendr('hardhat_impersonateAccount', [GovernanceContract.address])
|
||||
await sendr('hardhat_setBalance', [GovernanceContract.address, '0xDE0B6B3A764000000'])
|
||||
impGov = await ethers.getSigner(GovernanceContract.address)
|
||||
|
||||
const factory = await TornadoInstanceFactoryContract.connect(impGov)
|
||||
|
||||
await factory.createInstanceClone(333, OHMAddress)
|
||||
const instanceAddress = await TornadoInstanceFactoryContract.getInstanceAddress(333, OHMAddress)
|
||||
|
||||
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddress)
|
||||
|
||||
const token = await instance.token()
|
||||
const denomination = await instance.denomination()
|
||||
const verifier = await instance.verifier()
|
||||
const hasher = await instance.hasher()
|
||||
const levels = await instance.levels()
|
||||
|
||||
expect(token).to.equal(OHMAddress)
|
||||
expect(denomination).to.equal(333)
|
||||
expect(verifier).to.equal(Verifier)
|
||||
expect(hasher).to.equal(Hasher)
|
||||
expect(levels).to.equal(20)
|
||||
})
|
||||
|
||||
it('Governance should be able to set factory params', async () => {
|
||||
const zeroAddress = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
const factory = await TornadoInstanceFactoryContract.connect(impGov)
|
||||
await factory.setVerifier(zeroAddress)
|
||||
await factory.setHasher(zeroAddress)
|
||||
await factory.setMerkleTreeHeight(25)
|
||||
|
||||
let fverifier = await factory.verifier()
|
||||
let fhasher = await factory.hasher()
|
||||
let merkleTreeHeight = await factory.merkleTreeHeight()
|
||||
|
||||
expect(fverifier).to.equal(zeroAddress)
|
||||
expect(fhasher).to.equal(zeroAddress)
|
||||
expect(merkleTreeHeight).to.equal(25)
|
||||
|
||||
await factory.setVerifier(Verifier)
|
||||
await factory.setHasher(Hasher)
|
||||
await factory.setMerkleTreeHeight(20)
|
||||
|
||||
fverifier = await factory.verifier()
|
||||
fhasher = await factory.hasher()
|
||||
merkleTreeHeight = await factory.merkleTreeHeight()
|
||||
|
||||
expect(fverifier).to.equal(Verifier)
|
||||
expect(fhasher).to.equal(Hasher)
|
||||
expect(merkleTreeHeight).to.equal(20)
|
||||
})
|
||||
|
||||
let whaleRAI, whaleRAIBalance, TornadoInstance, mixerContract, instanceAddresses
|
||||
instanceAddresses = []
|
||||
|
||||
it('Should prepare data for instance deposit/withdraw tests', async () => {
|
||||
const RAITokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919'
|
||||
await sendr('hardhat_impersonateAccount', ['0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100'])
|
||||
RAIToken = await ethers.getContractAt(
|
||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||
RAITokenAddress,
|
||||
)
|
||||
whaleRAI = await ethers.getSigner('0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100')
|
||||
|
||||
const tx = {
|
||||
to: whaleRAI.address,
|
||||
value: pE(50),
|
||||
}
|
||||
await accounts[0].sendTransaction(tx)
|
||||
|
||||
whaleRAIBalance = await RAIToken.balanceOf(whaleRAI.address)
|
||||
RAIToken = await RAIToken.connect(whaleRAI)
|
||||
TornadoProxy = await TornadoProxy.connect(whaleRAI)
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
instanceAddresses[i] = await TornadoInstanceFactoryContract.getInstanceAddress(
|
||||
denominations[i],
|
||||
RAIToken.address,
|
||||
)
|
||||
}
|
||||
|
||||
mixerContract = await ethers.getContractAt(MixerContractABI, instanceAddresses[0])
|
||||
mixerContract = await mixerContract.connect(whaleRAI)
|
||||
|
||||
snapshotId = await sendr('evm_snapshot', [])
|
||||
})
|
||||
|
||||
it('Should test depositing and withdrawing into the new instance over proxy', async () => {
|
||||
const depo = createDeposit({
|
||||
nullifier: rbigint(31),
|
||||
secret: rbigint(31),
|
||||
})
|
||||
|
||||
const note = toHex(depo.preimage, 62)
|
||||
const noteString = `tornado-RAI-33-1-${note}`
|
||||
clog('Note: ', note)
|
||||
clog('Note string: ', noteString)
|
||||
clog('Commitment: ', toHex(depo.commitment))
|
||||
|
||||
await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted
|
||||
TornadoInstance = await ethers.getContractAt(
|
||||
'contracts/tornado_proxy/ITornadoInstance.sol:ITornadoInstance',
|
||||
instanceAddresses[0],
|
||||
)
|
||||
|
||||
await expect(() =>
|
||||
TornadoProxy.deposit(instanceAddresses[0], toHex(depo.commitment), []),
|
||||
).to.changeTokenBalance(RAIToken, whaleRAI, BigNumber.from(0).sub(await TornadoInstance.denomination()))
|
||||
|
||||
let pevents = await mixerContract.queryFilter('Deposit')
|
||||
await initialize({ merkleTreeHeight: 20 })
|
||||
|
||||
const { proof, args } = await generateProof({
|
||||
deposit: depo,
|
||||
recipient: whaleRAI.address,
|
||||
events: pevents,
|
||||
})
|
||||
|
||||
await expect(() =>
|
||||
TornadoProxy.withdraw(TornadoInstance.address, proof, ...args),
|
||||
).to.changeTokenBalance(RAIToken, whaleRAI, await TornadoInstance.denomination())
|
||||
|
||||
await sendr('evm_revert', [snapshotId])
|
||||
snapshotId = await sendr('evm_snapshot', [])
|
||||
})
|
||||
|
||||
it('Should prepare for multiple account deposits', async () => {
|
||||
let toSend = whaleRAIBalance.div(5)
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await RAIToken.transfer(accounts[i].address, toSend)
|
||||
const rai = await RAIToken.connect(accounts[i])
|
||||
await rai.approve(TornadoProxy.address, pE(600000))
|
||||
}
|
||||
})
|
||||
|
||||
it('Should test depositing with multiple accounts over proxy', async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const depo = createDeposit({
|
||||
nullifier: rbigint(31),
|
||||
secret: rbigint(31),
|
||||
})
|
||||
const note = toHex(depo.preimage, 62)
|
||||
const noteString = `tornado-RAI-33-1-${note}`
|
||||
clog('Note: ', note)
|
||||
clog('Note string: ', noteString)
|
||||
clog('Commitment: ', toHex(depo.commitment))
|
||||
const proxy = await TornadoProxy.connect(accounts[i])
|
||||
|
||||
await expect(() =>
|
||||
proxy.deposit(TornadoInstance.address, toHex(depo.commitment), []),
|
||||
).to.changeTokenBalance(
|
||||
RAIToken,
|
||||
accounts[i],
|
||||
BigNumber.from(0).sub(await TornadoInstance.denomination()),
|
||||
)
|
||||
|
||||
let pevents = await mixerContract.queryFilter('Deposit')
|
||||
await initialize({ merkleTreeHeight: 20 })
|
||||
|
||||
const { proof, args } = await generateProof({
|
||||
deposit: depo,
|
||||
recipient: accounts[i].address,
|
||||
events: pevents,
|
||||
})
|
||||
|
||||
await expect(() => proxy.withdraw(TornadoInstance.address, proof, ...args)).to.changeTokenBalance(
|
||||
RAIToken,
|
||||
accounts[i],
|
||||
await TornadoInstance.denomination(),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await ethers.provider.send('hardhat_reset', [
|
||||
{
|
||||
forking: {
|
||||
jsonRpcUrl: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`,
|
||||
blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
@ -1,355 +0,0 @@
|
||||
require('dotenv').config()
|
||||
const { ethers } = require('hardhat')
|
||||
const { expect } = require('chai')
|
||||
const { BigNumber } = require('@ethersproject/bignumber')
|
||||
const { rbigint, createDeposit, toHex, generateProof, initialize } = require('tornado-cli')
|
||||
|
||||
const { propose } = require('../scripts/helper/propose_proposal.js')
|
||||
|
||||
const MixerContractABI = require('tornado-cli/build/contracts/Mixer.abi.json')
|
||||
|
||||
describe('Deployments test setup', () => {
|
||||
const Verifier = `${process.env.VERIFIER}`
|
||||
const Hasher = `${process.env.HASHER}`
|
||||
const Proxy = `${process.env.PROXY}`
|
||||
|
||||
//// IMPERSONATED ACCOUNTS
|
||||
let accounts
|
||||
let whale
|
||||
let impGov
|
||||
|
||||
//// CONTRACTS / FACTORIES
|
||||
let ProposalFactory
|
||||
let ProposalContract
|
||||
|
||||
let GovernanceContract
|
||||
let TornToken
|
||||
let RAIToken
|
||||
let TornadoProxy
|
||||
|
||||
let TornadoInstanceFactoryFactory
|
||||
let TornadoInstanceFactoryContract
|
||||
|
||||
/// HARDCODED
|
||||
let denominations = [
|
||||
'33333333333333333333',
|
||||
'333333333333333333333',
|
||||
'3333333333333333333333',
|
||||
'33333333333333333333333',
|
||||
]
|
||||
let tokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919'
|
||||
|
||||
let minewait = async (time) => {
|
||||
await ethers.provider.send('evm_increaseTime', [time])
|
||||
await ethers.provider.send('evm_mine', [])
|
||||
}
|
||||
|
||||
let sendr = async (method, params) => {
|
||||
return await ethers.provider.send(method, params)
|
||||
}
|
||||
|
||||
let clog = (...x) => {
|
||||
console.log(x)
|
||||
}
|
||||
|
||||
let pE = (x) => {
|
||||
return ethers.utils.parseEther(`${x}`)
|
||||
}
|
||||
|
||||
const ProposalState = {
|
||||
Pending: 0,
|
||||
Active: 1,
|
||||
Defeated: 2,
|
||||
Timelocked: 3,
|
||||
AwaitingExecution: 4,
|
||||
Executed: 5,
|
||||
Expired: 6,
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
accounts = await ethers.getSigners()
|
||||
ProposalFactory = await ethers.getContractFactory('Add4Instances')
|
||||
GovernanceContract = await ethers.getContractAt(
|
||||
'Governance',
|
||||
'0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
|
||||
)
|
||||
TornToken = await ethers.getContractAt(
|
||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||
'0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
||||
)
|
||||
TornadoProxy = await ethers.getContractAt('TornadoProxy', '0x722122dF12D4e14e13Ac3b6895a86e84145b6967')
|
||||
TornadoInstanceFactoryFactory = await ethers.getContractFactory('TornadoInstanceCloneFactory')
|
||||
})
|
||||
|
||||
describe('Test instance deployment', () => {
|
||||
let snapshotId
|
||||
|
||||
it('Should have initialized all successfully', () => {
|
||||
expect(accounts[0].address).to.exist
|
||||
expect(GovernanceContract.address).to.exist
|
||||
expect(TornToken.address).to.exist
|
||||
expect(TornadoProxy.address).to.exist
|
||||
})
|
||||
|
||||
it('Should set correct params for factory', async () => {
|
||||
TornadoInstanceFactoryContract = await TornadoInstanceFactoryFactory.deploy(
|
||||
Verifier,
|
||||
Hasher,
|
||||
BigNumber.from(20),
|
||||
)
|
||||
await TornadoInstanceFactoryContract.transferOwnership(GovernanceContract.address)
|
||||
expect(await TornadoInstanceFactoryContract.verifier()).to.equal(Verifier)
|
||||
expect(await TornadoInstanceFactoryContract.hasher()).to.equal(Hasher)
|
||||
expect(await TornadoInstanceFactoryContract.merkleTreeHeight()).to.equal(20)
|
||||
clog(await TornadoInstanceFactoryContract.implementation())
|
||||
})
|
||||
|
||||
it('Factory should be able to generate an instance without reverting', async () => {
|
||||
const OHMAddress = '0x383518188C0C6d7730D91b2c03a03C837814a899'
|
||||
|
||||
await sendr('hardhat_impersonateAccount', [GovernanceContract.address])
|
||||
await sendr('hardhat_setBalance', [GovernanceContract.address, '0xDE0B6B3A764000000'])
|
||||
impGov = await ethers.getSigner(GovernanceContract.address)
|
||||
|
||||
const factory = await TornadoInstanceFactoryContract.connect(impGov)
|
||||
|
||||
await factory.createInstanceClone(333, OHMAddress)
|
||||
const instanceAddress = await TornadoInstanceFactoryContract.getInstanceAddress(333, OHMAddress)
|
||||
|
||||
const instance = await ethers.getContractAt('ERC20TornadoCloneable', instanceAddress)
|
||||
|
||||
const token = await instance.token()
|
||||
const denomination = await instance.denomination()
|
||||
const verifier = await instance.verifier()
|
||||
const hasher = await instance.hasher()
|
||||
const levels = await instance.levels()
|
||||
|
||||
expect(token).to.equal(OHMAddress)
|
||||
expect(denomination).to.equal(333)
|
||||
expect(verifier).to.equal(Verifier)
|
||||
expect(hasher).to.equal(Hasher)
|
||||
expect(levels).to.equal(20)
|
||||
})
|
||||
|
||||
it('Governance should be able to set factory params', async () => {
|
||||
const zeroAddress = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
const factory = await TornadoInstanceFactoryContract.connect(impGov)
|
||||
await factory.setVerifier(zeroAddress)
|
||||
await factory.setHasher(zeroAddress)
|
||||
await factory.setMerkleTreeHeight(25)
|
||||
|
||||
let fverifier = await factory.verifier()
|
||||
let fhasher = await factory.hasher()
|
||||
let merkleTreeHeight = await factory.merkleTreeHeight()
|
||||
|
||||
expect(fverifier).to.equal(zeroAddress)
|
||||
expect(fhasher).to.equal(zeroAddress)
|
||||
expect(merkleTreeHeight).to.equal(25)
|
||||
|
||||
await factory.setVerifier(Verifier)
|
||||
await factory.setHasher(Hasher)
|
||||
await factory.setMerkleTreeHeight(20)
|
||||
|
||||
fverifier = await factory.verifier()
|
||||
fhasher = await factory.hasher()
|
||||
merkleTreeHeight = await factory.merkleTreeHeight()
|
||||
|
||||
expect(fverifier).to.equal(Verifier)
|
||||
expect(fhasher).to.equal(Hasher)
|
||||
expect(merkleTreeHeight).to.equal(20)
|
||||
})
|
||||
|
||||
it('Should successfully imitate whale', async () => {
|
||||
await sendr('hardhat_impersonateAccount', ['0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3'])
|
||||
whale = await ethers.getSigner('0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3')
|
||||
GovernanceContract = await GovernanceContract.connect(whale)
|
||||
|
||||
let balance = await TornToken.balanceOf(whale.address)
|
||||
TornToken = await TornToken.connect(whale)
|
||||
|
||||
await TornToken.approve(GovernanceContract.address, ethers.utils.parseEther('8000000000'))
|
||||
await expect(GovernanceContract.lockWithApproval(balance)).to.not.be.reverted
|
||||
|
||||
expect((await GovernanceContract.lockedBalance(whale.address)).toString()).to.equal(balance.toString())
|
||||
})
|
||||
|
||||
it('Should successfully deploy proposal', async () => {
|
||||
ProposalContract = await ProposalFactory.deploy(
|
||||
Proxy,
|
||||
TornadoInstanceFactoryContract.address,
|
||||
denominations,
|
||||
tokenAddress,
|
||||
)
|
||||
expect(await ProposalContract.token()).to.equal(tokenAddress)
|
||||
expect(await ProposalContract.instanceFactory()).to.equal(TornadoInstanceFactoryContract.address)
|
||||
expect(await ProposalContract.denomination1()).to.equal(denominations[0])
|
||||
expect(await ProposalContract.denomination2()).to.equal(denominations[1])
|
||||
expect(await ProposalContract.denomination3()).to.equal(denominations[2])
|
||||
expect(await ProposalContract.denomination4()).to.equal(denominations[3])
|
||||
})
|
||||
|
||||
it('Should successfully pass the proposal', async () => {
|
||||
let response, id, state
|
||||
;[response, id, state] = await propose([whale, ProposalContract, 'Instances'])
|
||||
|
||||
let { events } = await response.wait()
|
||||
let args = events.find(({ event }) => event == 'ProposalCreated').args
|
||||
expect(args.id).to.be.equal(id)
|
||||
expect(args.proposer).to.be.equal(whale.address)
|
||||
expect(args.target).to.be.equal(ProposalContract.address)
|
||||
expect(args.description).to.be.equal('Instances')
|
||||
expect(state).to.be.equal(ProposalState.Pending)
|
||||
|
||||
await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber())
|
||||
await expect(GovernanceContract.castVote(id, true)).to.not.be.reverted
|
||||
state = await GovernanceContract.state(id)
|
||||
expect(state).to.be.equal(ProposalState.Active)
|
||||
await minewait(
|
||||
(
|
||||
await GovernanceContract.VOTING_PERIOD()
|
||||
)
|
||||
.add(await GovernanceContract.EXECUTION_DELAY())
|
||||
.add(86400)
|
||||
.toNumber(),
|
||||
)
|
||||
const overrides = {
|
||||
gasLimit: BigNumber.from('30000000'),
|
||||
}
|
||||
await GovernanceContract.execute(id, overrides)
|
||||
})
|
||||
|
||||
let whaleRAI, whaleRAIBalance, TornadoInstance, mixerContract, instanceAddresses
|
||||
instanceAddresses = []
|
||||
|
||||
it('Should prepare data for instance deposit/withdraw tests', async () => {
|
||||
const RAITokenAddress = '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919'
|
||||
await sendr('hardhat_impersonateAccount', ['0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100'])
|
||||
RAIToken = await ethers.getContractAt(
|
||||
'@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
|
||||
RAITokenAddress,
|
||||
)
|
||||
whaleRAI = await ethers.getSigner('0x46a0B4Fa58141ABa23185e79f7047A7dFd0FF100')
|
||||
|
||||
const tx = {
|
||||
to: whaleRAI.address,
|
||||
value: pE(50),
|
||||
}
|
||||
await accounts[0].sendTransaction(tx)
|
||||
|
||||
whaleRAIBalance = await RAIToken.balanceOf(whaleRAI.address)
|
||||
RAIToken = await RAIToken.connect(whaleRAI)
|
||||
TornadoProxy = await TornadoProxy.connect(whaleRAI)
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
instanceAddresses[i] = await TornadoInstanceFactoryContract.getInstanceAddress(
|
||||
denominations[i],
|
||||
RAIToken.address,
|
||||
)
|
||||
}
|
||||
|
||||
mixerContract = await ethers.getContractAt(MixerContractABI, instanceAddresses[0])
|
||||
mixerContract = await mixerContract.connect(whaleRAI)
|
||||
|
||||
snapshotId = await sendr('evm_snapshot', [])
|
||||
})
|
||||
|
||||
it('Should test depositing and withdrawing into the new instance over proxy', async () => {
|
||||
const depo = createDeposit({
|
||||
nullifier: rbigint(31),
|
||||
secret: rbigint(31),
|
||||
})
|
||||
|
||||
const note = toHex(depo.preimage, 62)
|
||||
const noteString = `tornado-RAI-33-1-${note}`
|
||||
clog('Note: ', note)
|
||||
clog('Note string: ', noteString)
|
||||
clog('Commitment: ', toHex(depo.commitment))
|
||||
|
||||
await expect(RAIToken.approve(TornadoProxy.address, pE(5000000))).to.not.be.reverted
|
||||
TornadoInstance = await ethers.getContractAt(
|
||||
'contracts/tornado_proxy/ITornadoInstance.sol:ITornadoInstance',
|
||||
instanceAddresses[0],
|
||||
)
|
||||
|
||||
await expect(() =>
|
||||
TornadoProxy.deposit(instanceAddresses[0], toHex(depo.commitment), []),
|
||||
).to.changeTokenBalance(RAIToken, whaleRAI, BigNumber.from(0).sub(await TornadoInstance.denomination()))
|
||||
|
||||
let pevents = await mixerContract.queryFilter('Deposit')
|
||||
await initialize({ merkleTreeHeight: 20 })
|
||||
|
||||
const { proof, args } = await generateProof({
|
||||
deposit: depo,
|
||||
recipient: whaleRAI.address,
|
||||
events: pevents,
|
||||
})
|
||||
|
||||
await expect(() =>
|
||||
TornadoProxy.withdraw(TornadoInstance.address, proof, ...args),
|
||||
).to.changeTokenBalance(RAIToken, whaleRAI, await TornadoInstance.denomination())
|
||||
|
||||
await sendr('evm_revert', [snapshotId])
|
||||
snapshotId = await sendr('evm_snapshot', [])
|
||||
})
|
||||
|
||||
it('Should prepare for multiple account deposits', async () => {
|
||||
let toSend = whaleRAIBalance.div(5)
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await RAIToken.transfer(accounts[i].address, toSend)
|
||||
const rai = await RAIToken.connect(accounts[i])
|
||||
await rai.approve(TornadoProxy.address, pE(600000))
|
||||
}
|
||||
})
|
||||
|
||||
it('Should test depositing with multiple accounts over proxy', async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const depo = createDeposit({
|
||||
nullifier: rbigint(31),
|
||||
secret: rbigint(31),
|
||||
})
|
||||
const note = toHex(depo.preimage, 62)
|
||||
const noteString = `tornado-RAI-33-1-${note}`
|
||||
clog('Note: ', note)
|
||||
clog('Note string: ', noteString)
|
||||
clog('Commitment: ', toHex(depo.commitment))
|
||||
const proxy = await TornadoProxy.connect(accounts[i])
|
||||
|
||||
await expect(() =>
|
||||
proxy.deposit(TornadoInstance.address, toHex(depo.commitment), []),
|
||||
).to.changeTokenBalance(
|
||||
RAIToken,
|
||||
accounts[i],
|
||||
BigNumber.from(0).sub(await TornadoInstance.denomination()),
|
||||
)
|
||||
|
||||
let pevents = await mixerContract.queryFilter('Deposit')
|
||||
await initialize({ merkleTreeHeight: 20 })
|
||||
|
||||
const { proof, args } = await generateProof({
|
||||
deposit: depo,
|
||||
recipient: accounts[i].address,
|
||||
events: pevents,
|
||||
})
|
||||
|
||||
await expect(() => proxy.withdraw(TornadoInstance.address, proof, ...args)).to.changeTokenBalance(
|
||||
RAIToken,
|
||||
accounts[i],
|
||||
await TornadoInstance.denomination(),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await ethers.provider.send('hardhat_reset', [
|
||||
{
|
||||
forking: {
|
||||
jsonRpcUrl: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`,
|
||||
blockNumber: process.env.use_latest_block == 'true' ? undefined : 13017436,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
43
test/utils.js
Normal file
43
test/utils.js
Normal file
@ -0,0 +1,43 @@
|
||||
/* global ethers, network */
|
||||
|
||||
async function setTime(timestamp) {
|
||||
await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp])
|
||||
}
|
||||
|
||||
async function takeSnapshot() {
|
||||
return await ethers.provider.send('evm_snapshot', [])
|
||||
}
|
||||
|
||||
async function revertSnapshot(id) {
|
||||
await ethers.provider.send('evm_revert', [id])
|
||||
}
|
||||
|
||||
async function advanceTime(sec) {
|
||||
const now = (await ethers.provider.getBlock('latest')).timestamp
|
||||
await setTime(now + sec)
|
||||
}
|
||||
|
||||
async function getSignerFromAddress(address) {
|
||||
await network.provider.request({
|
||||
method: 'hardhat_impersonateAccount',
|
||||
params: [address],
|
||||
})
|
||||
|
||||
let signer = await ethers.provider.getSigner(address)
|
||||
signer.address = signer._address
|
||||
return signer
|
||||
}
|
||||
|
||||
async function minewait(time) {
|
||||
await ethers.provider.send('evm_increaseTime', [time])
|
||||
await ethers.provider.send('evm_mine', [])
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setTime,
|
||||
advanceTime,
|
||||
takeSnapshot,
|
||||
revertSnapshot,
|
||||
getSignerFromAddress,
|
||||
minewait,
|
||||
}
|
Loading…
Reference in New Issue
Block a user