mirror of
https://github.com/tornadocash/tornado-core.git
synced 2025-01-12 05:29:26 -05:00
commit
5ef6e33c78
13
.env.example
13
.env.example
@ -1,5 +1,16 @@
|
|||||||
MERKLE_TREE_HEIGHT=16
|
MERKLE_TREE_HEIGHT=16
|
||||||
# in wei
|
# in wei
|
||||||
AMOUNT=1000000000000000000
|
ETH_AMOUNT=100000000000000000
|
||||||
|
TOKEN_AMOUNT=100000000000000000
|
||||||
EMPTY_ELEMENT=1
|
EMPTY_ELEMENT=1
|
||||||
PRIVATE_KEY=
|
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 Local files
|
||||||
.dynamodb/
|
.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. `npx ganache-cli`
|
||||||
1. `npm run test` - optionally run tests. It may fail for the first time, just run one more time.
|
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:
|
Use browser version on Kovan:
|
||||||
|
|
||||||
1. `vi .env` - add your Kovan private key to deploy contracts
|
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. `npx http-server` - serve current dir, you can use any other static http server
|
||||||
1. Open `localhost:8080`
|
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
|
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
|
## Credits
|
||||||
|
|
||||||
Special thanks to @barryWhiteHat and @kobigurk for valuable input,
|
Special thanks to @barryWhiteHat and @kobigurk for valuable input,
|
||||||
|
@ -31,6 +31,7 @@ template Withdraw(levels, rounds) {
|
|||||||
signal input root;
|
signal input root;
|
||||||
signal input nullifierHash;
|
signal input nullifierHash;
|
||||||
signal input receiver; // not taking part in any computations
|
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 input fee; // not taking part in any computations
|
||||||
signal private input nullifier;
|
signal private input nullifier;
|
||||||
signal private input secret;
|
signal private input secret;
|
||||||
@ -56,8 +57,10 @@ template Withdraw(levels, rounds) {
|
|||||||
// Squares are used to prevent optimizer from removing those constraints
|
// Squares are used to prevent optimizer from removing those constraints
|
||||||
signal receiverSquare;
|
signal receiverSquare;
|
||||||
signal feeSquare;
|
signal feeSquare;
|
||||||
|
signal relayerSquare;
|
||||||
receiverSquare <== receiver * receiver;
|
receiverSquare <== receiver * receiver;
|
||||||
feeSquare <== fee * fee;
|
feeSquare <== fee * fee;
|
||||||
|
relayerSquare <== relayer * relayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
component main = Withdraw(16, 220);
|
component main = Withdraw(16, 220);
|
||||||
|
165
cli.js
165
cli.js
@ -12,8 +12,9 @@ const Web3 = require('web3')
|
|||||||
const buildGroth16 = require('websnark/src/groth16')
|
const buildGroth16 = require('websnark/src/groth16')
|
||||||
const websnarkUtils = require('websnark/src/utils')
|
const websnarkUtils = require('websnark/src/utils')
|
||||||
|
|
||||||
let web3, mixer, circuit, proving_key, groth16
|
let web3, mixer, erc20mixer, circuit, proving_key, groth16, erc20
|
||||||
let MERKLE_TREE_HEIGHT, AMOUNT, EMPTY_ELEMENT
|
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT, ERC20_TOKEN
|
||||||
|
const inBrowser = (typeof window !== 'undefined')
|
||||||
|
|
||||||
/** Generate random number of specified byte length */
|
/** Generate random number of specified byte length */
|
||||||
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||||
@ -39,19 +40,103 @@ async function deposit() {
|
|||||||
const deposit = createDeposit(rbigint(31), rbigint(31))
|
const deposit = createDeposit(rbigint(31), rbigint(31))
|
||||||
|
|
||||||
console.log('Submitting deposit transaction')
|
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')
|
const note = '0x' + deposit.preimage.toString('hex')
|
||||||
console.log('Your note:', note)
|
console.log('Your note:', note)
|
||||||
return note
|
return note
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function depositErc20() {
|
||||||
* Make a withdrawal
|
const account = (await web3.eth.getAccounts())[0]
|
||||||
* @param note A preimage containing secret and nullifier
|
const tokenAmount = process.env.TOKEN_AMOUNT
|
||||||
* @param receiver Address for receiving funds
|
await erc20.methods.mint(account, tokenAmount).send({ from: account, gas:1e6 })
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
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) {
|
async function withdraw(note, receiver) {
|
||||||
// Decode hex string and restore the deposit object
|
// Decode hex string and restore the deposit object
|
||||||
let buf = Buffer.from(note.slice(2), 'hex')
|
let buf = Buffer.from(note.slice(2), 'hex')
|
||||||
@ -88,6 +173,7 @@ async function withdraw(note, receiver) {
|
|||||||
root: root,
|
root: root,
|
||||||
nullifierHash,
|
nullifierHash,
|
||||||
receiver: bigInt(receiver),
|
receiver: bigInt(receiver),
|
||||||
|
relayer: bigInt(0),
|
||||||
fee: bigInt(0),
|
fee: bigInt(0),
|
||||||
|
|
||||||
// Private snark inputs
|
// Private snark inputs
|
||||||
@ -108,47 +194,52 @@ async function withdraw(note, receiver) {
|
|||||||
console.log('Done')
|
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
|
* Init web3, contracts, and snark
|
||||||
*/
|
*/
|
||||||
async function init() {
|
async function init() {
|
||||||
let contractJson
|
let contractJson, erc20ContractJson, erc20mixerJson
|
||||||
if (inBrowser) {
|
if (inBrowser) {
|
||||||
// Initialize using injected web3 (Metamask)
|
// Initialize using injected web3 (Metamask)
|
||||||
// To assemble web version run `npm run browserify`
|
// To assemble web version run `npm run browserify`
|
||||||
web3 = new Web3(window.web3.currentProvider, null, { transactionConfirmationBlocks: 1 })
|
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()
|
circuit = await (await fetch('build/circuits/withdraw.json')).json()
|
||||||
proving_key = await (await fetch('build/circuits/withdraw_proving_key.bin')).arrayBuffer()
|
proving_key = await (await fetch('build/circuits/withdraw_proving_key.bin')).arrayBuffer()
|
||||||
MERKLE_TREE_HEIGHT = 16
|
MERKLE_TREE_HEIGHT = 16
|
||||||
AMOUNT = 1e18
|
ETH_AMOUNT = 1e18
|
||||||
EMPTY_ELEMENT = 1
|
EMPTY_ELEMENT = 1
|
||||||
} else {
|
} else {
|
||||||
// Initialize from local node
|
// Initialize from local node
|
||||||
web3 = new Web3('http://localhost:8545', null, { transactionConfirmationBlocks: 1 })
|
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')
|
circuit = require('./build/circuits/withdraw.json')
|
||||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT
|
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT
|
||||||
AMOUNT = process.env.AMOUNT
|
ETH_AMOUNT = process.env.ETH_AMOUNT
|
||||||
EMPTY_ELEMENT = process.env.EMPTY_ELEMENT
|
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()
|
groth16 = await buildGroth16()
|
||||||
let netId = await web3.eth.net.getId()
|
let netId = await web3.eth.net.getId()
|
||||||
const tx = await web3.eth.getTransaction(contractJson.networks[netId].transactionHash)
|
if (contractJson.networks[netId]) {
|
||||||
mixer = new web3.eth.Contract(contractJson.abi, contractJson.networks[netId].address)
|
const tx = await web3.eth.getTransaction(contractJson.networks[netId].transactionHash)
|
||||||
mixer.deployedBlock = tx.blockNumber
|
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')
|
console.log('Loaded')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,12 +287,25 @@ if (inBrowser) {
|
|||||||
else
|
else
|
||||||
printHelp(1)
|
printHelp(1)
|
||||||
break
|
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':
|
case 'balance':
|
||||||
if (args.length === 2 && /^0x[0-9a-fA-F]{40}$/.test(args[1])) {
|
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)})
|
init().then(() => getBalance(args[1])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||||
} else
|
} else
|
||||||
printHelp(1)
|
printHelp(1)
|
||||||
break
|
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':
|
case 'withdraw':
|
||||||
if (args.length === 3 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
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)})
|
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
|
else
|
||||||
printHelp(1)
|
printHelp(1)
|
||||||
break
|
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':
|
case 'auto':
|
||||||
if (args.length === 1) {
|
if (args.length === 1) {
|
||||||
(async () => {
|
(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 {
|
function _insert(uint256 leaf) internal {
|
||||||
uint32 current_index = next_index;
|
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;
|
next_index += 1;
|
||||||
uint256 current_level_hash = leaf;
|
uint256 current_level_hash = leaf;
|
||||||
uint256 left;
|
uint256 left;
|
||||||
|
@ -14,11 +14,10 @@ pragma solidity ^0.5.8;
|
|||||||
import "./MerkleTreeWithHistory.sol";
|
import "./MerkleTreeWithHistory.sol";
|
||||||
|
|
||||||
contract IVerifier {
|
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 {
|
contract Mixer is MerkleTreeWithHistory {
|
||||||
uint256 public transferValue;
|
|
||||||
bool public isDepositsEnabled = true;
|
bool public isDepositsEnabled = true;
|
||||||
// operator can disable new deposits in case of emergency
|
// operator can disable new deposits in case of emergency
|
||||||
// it also receives a relayer fee
|
// 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
|
// we store all commitments just to prevent accidental deposits with the same commitment
|
||||||
mapping(uint256 => bool) public commitments;
|
mapping(uint256 => bool) public commitments;
|
||||||
IVerifier public verifier;
|
IVerifier public verifier;
|
||||||
|
uint256 public mixDenomination;
|
||||||
|
|
||||||
event Deposit(uint256 indexed commitment, uint256 leafIndex, uint256 timestamp);
|
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
|
@dev The constructor
|
||||||
@param _verifier the address of SNARK verifier for this contract
|
@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(
|
constructor(
|
||||||
address _verifier,
|
address _verifier,
|
||||||
uint256 _transferValue,
|
uint256 _mixDenomination,
|
||||||
uint8 _merkleTreeHeight,
|
uint8 _merkleTreeHeight,
|
||||||
uint256 _emptyElement,
|
uint256 _emptyElement,
|
||||||
address payable _operator
|
address payable _operator
|
||||||
) MerkleTreeWithHistory(_merkleTreeHeight, _emptyElement) public {
|
) MerkleTreeWithHistory(_merkleTreeHeight, _emptyElement) public {
|
||||||
verifier = IVerifier(_verifier);
|
verifier = IVerifier(_verifier);
|
||||||
transferValue = _transferValue;
|
|
||||||
operator = _operator;
|
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)
|
@param commitment the note commitment, which is PedersenHash(nullifier + secret)
|
||||||
*/
|
*/
|
||||||
function deposit(uint256 commitment) public payable {
|
function deposit(uint256 commitment) public payable {
|
||||||
require(isDepositsEnabled, "deposits disabled");
|
require(isDepositsEnabled, "deposits disabled");
|
||||||
require(msg.value == transferValue, "Please send `transferValue` ETH along with transaction");
|
|
||||||
require(!commitments[commitment], "The commitment has been submitted");
|
require(!commitments[commitment], "The commitment has been submitted");
|
||||||
|
_processDeposit();
|
||||||
_insert(commitment);
|
_insert(commitment);
|
||||||
commitments[commitment] = true;
|
commitments[commitment] = true;
|
||||||
|
|
||||||
emit Deposit(commitment, next_index - 1, block.timestamp);
|
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
|
@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:
|
`input` array consists of:
|
||||||
@ -69,23 +75,20 @@ contract Mixer is MerkleTreeWithHistory {
|
|||||||
- the receiver of funds
|
- the receiver of funds
|
||||||
- optional fee that goes to the transaction sender (usually a relay)
|
- 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 root = input[0];
|
||||||
uint256 nullifierHash = input[1];
|
uint256 nullifierHash = input[1];
|
||||||
address payable receiver = address(input[2]);
|
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(!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(isKnownRoot(root), "Cannot find your merkle root"); // Make sure to use a recent one
|
||||||
require(verifier.verifyProof(a, b, c, input), "Invalid withdraw proof");
|
require(verifier.verifyProof(a, b, c, input), "Invalid withdraw proof");
|
||||||
|
|
||||||
nullifierHashes[nullifierHash] = true;
|
nullifierHashes[nullifierHash] = true;
|
||||||
receiver.transfer(transferValue - fee);
|
_processWithdraw(receiver, relayer, fee);
|
||||||
if (fee > 0) {
|
emit Withdraw(receiver, nullifierHash, relayer, fee);
|
||||||
operator.transfer(fee);
|
|
||||||
}
|
|
||||||
emit Withdraw(receiver, nullifierHash, fee);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDeposits() external {
|
function toggleDeposits() external {
|
||||||
@ -101,4 +104,8 @@ contract Mixer is MerkleTreeWithHistory {
|
|||||||
function isSpent(uint256 nullifier) public view returns(bool) {
|
function isSpent(uint256 nullifier) public view returns(bool) {
|
||||||
return nullifierHashes[nullifier];
|
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"
|
"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": {
|
"@resolver-engine/core": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.2.1.tgz",
|
||||||
@ -2357,9 +2362,9 @@
|
|||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "6.0.1",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.2.tgz",
|
||||||
"integrity": "sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==",
|
"integrity": "sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
"ajv": "^6.10.0",
|
"ajv": "^6.10.0",
|
||||||
@ -2367,36 +2372,92 @@
|
|||||||
"cross-spawn": "^6.0.5",
|
"cross-spawn": "^6.0.5",
|
||||||
"debug": "^4.0.1",
|
"debug": "^4.0.1",
|
||||||
"doctrine": "^3.0.0",
|
"doctrine": "^3.0.0",
|
||||||
"eslint-scope": "^4.0.3",
|
"eslint-scope": "^5.0.0",
|
||||||
"eslint-utils": "^1.3.1",
|
"eslint-utils": "^1.4.2",
|
||||||
"eslint-visitor-keys": "^1.0.0",
|
"eslint-visitor-keys": "^1.1.0",
|
||||||
"espree": "^6.0.0",
|
"espree": "^6.1.1",
|
||||||
"esquery": "^1.0.1",
|
"esquery": "^1.0.1",
|
||||||
"esutils": "^2.0.2",
|
"esutils": "^2.0.2",
|
||||||
"file-entry-cache": "^5.0.1",
|
"file-entry-cache": "^5.0.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"glob-parent": "^3.1.0",
|
"glob-parent": "^5.0.0",
|
||||||
"globals": "^11.7.0",
|
"globals": "^11.7.0",
|
||||||
"ignore": "^4.0.6",
|
"ignore": "^4.0.6",
|
||||||
"import-fresh": "^3.0.0",
|
"import-fresh": "^3.0.0",
|
||||||
"imurmurhash": "^0.1.4",
|
"imurmurhash": "^0.1.4",
|
||||||
"inquirer": "^6.2.2",
|
"inquirer": "^6.4.1",
|
||||||
"is-glob": "^4.0.0",
|
"is-glob": "^4.0.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||||
"levn": "^0.3.0",
|
"levn": "^0.3.0",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.14",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"optionator": "^0.8.2",
|
"optionator": "^0.8.2",
|
||||||
"progress": "^2.0.0",
|
"progress": "^2.0.0",
|
||||||
"regexpp": "^2.0.1",
|
"regexpp": "^2.0.1",
|
||||||
"semver": "^5.5.1",
|
"semver": "^6.1.2",
|
||||||
"strip-ansi": "^4.0.0",
|
"strip-ansi": "^5.2.0",
|
||||||
"strip-json-comments": "^2.0.1",
|
"strip-json-comments": "^3.0.1",
|
||||||
"table": "^5.2.3",
|
"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": {
|
"eslint-scope": {
|
||||||
@ -2422,13 +2483,30 @@
|
|||||||
"integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ=="
|
"integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ=="
|
||||||
},
|
},
|
||||||
"espree": {
|
"espree": {
|
||||||
"version": "6.0.0",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
|
||||||
"integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==",
|
"integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"acorn": "^6.0.7",
|
"acorn": "^7.0.0",
|
||||||
"acorn-jsx": "^5.0.0",
|
"acorn-jsx": "^5.0.2",
|
||||||
"eslint-visitor-keys": "^1.0.0"
|
"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": {
|
"esprima": {
|
||||||
@ -9867,6 +9945,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
|
||||||
"integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w="
|
"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": {
|
"v8flags": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz",
|
||||||
|
@ -16,12 +16,13 @@
|
|||||||
"migrate:dev": "npx truffle migrate --network development --reset",
|
"migrate:dev": "npx truffle migrate --network development --reset",
|
||||||
"browserify": "npx browserify cli.js -o index.js --exclude worker_threads",
|
"browserify": "npx browserify cli.js -o index.js --exclude worker_threads",
|
||||||
"eslint": "npx eslint --ignore-path .gitignore .",
|
"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": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@openzeppelin/contracts": "^2.3.0",
|
||||||
"bn-chai": "^1.0.1",
|
"bn-chai": "^1.0.1",
|
||||||
"browserify": "^16.3.0",
|
"browserify": "^16.3.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
@ -29,7 +30,7 @@
|
|||||||
"circom": "0.0.30",
|
"circom": "0.0.30",
|
||||||
"circomlib": "^0.0.10",
|
"circomlib": "^0.0.10",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"eslint": "^6.0.1",
|
"eslint": "^6.2.2",
|
||||||
"ganache-cli": "^6.4.5",
|
"ganache-cli": "^6.4.5",
|
||||||
"snarkjs": "^0.1.16",
|
"snarkjs": "^0.1.16",
|
||||||
"truffle": "^5.0.27",
|
"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 { toBN, toHex, randomHex } = require('web3-utils')
|
||||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
||||||
|
|
||||||
const Mixer = artifacts.require('./Mixer.sol')
|
const Mixer = artifacts.require('./ETHMixer.sol')
|
||||||
const { AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
|
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
|
||||||
|
|
||||||
const websnarkUtils = require('websnark/src/utils')
|
const websnarkUtils = require('websnark/src/utils')
|
||||||
const buildGroth16 = require('websnark/src/groth16')
|
const buildGroth16 = require('websnark/src/groth16')
|
||||||
@ -57,17 +57,17 @@ function snarkVerify(proof) {
|
|||||||
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
|
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
|
||||||
}
|
}
|
||||||
|
|
||||||
contract('Mixer', accounts => {
|
contract('ETHMixer', accounts => {
|
||||||
let mixer
|
let mixer
|
||||||
const sender = accounts[0]
|
const sender = accounts[0]
|
||||||
const operator = accounts[0]
|
const operator = accounts[0]
|
||||||
const levels = MERKLE_TREE_HEIGHT || 16
|
const levels = MERKLE_TREE_HEIGHT || 16
|
||||||
const zeroValue = EMPTY_ELEMENT || 1337
|
const zeroValue = EMPTY_ELEMENT || 1337
|
||||||
const value = AMOUNT || '1000000000000000000' // 1 ether
|
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
||||||
let snapshotId
|
let snapshotId
|
||||||
let prefix = 'test'
|
let prefix = 'test'
|
||||||
let tree
|
let tree
|
||||||
const fee = bigInt(AMOUNT).shr(1) || bigInt(1e17)
|
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
||||||
const receiver = getRandomReceiver()
|
const receiver = getRandomReceiver()
|
||||||
const relayer = accounts[1]
|
const relayer = accounts[1]
|
||||||
let groth16
|
let groth16
|
||||||
@ -90,8 +90,8 @@ contract('Mixer', accounts => {
|
|||||||
|
|
||||||
describe('#constructor', () => {
|
describe('#constructor', () => {
|
||||||
it('should initialize', async () => {
|
it('should initialize', async () => {
|
||||||
const transferValue = await mixer.transferValue()
|
const etherDenomination = await mixer.mixDenomination()
|
||||||
transferValue.should.be.eq.BN(toBN(value))
|
etherDenomination.should.be.eq.BN(toBN(value))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -141,6 +141,7 @@ contract('Mixer', accounts => {
|
|||||||
root,
|
root,
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
|
relayer: operator,
|
||||||
receiver,
|
receiver,
|
||||||
fee,
|
fee,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
@ -196,6 +197,7 @@ contract('Mixer', accounts => {
|
|||||||
// public
|
// public
|
||||||
root,
|
root,
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
|
relayer: operator,
|
||||||
receiver,
|
receiver,
|
||||||
fee,
|
fee,
|
||||||
|
|
||||||
@ -235,6 +237,7 @@ contract('Mixer', accounts => {
|
|||||||
|
|
||||||
logs[0].event.should.be.equal('Withdraw')
|
logs[0].event.should.be.equal('Withdraw')
|
||||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
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)
|
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||||
isSpent.should.be.equal(true)
|
isSpent.should.be.equal(true)
|
||||||
@ -251,6 +254,7 @@ contract('Mixer', accounts => {
|
|||||||
root,
|
root,
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
|
relayer: operator,
|
||||||
receiver,
|
receiver,
|
||||||
fee,
|
fee,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
@ -275,6 +279,7 @@ contract('Mixer', accounts => {
|
|||||||
root,
|
root,
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
|
relayer: operator,
|
||||||
receiver,
|
receiver,
|
||||||
fee,
|
fee,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
@ -299,6 +304,7 @@ contract('Mixer', accounts => {
|
|||||||
root,
|
root,
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
|
relayer: operator,
|
||||||
receiver,
|
receiver,
|
||||||
fee: oneEtherFee,
|
fee: oneEtherFee,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
@ -323,6 +329,7 @@ contract('Mixer', accounts => {
|
|||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
root,
|
root,
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
|
relayer: operator,
|
||||||
receiver,
|
receiver,
|
||||||
fee,
|
fee,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
@ -350,6 +357,7 @@ contract('Mixer', accounts => {
|
|||||||
root,
|
root,
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
|
relayer: operator,
|
||||||
receiver,
|
receiver,
|
||||||
fee,
|
fee,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
@ -12,7 +12,7 @@ const MiMC = artifacts.require('./MiMC.sol')
|
|||||||
const MerkleTree = require('../lib/MerkleTree')
|
const MerkleTree = require('../lib/MerkleTree')
|
||||||
const MimcHasher = require('../lib/MiMC')
|
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
|
// eslint-disable-next-line no-unused-vars
|
||||||
function BNArrayToStringArray(array) {
|
function BNArrayToStringArray(array) {
|
||||||
@ -30,7 +30,7 @@ contract('MerkleTreeWithHistory', accounts => {
|
|||||||
let zeroValue = EMPTY_ELEMENT || 1337
|
let zeroValue = EMPTY_ELEMENT || 1337
|
||||||
const sender = accounts[0]
|
const sender = accounts[0]
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const value = AMOUNT || '1000000000000000000'
|
const value = ETH_AMOUNT || '1000000000000000000'
|
||||||
let snapshotId
|
let snapshotId
|
||||||
let prefix = 'test'
|
let prefix = 'test'
|
||||||
let tree
|
let tree
|
||||||
@ -180,15 +180,24 @@ contract('MerkleTreeWithHistory', accounts => {
|
|||||||
zeroValue = 1337
|
zeroValue = 1337
|
||||||
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, zeroValue)
|
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
|
await merkleTreeWithHistory.insert(i+42).should.be.fulfilled
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = await merkleTreeWithHistory.insert(1337).should.be.rejected
|
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 = 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: {
|
kovan: {
|
||||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://kovan.infura.io/v3/c7463beadf2144e68646ff049917b716'),
|
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://kovan.infura.io/v3/c7463beadf2144e68646ff049917b716'),
|
||||||
network_id: 42,
|
network_id: 42,
|
||||||
gas: 7000000,
|
gas: 6000000,
|
||||||
gasPrice: utils.toWei('1', 'gwei'),
|
gasPrice: utils.toWei('1', 'gwei'),
|
||||||
// confirmations: 0,
|
// confirmations: 0,
|
||||||
// timeoutBlocks: 200,
|
// timeoutBlocks: 200,
|
||||||
@ -77,7 +77,7 @@ module.exports = {
|
|||||||
// Configure your compilers
|
// Configure your compilers
|
||||||
compilers: {
|
compilers: {
|
||||||
solc: {
|
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)
|
// 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
|
settings: { // See the solidity docs for advice about optimization and evmVersion
|
||||||
optimizer: {
|
optimizer: {
|
||||||
|
Loading…
Reference in New Issue
Block a user