tornado-trees/test/tornadoTrees.test.js
2021-02-05 23:28:51 +03:00

283 lines
11 KiB
JavaScript

/* global ethers */
const { expect } = require('chai')
const { toFixedHex, poseidonHash2, randomBN } = require('../src/utils')
const MerkleTree = require('fixed-merkle-tree')
const controller = require('../src/controller')
async function register(note, tornadoTrees, from) {
await tornadoTrees
.connect(from)
.register(
note.instance,
toFixedHex(note.commitment),
toFixedHex(note.nullifierHash),
note.depositBlock,
note.withdrawalBlock,
)
}
const levels = 20
const CHUNK_TREE_HEIGHT = 2
const instances = [
'0x1111000000000000000000000000000000001111',
'0x2222000000000000000000000000000000002222',
'0x3333000000000000000000000000000000003333',
'0x4444000000000000000000000000000000004444',
]
const blocks = ['0xaaaaaaaa', '0xbbbbbbbb', '0xcccccccc', '0xdddddddd']
describe('TornadoTrees', function () {
let tree
let operator
let tornadoProxy
let verifier
let tornadoTrees
let tornadoTreesV1
let notes
let depositDataEventFilter
const depositEvents = []
const withdrawalEvents = []
beforeEach(async function () {
tree = new MerkleTree(levels, [], { hashFunction: poseidonHash2 })
;[operator, tornadoProxy] = await ethers.getSigners()
const BatchTreeUpdateVerifier = await ethers.getContractFactory('BatchTreeUpdateVerifier')
verifier = await BatchTreeUpdateVerifier.deploy()
const TornadoTreesV1 = await ethers.getContractFactory('TornadoTreesV1Mock')
tornadoTreesV1 = await TornadoTreesV1.deploy(0, 0, tree.root(), tree.root())
notes = []
for (let i = 0; i < 2 ** CHUNK_TREE_HEIGHT; i++) {
notes[i] = {
instance: instances[i % instances.length],
depositBlock: blocks[i % blocks.length],
withdrawalBlock: 2 + i + i * 4 * 60 * 24,
commitment: randomBN(),
nullifierHash: randomBN(),
}
await register(notes[i], tornadoTreesV1, tornadoProxy)
depositEvents[i] = {
hash: toFixedHex(notes[i].commitment),
instance: toFixedHex(notes[i].instance, 20),
block: toFixedHex(notes[i].depositBlock, 4),
}
withdrawalEvents[i] = {
hash: toFixedHex(notes[i].nullifierHash),
instance: toFixedHex(notes[i].instance, 20),
block: toFixedHex(notes[i].withdrawalBlock, 4),
}
}
const TornadoTrees = await ethers.getContractFactory('TornadoTreesMock')
tornadoTrees = await TornadoTrees.deploy(
operator.address,
tornadoProxy.address,
tornadoTreesV1.address,
verifier.address,
)
depositDataEventFilter = tornadoTrees.filters.DepositData()
})
describe('#updateDepositTree', () => {
it('should check hash', async () => {
const { args } = controller.batchTreeUpdate(tree, depositEvents)
const solHash = await tornadoTrees.updateDepositTreeMock(...args.slice(1))
expect(solHash).to.be.equal(args[0])
})
it('should prove snark', async () => {
const { input, args } = controller.batchTreeUpdate(tree, depositEvents)
const proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
const updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
})
it('should work for non-empty tree', async () => {
let { input, args } = controller.batchTreeUpdate(tree, depositEvents)
let proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
let updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
//
for (let i = 0; i < notes.length; i++) {
await register(notes[i], tornadoTrees, tornadoProxy)
}
;({ input, args } = controller.batchTreeUpdate(tree, depositEvents))
proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
})
it('should work with events from contracts', async () => {
let { input, args } = controller.batchTreeUpdate(tree, depositEvents)
let proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
let updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
const migratedEvents = await tornadoTrees.queryFilter(depositDataEventFilter)
migratedEvents.forEach((e, i) => {
expect(e.args.index).to.be.equal(i)
})
//
for (let i = 0; i < notes.length; i++) {
await register(notes[i], tornadoTrees, tornadoProxy)
}
let registeredEvents = await tornadoTrees.queryFilter(depositDataEventFilter)
registeredEvents = registeredEvents.map((e) => ({
hash: toFixedHex(e.args.hash),
instance: toFixedHex(e.args.instance, 20),
block: toFixedHex(e.args.block, 4),
}))
;({ input, args } = controller.batchTreeUpdate(tree, registeredEvents.slice(0, 4)))
proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
})
// it('should work for batch+N filled v1 tree', async () => {
// for (let i = 4; i < 6; i++) {
// notes.push({
// instance: instances[i % instances.length],
// depositBlock: blocks[i % blocks.length],
// withdrawalBlock: 2 + i + i * 4 * 60 * 24,
// commitment: randomBN(),
// nullifierHash: randomBN(),
// })
// await register(notes[i], tornadoTreesV1, tornadoProxy)
// }
// const TornadoTrees = await ethers.getContractFactory('TornadoTreesMock')
// const newTornadoTrees = await TornadoTrees.deploy(
// operator.address,
// tornadoProxy.address,
// tornadoTreesV1.address,
// verifier.address,
// )
// let { input, args } = controller.batchTreeUpdate(tree, depositEvents)
// let proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
// await newTornadoTrees.updateDepositTree(proof, ...args)
// let updatedRoot = await newTornadoTrees.depositRoot()
// expect(updatedRoot).to.be.equal(tree.root())
// // register 6 new deposits for the new trees
// for (let i = 0; i < notes.length; i++) {
// await register(notes[i], newTornadoTrees, tornadoProxy)
// }
// // get 2 events from v1 tress
// const events = notes.slice(4).map((note) => ({
// hash: toFixedHex(note.commitment),
// instance: toFixedHex(note.instance, 20),
// block: toFixedHex(note.depositBlock, 4),
// }))
// const registeredEvents = await newTornadoTrees.queryFilter(depositDataEventFilter)
// console.log('registeredEvents', JSON.stringify(registeredEvents, null, 2))
// // events = events.concat(
// // registeredEvents.slice(0, 2).map((e) => ({
// // hash: toFixedHex(e.args.hash),
// // instance: toFixedHex(e.args.instance, 20),
// // block: toFixedHex(e.args.block, 4),
// // })),
// // )
// // console.log('events', events)
// // ;({ input, args } = controller.batchTreeUpdate(tree, events))
// // proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
// // await newTornadoTrees.updateDepositTree(proof, ...args)
// // updatedRoot = await newTornadoTrees.depositRoot()
// // expect(updatedRoot).to.be.equal(tree.root())
// })
it('should reject for partially filled tree')
it('should reject for outdated deposit root')
it('should reject for incorrect insert index')
it('should reject for overflows of newRoot')
it('should reject for invalid sha256 args')
})
describe('#getRegisteredDeposits', () => {
it('should work', async () => {
for (let i = 0; i < 2 ** CHUNK_TREE_HEIGHT; i++) {
notes[i] = {
instance: instances[i % instances.length],
depositBlock: blocks[i % blocks.length],
withdrawalBlock: 2 + i + i * 4 * 60 * 24,
commitment: randomBN(),
nullifierHash: randomBN(),
}
await register(notes[i], tornadoTrees, tornadoProxy)
}
const abi = new ethers.utils.AbiCoder()
const count = await tornadoTrees.depositsLength()
const _deposits = await tornadoTrees.getRegisteredDeposits()
expect(count).to.be.equal(notes.length * 2)
_deposits.forEach((hash, i) => {
if (i < notes.length) {
expect(hash).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000')
} else {
const index = i - notes.length
const encodedData = abi.encode(
['address', 'bytes32', 'uint256'],
[notes[index].instance, toFixedHex(notes[index].commitment), notes[index].depositBlock],
)
const leaf = ethers.utils.keccak256(encodedData)
expect(leaf).to.be.equal(hash)
}
})
// res.length.should.be.equal(1)
// res[0].should.be.true
// await tornadoTrees.updateRoots([note1DepositLeaf], [])
// res = await tornadoTrees.getRegisteredDeposits()
// res.length.should.be.equal(0)
// await registerDeposit(note2, tornadoTrees)
// res = await tornadoTrees.getRegisteredDeposits()
// // res[0].should.be.true
})
})
describe('#getRegisteredWithdrawals', () => {
it('should work', async () => {
for (let i = 0; i < 2 ** CHUNK_TREE_HEIGHT; i++) {
notes[i] = {
instance: instances[i % instances.length],
depositBlock: blocks[i % blocks.length],
withdrawalBlock: 2 + i + i * 4 * 60 * 24,
commitment: randomBN(),
nullifierHash: randomBN(),
}
await register(notes[i], tornadoTrees, tornadoProxy)
}
const abi = new ethers.utils.AbiCoder()
const count = await tornadoTrees.withdrawalsLength()
const _withdrawals = await tornadoTrees.getRegisteredWithdrawals()
expect(count).to.be.equal(notes.length * 2)
_withdrawals.forEach((hash, i) => {
if (i < notes.length) {
expect(hash).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000')
} else {
const index = i - notes.length
const encodedData = abi.encode(
['address', 'bytes32', 'uint256'],
[notes[index].instance, toFixedHex(notes[index].nullifierHash), notes[index].withdrawalBlock],
)
const leaf = ethers.utils.keccak256(encodedData)
expect(leaf).to.be.equal(hash)
}
})
})
})
})