mirror of
https://github.com/tornadocash/tornado-core.git
synced 2024-12-24 22:09:24 -05:00
commit
5ef6e33c78
13
.env.example
13
.env.example
@ -1,5 +1,16 @@
|
||||
MERKLE_TREE_HEIGHT=16
|
||||
# in wei
|
||||
AMOUNT=1000000000000000000
|
||||
ETH_AMOUNT=100000000000000000
|
||||
TOKEN_AMOUNT=100000000000000000
|
||||
EMPTY_ELEMENT=1
|
||||
PRIVATE_KEY=
|
||||
ERC20_TOKEN=
|
||||
|
||||
# DAI mirror in Kovan
|
||||
#ERC20_TOKEN=0xd2b1a6b34f4a68425e7c28b4db5a37be3b7a4947
|
||||
# the block when 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 has some DAI is 13146218
|
||||
|
||||
# USDT mirror in Kovan
|
||||
#ERC20_TOKEN=0xf3e0d7bf58c5d455d31ef1c2d5375904df525105
|
||||
#TOKEN_AMOUNT=1000000
|
||||
# the block when 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 has some USDT is 13147586
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -94,3 +94,5 @@ typings/
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
ERC20Mixer_flat.sol
|
||||
ETHMixer_flat.sol
|
||||
|
33
README.md
33
README.md
@ -45,13 +45,6 @@ You can see example usage in cli.js, it works both in console and in browser.
|
||||
1. `npx ganache-cli`
|
||||
1. `npm run test` - optionally run tests. It may fail for the first time, just run one more time.
|
||||
|
||||
Use with command line version with Ganache:
|
||||
|
||||
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>`
|
||||
|
||||
Use browser version on Kovan:
|
||||
|
||||
1. `vi .env` - add your Kovan private key to deploy contracts
|
||||
@ -59,8 +52,34 @@ Use browser version on Kovan:
|
||||
1. `npx http-server` - serve current dir, you can use any other static http server
|
||||
1. Open `localhost:8080`
|
||||
|
||||
Use with command line version with Ganache:
|
||||
### ETHMixer
|
||||
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
|
||||
1. `npm run migrate:dev`
|
||||
1. `./cli.js depositErc20`
|
||||
1. `./cli.js withdrawErc20 <note from previous step> <destination eth address> <relayer eth address>`
|
||||
1. `./cli.js balanceErc20 <destination eth address> <relayer eth address>`
|
||||
|
||||
If you want, you can point the app to existing tornado contracts on Mainnet or Kovan, it should work without any changes
|
||||
|
||||
## Deploy ETH Tornado Cash
|
||||
1. `cp .env.example .env`
|
||||
1. Tune all necessary params
|
||||
1. `npx truffle migrate --network kovan --reset --f 2 --to 4`
|
||||
|
||||
## Deploy ERC20 Tornado Cash
|
||||
1. `cp .env.example .env`
|
||||
1. Tune all necessary params
|
||||
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`).
|
||||
|
||||
## Credits
|
||||
|
||||
Special thanks to @barryWhiteHat and @kobigurk for valuable input,
|
||||
|
@ -31,6 +31,7 @@ template Withdraw(levels, rounds) {
|
||||
signal input root;
|
||||
signal input nullifierHash;
|
||||
signal input receiver; // not taking part in any computations
|
||||
signal input relayer; // not taking part in any computations
|
||||
signal input fee; // not taking part in any computations
|
||||
signal private input nullifier;
|
||||
signal private input secret;
|
||||
@ -56,8 +57,10 @@ template Withdraw(levels, rounds) {
|
||||
// Squares are used to prevent optimizer from removing those constraints
|
||||
signal receiverSquare;
|
||||
signal feeSquare;
|
||||
signal relayerSquare;
|
||||
receiverSquare <== receiver * receiver;
|
||||
feeSquare <== fee * fee;
|
||||
relayerSquare <== relayer * relayer;
|
||||
}
|
||||
|
||||
component main = Withdraw(16, 220);
|
||||
|
159
cli.js
159
cli.js
@ -12,8 +12,9 @@ const Web3 = require('web3')
|
||||
const buildGroth16 = require('websnark/src/groth16')
|
||||
const websnarkUtils = require('websnark/src/utils')
|
||||
|
||||
let web3, mixer, circuit, proving_key, groth16
|
||||
let MERKLE_TREE_HEIGHT, AMOUNT, EMPTY_ELEMENT
|
||||
let web3, mixer, erc20mixer, circuit, proving_key, groth16, erc20
|
||||
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT, ERC20_TOKEN
|
||||
const inBrowser = (typeof window !== 'undefined')
|
||||
|
||||
/** Generate random number of specified byte length */
|
||||
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||
@ -39,19 +40,103 @@ async function deposit() {
|
||||
const deposit = createDeposit(rbigint(31), rbigint(31))
|
||||
|
||||
console.log('Submitting deposit transaction')
|
||||
await mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: AMOUNT, from: (await web3.eth.getAccounts())[0], gas:1e6 })
|
||||
await mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: ETH_AMOUNT, from: (await web3.eth.getAccounts())[0], gas:1e6 })
|
||||
|
||||
const note = '0x' + deposit.preimage.toString('hex')
|
||||
console.log('Your note:', note)
|
||||
return note
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a withdrawal
|
||||
* @param note A preimage containing secret and nullifier
|
||||
* @param receiver Address for receiving funds
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function depositErc20() {
|
||||
const account = (await web3.eth.getAccounts())[0]
|
||||
const tokenAmount = process.env.TOKEN_AMOUNT
|
||||
await erc20.methods.mint(account, tokenAmount).send({ from: account, gas:1e6 })
|
||||
|
||||
await erc20.methods.approve(erc20mixer.address, tokenAmount).send({ from: account, gas:1e6 })
|
||||
const allowance = await erc20.methods.allowance(account, erc20mixer.address).call()
|
||||
console.log('erc20mixer allowance', allowance.toString(10))
|
||||
|
||||
const deposit = createDeposit(rbigint(31), rbigint(31))
|
||||
await erc20mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: ETH_AMOUNT, from: account, gas:1e6 })
|
||||
|
||||
const balance = await erc20.methods.balanceOf(erc20mixer.address).call()
|
||||
console.log('erc20mixer balance', balance.toString(10))
|
||||
const note = '0x' + deposit.preimage.toString('hex')
|
||||
console.log('Your note:', note)
|
||||
return note
|
||||
}
|
||||
|
||||
async function withdrawErc20(note, receiver, relayer) {
|
||||
let buf = Buffer.from(note.slice(2), 'hex')
|
||||
let deposit = createDeposit(bigInt.leBuff2int(buf.slice(0, 31)), bigInt.leBuff2int(buf.slice(31, 62)))
|
||||
|
||||
console.log('Getting current state from mixer contract')
|
||||
const events = await erc20mixer.getPastEvents('Deposit', { fromBlock: erc20mixer.deployedBlock, toBlock: 'latest' })
|
||||
let leafIndex
|
||||
|
||||
const commitment = deposit.commitment.toString(16).padStart('66', '0x000000')
|
||||
const leaves = events
|
||||
.sort((a, b) => a.returnValues.leafIndex.sub(b.returnValues.leafIndex))
|
||||
.map(e => {
|
||||
if (e.returnValues.commitment.eq(commitment)) {
|
||||
leafIndex = e.returnValues.leafIndex.toNumber()
|
||||
}
|
||||
return e.returnValues.commitment
|
||||
})
|
||||
const tree = new merkleTree(MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, leaves)
|
||||
const validRoot = await erc20mixer.methods.isKnownRoot(await tree.root()).call()
|
||||
const nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
||||
const nullifierHashToCheck = nullifierHash.toString(16).padStart('66', '0x000000')
|
||||
const isSpent = await erc20mixer.methods.isSpent(nullifierHashToCheck).call()
|
||||
assert(validRoot === true)
|
||||
assert(isSpent === false)
|
||||
|
||||
assert(leafIndex >= 0)
|
||||
const { root, path_elements, path_index } = await tree.path(leafIndex)
|
||||
// Circuit input
|
||||
const input = {
|
||||
// public
|
||||
root: root,
|
||||
nullifierHash,
|
||||
receiver: bigInt(receiver),
|
||||
relayer: bigInt(relayer),
|
||||
fee: bigInt(web3.utils.toWei('0.01')),
|
||||
|
||||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndex: path_index,
|
||||
}
|
||||
|
||||
console.log('Generating SNARK proof')
|
||||
console.time('Proof time')
|
||||
const proof = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { pi_a, pi_b, pi_c, publicSignals } = websnarkUtils.toSolidityInput(proof)
|
||||
console.timeEnd('Proof time')
|
||||
|
||||
console.log('Submitting withdraw transaction')
|
||||
await erc20mixer.methods.withdraw(pi_a, pi_b, pi_c, publicSignals).send({ from: (await web3.eth.getAccounts())[0], gas: 1e6 })
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
async function getBalance(receiver) {
|
||||
const balance = await web3.eth.getBalance(receiver)
|
||||
console.log('Balance is ', web3.utils.fromWei(balance))
|
||||
}
|
||||
|
||||
async function getBalanceErc20(receiver, relayer) {
|
||||
const balanceReceiver = await web3.eth.getBalance(receiver)
|
||||
const balanceRelayer = await web3.eth.getBalance(relayer)
|
||||
const tokenBalanceReceiver = await erc20.methods.balanceOf(receiver).call()
|
||||
const tokenBalanceRelayer = await erc20.methods.balanceOf(relayer).call()
|
||||
console.log('Receiver eth Balance is ', web3.utils.fromWei(balanceReceiver))
|
||||
console.log('Relayer eth Balance is ', web3.utils.fromWei(balanceRelayer))
|
||||
|
||||
console.log('Receiver token Balance is ', web3.utils.fromWei(tokenBalanceReceiver.toString()))
|
||||
console.log('Relayer token Balance is ', web3.utils.fromWei(tokenBalanceRelayer.toString()))
|
||||
}
|
||||
|
||||
async function withdraw(note, receiver) {
|
||||
// Decode hex string and restore the deposit object
|
||||
let buf = Buffer.from(note.slice(2), 'hex')
|
||||
@ -88,6 +173,7 @@ async function withdraw(note, receiver) {
|
||||
root: root,
|
||||
nullifierHash,
|
||||
receiver: bigInt(receiver),
|
||||
relayer: bigInt(0),
|
||||
fee: bigInt(0),
|
||||
|
||||
// Private snark inputs
|
||||
@ -108,47 +194,52 @@ async function withdraw(note, receiver) {
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default wallet balance
|
||||
*/
|
||||
async function getBalance(receiver) {
|
||||
const balance = await web3.eth.getBalance(receiver)
|
||||
console.log('Balance is ', web3.utils.fromWei(balance))
|
||||
}
|
||||
|
||||
const inBrowser = (typeof window !== 'undefined')
|
||||
|
||||
/**
|
||||
* Init web3, contracts, and snark
|
||||
*/
|
||||
async function init() {
|
||||
let contractJson
|
||||
let contractJson, erc20ContractJson, erc20mixerJson
|
||||
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/Mixer.json')).json()
|
||||
contractJson = await (await fetch('build/contracts/ETHMixer.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
|
||||
AMOUNT = 1e18
|
||||
ETH_AMOUNT = 1e18
|
||||
EMPTY_ELEMENT = 1
|
||||
} else {
|
||||
// Initialize from local node
|
||||
web3 = new Web3('http://localhost:8545', null, { transactionConfirmationBlocks: 1 })
|
||||
contractJson = require('./build/contracts/Mixer.json')
|
||||
contractJson = require('./build/contracts/ETHMixer.json')
|
||||
circuit = require('./build/circuits/withdraw.json')
|
||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
||||
require('dotenv').config()
|
||||
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT
|
||||
AMOUNT = process.env.AMOUNT
|
||||
ETH_AMOUNT = process.env.ETH_AMOUNT
|
||||
EMPTY_ELEMENT = process.env.EMPTY_ELEMENT
|
||||
ERC20_TOKEN = process.env.ERC20_TOKEN
|
||||
erc20ContractJson = require('./build/contracts/ERC20Mock.json')
|
||||
erc20mixerJson = require('./build/contracts/ERC20Mixer.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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if(ERC20_TOKEN === '') {
|
||||
erc20 = new web3.eth.Contract(erc20ContractJson.abi, erc20ContractJson.networks[netId].address)
|
||||
const tx2 = await web3.eth.getTransaction(erc20ContractJson.networks[netId].transactionHash)
|
||||
erc20.deployedBlock = tx2.blockNumber
|
||||
}
|
||||
console.log('Loaded')
|
||||
}
|
||||
|
||||
@ -196,12 +287,25 @@ if (inBrowser) {
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'depositErc20':
|
||||
if (args.length === 1) {
|
||||
init().then(() => depositErc20()).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
}
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'balance':
|
||||
if (args.length === 2 && /^0x[0-9a-fA-F]{40}$/.test(args[1])) {
|
||||
init().then(() => getBalance(args[1])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
} else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'balanceErc20':
|
||||
if (args.length === 3 && /^0x[0-9a-fA-F]{40}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
||||
init().then(() => getBalanceErc20(args[1], args[2])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
} else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'withdraw':
|
||||
if (args.length === 3 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
||||
init().then(() => withdraw(args[1], args[2])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
@ -209,6 +313,13 @@ if (inBrowser) {
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'withdrawErc20':
|
||||
if (args.length === 4 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2]) && /^0x[0-9a-fA-F]{40}$/.test(args[3])) {
|
||||
init().then(() => withdrawErc20(args[1], args[2], args[3])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
}
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'auto':
|
||||
if (args.length === 1) {
|
||||
(async () => {
|
||||
|
89
contracts/ERC20Mixer.sol
Normal file
89
contracts/ERC20Mixer.sol
Normal file
@ -0,0 +1,89 @@
|
||||
// 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
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.8;
|
||||
|
||||
import "./Mixer.sol";
|
||||
|
||||
contract ERC20Mixer is Mixer {
|
||||
address public token;
|
||||
// ether value to cover network fee (for relayer) and to have some ETH on a brand new address
|
||||
uint256 public userEther;
|
||||
|
||||
constructor(
|
||||
address _verifier,
|
||||
uint256 _userEther,
|
||||
uint8 _merkleTreeHeight,
|
||||
uint256 _emptyElement,
|
||||
address payable _operator,
|
||||
address _token,
|
||||
uint256 _mixDenomination
|
||||
) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public {
|
||||
token = _token;
|
||||
userEther = _userEther;
|
||||
}
|
||||
|
||||
function _processDeposit() internal {
|
||||
require(msg.value == userEther, "Please send `userEther` ETH along with transaction");
|
||||
safeErc20TransferFrom(msg.sender, address(this), mixDenomination);
|
||||
}
|
||||
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {
|
||||
_receiver.transfer(userEther);
|
||||
|
||||
safeErc20Transfer(_receiver, mixDenomination - _fee);
|
||||
if (_fee > 0) {
|
||||
safeErc20Transfer(_relayer, _fee);
|
||||
}
|
||||
}
|
||||
|
||||
function safeErc20TransferFrom(address from, address to, uint256 amount) internal {
|
||||
bool success;
|
||||
bytes memory data;
|
||||
bytes4 transferFromSelector = 0x23b872dd;
|
||||
(success, data) = token.call(
|
||||
abi.encodeWithSelector(
|
||||
transferFromSelector,
|
||||
from, to, amount
|
||||
)
|
||||
);
|
||||
require(success, "not enough allowed tokens");
|
||||
|
||||
// if contract returns some data let's make sure that is `true` according to standard
|
||||
if (data.length > 0) {
|
||||
assembly {
|
||||
success := mload(add(data, 0x20))
|
||||
}
|
||||
require(success, "not enough allowed tokens");
|
||||
}
|
||||
}
|
||||
|
||||
function safeErc20Transfer(address to, uint256 amount) internal {
|
||||
bool success;
|
||||
bytes memory data;
|
||||
bytes4 transferSelector = 0xa9059cbb;
|
||||
(success, data) = token.call(
|
||||
abi.encodeWithSelector(
|
||||
transferSelector,
|
||||
to, amount
|
||||
)
|
||||
);
|
||||
require(success, "not enough tokens");
|
||||
|
||||
// if contract returns some data let's make sure that is `true` according to standard
|
||||
if (data.length > 0) {
|
||||
assembly {
|
||||
success := mload(add(data, 0x20))
|
||||
}
|
||||
require(success, "not enough tokens");
|
||||
}
|
||||
}
|
||||
}
|
36
contracts/ETHMixer.sol
Normal file
36
contracts/ETHMixer.sol
Normal file
@ -0,0 +1,36 @@
|
||||
// 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
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.8;
|
||||
|
||||
import "./Mixer.sol";
|
||||
|
||||
contract ETHMixer is Mixer {
|
||||
constructor(
|
||||
address _verifier,
|
||||
uint256 _mixDenomination,
|
||||
uint8 _merkleTreeHeight,
|
||||
uint256 _emptyElement,
|
||||
address payable _operator
|
||||
) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public {
|
||||
}
|
||||
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {
|
||||
_receiver.transfer(mixDenomination - _fee);
|
||||
if (_fee > 0) {
|
||||
_relayer.transfer(_fee);
|
||||
}
|
||||
}
|
||||
|
||||
function _processDeposit() internal {
|
||||
require(msg.value == mixDenomination, "Please send `mixDenomination` ETH along with transaction");
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ contract MerkleTreeWithHistory {
|
||||
|
||||
function _insert(uint256 leaf) internal {
|
||||
uint32 current_index = next_index;
|
||||
require(current_index != 2**(levels - 1), "Merkle tree is full");
|
||||
require(current_index != 2**levels, "Merkle tree is full. No more leafs can be added");
|
||||
next_index += 1;
|
||||
uint256 current_level_hash = leaf;
|
||||
uint256 left;
|
||||
|
@ -14,11 +14,10 @@ pragma solidity ^0.5.8;
|
||||
import "./MerkleTreeWithHistory.sol";
|
||||
|
||||
contract IVerifier {
|
||||
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public returns(bool);
|
||||
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[5] memory input) public returns(bool);
|
||||
}
|
||||
|
||||
contract Mixer is MerkleTreeWithHistory {
|
||||
uint256 public transferValue;
|
||||
bool public isDepositsEnabled = true;
|
||||
// operator can disable new deposits in case of emergency
|
||||
// it also receives a relayer fee
|
||||
@ -27,40 +26,47 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
// we store all commitments just to prevent accidental deposits with the same commitment
|
||||
mapping(uint256 => bool) public commitments;
|
||||
IVerifier public verifier;
|
||||
uint256 public mixDenomination;
|
||||
|
||||
event Deposit(uint256 indexed commitment, uint256 leafIndex, uint256 timestamp);
|
||||
event Withdraw(address to, uint256 nullifierHash, uint256 fee);
|
||||
event Withdraw(address to, uint256 nullifierHash, address indexed relayer, uint256 fee);
|
||||
|
||||
/**
|
||||
@dev The constructor
|
||||
@param _verifier the address of SNARK verifier for this contract
|
||||
@param _transferValue the value for all deposits in this contract in wei
|
||||
@param _merkleTreeHeight the height of deposits' Merkle Tree
|
||||
@param _emptyElement default element of the deposits' Merkle Tree
|
||||
@param _operator operator address (see operator above)
|
||||
*/
|
||||
constructor(
|
||||
address _verifier,
|
||||
uint256 _transferValue,
|
||||
uint256 _mixDenomination,
|
||||
uint8 _merkleTreeHeight,
|
||||
uint256 _emptyElement,
|
||||
address payable _operator
|
||||
) MerkleTreeWithHistory(_merkleTreeHeight, _emptyElement) public {
|
||||
verifier = IVerifier(_verifier);
|
||||
transferValue = _transferValue;
|
||||
operator = _operator;
|
||||
mixDenomination = _mixDenomination;
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Deposit funds into mixer. The caller must send value equal to `transferValue` of this mixer.
|
||||
@dev Deposit funds into mixer. The caller must send value equal to `mixDenomination` of this mixer.
|
||||
@param commitment the note commitment, which is PedersenHash(nullifier + secret)
|
||||
*/
|
||||
/**
|
||||
@dev Deposit funds into the mixer. The caller must send ETH value equal to `userEther` of this mixer.
|
||||
The caller also has to have at least `mixDenomination` amount approved for the mixer.
|
||||
@param commitment the note commitment, which is PedersenHash(nullifier + secret)
|
||||
*/
|
||||
function deposit(uint256 commitment) public payable {
|
||||
require(isDepositsEnabled, "deposits disabled");
|
||||
require(msg.value == transferValue, "Please send `transferValue` ETH along with transaction");
|
||||
require(!commitments[commitment], "The commitment has been submitted");
|
||||
_processDeposit();
|
||||
_insert(commitment);
|
||||
commitments[commitment] = true;
|
||||
|
||||
emit Deposit(commitment, next_index - 1, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs
|
||||
`input` array consists of:
|
||||
@ -69,23 +75,20 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
- the receiver of funds
|
||||
- optional fee that goes to the transaction sender (usually a relay)
|
||||
*/
|
||||
function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public {
|
||||
function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[5] memory input) public {
|
||||
uint256 root = input[0];
|
||||
uint256 nullifierHash = input[1];
|
||||
address payable receiver = address(input[2]);
|
||||
uint256 fee = input[3];
|
||||
|
||||
address payable relayer = address(input[3]);
|
||||
uint256 fee = input[4];
|
||||
require(fee < mixDenomination, "Fee exceeds transfer value");
|
||||
require(!nullifierHashes[nullifierHash], "The note has been already spent");
|
||||
require(fee < transferValue, "Fee exceeds transfer value");
|
||||
|
||||
require(isKnownRoot(root), "Cannot find your merkle root"); // Make sure to use a recent one
|
||||
require(verifier.verifyProof(a, b, c, input), "Invalid withdraw proof");
|
||||
|
||||
nullifierHashes[nullifierHash] = true;
|
||||
receiver.transfer(transferValue - fee);
|
||||
if (fee > 0) {
|
||||
operator.transfer(fee);
|
||||
}
|
||||
emit Withdraw(receiver, nullifierHash, fee);
|
||||
_processWithdraw(receiver, relayer, fee);
|
||||
emit Withdraw(receiver, nullifierHash, relayer, fee);
|
||||
}
|
||||
|
||||
function toggleDeposits() external {
|
||||
@ -101,4 +104,8 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
function isSpent(uint256 nullifier) public view returns(bool) {
|
||||
return nullifierHashes[nullifier];
|
||||
}
|
||||
|
||||
function _processDeposit() internal {}
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {}
|
||||
|
||||
}
|
||||
|
10
contracts/Mocks/ERC20Mock.sol
Normal file
10
contracts/Mocks/ERC20Mock.sol
Normal file
@ -0,0 +1,10 @@
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20Mintable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
|
||||
|
||||
contract ERC20Mock is ERC20Detailed, ERC20Mintable {
|
||||
constructor() ERC20Detailed("DAIMock", "DAIM", 18) public {
|
||||
}
|
||||
}
|
18
contracts/Mocks/IUSDT.sol
Normal file
18
contracts/Mocks/IUSDT.sol
Normal file
@ -0,0 +1,18 @@
|
||||
contract ERC20Basic {
|
||||
uint public _totalSupply;
|
||||
function totalSupply() public view returns (uint);
|
||||
function balanceOf(address who) public view returns (uint);
|
||||
function transfer(address to, uint value) public;
|
||||
event Transfer(address indexed from, address indexed to, uint value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @title ERC20 interface
|
||||
* @dev see https://github.com/ethereum/EIPs/issues/20
|
||||
*/
|
||||
contract IUSDT is ERC20Basic {
|
||||
function allowance(address owner, address spender) public view returns (uint);
|
||||
function transferFrom(address from, address to, uint value) public;
|
||||
function approve(address spender, uint value) public;
|
||||
event Approval(address indexed owner, address indexed spender, uint value);
|
||||
}
|
17
migrations/4_deploy_eth_mixer.js
Normal file
17
migrations/4_deploy_eth_mixer.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* global artifacts */
|
||||
require('dotenv').config({ path: '../.env' })
|
||||
const ETHMixer = artifacts.require('ETHMixer')
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
const MiMC = artifacts.require('MiMC')
|
||||
|
||||
|
||||
module.exports = function(deployer, network, accounts) {
|
||||
return deployer.then(async () => {
|
||||
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT } = process.env
|
||||
const verifier = await Verifier.deployed()
|
||||
const miMC = await MiMC.deployed()
|
||||
await ETHMixer.link(MiMC, miMC.address)
|
||||
const mixer = await deployer.deploy(ETHMixer, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, accounts[0])
|
||||
console.log('ETHMixer\'s address ', mixer.address)
|
||||
})
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/* global artifacts */
|
||||
require('dotenv').config({ path: '../.env' })
|
||||
const Mixer = artifacts.require('Mixer')
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
const MiMC = artifacts.require('MiMC')
|
||||
|
||||
|
||||
module.exports = function(deployer, network, accounts) {
|
||||
return deployer.then(async () => {
|
||||
const { MERKLE_TREE_HEIGHT, AMOUNT, EMPTY_ELEMENT } = process.env
|
||||
const verifier = await Verifier.deployed()
|
||||
const miMC = await MiMC.deployed()
|
||||
await Mixer.link(MiMC, miMC.address)
|
||||
const mixer = await deployer.deploy(Mixer, verifier.address, AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, accounts[0])
|
||||
console.log('Mixer\'s address ', mixer.address)
|
||||
})
|
||||
}
|
32
migrations/5_deploy_erc20_mixer.js
Normal file
32
migrations/5_deploy_erc20_mixer.js
Normal file
@ -0,0 +1,32 @@
|
||||
/* global artifacts */
|
||||
require('dotenv').config({ path: '../.env' })
|
||||
const ERC20Mixer = artifacts.require('ERC20Mixer')
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
const MiMC = artifacts.require('MiMC')
|
||||
const ERC20Mock = artifacts.require('ERC20Mock')
|
||||
|
||||
|
||||
module.exports = function(deployer, network, accounts) {
|
||||
return deployer.then(async () => {
|
||||
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT, ERC20_TOKEN, TOKEN_AMOUNT } = process.env
|
||||
const verifier = await Verifier.deployed()
|
||||
const miMC = await MiMC.deployed()
|
||||
await ERC20Mixer.link(MiMC, miMC.address)
|
||||
let token = ERC20_TOKEN
|
||||
if(token === '') {
|
||||
const tokenInstance = await deployer.deploy(ERC20Mock)
|
||||
token = tokenInstance.address
|
||||
}
|
||||
const mixer = await deployer.deploy(
|
||||
ERC20Mixer,
|
||||
verifier.address,
|
||||
ETH_AMOUNT,
|
||||
MERKLE_TREE_HEIGHT,
|
||||
EMPTY_ELEMENT,
|
||||
accounts[0],
|
||||
token,
|
||||
TOKEN_AMOUNT
|
||||
)
|
||||
console.log('ERC20Mixer\'s address ', mixer.address)
|
||||
})
|
||||
}
|
123
package-lock.json
generated
123
package-lock.json
generated
@ -30,6 +30,11 @@
|
||||
"regenerator-runtime": "^0.13.2"
|
||||
}
|
||||
},
|
||||
"@openzeppelin/contracts": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-2.3.0.tgz",
|
||||
"integrity": "sha512-lf8C3oULQAnsu3OTRP4tP5/ddfil6l65Lg3JQCwAIgc99vZ1jz5qeBoETGGGmczxt+bIyMI06WPP2apC74EZag=="
|
||||
},
|
||||
"@resolver-engine/core": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.2.1.tgz",
|
||||
@ -2357,9 +2362,9 @@
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"eslint": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.0.1.tgz",
|
||||
"integrity": "sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.2.tgz",
|
||||
"integrity": "sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"ajv": "^6.10.0",
|
||||
@ -2367,36 +2372,92 @@
|
||||
"cross-spawn": "^6.0.5",
|
||||
"debug": "^4.0.1",
|
||||
"doctrine": "^3.0.0",
|
||||
"eslint-scope": "^4.0.3",
|
||||
"eslint-utils": "^1.3.1",
|
||||
"eslint-visitor-keys": "^1.0.0",
|
||||
"espree": "^6.0.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^1.4.2",
|
||||
"eslint-visitor-keys": "^1.1.0",
|
||||
"espree": "^6.1.1",
|
||||
"esquery": "^1.0.1",
|
||||
"esutils": "^2.0.2",
|
||||
"file-entry-cache": "^5.0.1",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"glob-parent": "^3.1.0",
|
||||
"glob-parent": "^5.0.0",
|
||||
"globals": "^11.7.0",
|
||||
"ignore": "^4.0.6",
|
||||
"import-fresh": "^3.0.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"inquirer": "^6.2.2",
|
||||
"inquirer": "^6.4.1",
|
||||
"is-glob": "^4.0.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"levn": "^0.3.0",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.14",
|
||||
"minimatch": "^3.0.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.8.2",
|
||||
"progress": "^2.0.0",
|
||||
"regexpp": "^2.0.1",
|
||||
"semver": "^5.5.1",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"strip-json-comments": "^2.0.1",
|
||||
"semver": "^6.1.2",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"strip-json-comments": "^3.0.1",
|
||||
"table": "^5.2.3",
|
||||
"text-table": "^0.2.0"
|
||||
"text-table": "^0.2.0",
|
||||
"v8-compile-cache": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
|
||||
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
|
||||
"requires": {
|
||||
"esrecurse": "^4.1.0",
|
||||
"estraverse": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"eslint-utils": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
|
||||
"integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
|
||||
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A=="
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
|
||||
"integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"requires": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
|
||||
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-scope": {
|
||||
@ -2422,13 +2483,30 @@
|
||||
"integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ=="
|
||||
},
|
||||
"espree": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz",
|
||||
"integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==",
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
|
||||
"integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
|
||||
"requires": {
|
||||
"acorn": "^6.0.7",
|
||||
"acorn-jsx": "^5.0.0",
|
||||
"eslint-visitor-keys": "^1.0.0"
|
||||
"acorn": "^7.0.0",
|
||||
"acorn-jsx": "^5.0.2",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
|
||||
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ=="
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
|
||||
"integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw=="
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
|
||||
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
@ -9867,6 +9945,11 @@
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
|
||||
"integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
|
||||
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g=="
|
||||
},
|
||||
"v8flags": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz",
|
||||
|
@ -16,12 +16,13 @@
|
||||
"migrate:dev": "npx truffle migrate --network development --reset",
|
||||
"browserify": "npx browserify cli.js -o index.js --exclude worker_threads",
|
||||
"eslint": "npx eslint --ignore-path .gitignore .",
|
||||
"flat": "truffle-flattener contracts/Mixer.sol > Mixer_flat.sol"
|
||||
"flat": "truffle-flattener contracts/ETHMixer.sol > ETHMixer_flat.sol contracts/ERC20Mixer.sol > ERC20Mixer_flat.sol"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "^2.3.0",
|
||||
"bn-chai": "^1.0.1",
|
||||
"browserify": "^16.3.0",
|
||||
"chai": "^4.2.0",
|
||||
@ -29,7 +30,7 @@
|
||||
"circom": "0.0.30",
|
||||
"circomlib": "^0.0.10",
|
||||
"dotenv": "^8.0.0",
|
||||
"eslint": "^6.0.1",
|
||||
"eslint": "^6.2.2",
|
||||
"ganache-cli": "^6.4.5",
|
||||
"snarkjs": "^0.1.16",
|
||||
"truffle": "^5.0.27",
|
||||
|
354
test/ERC20Mixer.test.js
Normal file
354
test/ERC20Mixer.test.js
Normal file
@ -0,0 +1,354 @@
|
||||
/* global artifacts, web3, contract */
|
||||
require('chai')
|
||||
.use(require('bn-chai')(web3.utils.BN))
|
||||
.use(require('chai-as-promised'))
|
||||
.should()
|
||||
const fs = require('fs')
|
||||
|
||||
const { toBN, toHex } = require('web3-utils')
|
||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
||||
|
||||
const Mixer = artifacts.require('./ERC20Mixer.sol')
|
||||
const Token = artifacts.require('./ERC20Mock.sol')
|
||||
const USDTToken = artifacts.require('./IUSDT.sol')
|
||||
const { ETH_AMOUNT, TOKEN_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, ERC20_TOKEN } = process.env
|
||||
|
||||
const websnarkUtils = require('websnark/src/utils')
|
||||
const buildGroth16 = require('websnark/src/groth16')
|
||||
const stringifyBigInts = require('websnark/tools/stringifybigint').stringifyBigInts
|
||||
const snarkjs = require('snarkjs')
|
||||
const bigInt = snarkjs.bigInt
|
||||
const crypto = require('crypto')
|
||||
const circomlib = require('circomlib')
|
||||
const MerkleTree = require('../lib/MerkleTree')
|
||||
|
||||
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
||||
|
||||
function generateDeposit() {
|
||||
let deposit = {
|
||||
secret: rbigint(31),
|
||||
nullifier: rbigint(31),
|
||||
}
|
||||
const preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
|
||||
deposit.commitment = pedersenHash(preimage)
|
||||
return deposit
|
||||
}
|
||||
|
||||
function getRandomReceiver() {
|
||||
let receiver = rbigint(20)
|
||||
while (toHex(receiver.toString()).length !== 42) {
|
||||
receiver = rbigint(20)
|
||||
}
|
||||
return receiver
|
||||
}
|
||||
|
||||
contract('ERC20Mixer', accounts => {
|
||||
let mixer
|
||||
let token
|
||||
let usdtToken
|
||||
const sender = accounts[0]
|
||||
const operator = accounts[0]
|
||||
const levels = MERKLE_TREE_HEIGHT || 16
|
||||
const zeroValue = EMPTY_ELEMENT || 1337
|
||||
let tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether
|
||||
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
||||
let snapshotId
|
||||
let prefix = 'test'
|
||||
let tree
|
||||
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
||||
const receiver = getRandomReceiver()
|
||||
const relayer = accounts[1]
|
||||
let groth16
|
||||
let circuit
|
||||
let proving_key
|
||||
|
||||
before(async () => {
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
zeroValue,
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
mixer = await Mixer.deployed()
|
||||
if (ERC20_TOKEN) {
|
||||
token = await Token.at(ERC20_TOKEN)
|
||||
usdtToken = await USDTToken.at(ERC20_TOKEN)
|
||||
} else {
|
||||
token = await Token.deployed()
|
||||
await token.mint(sender, tokenDenomination)
|
||||
}
|
||||
snapshotId = await takeSnapshot()
|
||||
groth16 = await buildGroth16()
|
||||
circuit = require('../build/circuits/withdraw.json')
|
||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should initialize', async () => {
|
||||
const tokenFromContract = await mixer.token()
|
||||
tokenFromContract.should.be.equal(token.address)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#deposit', () => {
|
||||
it('should work', async () => {
|
||||
const commitment = 43
|
||||
await token.approve(mixer.address, tokenDenomination)
|
||||
|
||||
let { logs } = await mixer.deposit(commitment, { value, from: sender })
|
||||
|
||||
logs[0].event.should.be.equal('Deposit')
|
||||
logs[0].args.commitment.should.be.eq.BN(toBN(commitment))
|
||||
logs[0].args.leafIndex.should.be.eq.BN(toBN(0))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#withdraw', () => {
|
||||
it('should work', async () => {
|
||||
const deposit = generateDeposit()
|
||||
const user = accounts[4]
|
||||
await tree.insert(deposit.commitment)
|
||||
await token.mint(user, tokenDenomination)
|
||||
|
||||
const balanceUserBefore = await token.balanceOf(user)
|
||||
await token.approve(mixer.address, tokenDenomination, { from: user })
|
||||
// Uncomment to measure gas usage
|
||||
// let gas = await mixer.deposit.estimateGas(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
|
||||
// console.log('deposit gas:', gas)
|
||||
await mixer.deposit(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
|
||||
|
||||
const balanceUserAfter = await token.balanceOf(user)
|
||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
// Circuit input
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndex: path_index,
|
||||
})
|
||||
|
||||
|
||||
const proof = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { pi_a, pi_b, pi_c, publicSignals } = websnarkUtils.toSolidityInput(proof)
|
||||
|
||||
const balanceMixerBefore = await token.balanceOf(mixer.address)
|
||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await token.balanceOf(toHex(receiver.toString()))
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toHex(receiver.toString()))
|
||||
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(false)
|
||||
// Uncomment to measure gas usage
|
||||
// gas = await mixer.withdraw.estimateGas(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// console.log('withdraw gas:', gas)
|
||||
const { logs } = await mixer.withdraw(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceMixerAfter = await token.balanceOf(mixer.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toHex(receiver.toString()))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toHex(receiver.toString()))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN)))
|
||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)))
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
logs[0].args.relayer.should.be.eq.BN(relayer)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(true)
|
||||
})
|
||||
|
||||
it.skip('should work with REAL USDT', async () => {
|
||||
// dont forget to specify your token in .env
|
||||
// USDT decimals is 6, so TOKEN_AMOUNT=1000000
|
||||
// and sent `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1)
|
||||
// run ganache as
|
||||
// ganache-cli --fork https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448@13147586 -d --keepAliveTimeout 20
|
||||
const deposit = generateDeposit()
|
||||
const user = accounts[4]
|
||||
const userBal = await usdtToken.balanceOf(user)
|
||||
console.log('userBal', userBal.toString())
|
||||
const senderBal = await usdtToken.balanceOf(sender)
|
||||
console.log('senderBal', senderBal.toString())
|
||||
await tree.insert(deposit.commitment)
|
||||
await usdtToken.transfer(user, tokenDenomination, { from: sender })
|
||||
console.log('transfer done')
|
||||
|
||||
const balanceUserBefore = await usdtToken.balanceOf(user)
|
||||
console.log('balanceUserBefore', balanceUserBefore.toString())
|
||||
await usdtToken.approve(mixer.address, tokenDenomination, { from: user })
|
||||
console.log('approve done')
|
||||
const allowanceUser = await usdtToken.allowance(user, mixer.address)
|
||||
console.log('allowanceUser', allowanceUser.toString())
|
||||
await mixer.deposit(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
|
||||
console.log('deposit done')
|
||||
|
||||
const balanceUserAfter = await usdtToken.balanceOf(user)
|
||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
|
||||
// Circuit input
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndex: path_index,
|
||||
})
|
||||
|
||||
|
||||
const proof = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { pi_a, pi_b, pi_c, publicSignals } = websnarkUtils.toSolidityInput(proof)
|
||||
|
||||
const balanceMixerBefore = await usdtToken.balanceOf(mixer.address)
|
||||
const balanceRelayerBefore = await usdtToken.balanceOf(relayer)
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await usdtToken.balanceOf(toHex(receiver.toString()))
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toHex(receiver.toString()))
|
||||
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
// Uncomment to measure gas usage
|
||||
// gas = await mixer.withdraw.estimateGas(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// console.log('withdraw gas:', gas)
|
||||
const { logs } = await mixer.withdraw(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceMixerAfter = await usdtToken.balanceOf(mixer.address)
|
||||
const balanceRelayerAfter = await usdtToken.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await usdtToken.balanceOf(toHex(receiver.toString()))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toHex(receiver.toString()))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).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)))
|
||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)).sub(feeBN))
|
||||
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
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.should.be.equal(true)
|
||||
})
|
||||
it.skip('should work with REAL DAI', async () => {
|
||||
// dont forget to specify your token in .env
|
||||
// and send `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1)
|
||||
// run ganache as
|
||||
// npx ganache-cli --fork https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448@13146218 -d --keepAliveTimeout 20
|
||||
const deposit = generateDeposit()
|
||||
const user = accounts[4]
|
||||
const userBal = await token.balanceOf(user)
|
||||
console.log('userBal', userBal.toString())
|
||||
const senderBal = await token.balanceOf(sender)
|
||||
console.log('senderBal', senderBal.toString())
|
||||
await tree.insert(deposit.commitment)
|
||||
await token.transfer(user, tokenDenomination, { from: sender })
|
||||
console.log('transfer done')
|
||||
|
||||
const balanceUserBefore = await token.balanceOf(user)
|
||||
console.log('balanceUserBefore', balanceUserBefore.toString())
|
||||
await token.approve(mixer.address, tokenDenomination, { from: user })
|
||||
console.log('approve done')
|
||||
await mixer.deposit(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
|
||||
console.log('deposit done')
|
||||
|
||||
const balanceUserAfter = await token.balanceOf(user)
|
||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
|
||||
// Circuit input
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndex: path_index,
|
||||
})
|
||||
|
||||
|
||||
const proof = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { pi_a, pi_b, pi_c, publicSignals } = websnarkUtils.toSolidityInput(proof)
|
||||
|
||||
const balanceMixerBefore = await token.balanceOf(mixer.address)
|
||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await token.balanceOf(toHex(receiver.toString()))
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toHex(receiver.toString()))
|
||||
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
// Uncomment to measure gas usage
|
||||
// gas = await mixer.withdraw.estimateGas(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
// console.log('withdraw gas:', gas)
|
||||
const { logs } = await mixer.withdraw(pi_a, pi_b, pi_c, publicSignals, { from: relayer, gasPrice: '0' })
|
||||
console.log('withdraw done')
|
||||
|
||||
const balanceMixerAfter = await token.balanceOf(mixer.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toHex(receiver.toString()))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toHex(receiver.toString()))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).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)))
|
||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)).sub(feeBN))
|
||||
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
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.should.be.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await revertSnapshot(snapshotId.result)
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
snapshotId = await takeSnapshot()
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
zeroValue,
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
})
|
||||
})
|
@ -8,8 +8,8 @@ const fs = require('fs')
|
||||
const { toBN, toHex, randomHex } = require('web3-utils')
|
||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
||||
|
||||
const Mixer = artifacts.require('./Mixer.sol')
|
||||
const { AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
|
||||
const Mixer = artifacts.require('./ETHMixer.sol')
|
||||
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
|
||||
|
||||
const websnarkUtils = require('websnark/src/utils')
|
||||
const buildGroth16 = require('websnark/src/groth16')
|
||||
@ -57,17 +57,17 @@ function snarkVerify(proof) {
|
||||
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
|
||||
}
|
||||
|
||||
contract('Mixer', accounts => {
|
||||
contract('ETHMixer', accounts => {
|
||||
let mixer
|
||||
const sender = accounts[0]
|
||||
const operator = accounts[0]
|
||||
const levels = MERKLE_TREE_HEIGHT || 16
|
||||
const zeroValue = EMPTY_ELEMENT || 1337
|
||||
const value = AMOUNT || '1000000000000000000' // 1 ether
|
||||
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
||||
let snapshotId
|
||||
let prefix = 'test'
|
||||
let tree
|
||||
const fee = bigInt(AMOUNT).shr(1) || bigInt(1e17)
|
||||
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
||||
const receiver = getRandomReceiver()
|
||||
const relayer = accounts[1]
|
||||
let groth16
|
||||
@ -90,8 +90,8 @@ contract('Mixer', accounts => {
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should initialize', async () => {
|
||||
const transferValue = await mixer.transferValue()
|
||||
transferValue.should.be.eq.BN(toBN(value))
|
||||
const etherDenomination = await mixer.mixDenomination()
|
||||
etherDenomination.should.be.eq.BN(toBN(value))
|
||||
})
|
||||
})
|
||||
|
||||
@ -141,6 +141,7 @@ contract('Mixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -196,6 +197,7 @@ contract('Mixer', accounts => {
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
@ -235,6 +237,7 @@ contract('Mixer', accounts => {
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
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.should.be.equal(true)
|
||||
@ -251,6 +254,7 @@ contract('Mixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -275,6 +279,7 @@ contract('Mixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -299,6 +304,7 @@ contract('Mixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee: oneEtherFee,
|
||||
secret: deposit.secret,
|
||||
@ -323,6 +329,7 @@ contract('Mixer', accounts => {
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
root,
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -350,6 +357,7 @@ contract('Mixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
@ -12,7 +12,7 @@ const MiMC = artifacts.require('./MiMC.sol')
|
||||
const MerkleTree = require('../lib/MerkleTree')
|
||||
const MimcHasher = require('../lib/MiMC')
|
||||
|
||||
const { AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
|
||||
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function BNArrayToStringArray(array) {
|
||||
@ -30,7 +30,7 @@ contract('MerkleTreeWithHistory', accounts => {
|
||||
let zeroValue = EMPTY_ELEMENT || 1337
|
||||
const sender = accounts[0]
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const value = AMOUNT || '1000000000000000000'
|
||||
const value = ETH_AMOUNT || '1000000000000000000'
|
||||
let snapshotId
|
||||
let prefix = 'test'
|
||||
let tree
|
||||
@ -180,15 +180,24 @@ contract('MerkleTreeWithHistory', accounts => {
|
||||
zeroValue = 1337
|
||||
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, zeroValue)
|
||||
|
||||
for (let i = 0; i < 2**(levels - 1); i++) {
|
||||
for (let i = 0; i < 2**levels; i++) {
|
||||
await merkleTreeWithHistory.insert(i+42).should.be.fulfilled
|
||||
}
|
||||
|
||||
let error = await merkleTreeWithHistory.insert(1337).should.be.rejected
|
||||
error.reason.should.be.equal('Merkle tree is full')
|
||||
error.reason.should.be.equal('Merkle tree is full. No more leafs can be added')
|
||||
|
||||
error = await merkleTreeWithHistory.insert(1).should.be.rejected
|
||||
error.reason.should.be.equal('Merkle tree is full')
|
||||
error.reason.should.be.equal('Merkle tree is full. No more leafs can be added')
|
||||
})
|
||||
|
||||
it.skip('mimc gas', async () => {
|
||||
levels = 6
|
||||
zeroValue = 1337
|
||||
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, zeroValue)
|
||||
|
||||
const gas = await merkleTreeWithHistory.hashLeftRight.estimateGas(zeroValue, zeroValue)
|
||||
console.log('gas', gas - 21000)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -45,7 +45,7 @@ module.exports = {
|
||||
kovan: {
|
||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://kovan.infura.io/v3/c7463beadf2144e68646ff049917b716'),
|
||||
network_id: 42,
|
||||
gas: 7000000,
|
||||
gas: 6000000,
|
||||
gasPrice: utils.toWei('1', 'gwei'),
|
||||
// confirmations: 0,
|
||||
// timeoutBlocks: 200,
|
||||
@ -77,7 +77,7 @@ module.exports = {
|
||||
// Configure your compilers
|
||||
compilers: {
|
||||
solc: {
|
||||
version: '0.5.10', // Fetch exact version from solc-bin (default: truffle's version)
|
||||
version: '0.5.11', // Fetch exact version from solc-bin (default: truffle's version)
|
||||
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
|
||||
settings: { // See the solidity docs for advice about optimization and evmVersion
|
||||
optimizer: {
|
||||
|
Loading…
Reference in New Issue
Block a user