2021-08-21 14:23:17 +02:00
|
|
|
const hre = require('hardhat')
|
2021-08-21 14:46:17 +02:00
|
|
|
const { ethers, waffle } = hre
|
|
|
|
const { loadFixture } = waffle
|
|
|
|
const { expect } = require('chai')
|
2021-06-16 02:31:31 +02:00
|
|
|
|
2021-09-26 18:22:19 +02:00
|
|
|
const { poseidonHash2 } = require('../src/utils')
|
2021-06-15 22:02:59 +02:00
|
|
|
const Utxo = require('../src/utxo')
|
2021-06-09 12:56:33 +02:00
|
|
|
|
|
|
|
const MERKLE_TREE_HEIGHT = 5
|
|
|
|
const MerkleTree = require('fixed-merkle-tree')
|
|
|
|
|
2021-07-22 16:01:22 +02:00
|
|
|
const { transaction, registerAndTransact } = require('../src/index')
|
|
|
|
const { Keypair } = require('../src/keypair')
|
2021-06-15 14:48:26 +02:00
|
|
|
|
2021-08-21 14:23:17 +02:00
|
|
|
describe('TornadoPool', function () {
|
|
|
|
this.timeout(20000)
|
2021-06-09 12:56:33 +02:00
|
|
|
|
2021-08-21 14:46:17 +02:00
|
|
|
async function deploy(contractName, ...args) {
|
|
|
|
const Factory = await ethers.getContractFactory(contractName)
|
|
|
|
const instance = await Factory.deploy(...args)
|
2021-08-21 14:54:24 +02:00
|
|
|
return instance.deployed()
|
2021-08-21 14:46:17 +02:00
|
|
|
}
|
2021-06-15 13:25:06 +02:00
|
|
|
|
2021-08-21 14:46:17 +02:00
|
|
|
async function fixture() {
|
|
|
|
const verifier2 = await deploy('Verifier2')
|
|
|
|
const verifier16 = await deploy('Verifier16')
|
2021-06-09 12:56:33 +02:00
|
|
|
|
|
|
|
const tree = new MerkleTree(MERKLE_TREE_HEIGHT, [], { hashFunction: poseidonHash2 })
|
|
|
|
|
2021-09-26 18:22:19 +02:00
|
|
|
/** @type {TornadoPool} */
|
|
|
|
const tornadoPool = await deploy('TornadoPool', verifier2.address, verifier16.address)
|
|
|
|
await tornadoPool.initialize(tree.root())
|
|
|
|
return { tornadoPool }
|
|
|
|
}
|
2021-08-05 09:29:49 +02:00
|
|
|
|
2021-09-26 18:22:19 +02:00
|
|
|
async function fixtureUpgradeable() {
|
|
|
|
const { tornadoPool } = await loadFixture(fixture)
|
|
|
|
const [, gov] = await ethers.getSigners()
|
|
|
|
const messenger = await deploy('MockOVM_CrossDomainMessenger', gov.address)
|
|
|
|
const proxy = await deploy(
|
|
|
|
'CrossChainUpgradeableProxy',
|
|
|
|
tornadoPool.address,
|
2021-09-07 11:46:43 +02:00
|
|
|
gov.address,
|
|
|
|
[],
|
|
|
|
messenger.address,
|
2021-08-21 14:54:24 +02:00
|
|
|
)
|
2021-08-05 09:29:49 +02:00
|
|
|
|
2021-09-26 18:22:19 +02:00
|
|
|
const TornadoPool = await ethers.getContractFactory('TornadoPool')
|
2021-09-07 11:46:43 +02:00
|
|
|
/** @type {TornadoPool} */
|
2021-09-26 18:22:19 +02:00
|
|
|
const tornadoPoolProxied = TornadoPool.attach(proxy.address)
|
|
|
|
await tornadoPoolProxied.initialize(await tornadoPool.currentRoot())
|
2021-08-05 09:29:49 +02:00
|
|
|
|
2021-09-26 18:22:19 +02:00
|
|
|
return { tornadoPool: tornadoPoolProxied, proxy, gov, messenger }
|
2021-08-21 14:46:17 +02:00
|
|
|
}
|
2021-06-09 12:56:33 +02:00
|
|
|
|
2021-08-05 09:29:49 +02:00
|
|
|
describe('Upgradeability tests', () => {
|
|
|
|
it('admin should be gov', async () => {
|
2021-09-26 18:22:19 +02:00
|
|
|
const { proxy, messenger, gov } = await loadFixture(fixtureUpgradeable)
|
2021-08-05 09:29:49 +02:00
|
|
|
const { data } = await proxy.populateTransaction.admin()
|
|
|
|
const { result } = await messenger.callStatic.execute(proxy.address, data)
|
|
|
|
expect('0x' + result.slice(26)).to.be.equal(gov.address.toLowerCase())
|
|
|
|
})
|
|
|
|
|
|
|
|
it('non admin cannot call', async () => {
|
2021-09-26 18:22:19 +02:00
|
|
|
const { proxy } = await loadFixture(fixtureUpgradeable)
|
2021-09-07 11:46:43 +02:00
|
|
|
await expect(proxy.admin()).to.be.revertedWith(
|
|
|
|
"Transaction reverted: function selector was not recognized and there's no fallback function",
|
|
|
|
)
|
2021-08-05 09:29:49 +02:00
|
|
|
})
|
|
|
|
})
|
2021-06-09 12:56:33 +02:00
|
|
|
|
2021-06-16 01:50:06 +02:00
|
|
|
it('encrypt -> decrypt should work', () => {
|
|
|
|
const data = Buffer.from([0xff, 0xaa, 0x00, 0x01])
|
2021-06-15 22:02:59 +02:00
|
|
|
const keypair = new Keypair()
|
|
|
|
|
2021-06-16 01:50:06 +02:00
|
|
|
const ciphertext = keypair.encrypt(data)
|
|
|
|
const result = keypair.decrypt(ciphertext)
|
|
|
|
expect(result).to.be.deep.equal(data)
|
2021-06-15 22:02:59 +02:00
|
|
|
})
|
|
|
|
|
2021-08-16 21:17:07 +02:00
|
|
|
it('constants check', async () => {
|
2021-08-21 14:46:17 +02:00
|
|
|
const { tornadoPool } = await loadFixture(fixture)
|
2021-08-16 21:17:07 +02:00
|
|
|
const maxFee = await tornadoPool.MAX_FEE()
|
|
|
|
const maxExtAmount = await tornadoPool.MAX_EXT_AMOUNT()
|
|
|
|
const fieldSize = await tornadoPool.FIELD_SIZE()
|
|
|
|
|
|
|
|
expect(maxExtAmount.add(maxFee)).to.be.lt(fieldSize)
|
|
|
|
})
|
|
|
|
|
2021-07-22 16:01:22 +02:00
|
|
|
it('should register and deposit', async function () {
|
2021-08-21 14:46:17 +02:00
|
|
|
let { tornadoPool } = await loadFixture(fixture)
|
2021-09-07 11:46:43 +02:00
|
|
|
const sender = (await ethers.getSigners())[0]
|
2021-08-21 14:46:17 +02:00
|
|
|
|
2021-07-22 16:01:22 +02:00
|
|
|
// Alice deposits into tornado pool
|
|
|
|
const aliceDepositAmount = 1e7
|
|
|
|
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount })
|
|
|
|
|
|
|
|
const backupAccount = new Keypair()
|
|
|
|
|
|
|
|
const bufferPrivateKey = Buffer.from(aliceDepositUtxo.keypair.privkey)
|
|
|
|
const packedPrivateKeyData = backupAccount.encrypt(bufferPrivateKey)
|
|
|
|
|
|
|
|
tornadoPool = tornadoPool.connect(sender)
|
|
|
|
await registerAndTransact({
|
|
|
|
tornadoPool,
|
|
|
|
packedPrivateKeyData,
|
|
|
|
outputs: [aliceDepositUtxo],
|
|
|
|
poolAddress: aliceDepositUtxo.keypair.address(),
|
|
|
|
})
|
|
|
|
|
|
|
|
const filter = tornadoPool.filters.NewCommitment()
|
|
|
|
const fromBlock = await ethers.provider.getBlock()
|
|
|
|
const events = await tornadoPool.queryFilter(filter, fromBlock.number)
|
|
|
|
|
|
|
|
let aliceReceiveUtxo
|
|
|
|
try {
|
|
|
|
aliceReceiveUtxo = Utxo.decrypt(
|
|
|
|
aliceDepositUtxo.keypair,
|
|
|
|
events[0].args.encryptedOutput,
|
|
|
|
events[0].args.index,
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
// we try to decrypt another output here because it shuffles outputs before sending to blockchain
|
|
|
|
aliceReceiveUtxo = Utxo.decrypt(
|
|
|
|
aliceDepositUtxo.keypair,
|
|
|
|
events[1].args.encryptedOutput,
|
|
|
|
events[1].args.index,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
expect(aliceReceiveUtxo.amount).to.be.equal(aliceDepositAmount)
|
|
|
|
|
|
|
|
const filterRegister = tornadoPool.filters.PublicKey(sender.address)
|
|
|
|
const filterFromBlock = await ethers.provider.getBlock()
|
|
|
|
const registerEvents = await tornadoPool.queryFilter(filterRegister, filterFromBlock.number)
|
|
|
|
|
|
|
|
const [registerEvent] = registerEvents.sort((a, b) => a.blockNumber - b.blockNumber).slice(-1)
|
|
|
|
|
|
|
|
expect(registerEvent.args.key).to.be.equal(aliceDepositUtxo.keypair.address())
|
|
|
|
|
|
|
|
const accountFilter = tornadoPool.filters.EncryptedAccount(sender.address)
|
|
|
|
const accountFromBlock = await ethers.provider.getBlock()
|
|
|
|
const accountEvents = await tornadoPool.queryFilter(accountFilter, accountFromBlock.number)
|
|
|
|
|
|
|
|
const [accountEvent] = accountEvents.sort((a, b) => a.blockNumber - b.blockNumber).slice(-1)
|
|
|
|
|
|
|
|
const privateKey = backupAccount.decrypt(accountEvent.args.account)
|
|
|
|
|
|
|
|
expect(bufferPrivateKey.toString('hex')).to.be.equal(privateKey.toString('hex'))
|
|
|
|
})
|
|
|
|
|
2021-06-15 22:02:59 +02:00
|
|
|
it('should deposit, transact and withdraw', async function () {
|
2021-08-21 14:46:17 +02:00
|
|
|
const { tornadoPool } = await loadFixture(fixture)
|
|
|
|
|
2021-06-15 22:02:59 +02:00
|
|
|
// Alice deposits into tornado pool
|
2021-06-16 01:50:06 +02:00
|
|
|
const aliceDepositAmount = 1e7
|
|
|
|
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount })
|
|
|
|
await transaction({ tornadoPool, outputs: [aliceDepositUtxo] })
|
2021-06-15 22:02:59 +02:00
|
|
|
|
2021-06-16 01:50:06 +02:00
|
|
|
// Bob gives Alice address to send some eth inside the shielded pool
|
2021-06-16 02:31:31 +02:00
|
|
|
const bobKeypair = new Keypair() // contains private and public keys
|
|
|
|
const bobAddress = bobKeypair.address() // contains only public key
|
2021-06-15 22:02:59 +02:00
|
|
|
|
2021-06-16 01:50:06 +02:00
|
|
|
// Alice sends some funds to Bob
|
|
|
|
const bobSendAmount = 3e6
|
|
|
|
const bobSendUtxo = new Utxo({ amount: bobSendAmount, keypair: Keypair.fromString(bobAddress) })
|
2021-06-16 02:31:31 +02:00
|
|
|
const aliceChangeUtxo = new Utxo({
|
|
|
|
amount: aliceDepositAmount - bobSendAmount,
|
|
|
|
keypair: aliceDepositUtxo.keypair,
|
|
|
|
})
|
2021-06-16 01:50:06 +02:00
|
|
|
await transaction({ tornadoPool, inputs: [aliceDepositUtxo], outputs: [bobSendUtxo, aliceChangeUtxo] })
|
2021-06-15 22:02:59 +02:00
|
|
|
|
2021-06-16 01:50:06 +02:00
|
|
|
// Bob parses chain to detect incoming funds
|
|
|
|
const filter = tornadoPool.filters.NewCommitment()
|
2021-06-15 22:02:59 +02:00
|
|
|
const fromBlock = await ethers.provider.getBlock()
|
2021-06-16 01:50:06 +02:00
|
|
|
const events = await tornadoPool.queryFilter(filter, fromBlock.number)
|
2021-06-21 19:05:10 +02:00
|
|
|
let bobReceiveUtxo
|
|
|
|
try {
|
|
|
|
bobReceiveUtxo = Utxo.decrypt(bobKeypair, events[0].args.encryptedOutput, events[0].args.index)
|
|
|
|
} catch (e) {
|
|
|
|
// we try to decrypt another output here because it shuffles outputs before sending to blockchain
|
|
|
|
bobReceiveUtxo = Utxo.decrypt(bobKeypair, events[1].args.encryptedOutput, events[1].args.index)
|
|
|
|
}
|
2021-06-16 01:50:06 +02:00
|
|
|
expect(bobReceiveUtxo.amount).to.be.equal(bobSendAmount)
|
|
|
|
|
2021-06-16 10:28:39 +02:00
|
|
|
// Bob withdraws a part of his funds from the shielded pool
|
2021-06-16 01:50:06 +02:00
|
|
|
const bobWithdrawAmount = 2e6
|
|
|
|
const bobEthAddress = '0xDeaD00000000000000000000000000000000BEEf'
|
|
|
|
const bobChangeUtxo = new Utxo({ amount: bobSendAmount - bobWithdrawAmount, keypair: bobKeypair })
|
2021-06-16 02:31:31 +02:00
|
|
|
await transaction({
|
|
|
|
tornadoPool,
|
|
|
|
inputs: [bobReceiveUtxo],
|
|
|
|
outputs: [bobChangeUtxo],
|
|
|
|
recipient: bobEthAddress,
|
|
|
|
})
|
2021-06-16 01:50:06 +02:00
|
|
|
|
|
|
|
const bobBalance = await ethers.provider.getBalance(bobEthAddress)
|
|
|
|
expect(bobBalance).to.be.equal(bobWithdrawAmount)
|
2021-06-09 12:56:33 +02:00
|
|
|
})
|
|
|
|
|
2021-06-15 13:47:54 +02:00
|
|
|
it('should work with 16 inputs', async function () {
|
2021-08-21 14:46:17 +02:00
|
|
|
const { tornadoPool } = await loadFixture(fixture)
|
2021-06-16 01:50:06 +02:00
|
|
|
await transaction({ tornadoPool, inputs: [new Utxo(), new Utxo(), new Utxo()] })
|
2021-06-15 13:47:54 +02:00
|
|
|
})
|
2021-06-09 12:56:33 +02:00
|
|
|
})
|