ERC20Tornado.test.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /* global artifacts, web3, contract */
  2. require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
  3. const fs = require('fs')
  4. const { toBN } = require('web3-utils')
  5. const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper')
  6. const Tornado = artifacts.require('./ERC20Tornado.sol')
  7. const BadRecipient = artifacts.require('./BadRecipient.sol')
  8. const Token = artifacts.require('./ERC20Mock.sol')
  9. const USDTToken = artifacts.require('./IUSDT.sol')
  10. const { ETH_AMOUNT, TOKEN_AMOUNT, MERKLE_TREE_HEIGHT, ERC20_TOKEN } = process.env
  11. const websnarkUtils = require('websnark/src/utils')
  12. const buildGroth16 = require('websnark/src/groth16')
  13. const stringifyBigInts = require('websnark/tools/stringifybigint').stringifyBigInts
  14. const snarkjs = require('snarkjs')
  15. const bigInt = snarkjs.bigInt
  16. const crypto = require('crypto')
  17. const circomlib = require('circomlib')
  18. const MerkleTree = require('fixed-merkle-tree')
  19. const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
  20. const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
  21. const toFixedHex = (number, length = 32) =>
  22. '0x' +
  23. bigInt(number)
  24. .toString(16)
  25. .padStart(length * 2, '0')
  26. const getRandomRecipient = () => rbigint(20)
  27. function generateDeposit() {
  28. let deposit = {
  29. secret: rbigint(31),
  30. nullifier: rbigint(31),
  31. }
  32. const preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
  33. deposit.commitment = pedersenHash(preimage)
  34. return deposit
  35. }
  36. contract('ERC20Tornado', (accounts) => {
  37. let tornado
  38. let token
  39. let usdtToken
  40. let badRecipient
  41. const sender = accounts[0]
  42. const operator = accounts[0]
  43. const levels = MERKLE_TREE_HEIGHT || 16
  44. let tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether
  45. let snapshotId
  46. let tree
  47. const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
  48. const refund = ETH_AMOUNT || '1000000000000000000' // 1 ether
  49. let recipient = getRandomRecipient()
  50. const relayer = accounts[1]
  51. let groth16
  52. let circuit
  53. let proving_key
  54. before(async () => {
  55. tree = new MerkleTree(levels)
  56. tornado = await Tornado.deployed()
  57. if (ERC20_TOKEN) {
  58. token = await Token.at(ERC20_TOKEN)
  59. usdtToken = await USDTToken.at(ERC20_TOKEN)
  60. } else {
  61. token = await Token.deployed()
  62. await token.mint(sender, tokenDenomination)
  63. }
  64. badRecipient = await BadRecipient.new()
  65. snapshotId = await takeSnapshot()
  66. groth16 = await buildGroth16()
  67. circuit = require('../build/circuits/withdraw.json')
  68. proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
  69. })
  70. describe('#constructor', () => {
  71. it('should initialize', async () => {
  72. const tokenFromContract = await tornado.token()
  73. tokenFromContract.should.be.equal(token.address)
  74. })
  75. })
  76. describe('#deposit', () => {
  77. it('should work', async () => {
  78. const commitment = toFixedHex(43)
  79. await token.approve(tornado.address, tokenDenomination)
  80. let { logs } = await tornado.deposit(commitment, { from: sender })
  81. logs[0].event.should.be.equal('Deposit')
  82. logs[0].args.commitment.should.be.equal(commitment)
  83. logs[0].args.leafIndex.should.be.eq.BN(0)
  84. })
  85. it('should not allow to send ether on deposit', async () => {
  86. const commitment = toFixedHex(43)
  87. await token.approve(tornado.address, tokenDenomination)
  88. let error = await tornado.deposit(commitment, { from: sender, value: 1e6 }).should.be.rejected
  89. error.reason.should.be.equal('ETH value is supposed to be 0 for ERC20 instance')
  90. })
  91. })
  92. describe('#withdraw', () => {
  93. it('should work', async () => {
  94. const deposit = generateDeposit()
  95. const user = accounts[4]
  96. tree.insert(deposit.commitment)
  97. await token.mint(user, tokenDenomination)
  98. const balanceUserBefore = await token.balanceOf(user)
  99. await token.approve(tornado.address, tokenDenomination, { from: user })
  100. // Uncomment to measure gas usage
  101. // let gas = await tornado.deposit.estimateGas(toBN(deposit.commitment.toString()), { from: user, gasPrice: '0' })
  102. // console.log('deposit gas:', gas)
  103. await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
  104. const balanceUserAfter = await token.balanceOf(user)
  105. balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
  106. const { pathElements, pathIndices } = tree.path(0)
  107. // Circuit input
  108. const input = stringifyBigInts({
  109. // public
  110. root: tree.root(),
  111. nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
  112. relayer,
  113. recipient,
  114. fee,
  115. refund,
  116. // private
  117. nullifier: deposit.nullifier,
  118. secret: deposit.secret,
  119. pathElements: pathElements,
  120. pathIndices: pathIndices,
  121. })
  122. const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
  123. const { proof } = websnarkUtils.toSolidityInput(proofData)
  124. const balanceTornadoBefore = await token.balanceOf(tornado.address)
  125. const balanceRelayerBefore = await token.balanceOf(relayer)
  126. const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
  127. const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
  128. const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
  129. const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
  130. let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
  131. isSpent.should.be.equal(false)
  132. // Uncomment to measure gas usage
  133. // gas = await tornado.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
  134. // console.log('withdraw gas:', gas)
  135. const args = [
  136. toFixedHex(input.root),
  137. toFixedHex(input.nullifierHash),
  138. toFixedHex(input.recipient, 20),
  139. toFixedHex(input.relayer, 20),
  140. toFixedHex(input.fee),
  141. toFixedHex(input.refund),
  142. ]
  143. const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
  144. const balanceTornadoAfter = await token.balanceOf(tornado.address)
  145. const balanceRelayerAfter = await token.balanceOf(relayer)
  146. const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
  147. const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
  148. const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
  149. const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
  150. const feeBN = toBN(fee.toString())
  151. balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
  152. balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
  153. balanceReceiverAfter.should.be.eq.BN(
  154. toBN(balanceReceiverBefore).add(toBN(tokenDenomination).sub(feeBN)),
  155. )
  156. ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
  157. ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)))
  158. ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore).sub(toBN(refund)))
  159. logs[0].event.should.be.equal('Withdrawal')
  160. logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
  161. logs[0].args.relayer.should.be.eq.BN(relayer)
  162. logs[0].args.fee.should.be.eq.BN(feeBN)
  163. isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
  164. isSpent.should.be.equal(true)
  165. })
  166. it('should return refund to the relayer is case of fail', async () => {
  167. const deposit = generateDeposit()
  168. const user = accounts[4]
  169. recipient = bigInt(badRecipient.address)
  170. tree.insert(deposit.commitment)
  171. await token.mint(user, tokenDenomination)
  172. const balanceUserBefore = await token.balanceOf(user)
  173. await token.approve(tornado.address, tokenDenomination, { from: user })
  174. await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
  175. const balanceUserAfter = await token.balanceOf(user)
  176. balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
  177. const { pathElements, pathIndices } = tree.path(0)
  178. // Circuit input
  179. const input = stringifyBigInts({
  180. // public
  181. root: tree.root(),
  182. nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
  183. relayer,
  184. recipient,
  185. fee,
  186. refund,
  187. // private
  188. nullifier: deposit.nullifier,
  189. secret: deposit.secret,
  190. pathElements: pathElements,
  191. pathIndices: pathIndices,
  192. })
  193. const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
  194. const { proof } = websnarkUtils.toSolidityInput(proofData)
  195. const balanceTornadoBefore = await token.balanceOf(tornado.address)
  196. const balanceRelayerBefore = await token.balanceOf(relayer)
  197. const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
  198. const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
  199. const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
  200. const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
  201. let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
  202. isSpent.should.be.equal(false)
  203. const args = [
  204. toFixedHex(input.root),
  205. toFixedHex(input.nullifierHash),
  206. toFixedHex(input.recipient, 20),
  207. toFixedHex(input.relayer, 20),
  208. toFixedHex(input.fee),
  209. toFixedHex(input.refund),
  210. ]
  211. const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
  212. const balanceTornadoAfter = await token.balanceOf(tornado.address)
  213. const balanceRelayerAfter = await token.balanceOf(relayer)
  214. const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
  215. const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
  216. const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
  217. const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
  218. const feeBN = toBN(fee.toString())
  219. balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
  220. balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
  221. balanceReceiverAfter.should.be.eq.BN(
  222. toBN(balanceReceiverBefore).add(toBN(tokenDenomination).sub(feeBN)),
  223. )
  224. ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
  225. ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore))
  226. ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore))
  227. logs[0].event.should.be.equal('Withdrawal')
  228. logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
  229. logs[0].args.relayer.should.be.eq.BN(relayer)
  230. logs[0].args.fee.should.be.eq.BN(feeBN)
  231. isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
  232. isSpent.should.be.equal(true)
  233. })
  234. it('should reject with wrong refund value', async () => {
  235. const deposit = generateDeposit()
  236. const user = accounts[4]
  237. tree.insert(deposit.commitment)
  238. await token.mint(user, tokenDenomination)
  239. await token.approve(tornado.address, tokenDenomination, { from: user })
  240. await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
  241. const { pathElements, pathIndices } = tree.path(0)
  242. // Circuit input
  243. const input = stringifyBigInts({
  244. // public
  245. root: tree.root(),
  246. nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
  247. relayer,
  248. recipient,
  249. fee,
  250. refund,
  251. // private
  252. nullifier: deposit.nullifier,
  253. secret: deposit.secret,
  254. pathElements: pathElements,
  255. pathIndices: pathIndices,
  256. })
  257. const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
  258. const { proof } = websnarkUtils.toSolidityInput(proofData)
  259. const args = [
  260. toFixedHex(input.root),
  261. toFixedHex(input.nullifierHash),
  262. toFixedHex(input.recipient, 20),
  263. toFixedHex(input.relayer, 20),
  264. toFixedHex(input.fee),
  265. toFixedHex(input.refund),
  266. ]
  267. let { reason } = await tornado.withdraw(proof, ...args, { value: 1, from: relayer, gasPrice: '0' })
  268. .should.be.rejected
  269. reason.should.be.equal('Incorrect refund amount received by the contract')
  270. ;({ reason } = await tornado.withdraw(proof, ...args, {
  271. value: toBN(refund).mul(toBN(2)),
  272. from: relayer,
  273. gasPrice: '0',
  274. }).should.be.rejected)
  275. reason.should.be.equal('Incorrect refund amount received by the contract')
  276. })
  277. it.skip('should work with REAL USDT', async () => {
  278. // dont forget to specify your token in .env
  279. // USDT decimals is 6, so TOKEN_AMOUNT=1000000
  280. // and sent `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1)
  281. // run ganache as
  282. // ganache-cli --fork https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448@13147586 -d --keepAliveTimeout 20
  283. const deposit = generateDeposit()
  284. const user = accounts[4]
  285. const userBal = await usdtToken.balanceOf(user)
  286. console.log('userBal', userBal.toString())
  287. const senderBal = await usdtToken.balanceOf(sender)
  288. console.log('senderBal', senderBal.toString())
  289. tree.insert(deposit.commitment)
  290. await usdtToken.transfer(user, tokenDenomination, { from: sender })
  291. console.log('transfer done')
  292. const balanceUserBefore = await usdtToken.balanceOf(user)
  293. console.log('balanceUserBefore', balanceUserBefore.toString())
  294. await usdtToken.approve(tornado.address, tokenDenomination, { from: user })
  295. console.log('approve done')
  296. const allowanceUser = await usdtToken.allowance(user, tornado.address)
  297. console.log('allowanceUser', allowanceUser.toString())
  298. await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
  299. console.log('deposit done')
  300. const balanceUserAfter = await usdtToken.balanceOf(user)
  301. balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
  302. const { pathElements, pathIndices } = tree.path(0)
  303. // Circuit input
  304. const input = stringifyBigInts({
  305. // public
  306. root: tree.root(),
  307. nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
  308. relayer: operator,
  309. recipient,
  310. fee,
  311. refund,
  312. // private
  313. nullifier: deposit.nullifier,
  314. secret: deposit.secret,
  315. pathElements: pathElements,
  316. pathIndices: pathIndices,
  317. })
  318. const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
  319. const { proof } = websnarkUtils.toSolidityInput(proofData)
  320. const balanceTornadoBefore = await usdtToken.balanceOf(tornado.address)
  321. const balanceRelayerBefore = await usdtToken.balanceOf(relayer)
  322. const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
  323. const balanceReceiverBefore = await usdtToken.balanceOf(toFixedHex(recipient, 20))
  324. const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
  325. let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
  326. isSpent.should.be.equal(false)
  327. // Uncomment to measure gas usage
  328. // gas = await tornado.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
  329. // console.log('withdraw gas:', gas)
  330. const args = [
  331. toFixedHex(input.root),
  332. toFixedHex(input.nullifierHash),
  333. toFixedHex(input.recipient, 20),
  334. toFixedHex(input.relayer, 20),
  335. toFixedHex(input.fee),
  336. toFixedHex(input.refund),
  337. ]
  338. const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
  339. const balanceTornadoAfter = await usdtToken.balanceOf(tornado.address)
  340. const balanceRelayerAfter = await usdtToken.balanceOf(relayer)
  341. const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
  342. const balanceReceiverAfter = await usdtToken.balanceOf(toFixedHex(recipient, 20))
  343. const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
  344. const feeBN = toBN(fee.toString())
  345. balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
  346. balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
  347. ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
  348. balanceReceiverAfter.should.be.eq.BN(toBN(balanceReceiverBefore).add(toBN(tokenDenomination)))
  349. ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)).sub(feeBN))
  350. logs[0].event.should.be.equal('Withdrawal')
  351. logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
  352. logs[0].args.relayer.should.be.eq.BN(operator)
  353. logs[0].args.fee.should.be.eq.BN(feeBN)
  354. isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
  355. isSpent.should.be.equal(true)
  356. })
  357. it.skip('should work with REAL DAI', async () => {
  358. // dont forget to specify your token in .env
  359. // and send `tokenDenomination` to accounts[0] (0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1)
  360. // run ganache as
  361. // npx ganache-cli --fork https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448@13146218 -d --keepAliveTimeout 20
  362. const deposit = generateDeposit()
  363. const user = accounts[4]
  364. const userBal = await token.balanceOf(user)
  365. console.log('userBal', userBal.toString())
  366. const senderBal = await token.balanceOf(sender)
  367. console.log('senderBal', senderBal.toString())
  368. tree.insert(deposit.commitment)
  369. await token.transfer(user, tokenDenomination, { from: sender })
  370. console.log('transfer done')
  371. const balanceUserBefore = await token.balanceOf(user)
  372. console.log('balanceUserBefore', balanceUserBefore.toString())
  373. await token.approve(tornado.address, tokenDenomination, { from: user })
  374. console.log('approve done')
  375. await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
  376. console.log('deposit done')
  377. const balanceUserAfter = await token.balanceOf(user)
  378. balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
  379. const { pathElements, pathIndices } = tree.path(0)
  380. // Circuit input
  381. const input = stringifyBigInts({
  382. // public
  383. root: tree.root(),
  384. nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
  385. relayer: operator,
  386. recipient,
  387. fee,
  388. refund,
  389. // private
  390. nullifier: deposit.nullifier,
  391. secret: deposit.secret,
  392. pathElements: pathElements,
  393. pathIndices: pathIndices,
  394. })
  395. const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
  396. const { proof } = websnarkUtils.toSolidityInput(proofData)
  397. const balanceTornadoBefore = await token.balanceOf(tornado.address)
  398. const balanceRelayerBefore = await token.balanceOf(relayer)
  399. const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
  400. const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
  401. const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
  402. let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
  403. isSpent.should.be.equal(false)
  404. // Uncomment to measure gas usage
  405. // gas = await tornado.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' })
  406. // console.log('withdraw gas:', gas)
  407. const args = [
  408. toFixedHex(input.root),
  409. toFixedHex(input.nullifierHash),
  410. toFixedHex(input.recipient, 20),
  411. toFixedHex(input.relayer, 20),
  412. toFixedHex(input.fee),
  413. toFixedHex(input.refund),
  414. ]
  415. const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
  416. console.log('withdraw done')
  417. const balanceTornadoAfter = await token.balanceOf(tornado.address)
  418. const balanceRelayerAfter = await token.balanceOf(relayer)
  419. const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
  420. const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
  421. const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
  422. const feeBN = toBN(fee.toString())
  423. balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
  424. balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
  425. ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
  426. balanceReceiverAfter.should.be.eq.BN(toBN(balanceReceiverBefore).add(toBN(tokenDenomination)))
  427. ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)).sub(feeBN))
  428. logs[0].event.should.be.equal('Withdrawal')
  429. logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
  430. logs[0].args.relayer.should.be.eq.BN(operator)
  431. logs[0].args.fee.should.be.eq.BN(feeBN)
  432. isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
  433. isSpent.should.be.equal(true)
  434. })
  435. })
  436. afterEach(async () => {
  437. await revertSnapshot(snapshotId.result)
  438. // eslint-disable-next-line require-atomic-updates
  439. snapshotId = await takeSnapshot()
  440. tree = new MerkleTree(levels)
  441. })
  442. })