mirror of
https://github.com/tornadocash/tornado-pool-factory
synced 2024-02-02 15:04:08 +01:00
rebasing
This commit is contained in:
commit
96592be181
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
12
.env.example
Normal file
12
.env.example
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
etherscan_api_key=
|
||||||
|
goerli_rpc_key=
|
||||||
|
mainnet_rpc_key=
|
||||||
|
goerli_account_pk=
|
||||||
|
mainnet_account_pk=
|
||||||
|
|
||||||
|
PROXY=0x722122dF12D4e14e13Ac3b6895a86e84145b6967
|
||||||
|
DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80
|
||||||
|
HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe
|
||||||
|
VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A
|
||||||
|
SALT=0x0000000000000000000000000000000000000000000000000000000047941987
|
||||||
|
COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888
|
5
.env.example.workflow
Normal file
5
.env.example.workflow
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80
|
||||||
|
HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe
|
||||||
|
VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A
|
||||||
|
SALT=0x0000000000000000000000000000000000000000000000000000000047941987
|
||||||
|
COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888
|
27
.eslintrc
Normal file
27
.eslintrc
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "plugin:prettier/recommended", "prettier"],
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": ["error", 2],
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
"quotes": ["error", "single"],
|
||||||
|
"semi": ["error", "never"],
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
|
"require-await": "error",
|
||||||
|
"prettier/prettier": ["error", { "printWidth": 110 }]
|
||||||
|
}
|
||||||
|
}
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.sol linguist-language=Solidity
|
37
.github/workflows/build.js.yml
vendored
Normal file
37
.github/workflows/build.js.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['*']
|
||||||
|
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
|
||||||
|
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
|
||||||
|
- run: yarn lint
|
||||||
|
- run: yarn test test/test_proposal_with_factory.js
|
||||||
|
- run: yarn test test/test_proposal_with_factory2.js
|
||||||
|
|
||||||
|
- name: Generate coverage
|
||||||
|
run: yarn coverage
|
||||||
|
|
||||||
|
- name: Coveralls
|
||||||
|
uses: coverallsapp/github-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
109
.gitignore
vendored
Normal file
109
.gitignore
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
artifacts
|
||||||
|
cache
|
||||||
|
coverage
|
||||||
|
coverage.json
|
8
.prettierignore
Normal file
8
.prettierignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
cache
|
||||||
|
artifacts
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
coverage.json
|
16
.prettierrc
Normal file
16
.prettierrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"semi": false,
|
||||||
|
"printWidth": 110,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.sol",
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 130
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
8
.solcover.js
Normal file
8
.solcover.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
skipFiles: [
|
||||||
|
'tornado_proxy/TornadoProxy.sol',
|
||||||
|
'tornado_proxy/ITornadoTrees.sol',
|
||||||
|
'tornado_proxy/ITornadoInstance.sol',
|
||||||
|
'ERC20TornadoVirtual.sol',
|
||||||
|
],
|
||||||
|
}
|
109
README.md
Normal file
109
README.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# 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/mirru2352/tornado-instances)](https://coveralls.io/github/mirru2352/tornado-instances)
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This repository serves as a general template for deploying a tornado instance factory, deploying a proposal for the addition of multiple ERC20 tornado instances and proposing the registration of these instances with the Tornado Proxy (0x722122dF12D4e14e13Ac3b6895a86e84145b6967) through governance vote.
|
||||||
|
|
||||||
|
The scripts should help users do this programmatically, quickly. There are three tasks (scripts). Note that non-task scripts have been deprecated but are still kept for more insight into the working process.
|
||||||
|
|
||||||
|
### How-To:
|
||||||
|
|
||||||
|
Setting up the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/mirru2532/tornado-instances.git
|
||||||
|
cd tornado-instances
|
||||||
|
yarn
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Please fill out .env according to the template provided in it.
|
||||||
|
|
||||||
|
### Testing and running scripts:
|
||||||
|
|
||||||
|
To run test scripts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
Test scripts cover instance factory deployment, proposal deployment and executing proposal (RAI instances).
|
||||||
|
|
||||||
|
Running **tasks:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# a list of yarn scripts specifically for instance deployment
|
||||||
|
"deploy:factory": "yarn hardhat --network mainnet deploy_factory",
|
||||||
|
"deploy:proposal": "yarn hardhat --network mainnet deploy_proposal --factory-address",
|
||||||
|
"deploy:factory:test": "yarn hardhat --network goerli deploy_factory",
|
||||||
|
"deploy:proposal:test": "yarn hardhat --network goerli deploy_proposal --factory-address",
|
||||||
|
"propose": "yarn hardhat --network mainnet propose_proposal --proposal-address"
|
||||||
|
|
||||||
|
# as an example
|
||||||
|
yarn deploy:factory
|
||||||
|
|
||||||
|
# to call a specific task
|
||||||
|
yarn hardhat --network <network> <task> <args>
|
||||||
|
```
|
||||||
|
|
||||||
|
Running scripts (deprecated):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn hardhat --network <network> run scripts/<script to run>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploying a proposal for an instance update
|
||||||
|
|
||||||
|
Open `resources/instances.js`, a single object which generates an instance contains the following fields (RAI as an example):
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
tokenAddress: "0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919",
|
||||||
|
denomination: "33333333333333333333",
|
||||||
|
domain: "rai-100.tornadocash.eth",
|
||||||
|
symbol: "RAI",
|
||||||
|
decimals: 18
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`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$.
|
||||||
|
`domain` - resolves to the address of the instance.
|
||||||
|
Fill out each of these fields for your own token in the `instance.js` file. Please note that these contracts support deployments of exactly 4 denominations, as is the standard with which we have been deploying. If you would like to add more instances, contact me below or modify contracts independently.
|
||||||
|
|
||||||
|
Now find the factory contract address, or deploy one if one has not been deployed (unlikely):
|
||||||
|
|
||||||
|
**If factory not deployed:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn deploy:factory
|
||||||
|
```
|
||||||
|
|
||||||
|
If testing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn deploy:factory:test
|
||||||
|
```
|
||||||
|
|
||||||
|
**If factory is already deployed, continue here:**
|
||||||
|
|
||||||
|
And now take the contract address which you should see in the command line interface and add this to:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn deploy:proposal <factory address>
|
||||||
|
```
|
||||||
|
|
||||||
|
If testing:
|
||||||
|
|
||||||
|
```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>
|
||||||
|
```
|
||||||
|
|
||||||
|
There is not test implementation for this.
|
67
contracts/AddWithFactoryProposal.sol
Normal file
67
contracts/AddWithFactoryProposal.sol
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
pragma abicoder v2;
|
||||||
|
|
||||||
|
import "./TornadoInstanceCloneFactory.sol";
|
||||||
|
import "./tornado_proxy/TornadoProxy.sol";
|
||||||
|
|
||||||
|
contract AddWithFactoryProposal {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
contracts/CompileDummy.sol
Normal file
6
contracts/CompileDummy.sol
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.6.12;
|
||||||
|
|
||||||
|
import { Governance } from "tornado-governance/contracts/Governance.sol";
|
||||||
|
|
||||||
|
contract CompileDummy {}
|
73
contracts/CreateFactoryAndAddInstancesProposal.sol
Normal file
73
contracts/CreateFactoryAndAddInstancesProposal.sol
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
contracts/ERC20TornadoCloneable.sol
Normal file
36
contracts/ERC20TornadoCloneable.sol
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
pragma abicoder v2;
|
||||||
|
|
||||||
|
import "./ERC20TornadoVirtual.sol";
|
||||||
|
|
||||||
|
contract ERC20TornadoCloneable is ERC20Tornado {
|
||||||
|
constructor() ERC20Tornado(IVerifier(address(0)), IHasher(address(0)), 1, 1, IERC20(address(0))) {}
|
||||||
|
|
||||||
|
function init(
|
||||||
|
IVerifier _verifier,
|
||||||
|
IHasher _hasher,
|
||||||
|
uint256 _denomination,
|
||||||
|
uint32 _merkleTreeHeight,
|
||||||
|
IERC20 _token
|
||||||
|
) external {
|
||||||
|
require(address(verifier) == address(0) && address(hasher) == address(0), "already initialized");
|
||||||
|
/// In Constructor: ERC20Tornado from ERC20TornadoVirtual.sol
|
||||||
|
token = _token;
|
||||||
|
/// In Constructor: Tornado from ERC20TornadoVirtual.sol
|
||||||
|
require(_denomination > 0, "denomination should be greater than 0");
|
||||||
|
verifier = _verifier;
|
||||||
|
denomination = _denomination;
|
||||||
|
/// In Constructor: MerkleTreeWithHistory from ERC20TornadoVirtual.sol
|
||||||
|
require(_merkleTreeHeight > 0, "_levels should be greater than zero");
|
||||||
|
require(_merkleTreeHeight < 32, "_levels should be less than 32");
|
||||||
|
|
||||||
|
hasher = _hasher;
|
||||||
|
levels = _merkleTreeHeight;
|
||||||
|
|
||||||
|
for (uint32 i = 0; i < _merkleTreeHeight; i++) {
|
||||||
|
filledSubtrees[i] = zeros(i);
|
||||||
|
}
|
||||||
|
roots[0] = zeros(_merkleTreeHeight - 1);
|
||||||
|
}
|
||||||
|
}
|
337
contracts/ERC20TornadoVirtual.sol
Normal file
337
contracts/ERC20TornadoVirtual.sol
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
// 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-solidity/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-solidity/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import "openzeppelin-solidity/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
contracts/TornadoInstanceCloneFactory.sol
Normal file
61
contracts/TornadoInstanceCloneFactory.sol
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
pragma abicoder v2;
|
||||||
|
|
||||||
|
import { Ownable } from "openzeppelin-solidity/contracts/access/Ownable.sol";
|
||||||
|
import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
|
||||||
|
|
||||||
|
import "openzeppelin-solidity/contracts/proxy/Clones.sol";
|
||||||
|
import "./ERC20TornadoCloneable.sol";
|
||||||
|
|
||||||
|
contract TornadoInstanceCloneFactory is Ownable {
|
||||||
|
using Clones for address;
|
||||||
|
|
||||||
|
mapping(address => mapping(uint256 => address)) public instanceClones;
|
||||||
|
|
||||||
|
address public implementation;
|
||||||
|
address public verifier;
|
||||||
|
address public hasher;
|
||||||
|
uint32 public merkleTreeHeight;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address _verifier,
|
||||||
|
address _hasher,
|
||||||
|
uint32 _merkleTreeHeight
|
||||||
|
) {
|
||||||
|
verifier = _verifier;
|
||||||
|
hasher = _hasher;
|
||||||
|
merkleTreeHeight = _merkleTreeHeight;
|
||||||
|
|
||||||
|
ERC20TornadoCloneable implContract = new ERC20TornadoCloneable();
|
||||||
|
implementation = address(implContract);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVerifier(address _verifier) external onlyOwner {
|
||||||
|
verifier = _verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHasher(address _hasher) external onlyOwner {
|
||||||
|
hasher = _hasher;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMerkleTreeHeight(uint32 _merkleTreeHeight) external onlyOwner {
|
||||||
|
merkleTreeHeight = _merkleTreeHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setImplementation(address _newImplementation) external onlyOwner {
|
||||||
|
implementation = _newImplementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInstanceClone(uint256 _denomination, address _token) external onlyOwner returns (address) {
|
||||||
|
require(instanceClones[_token][_denomination] == address(0), "Instance for this denomination already exists");
|
||||||
|
address newImpl = implementation.clone();
|
||||||
|
ERC20TornadoCloneable(newImpl).init(IVerifier(verifier), IHasher(hasher), _denomination, merkleTreeHeight, IERC20(_token));
|
||||||
|
instanceClones[_token][_denomination] = newImpl;
|
||||||
|
return newImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInstanceAddress(uint256 _denomination, address _token) external view returns (address) {
|
||||||
|
return instanceClones[_token][_denomination];
|
||||||
|
}
|
||||||
|
}
|
21
contracts/tornado_proxy/ITornadoInstance.sol
Normal file
21
contracts/tornado_proxy/ITornadoInstance.sol
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
9
contracts/tornado_proxy/ITornadoTrees.sol
Normal file
9
contracts/tornado_proxy/ITornadoTrees.sol
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
148
contracts/tornado_proxy/TornadoProxy.sol
Normal file
148
contracts/tornado_proxy/TornadoProxy.sol
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
|
||||||
|
import "openzeppelin-solidity/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);
|
||||||
|
}
|
||||||
|
}
|
68
hardhat.config.js
Normal file
68
hardhat.config.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
require('dotenv').config()
|
||||||
|
require('@nomiclabs/hardhat-ethers')
|
||||||
|
require('@nomiclabs/hardhat-etherscan')
|
||||||
|
require('@nomiclabs/hardhat-waffle')
|
||||||
|
require('hardhat-log-remover')
|
||||||
|
require('solidity-coverage')
|
||||||
|
|
||||||
|
require('./tasks/deploy_proposal.js')
|
||||||
|
require('./tasks/deploy_factory.js')
|
||||||
|
require('./tasks/propose_proposal.js')
|
||||||
|
/**
|
||||||
|
* @type import('hardhat/config').HardhatUserConfig
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
solidity: {
|
||||||
|
compilers: [
|
||||||
|
{
|
||||||
|
version: '0.6.12',
|
||||||
|
settings: {
|
||||||
|
optimizer: {
|
||||||
|
enabled: true,
|
||||||
|
runs: 2000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: '0.7.6',
|
||||||
|
settings: {
|
||||||
|
optimizer: {
|
||||||
|
enabled: true,
|
||||||
|
runs: 2000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
hardhat: {
|
||||||
|
forking: {
|
||||||
|
url: `https://mainnet.infura.io/v3/${process.env.mainnet_rpc_key}`,
|
||||||
|
blockNumber: 13017436,
|
||||||
|
},
|
||||||
|
loggingEnabled: false,
|
||||||
|
},
|
||||||
|
localhost: {
|
||||||
|
url: 'http://localhost:8545',
|
||||||
|
timeout: 120000,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mocha: { timeout: 9999999999 },
|
||||||
|
spdxLicenseIdentifier: {
|
||||||
|
overwrite: true,
|
||||||
|
runOnCompile: true,
|
||||||
|
},
|
||||||
|
etherscan: {
|
||||||
|
apiKey: `${process.env.etherscan_api_key}`,
|
||||||
|
},
|
||||||
|
}
|
51
package.json
Normal file
51
package.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"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:factory": "yarn hardhat --network mainnet deploy_factory",
|
||||||
|
"deploy:proposal": "yarn hardhat --network mainnet deploy_proposal --factory-address",
|
||||||
|
"deploy:factory:test": "yarn hardhat --network goerli deploy_factory",
|
||||||
|
"deploy:proposal:test": "yarn hardhat --network goerli deploy_proposal --factory-address",
|
||||||
|
"propose": "yarn hardhat --network mainnet propose_proposal --proposal-address",
|
||||||
|
"test": "yarn hardhat test",
|
||||||
|
"f:test": "yarn test && yarn clean",
|
||||||
|
"clean": "yarn prettier:fix && yarn lint",
|
||||||
|
"coverage": "yarn hardhat coverage --testfiles \"test/*.js\""
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||||
|
"@nomiclabs/hardhat-etherscan": "^2.1.4",
|
||||||
|
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"chai": "^4.3.4",
|
||||||
|
"coveralls": "^3.1.1",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
|
"ethereum-waffle": "^3.4.0",
|
||||||
|
"hardhat": "^2.4.3",
|
||||||
|
"hardhat-log-remover": "^2.0.2",
|
||||||
|
"mocha-lcov-reporter": "^1.3.0",
|
||||||
|
"prettier": "^2.3.2",
|
||||||
|
"prettier-plugin-solidity": "^1.0.0-beta.17",
|
||||||
|
"solhint-plugin-prettier": "^0.0.5",
|
||||||
|
"solidity-coverage": "^0.7.17",
|
||||||
|
"websnark": "^0.0.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@openzeppelin/contracts": "3.2.0",
|
||||||
|
"@openzeppelin/upgrades-core": "^1.0.1",
|
||||||
|
"openzeppelin-solidity": "https://github.com/OpenZeppelin/openzeppelin-contracts.git#v3.4.0-solc-0.7",
|
||||||
|
"torn-token": "^1.0.4",
|
||||||
|
"tornado-cli": "^0.0.1",
|
||||||
|
"tornado-governance": "^1.0.2"
|
||||||
|
}
|
||||||
|
}
|
30
resources/instances.js
Normal file
30
resources/instances.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||||
|
denomination: '33333333333333333333',
|
||||||
|
domain: 'rai-33.tornadocash.eth',
|
||||||
|
symbol: 'RAI',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||||
|
denomination: '333333333333333333333',
|
||||||
|
domain: 'rai-333.tornadocash.eth',
|
||||||
|
symbol: 'RAI',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||||
|
denomination: '3333333333333333333333',
|
||||||
|
domain: 'rai-3333.tornadocash.eth',
|
||||||
|
symbol: 'RAI',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenAddress: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919',
|
||||||
|
denomination: '33333333333333333333333',
|
||||||
|
domain: 'rai-33333.tornadocash.eth',
|
||||||
|
symbol: 'RAI',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
]
|
22
scripts/helper/propose_proposal.js
Normal file
22
scripts/helper/propose_proposal.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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
|
26
tasks/deploy_factory.js
Normal file
26
tasks/deploy_factory.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
require('dotenv').config()
|
||||||
|
const { task } = require('hardhat/config')
|
||||||
|
const { BigNumber } = require('@ethersproject/bignumber')
|
||||||
|
|
||||||
|
task('deploy_factory', 'deploy the instance factory').setAction(async (taskArgs, hre) => {
|
||||||
|
const GovernanceAddress = '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce'
|
||||||
|
const Verifier = `${process.env.VERIFIER}`
|
||||||
|
const Hasher = `${process.env.HASHER}`
|
||||||
|
|
||||||
|
const TornadoInstanceFactoryFactory = await hre.ethers.getContractFactory('TornadoInstanceCloneFactory')
|
||||||
|
const TornadoInstanceFactoryContract = await TornadoInstanceFactoryFactory.deploy(
|
||||||
|
Verifier,
|
||||||
|
Hasher,
|
||||||
|
BigNumber.from(20),
|
||||||
|
)
|
||||||
|
|
||||||
|
await TornadoInstanceFactoryContract.transferOwnership(GovernanceAddress)
|
||||||
|
await TornadoInstanceFactoryContract.deployTransaction.wait(5)
|
||||||
|
|
||||||
|
await hre.run('verify:verify', {
|
||||||
|
address: TornadoInstanceFactoryContract.address,
|
||||||
|
constructorArguments: [Verifier, Hasher, BigNumber.from(20)],
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Verified TornadoInstanceFactory deployed at: ', TornadoInstanceFactoryContract.address)
|
||||||
|
})
|
25
tasks/deploy_proposal.js
Normal file
25
tasks/deploy_proposal.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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').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(denominations, tokenAddress)
|
||||||
|
|
||||||
|
await ProposalContract.deployTransaction.wait(5)
|
||||||
|
|
||||||
|
await hre.run('verify:verify', {
|
||||||
|
address: ProposalContract.address,
|
||||||
|
constructorArguments: [denominations, tokenAddress],
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Verified CreateFactoryAndAddInstancesProposal deployed at: ', ProposalContract.address)
|
||||||
|
})
|
20
tasks/propose_proposal.js
Normal file
20
tasks/propose_proposal.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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)
|
||||||
|
})
|
344
test/test_proposal_with_factory.js
Normal file
344
test/test_proposal_with_factory.js
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
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('AddWithFactoryProposal')
|
||||||
|
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('6000000'),
|
||||||
|
}
|
||||||
|
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.instanceClones(
|
||||||
|
RAIToken.address,
|
||||||
|
denominations[i],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
335
test/test_proposal_with_factory2.js
Normal file
335
test/test_proposal_with_factory2.js
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
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('6000000'),
|
||||||
|
}
|
||||||
|
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.instanceClones(
|
||||||
|
RAIToken.address,
|
||||||
|
denominations[i],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user