tornado-trees/test/tornadoTrees.test.js

300 lines
12 KiB
JavaScript

/* global ethers */
const { expect } = require('chai')
const { toFixedHex, poseidonHash2, randomBN } = require('../src/utils')
const MerkleTree = require('@tornado/fixed-merkle-tree')
const controller = require('../src/index')
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 = 8
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, tornadoTreesV1.address, {
depositsFrom: 1,
depositsStep: 1,
withdrawalsFrom: 2,
withdrawalsStep: 2,
})
await tornadoTrees.initialize(tornadoProxy.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, notes.length)))
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 () => {
const batchSize = 2 ** CHUNK_TREE_HEIGHT
for (let i = batchSize; i < batchSize + 2; 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, tornadoTreesV1.address, {
depositsFrom: 1,
depositsStep: 1,
withdrawalsFrom: 2,
withdrawalsStep: 2,
})
await newTornadoTrees.initialize(tornadoProxy.address, verifier.address)
// load first batchSize deposits
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 2 * `notes.length` new deposits on the new trees
for (let i = 0; i < notes.length; i++) {
await register(notes[i], newTornadoTrees, tornadoProxy)
}
for (let i = 0; i < notes.length; i++) {
await register(notes[i], newTornadoTrees, tornadoProxy)
}
// get 2 extra events from v1 tress
let events = notes.slice(batchSize).map((note) => ({
hash: toFixedHex(note.commitment),
instance: toFixedHex(note.instance, 20),
block: toFixedHex(note.depositBlock, 4),
}))
let registeredEvents = await newTornadoTrees.queryFilter(depositDataEventFilter)
registeredEvents = registeredEvents.slice(batchSize) // cut processed deposits from v1
events = events.concat(
registeredEvents.slice(0, batchSize - 2).map((e) => ({
hash: toFixedHex(e.args.hash),
instance: toFixedHex(e.args.instance, 20),
block: toFixedHex(e.args.block, 4),
})),
)
//
;({ 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())
events = registeredEvents.slice(batchSize - 2, 2 * batchSize - 2).map((e) => ({
hash: toFixedHex(e.args.hash),
instance: toFixedHex(e.args.instance, 20),
block: toFixedHex(e.args.block, 4),
}))
;({ 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)
}
})
})
})
})