Merge pull request #1 from tornadocash/refactoring

Refactoring
This commit is contained in:
Alexey Pertsev 2022-03-15 19:38:59 +01:00 committed by GitHub
commit 281c8c50ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2334 additions and 1996 deletions

View File

@ -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=

View File

@ -1,8 +0,0 @@
use_latest_block=false
PROXY=0x722122dF12D4e14e13Ac3b6895a86e84145b6967
DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80
HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe
VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A
SALT=0x0000000000000000000000000000000000000000000000000000000047941987
COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888

View File

@ -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 }}

2
.gitignore vendored
View File

@ -107,3 +107,5 @@ artifacts
cache
coverage
coverage.json
.vscode

View File

@ -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
View File

@ -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
View 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,
}

View 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");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View 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;
}

View 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

View File

@ -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 {}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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}`,
},
}

View File

@ -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"
}
}

View File

@ -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,
},
]

View 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)
})

View 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)
})

View File

@ -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
View 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
View 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 }

View File

@ -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)
},
)

View File

@ -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)
})

View File

@ -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
View 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],
)
})
})

View 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')
})
})

View File

@ -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,
},
},
])
})
})

View File

@ -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
View 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,
}

1011
yarn.lock

File diff suppressed because it is too large Load Diff