This commit is contained in:
mirru2532 2021-10-24 21:54:07 +02:00
commit 96592be181
31 changed files with 12447 additions and 0 deletions

9
.editorconfig Normal file
View 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
View 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
View File

@ -0,0 +1,5 @@
DEPLOYER=0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80
HASHER=0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe
VERIFIER=0xce172ce1F20EC0B3728c9965470eaf994A03557A
SALT=0x0000000000000000000000000000000000000000000000000000000047941987
COMP_ADDRESS=0xc00e94Cb662C3520282E6f5717214004A7f26888

27
.eslintrc Normal file
View 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
View File

@ -0,0 +1 @@
*.sol linguist-language=Solidity

37
.github/workflows/build.js.yml vendored Normal file
View 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
View 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

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
14

8
.prettierignore Normal file
View File

@ -0,0 +1,8 @@
.vscode
.idea
cache
artifacts
build
dist
coverage
coverage.json

16
.prettierrc Normal file
View 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
View 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
View 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.

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

View File

@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { Governance } from "tornado-governance/contracts/Governance.sol";
contract CompileDummy {}

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

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

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

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

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

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

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

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

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

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

10426
yarn.lock Normal file

File diff suppressed because it is too large Load Diff