mirror of
https://github.com/tornadocash/tornado-core.git
synced 2024-10-01 01:06:17 -04:00
Merge branch 'master' into migrationScript
This commit is contained in:
commit
68a861ed35
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
build
|
||||
.vscode
|
||||
/index.js
|
||||
Mixer_flat.sol
|
||||
Tornado_flat.sol
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
@ -94,5 +94,5 @@ typings/
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
ERC20Mixer_flat.sol
|
||||
ETHMixer_flat.sol
|
||||
ERC20Tornado_flat.sol
|
||||
ETHTornado_flat.sol
|
||||
|
16
README.md
16
README.md
@ -1,6 +1,6 @@
|
||||
# Tornado mixer [![Build Status](https://travis-ci.org/tornadocash/tornado-core.svg?branch=master)](https://travis-ci.org/tornadocash/tornado-core)
|
||||
# Tornado Privacy Solution [![Build Status](https://travis-ci.org/tornadocash/tornado-core.svg?branch=master)](https://travis-ci.org/tornadocash/tornado-core)
|
||||
|
||||
Tornado is a non-custodial Ethereum and ERC20 mixer based on zkSNARKs. It improves transaction privacy by breaking the on-chain link between recipient and destination addresses. It uses a smart contract that accepts ETH deposits that can be withdrawn by a different address. Whenever ETH is withdrawn by the new address, there is no way to link the withdrawal to the deposit, ensuring complete privacy.
|
||||
Tornado is a non-custodial Ethereum and ERC20 privacy solution based on zkSNARKs. It improves transaction privacy by breaking the on-chain link between recipient and destination addresses. It uses a smart contract that accepts ETH deposits that can be withdrawn by a different address. Whenever ETH is withdrawn by the new address, there is no way to link the withdrawal to the deposit, ensuring complete privacy.
|
||||
|
||||
To make a deposit user generates a secret and sends its hash (called a commitment) along with the deposit amount to the Tornado smart contract. The contract accepts the deposit and adds the commitment to its list of deposits.
|
||||
|
||||
@ -15,12 +15,12 @@ You can read more about it in [this medium article](https://medium.com/@tornado.
|
||||
- Circuit Proof time = 6116ms (1071 + 347 * tree_depth)
|
||||
- Serverless
|
||||
|
||||
![mixer image](./mixer.png)
|
||||
![image](diagram.png)
|
||||
|
||||
## Security risks
|
||||
* Cryptographic tools used by mixer (zkSNARKS, Pedersen commitment, MiMC hash) are yet NOT extensively audited by cryptographic experts and may be vulnerable
|
||||
* Cryptographic tools used by Tornado (zkSNARKS, Pedersen commitment, MiMC hash) are yet NOT extensively audited by cryptographic experts and may be vulnerable
|
||||
* Note: we use MiMC hash only for merkle tree, so even if a preimage attack on MiMC is discovered, it will not allow to deanonymize users. To drain funds attacker needs to be able to generate arbitrary hash collisions, which is a pretty strong assumption.
|
||||
* Bugs in contract. Even though we have an extensive experience in smart contract security audits, we can still make mistakes. An external audit is needed to reduce probablility of bugs. Our mixer is currently being audited, stay tuned.
|
||||
* Bugs in contract. Even though we have an extensive experience in smart contract security audits, we can still make mistakes. An external audit is needed to reduce probablility of bugs. Our code is currently being audited, stay tuned.
|
||||
* Relayer is frontrunnable. When relayer submits a transaction someone can see it in tx pool and frontrun it with higher gas price to get the fee and drain relayer funds.
|
||||
* Workaround: we can set high gas price so that (almost) all fee is used on gas
|
||||
* Second workaround: allow only single hardcoded relayer, we use this approach for now
|
||||
@ -51,13 +51,13 @@ Use browser version on Kovan:
|
||||
1. Open `localhost:8080`
|
||||
|
||||
Use with command line version with Ganache:
|
||||
### ETHMixer
|
||||
### ETHTornado
|
||||
1. `npm run migrate:dev`
|
||||
1. `./cli.js deposit`
|
||||
1. `./cli.js withdraw <note from previous step> <destination eth address>`
|
||||
1. `./cli.js balance <destination eth address>`
|
||||
|
||||
### ERC20Mixer
|
||||
### ERC20Tornado
|
||||
1. `npm run migrate:dev`
|
||||
1. `./cli.js depositErc20`
|
||||
1. `./cli.js withdrawErc20 <note from previous step> <destination eth address> <relayer eth address>`
|
||||
@ -76,7 +76,7 @@ If you want, you can point the app to existing tornado contracts on Mainnet or K
|
||||
1. `npx truffle migrate --network kovan --reset --f 2 --to 3`
|
||||
1. `npx truffle migrate --network kovan --reset --f 5`
|
||||
|
||||
**Note**. If you want to reuse the same verifier for all the mixers, then after you deployed one of the mixers you should only run 4th or 5th migration for ETH or ERC20 mixers respectively (`--f 4 --to 4` or `--f 5`).
|
||||
**Note**. If you want to reuse the same verifier for all the instances, then after you deployed one of the instances you should only run 4th or 5th migration for ETH or ERC20 contracts respectively (`--f 4 --to 4` or `--f 5`).
|
||||
|
||||
## Credits
|
||||
|
||||
|
60
cli.js
60
cli.js
@ -14,7 +14,7 @@ const buildGroth16 = require('websnark/src/groth16')
|
||||
const websnarkUtils = require('websnark/src/utils')
|
||||
const { toWei, fromWei } = require('web3-utils')
|
||||
|
||||
let web3, mixer, erc20mixer, circuit, proving_key, groth16, erc20, senderAccount
|
||||
let web3, tornado, erc20tornado, circuit, proving_key, groth16, erc20, senderAccount
|
||||
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, ERC20_TOKEN
|
||||
|
||||
/** Whether we are in a browser or node.js */
|
||||
@ -75,7 +75,7 @@ async function depositErc20() {
|
||||
}
|
||||
|
||||
console.log('Approving tokens for deposit')
|
||||
await erc20.methods.approve(erc20mixer._address, TOKEN_AMOUNT).send({ from: senderAccount, gas:1e6 })
|
||||
await erc20.methods.approve(erc20tornado._address, TOKEN_AMOUNT).send({ from: senderAccount, gas:1e6 })
|
||||
|
||||
console.log('Submitting deposit transaction')
|
||||
await erc20mixer.methods.deposit(toHex(deposit.commitment)).send({ from: senderAccount, gas:2e6 })
|
||||
@ -89,12 +89,12 @@ async function depositErc20() {
|
||||
* Generate merkle tree for a deposit.
|
||||
* Download deposit events from the contract, reconstructs merkle tree, finds our deposit leaf
|
||||
* in it and generates merkle proof
|
||||
* @param contract Mixer contract address
|
||||
* @param contract Tornado contract address
|
||||
* @param deposit Deposit object
|
||||
*/
|
||||
async function generateMerkleProof(contract, deposit) {
|
||||
// Get all deposit events from smart contract and assemble merkle tree from them
|
||||
console.log('Getting current state from mixer contract')
|
||||
console.log('Getting current state from tornado contract')
|
||||
const events = await contract.getPastEvents('Deposit', { fromBlock: contract.deployedBlock, toBlock: 'latest' })
|
||||
const leaves = events
|
||||
.sort((a, b) => a.returnValues.leafIndex - b.returnValues.leafIndex) // Sort events in chronological order
|
||||
@ -118,7 +118,7 @@ async function generateMerkleProof(contract, deposit) {
|
||||
|
||||
/**
|
||||
* Generate SNARK proof for withdrawal
|
||||
* @param contract Mixer contract address
|
||||
* @param contract Tornado contract address
|
||||
* @param note Note string
|
||||
* @param recipient Funds recipient
|
||||
* @param relayer Relayer address
|
||||
@ -174,10 +174,10 @@ async function generateProof(contract, note, recipient, relayer = 0, fee = 0, re
|
||||
* @param recipient Recipient address
|
||||
*/
|
||||
async function withdraw(note, recipient) {
|
||||
const { proof, args } = await generateProof(mixer, note, recipient)
|
||||
const { proof, args } = await generateProof(tornado, note, recipient)
|
||||
|
||||
console.log('Submitting withdraw transaction')
|
||||
await mixer.methods.withdraw(proof, ...args).send({ from: senderAccount, gas: 1e6 })
|
||||
await tornado.methods.withdraw(proof, ...args).send({ from: senderAccount, gas: 1e6 })
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
@ -187,10 +187,10 @@ async function withdraw(note, recipient) {
|
||||
* @param recipient Recipient address
|
||||
*/
|
||||
async function withdrawErc20(note, recipient) {
|
||||
const { proof, args } = await generateProof(erc20mixer, note, recipient)
|
||||
const { proof, args } = await generateProof(erc20tornado, note, recipient)
|
||||
|
||||
console.log('Submitting withdraw transaction')
|
||||
await erc20mixer.methods.withdraw(proof, ...args).send({ from: senderAccount, gas: 1e6 })
|
||||
await erc20tornado.methods.withdraw(proof, ...args).send({ from: senderAccount, gas: 1e6 })
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
@ -207,10 +207,10 @@ async function withdrawRelay(note, recipient, relayUrl) {
|
||||
console.log('Relay address: ', relayerAddress)
|
||||
|
||||
const fee = bigInt(toWei(gasPrices.fast.toString(), 'gwei')).mul(bigInt(1e6))
|
||||
const { proof, args } = await generateProof(mixer, note, recipient, relayerAddress, fee)
|
||||
const { proof, args } = await generateProof(tornado, note, recipient, relayerAddress, fee)
|
||||
|
||||
console.log('Sending withdraw transaction through relay')
|
||||
const resp2 = await axios.post(relayUrl + '/relay', { contract: mixer._address, proof: { proof, publicSignals: args } })
|
||||
const resp2 = await axios.post(relayUrl + '/relay', { contract: tornado._address, proof: { proof, publicSignals: args } })
|
||||
console.log(`Transaction submitted through relay, tx hash: ${resp2.data.txHash}`)
|
||||
|
||||
let receipt = await waitForTxReceipt(resp2.data.txHash)
|
||||
@ -232,10 +232,10 @@ async function withdrawRelayErc20(note, recipient, relayUrl) {
|
||||
|
||||
const refund = bigInt(toWei('0.001'))
|
||||
const fee = bigInt(toWei(gasPrices.fast.toString(), 'gwei')).mul(bigInt(1e6)).add(refund).mul(bigInt(fromWei(ethPriceInDai.toString())))
|
||||
const { proof, args } = await generateProof(erc20mixer, note, recipient, relayerAddress, fee, refund)
|
||||
const { proof, args } = await generateProof(erc20tornado, note, recipient, relayerAddress, fee, refund)
|
||||
|
||||
console.log('Sending withdraw transaction through relay')
|
||||
const resp2 = await axios.post(relayUrl + '/relay', { contract: erc20mixer._address, proof: { proof, publicSignals: args } })
|
||||
const resp2 = await axios.post(relayUrl + '/relay', { contract: erc20tornado._address, proof: { proof, publicSignals: args } })
|
||||
console.log(`Transaction submitted through relay, tx hash: ${resp2.data.txHash}`)
|
||||
|
||||
let receipt = await waitForTxReceipt(resp2.data.txHash)
|
||||
@ -271,12 +271,12 @@ function waitForTxReceipt(txHash, attempts = 60, delay = 1000) {
|
||||
* Init web3, contracts, and snark
|
||||
*/
|
||||
async function init() {
|
||||
let contractJson, erc20ContractJson, erc20mixerJson
|
||||
let contractJson, erc20ContractJson, erc20tornadoJson
|
||||
if (inBrowser) {
|
||||
// Initialize using injected web3 (Metamask)
|
||||
// To assemble web version run `npm run browserify`
|
||||
web3 = new Web3(window.web3.currentProvider, null, { transactionConfirmationBlocks: 1 })
|
||||
contractJson = await (await fetch('build/contracts/ETHMixer.json')).json()
|
||||
contractJson = await (await fetch('build/contracts/ETHTornado.json')).json()
|
||||
circuit = await (await fetch('build/circuits/withdraw.json')).json()
|
||||
proving_key = await (await fetch('build/circuits/withdraw_proving_key.bin')).arrayBuffer()
|
||||
MERKLE_TREE_HEIGHT = 16
|
||||
@ -285,7 +285,7 @@ async function init() {
|
||||
} else {
|
||||
// Initialize from local node
|
||||
web3 = new Web3('http://localhost:8545', null, { transactionConfirmationBlocks: 1 })
|
||||
contractJson = require('./build/contracts/ETHMixer.json')
|
||||
contractJson = require('./build/contracts/ETHTornado.json')
|
||||
circuit = require('./build/circuits/withdraw.json')
|
||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
||||
require('dotenv').config()
|
||||
@ -294,19 +294,19 @@ async function init() {
|
||||
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
|
||||
ERC20_TOKEN = process.env.ERC20_TOKEN
|
||||
erc20ContractJson = require('./build/contracts/ERC20Mock.json')
|
||||
erc20mixerJson = require('./build/contracts/ERC20Mixer.json')
|
||||
erc20tornadoJson = require('./build/contracts/ERC20Tornado.json')
|
||||
}
|
||||
groth16 = await buildGroth16()
|
||||
let netId = await web3.eth.net.getId()
|
||||
if (contractJson.networks[netId]) {
|
||||
const tx = await web3.eth.getTransaction(contractJson.networks[netId].transactionHash)
|
||||
mixer = new web3.eth.Contract(contractJson.abi, contractJson.networks[netId].address)
|
||||
mixer.deployedBlock = tx.blockNumber
|
||||
tornado = new web3.eth.Contract(contractJson.abi, contractJson.networks[netId].address)
|
||||
tornado.deployedBlock = tx.blockNumber
|
||||
}
|
||||
|
||||
const tx3 = await web3.eth.getTransaction(erc20mixerJson.networks[netId].transactionHash)
|
||||
erc20mixer = new web3.eth.Contract(erc20mixerJson.abi, erc20mixerJson.networks[netId].address)
|
||||
erc20mixer.deployedBlock = tx3.blockNumber
|
||||
const tx3 = await web3.eth.getTransaction(erc20tornadoJson.networks[netId].transactionHash)
|
||||
erc20tornado = new web3.eth.Contract(erc20tornadoJson.abi, erc20tornadoJson.networks[netId].address)
|
||||
erc20tornado.deployedBlock = tx3.blockNumber
|
||||
|
||||
if(ERC20_TOKEN === '') {
|
||||
erc20 = new web3.eth.Contract(erc20ContractJson.abi, erc20ContractJson.networks[netId].address)
|
||||
@ -357,10 +357,10 @@ async function runConsole(args) {
|
||||
case 'deposit':
|
||||
if (args.length === 1) {
|
||||
await init()
|
||||
await printBalance(mixer._address, 'Mixer')
|
||||
await printBalance(tornado._address, 'Tornado')
|
||||
await printBalance(senderAccount, 'Sender account')
|
||||
await deposit()
|
||||
await printBalance(mixer._address, 'Mixer')
|
||||
await printBalance(tornado._address, 'Tornado')
|
||||
await printBalance(senderAccount, 'Sender account')
|
||||
} else {
|
||||
printHelp(1)
|
||||
@ -369,10 +369,10 @@ async function runConsole(args) {
|
||||
case 'depositErc20':
|
||||
if (args.length === 1) {
|
||||
await init()
|
||||
await printBalance(erc20mixer._address, 'Mixer')
|
||||
await printBalance(erc20tornado._address, 'Tornado')
|
||||
await printBalance(senderAccount, 'Sender account')
|
||||
await depositErc20()
|
||||
await printBalance(erc20mixer._address, 'Mixer')
|
||||
await printBalance(erc20tornado._address, 'Tornado')
|
||||
await printBalance(senderAccount, 'Sender account')
|
||||
} else {
|
||||
printHelp(1)
|
||||
@ -389,14 +389,14 @@ async function runConsole(args) {
|
||||
case 'withdraw':
|
||||
if (args.length >= 3 && args.length <= 4 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
||||
await init()
|
||||
await printBalance(mixer._address, 'Mixer')
|
||||
await printBalance(tornado._address, 'Tornado')
|
||||
await printBalance(args[2], 'Recipient account')
|
||||
if (args[3]) {
|
||||
await withdrawRelay(args[1], args[2], args[3])
|
||||
} else {
|
||||
await withdraw(args[1], args[2])
|
||||
}
|
||||
await printBalance(mixer._address, 'Mixer')
|
||||
await printBalance(tornado._address, 'Tornado')
|
||||
await printBalance(args[2], 'Recipient account')
|
||||
} else {
|
||||
printHelp(1)
|
||||
@ -405,14 +405,14 @@ async function runConsole(args) {
|
||||
case 'withdrawErc20':
|
||||
if (args.length >= 3 && args.length <= 4 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
||||
await init()
|
||||
await printBalance(erc20mixer._address, 'Mixer')
|
||||
await printBalance(erc20tornado._address, 'Tornado')
|
||||
await printBalance(args[2], 'Recipient account')
|
||||
if (args[3]) {
|
||||
await withdrawRelayErc20(args[1], args[2], args[3])
|
||||
} else {
|
||||
await withdrawErc20(args[1], args[2])
|
||||
}
|
||||
await printBalance(erc20mixer._address, 'Mixer')
|
||||
await printBalance(erc20tornado._address, 'Tornado')
|
||||
await printBalance(args[2], 'Recipient account')
|
||||
} else {
|
||||
printHelp(1)
|
||||
|
@ -11,9 +11,9 @@
|
||||
|
||||
pragma solidity ^0.5.8;
|
||||
|
||||
import "./Mixer.sol";
|
||||
import "./Tornado.sol";
|
||||
|
||||
contract ERC20Mixer is Mixer {
|
||||
contract ERC20Tornado is Tornado {
|
||||
address public token;
|
||||
|
||||
constructor(
|
||||
@ -22,12 +22,12 @@ contract ERC20Mixer is Mixer {
|
||||
uint32 _merkleTreeHeight,
|
||||
address _operator,
|
||||
address _token
|
||||
) Mixer(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
||||
) Tornado(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
||||
token = _token;
|
||||
}
|
||||
|
||||
function _processDeposit() internal {
|
||||
require(msg.value == 0, "ETH value is supposed to be 0 for ERC20 mixer");
|
||||
require(msg.value == 0, "ETH value is supposed to be 0 for ERC20 instance");
|
||||
_safeErc20TransferFrom(msg.sender, address(this), denomination);
|
||||
}
|
||||
|
@ -11,15 +11,15 @@
|
||||
|
||||
pragma solidity ^0.5.8;
|
||||
|
||||
import "./Mixer.sol";
|
||||
import "./Tornado.sol";
|
||||
|
||||
contract ETHMixer is Mixer {
|
||||
contract ETHTornado is Tornado {
|
||||
constructor(
|
||||
IVerifier _verifier,
|
||||
uint256 _denomination,
|
||||
uint32 _merkleTreeHeight,
|
||||
address _operator
|
||||
) Mixer(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
||||
) Tornado(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
||||
}
|
||||
|
||||
function _processDeposit() internal {
|
||||
@ -28,8 +28,8 @@ contract ETHMixer is Mixer {
|
||||
|
||||
function _processWithdraw(address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) internal {
|
||||
// sanity checks
|
||||
require(msg.value == 0, "Message value is supposed to be zero for ETH mixer");
|
||||
require(_refund == 0, "Refund value is supposed to be zero for ETH mixer");
|
||||
require(msg.value == 0, "Message value is supposed to be zero for ETH instance");
|
||||
require(_refund == 0, "Refund value is supposed to be zero for ETH instance");
|
||||
|
||||
(bool success, ) = _recipient.call.value(denomination - _fee)("");
|
||||
require(success, "payment to _recipient did not go thru");
|
@ -18,7 +18,7 @@ contract IVerifier {
|
||||
function verifyProof(bytes memory _proof, uint256[6] memory _input) public returns(bool);
|
||||
}
|
||||
|
||||
contract Mixer is MerkleTreeWithHistory, ReentrancyGuard {
|
||||
contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
||||
uint256 public denomination;
|
||||
mapping(bytes32 => bool) public nullifierHashes;
|
||||
// we store all commitments just to prevent accidental deposits with the same commitment
|
||||
@ -56,7 +56,7 @@ contract Mixer is MerkleTreeWithHistory, ReentrancyGuard {
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Deposit funds into mixer. The caller must send (for ETH) or approve (for ERC20) value equal to or `denomination` of this mixer.
|
||||
@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 {
|
||||
@ -73,9 +73,9 @@ contract Mixer is MerkleTreeWithHistory, ReentrancyGuard {
|
||||
function _processDeposit() internal;
|
||||
|
||||
/**
|
||||
@dev Withdraw a deposit from the mixer. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs
|
||||
@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 mixer
|
||||
- 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)
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Snark mixer test</title>
|
||||
<title>Tornado test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* global artifacts */
|
||||
require('dotenv').config({ path: '../.env' })
|
||||
const ETHMixer = artifacts.require('ETHMixer')
|
||||
const ETHTornado = artifacts.require('ETHTornado')
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
const hasherContract = artifacts.require('Hasher')
|
||||
|
||||
@ -10,8 +10,8 @@ module.exports = function(deployer, network, accounts) {
|
||||
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT } = process.env
|
||||
const verifier = await Verifier.deployed()
|
||||
const hasherInstance = await hasherContract.deployed()
|
||||
await ETHMixer.link(hasherContract, hasherInstance.address)
|
||||
const mixer = await deployer.deploy(ETHMixer, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, accounts[0])
|
||||
console.log('ETHMixer\'s address ', mixer.address)
|
||||
await ETHTornado.link(hasherContract, hasherInstance.address)
|
||||
const tornado = await deployer.deploy(ETHTornado, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, accounts[0])
|
||||
console.log('ETHTornado\'s address ', tornado.address)
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* global artifacts */
|
||||
require('dotenv').config({ path: '../.env' })
|
||||
const ERC20Mixer = artifacts.require('ERC20Mixer')
|
||||
const ERC20Tornado = artifacts.require('ERC20Tornado')
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
const hasherContract = artifacts.require('Hasher')
|
||||
const ERC20Mock = artifacts.require('ERC20Mock')
|
||||
@ -11,20 +11,20 @@ module.exports = function(deployer, network, accounts) {
|
||||
const { MERKLE_TREE_HEIGHT, ERC20_TOKEN, TOKEN_AMOUNT } = process.env
|
||||
const verifier = await Verifier.deployed()
|
||||
const hasherInstance = await hasherContract.deployed()
|
||||
await ERC20Mixer.link(hasherContract, hasherInstance.address)
|
||||
await ERC20Tornado.link(hasherContract, hasherInstance.address)
|
||||
let token = ERC20_TOKEN
|
||||
if(token === '') {
|
||||
const tokenInstance = await deployer.deploy(ERC20Mock)
|
||||
token = tokenInstance.address
|
||||
}
|
||||
const mixer = await deployer.deploy(
|
||||
ERC20Mixer,
|
||||
const tornado = await deployer.deploy(
|
||||
ERC20Tornado,
|
||||
verifier.address,
|
||||
TOKEN_AMOUNT,
|
||||
MERKLE_TREE_HEIGHT,
|
||||
accounts[0],
|
||||
token,
|
||||
)
|
||||
console.log('ERC20Mixer\'s address ', mixer.address)
|
||||
console.log('ERC20Tornado\'s address ', tornado.address)
|
||||
})
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
"migrate:rinkeby": "npx truffle migrate --network rinkeby --reset",
|
||||
"migrate:mainnet": "npx truffle migrate --network mainnet",
|
||||
"eslint": "npx eslint --ignore-path .gitignore .",
|
||||
"flat": "npx truffle-flattener contracts/ETHMixer.sol > ETHMixer_flat.sol && npx truffle-flattener contracts/ERC20Mixer.sol > ERC20Mixer_flat.sol"
|
||||
"flat": "npx truffle-flattener contracts/ETHTornado.sol > ETHTornado_flat.sol && npx truffle-flattener contracts/ERC20Tornado.sol > ERC20Tornado_flat.sol"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
@ -8,7 +8,7 @@ const fs = require('fs')
|
||||
const { toBN } = require('web3-utils')
|
||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
||||
|
||||
const Mixer = artifacts.require('./ERC20Mixer.sol')
|
||||
const Tornado = artifacts.require('./ERC20Tornado.sol')
|
||||
const BadRecipient = artifacts.require('./BadRecipient.sol')
|
||||
const Token = artifacts.require('./ERC20Mock.sol')
|
||||
const USDTToken = artifacts.require('./IUSDT.sol')
|
||||
@ -38,8 +38,8 @@ function generateDeposit() {
|
||||
return deposit
|
||||
}
|
||||
|
||||
contract('ERC20Mixer', accounts => {
|
||||
let mixer
|
||||
contract('ERC20Tornado', accounts => {
|
||||
let tornado
|
||||
let token
|
||||
let usdtToken
|
||||
let badRecipient
|
||||
@ -64,7 +64,7 @@ contract('ERC20Mixer', accounts => {
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
mixer = await Mixer.deployed()
|
||||
tornado = await Tornado.deployed()
|
||||
if (ERC20_TOKEN) {
|
||||
token = await Token.at(ERC20_TOKEN)
|
||||
usdtToken = await USDTToken.at(ERC20_TOKEN)
|
||||
@ -81,7 +81,7 @@ contract('ERC20Mixer', accounts => {
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should initialize', async () => {
|
||||
const tokenFromContract = await mixer.token()
|
||||
const tokenFromContract = await tornado.token()
|
||||
tokenFromContract.should.be.equal(token.address)
|
||||
})
|
||||
})
|
||||
@ -89,9 +89,9 @@ contract('ERC20Mixer', accounts => {
|
||||
describe('#deposit', () => {
|
||||
it('should work', async () => {
|
||||
const commitment = toFixedHex(43)
|
||||
await token.approve(mixer.address, tokenDenomination)
|
||||
await token.approve(tornado.address, tokenDenomination)
|
||||
|
||||
let { logs } = await mixer.deposit(commitment, { from: sender })
|
||||
let { logs } = await tornado.deposit(commitment, { from: sender })
|
||||
|
||||
logs[0].event.should.be.equal('Deposit')
|
||||
logs[0].args.commitment.should.be.equal(commitment)
|
||||
@ -100,10 +100,10 @@ contract('ERC20Mixer', accounts => {
|
||||
|
||||
it('should not allow to send ether on deposit', async () => {
|
||||
const commitment = toFixedHex(43)
|
||||
await token.approve(mixer.address, tokenDenomination)
|
||||
await token.approve(tornado.address, tokenDenomination)
|
||||
|
||||
let error = await mixer.deposit(commitment, { from: sender, value: 1e6 }).should.be.rejected
|
||||
error.reason.should.be.equal('ETH value is supposed to be 0 for ERC20 mixer')
|
||||
let error = await tornado.deposit(commitment, { from: sender, value: 1e6 }).should.be.rejected
|
||||
error.reason.should.be.equal('ETH value is supposed to be 0 for ERC20 instance')
|
||||
})
|
||||
})
|
||||
|
||||
@ -115,11 +115,11 @@ contract('ERC20Mixer', accounts => {
|
||||
await token.mint(user, tokenDenomination)
|
||||
|
||||
const balanceUserBefore = await token.balanceOf(user)
|
||||
await token.approve(mixer.address, tokenDenomination, { from: user })
|
||||
await token.approve(tornado.address, tokenDenomination, { from: user })
|
||||
// Uncomment to measure gas usage
|
||||
// let gas = await mixer.deposit.estimateGas(toBN(deposit.commitment.toString()), { from: user, gasPrice: '0' })
|
||||
// let gas = await tornado.deposit.estimateGas(toBN(deposit.commitment.toString()), { from: user, gasPrice: '0' })
|
||||
// console.log('deposit gas:', gas)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
|
||||
const balanceUserAfter = await token.balanceOf(user)
|
||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||
@ -146,17 +146,17 @@ contract('ERC20Mixer', accounts => {
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceMixerBefore = await token.balanceOf(mixer.address)
|
||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||
const balanceRecieverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||
let isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
|
||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(false)
|
||||
// Uncomment to measure gas usage
|
||||
// gas = await mixer.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// gas = await tornado.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// console.log('withdraw gas:', gas)
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
@ -166,16 +166,16 @@ contract('ERC20Mixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const { logs } = await mixer.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceMixerAfter = await token.balanceOf(mixer.address)
|
||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN)))
|
||||
|
||||
@ -187,7 +187,7 @@ contract('ERC20Mixer', accounts => {
|
||||
logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
|
||||
logs[0].args.relayer.should.be.eq.BN(relayer)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(true)
|
||||
})
|
||||
|
||||
@ -199,8 +199,8 @@ contract('ERC20Mixer', accounts => {
|
||||
await token.mint(user, tokenDenomination)
|
||||
|
||||
const balanceUserBefore = await token.balanceOf(user)
|
||||
await token.approve(mixer.address, tokenDenomination, { from: user })
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
await token.approve(tornado.address, tokenDenomination, { from: user })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
|
||||
const balanceUserAfter = await token.balanceOf(user)
|
||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||
@ -227,14 +227,14 @@ contract('ERC20Mixer', accounts => {
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceMixerBefore = await token.balanceOf(mixer.address)
|
||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||
const balanceRecieverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||
let isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
|
||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
const args = [
|
||||
@ -245,16 +245,16 @@ contract('ERC20Mixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const { logs } = await mixer.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceMixerAfter = await token.balanceOf(mixer.address)
|
||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN)))
|
||||
|
||||
@ -266,7 +266,7 @@ contract('ERC20Mixer', accounts => {
|
||||
logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
|
||||
logs[0].args.relayer.should.be.eq.BN(relayer)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(true)
|
||||
})
|
||||
|
||||
@ -275,8 +275,8 @@ contract('ERC20Mixer', accounts => {
|
||||
const user = accounts[4]
|
||||
await tree.insert(deposit.commitment)
|
||||
await token.mint(user, tokenDenomination)
|
||||
await token.approve(mixer.address, tokenDenomination, { from: user })
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
await token.approve(tornado.address, tokenDenomination, { from: user })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
@ -309,11 +309,11 @@ contract('ERC20Mixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
let { reason } = await mixer.withdraw(proof, ...args, { value: 1, from: relayer, gasPrice: '0' }).should.be.rejected
|
||||
let { reason } = await tornado.withdraw(proof, ...args, { value: 1, from: relayer, gasPrice: '0' }).should.be.rejected
|
||||
reason.should.be.equal('Incorrect refund amount received by the contract')
|
||||
|
||||
|
||||
;({ reason } = await mixer.withdraw(proof, ...args, { value: toBN(refund).mul(toBN(2)), from: relayer, gasPrice: '0' }).should.be.rejected)
|
||||
;({ reason } = await tornado.withdraw(proof, ...args, { value: toBN(refund).mul(toBN(2)), from: relayer, gasPrice: '0' }).should.be.rejected)
|
||||
reason.should.be.equal('Incorrect refund amount received by the contract')
|
||||
})
|
||||
|
||||
@ -335,11 +335,11 @@ contract('ERC20Mixer', accounts => {
|
||||
|
||||
const balanceUserBefore = await usdtToken.balanceOf(user)
|
||||
console.log('balanceUserBefore', balanceUserBefore.toString())
|
||||
await usdtToken.approve(mixer.address, tokenDenomination, { from: user })
|
||||
await usdtToken.approve(tornado.address, tokenDenomination, { from: user })
|
||||
console.log('approve done')
|
||||
const allowanceUser = await usdtToken.allowance(user, mixer.address)
|
||||
const allowanceUser = await usdtToken.allowance(user, tornado.address)
|
||||
console.log('allowanceUser', allowanceUser.toString())
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
console.log('deposit done')
|
||||
|
||||
const balanceUserAfter = await usdtToken.balanceOf(user)
|
||||
@ -368,16 +368,16 @@ contract('ERC20Mixer', accounts => {
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceMixerBefore = await usdtToken.balanceOf(mixer.address)
|
||||
const balanceTornadoBefore = await usdtToken.balanceOf(tornado.address)
|
||||
const balanceRelayerBefore = await usdtToken.balanceOf(relayer)
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
// Uncomment to measure gas usage
|
||||
// gas = await mixer.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// gas = await tornado.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// console.log('withdraw gas:', gas)
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
@ -387,15 +387,15 @@ contract('ERC20Mixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const { logs } = await mixer.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceMixerAfter = await usdtToken.balanceOf(mixer.address)
|
||||
const balanceTornadoAfter = await usdtToken.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await usdtToken.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
|
||||
@ -406,7 +406,7 @@ contract('ERC20Mixer', accounts => {
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
logs[0].args.relayer.should.be.eq.BN(operator)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(true)
|
||||
})
|
||||
it.skip('should work with REAL DAI', async () => {
|
||||
@ -426,9 +426,9 @@ contract('ERC20Mixer', accounts => {
|
||||
|
||||
const balanceUserBefore = await token.balanceOf(user)
|
||||
console.log('balanceUserBefore', balanceUserBefore.toString())
|
||||
await token.approve(mixer.address, tokenDenomination, { from: user })
|
||||
await token.approve(tornado.address, tokenDenomination, { from: user })
|
||||
console.log('approve done')
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
console.log('deposit done')
|
||||
|
||||
const balanceUserAfter = await token.balanceOf(user)
|
||||
@ -457,16 +457,16 @@ contract('ERC20Mixer', accounts => {
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceMixerBefore = await token.balanceOf(mixer.address)
|
||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
// Uncomment to measure gas usage
|
||||
// gas = await mixer.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// gas = await tornado.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// console.log('withdraw gas:', gas)
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
@ -476,16 +476,16 @@ contract('ERC20Mixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const { logs } = await mixer.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
console.log('withdraw done')
|
||||
|
||||
const balanceMixerAfter = await token.balanceOf(mixer.address)
|
||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
|
||||
@ -496,7 +496,7 @@ contract('ERC20Mixer', accounts => {
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
logs[0].args.relayer.should.be.eq.BN(operator)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(true)
|
||||
})
|
||||
})
|
@ -8,7 +8,7 @@ const fs = require('fs')
|
||||
const { toBN, randomHex } = require('web3-utils')
|
||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
||||
|
||||
const Mixer = artifacts.require('./ETHMixer.sol')
|
||||
const Tornado = artifacts.require('./ETHTornado.sol')
|
||||
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT } = process.env
|
||||
|
||||
const websnarkUtils = require('websnark/src/utils')
|
||||
@ -51,8 +51,8 @@ function snarkVerify(proof) {
|
||||
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
|
||||
}
|
||||
|
||||
contract('ETHMixer', accounts => {
|
||||
let mixer
|
||||
contract('ETHTornado', accounts => {
|
||||
let tornado
|
||||
const sender = accounts[0]
|
||||
const operator = accounts[0]
|
||||
const levels = MERKLE_TREE_HEIGHT || 16
|
||||
@ -74,7 +74,7 @@ contract('ETHMixer', accounts => {
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
mixer = await Mixer.deployed()
|
||||
tornado = await Tornado.deployed()
|
||||
snapshotId = await takeSnapshot()
|
||||
groth16 = await buildGroth16()
|
||||
circuit = require('../build/circuits/withdraw.json')
|
||||
@ -83,7 +83,7 @@ contract('ETHMixer', accounts => {
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should initialize', async () => {
|
||||
const etherDenomination = await mixer.denomination()
|
||||
const etherDenomination = await tornado.denomination()
|
||||
etherDenomination.should.be.eq.BN(toBN(value))
|
||||
})
|
||||
})
|
||||
@ -91,14 +91,14 @@ contract('ETHMixer', accounts => {
|
||||
describe('#deposit', () => {
|
||||
it('should emit event', async () => {
|
||||
let commitment = toFixedHex(42)
|
||||
let { logs } = await mixer.deposit(commitment, { value, from: sender })
|
||||
let { logs } = await tornado.deposit(commitment, { value, from: sender })
|
||||
|
||||
logs[0].event.should.be.equal('Deposit')
|
||||
logs[0].args.commitment.should.be.equal(commitment)
|
||||
logs[0].args.leafIndex.should.be.eq.BN(0)
|
||||
|
||||
commitment = toFixedHex(12);
|
||||
({ logs } = await mixer.deposit(commitment, { value, from: accounts[2] }))
|
||||
({ logs } = await tornado.deposit(commitment, { value, from: accounts[2] }))
|
||||
|
||||
logs[0].event.should.be.equal('Deposit')
|
||||
logs[0].args.commitment.should.be.equal(commitment)
|
||||
@ -107,8 +107,8 @@ contract('ETHMixer', accounts => {
|
||||
|
||||
it('should throw if there is a such commitment', async () => {
|
||||
const commitment = toFixedHex(42)
|
||||
await mixer.deposit(commitment, { value, from: sender }).should.be.fulfilled
|
||||
const error = await mixer.deposit(commitment, { value, from: sender }).should.be.rejected
|
||||
await tornado.deposit(commitment, { value, from: sender }).should.be.fulfilled
|
||||
const error = await tornado.deposit(commitment, { value, from: sender }).should.be.rejected
|
||||
error.reason.should.be.equal('The commitment has been submitted')
|
||||
})
|
||||
})
|
||||
@ -166,9 +166,9 @@ contract('ETHMixer', accounts => {
|
||||
const balanceUserBefore = await web3.eth.getBalance(user)
|
||||
|
||||
// Uncomment to measure gas usage
|
||||
// let gas = await mixer.deposit.estimateGas(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
|
||||
// let gas = await tornado.deposit.estimateGas(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
|
||||
// console.log('deposit gas:', gas)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { value, from: user, gasPrice: '0' })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: user, gasPrice: '0' })
|
||||
|
||||
const balanceUserAfter = await web3.eth.getBalance(user)
|
||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(value)))
|
||||
@ -196,15 +196,15 @@ contract('ETHMixer', accounts => {
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceMixerBefore = await web3.eth.getBalance(mixer.address)
|
||||
const balanceTornadoBefore = await web3.eth.getBalance(tornado.address)
|
||||
const balanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||
const balanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
let isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
|
||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
// Uncomment to measure gas usage
|
||||
// gas = await mixer.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// gas = await tornado.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// console.log('withdraw gas:', gas)
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
@ -214,14 +214,14 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const { logs } = await mixer.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceMixerAfter = await web3.eth.getBalance(mixer.address)
|
||||
const balanceTornadoAfter = await web3.eth.getBalance(tornado.address)
|
||||
const balanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||
const balanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(value)))
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(value)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||
balanceOperatorAfter.should.be.eq.BN(toBN(balanceOperatorBefore).add(feeBN))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(value)).sub(feeBN))
|
||||
@ -231,14 +231,14 @@ contract('ETHMixer', accounts => {
|
||||
logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
|
||||
logs[0].args.relayer.should.be.eq.BN(operator)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(true)
|
||||
})
|
||||
|
||||
it('should prevent double spend', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
|
||||
@ -264,15 +264,15 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
await mixer.withdraw(proof, ...args, { from: relayer }).should.be.fulfilled
|
||||
const error = await mixer.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
await tornado.withdraw(proof, ...args, { from: relayer }).should.be.fulfilled
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('The note has been already spent')
|
||||
})
|
||||
|
||||
it('should prevent double spend with overflow', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
|
||||
@ -298,14 +298,14 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const error = await mixer.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('verifier-gte-snark-scalar-field')
|
||||
})
|
||||
|
||||
it('fee should be less or equal transfer value', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
const largeFee = bigInt(value).add(bigInt(1))
|
||||
@ -332,14 +332,14 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const error = await mixer.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Fee exceeds transfer value')
|
||||
})
|
||||
|
||||
it('should throw for corrupted merkle tree root', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
|
||||
@ -367,14 +367,14 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const error = await mixer.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Cannot find your merkle root')
|
||||
})
|
||||
|
||||
it('should reject with tampered public inputs', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
|
||||
let { root, path_elements, path_index } = await tree.path(0)
|
||||
|
||||
@ -412,7 +412,7 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
let error = await mixer.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
let error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Invalid withdraw proof')
|
||||
|
||||
// fee
|
||||
@ -424,7 +424,7 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex('0x000000000000000000000000000000000000000000000000015345785d8a0000'),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
error = await mixer.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Invalid withdraw proof')
|
||||
|
||||
// nullifier
|
||||
@ -436,21 +436,21 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
error = await mixer.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Invalid withdraw proof')
|
||||
|
||||
// proof itself
|
||||
proof = '0xbeef' + proof.substr(6)
|
||||
await mixer.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
|
||||
// should work with original values
|
||||
await mixer.withdraw(originalProof, ...args, { from: relayer }).should.be.fulfilled
|
||||
await tornado.withdraw(originalProof, ...args, { from: relayer }).should.be.fulfilled
|
||||
})
|
||||
|
||||
it('should reject with non zero refund', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
await mixer.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
|
||||
@ -478,29 +478,29 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
const error = await mixer.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Refund value is supposed to be zero for ETH mixer')
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Refund value is supposed to be zero for ETH instance')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#changeOperator', () => {
|
||||
it('should work', async () => {
|
||||
let operator = await mixer.operator()
|
||||
let operator = await tornado.operator()
|
||||
operator.should.be.equal(sender)
|
||||
|
||||
const newOperator = accounts[7]
|
||||
await mixer.changeOperator(newOperator).should.be.fulfilled
|
||||
await tornado.changeOperator(newOperator).should.be.fulfilled
|
||||
|
||||
operator = await mixer.operator()
|
||||
operator = await tornado.operator()
|
||||
operator.should.be.equal(newOperator)
|
||||
})
|
||||
|
||||
it('cannot change from different address', async () => {
|
||||
let operator = await mixer.operator()
|
||||
let operator = await tornado.operator()
|
||||
operator.should.be.equal(sender)
|
||||
|
||||
const newOperator = accounts[7]
|
||||
const error = await mixer.changeOperator(newOperator, { from: accounts[7] }).should.be.rejected
|
||||
const error = await tornado.changeOperator(newOperator, { from: accounts[7] }).should.be.rejected
|
||||
error.reason.should.be.equal('Only operator can call this function.')
|
||||
|
||||
})
|
||||
@ -508,22 +508,22 @@ contract('ETHMixer', accounts => {
|
||||
|
||||
describe('#updateVerifier', () => {
|
||||
it('should work', async () => {
|
||||
let operator = await mixer.operator()
|
||||
let operator = await tornado.operator()
|
||||
operator.should.be.equal(sender)
|
||||
|
||||
const newVerifier = accounts[7]
|
||||
await mixer.updateVerifier(newVerifier).should.be.fulfilled
|
||||
await tornado.updateVerifier(newVerifier).should.be.fulfilled
|
||||
|
||||
const verifier = await mixer.verifier()
|
||||
const verifier = await tornado.verifier()
|
||||
verifier.should.be.equal(newVerifier)
|
||||
})
|
||||
|
||||
it('cannot change from different address', async () => {
|
||||
let operator = await mixer.operator()
|
||||
let operator = await tornado.operator()
|
||||
operator.should.be.equal(sender)
|
||||
|
||||
const newVerifier = accounts[7]
|
||||
const error = await mixer.updateVerifier(newVerifier, { from: accounts[7] }).should.be.rejected
|
||||
const error = await tornado.updateVerifier(newVerifier, { from: accounts[7] }).should.be.rejected
|
||||
error.reason.should.be.equal('Only operator can call this function.')
|
||||
|
||||
})
|
||||
@ -535,8 +535,8 @@ contract('ETHMixer', accounts => {
|
||||
const deposit2 = generateDeposit()
|
||||
await tree.insert(deposit1.commitment)
|
||||
await tree.insert(deposit2.commitment)
|
||||
await mixer.deposit(toFixedHex(deposit1.commitment), { value, gasPrice: '0' })
|
||||
await mixer.deposit(toFixedHex(deposit2.commitment), { value, gasPrice: '0' })
|
||||
await tornado.deposit(toFixedHex(deposit1.commitment), { value, gasPrice: '0' })
|
||||
await tornado.deposit(toFixedHex(deposit2.commitment), { value, gasPrice: '0' })
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(1)
|
||||
|
||||
@ -570,11 +570,11 @@ contract('ETHMixer', accounts => {
|
||||
toFixedHex(input.refund)
|
||||
]
|
||||
|
||||
await mixer.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||
await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||
|
||||
const nullifierHash1 = toFixedHex(pedersenHash(deposit1.nullifier.leInt2Buff(31)))
|
||||
const nullifierHash2 = toFixedHex(pedersenHash(deposit2.nullifier.leInt2Buff(31)))
|
||||
const spentArray = await mixer.isSpentArray([nullifierHash1, nullifierHash2])
|
||||
const spentArray = await tornado.isSpentArray([nullifierHash1, nullifierHash2])
|
||||
spentArray.should.be.deep.equal([false, true])
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user