tornado-nova/test/full.test.js

278 lines
9.9 KiB
JavaScript
Raw Normal View History

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-09-29 21:39:30 +02:00
const { utils } = ethers
2021-06-16 02:31:31 +02:00
2021-06-15 22:02:59 +02:00
const Utxo = require('../src/utxo')
2021-09-30 17:34:07 +02:00
const { transaction, registerAndTransact, prepareTransaction } = require('../src/index')
2021-07-22 16:01:22 +02:00
const { Keypair } = require('../src/keypair')
const { encodeDataForBridge } = require('./utils')
2021-06-15 14:48:26 +02:00
2021-09-26 18:14:05 +02:00
const MERKLE_TREE_HEIGHT = 5
const l1ChainId = 1
const MINIMUM_WITHDRAWAL_AMOUNT = utils.parseEther(process.env.MINIMUM_WITHDRAWAL_AMOUNT || '0.05')
const MAXIMUM_DEPOSIT_AMOUNT = utils.parseEther(process.env.MAXIMUM_DEPOSIT_AMOUNT || '1')
2021-09-26 18:14:05 +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-08-21 14:46:17 +02:00
async function fixture() {
2021-09-26 18:14:05 +02:00
require('../scripts/compileHasher')
2021-09-30 17:34:07 +02:00
const [sender, gov, l1Unwrapper] = await ethers.getSigners()
2021-08-21 14:46:17 +02:00
const verifier2 = await deploy('Verifier2')
const verifier16 = await deploy('Verifier16')
2021-09-26 18:14:05 +02:00
const hasher = await deploy('Hasher')
2021-09-29 21:39:30 +02:00
const token = await deploy('PermittableToken', 'Wrapped ETH', 'WETH', 18, l1ChainId)
2021-09-29 21:39:30 +02:00
await token.mint(sender.address, utils.parseEther('10000'))
const amb = await deploy('MockAMB', gov.address, l1ChainId)
2021-09-29 21:39:30 +02:00
const omniBridge = await deploy('MockOmniBridge', amb.address)
2021-09-26 18:22:19 +02:00
/** @type {TornadoPool} */
2021-10-05 14:12:39 +02:00
const tornadoPoolImpl = await deploy(
2021-09-26 18:14:05 +02:00
'TornadoPool',
verifier2.address,
verifier16.address,
MERKLE_TREE_HEIGHT,
hasher.address,
2021-09-29 21:39:30 +02:00
token.address,
omniBridge.address,
2021-09-30 17:34:07 +02:00
l1Unwrapper.address,
gov.address,
2021-10-12 17:50:25 +02:00
l1ChainId,
2021-09-26 18:14:05 +02:00
)
2021-08-05 09:29:49 +02:00
2021-10-19 10:09:04 +02:00
const { data } = await tornadoPoolImpl.populateTransaction.initialize(
MINIMUM_WITHDRAWAL_AMOUNT,
MAXIMUM_DEPOSIT_AMOUNT,
)
2021-09-26 18:22:19 +02:00
const proxy = await deploy(
'CrossChainUpgradeableProxy',
2021-10-05 14:12:39 +02:00
tornadoPoolImpl.address,
2021-09-07 11:46:43 +02:00
gov.address,
2021-10-19 10:09:04 +02:00
data,
2021-10-05 11:08:04 +02:00
amb.address,
l1ChainId,
2021-08-21 14:54:24 +02:00
)
2021-08-05 09:29:49 +02:00
2021-10-19 10:09:04 +02:00
const tornadoPool = tornadoPoolImpl.attach(proxy.address)
2021-08-05 09:29:49 +02:00
2021-10-05 14:12:39 +02:00
await token.approve(tornadoPool.address, utils.parseEther('10000'))
return { tornadoPool, token, proxy, omniBridge, amb, gov }
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-10-05 14:12:39 +02:00
const { proxy, amb, gov } = await loadFixture(fixture)
2021-08-05 09:29:49 +02:00
const { data } = await proxy.populateTransaction.admin()
2021-10-05 11:08:04 +02:00
const { result } = await amb.callStatic.execute(proxy.address, data)
2021-08-05 09:29:49 +02:00
expect('0x' + result.slice(26)).to.be.equal(gov.address.toLowerCase())
})
it('non admin cannot call', async () => {
2021-10-05 14:12:39 +02:00
const { proxy } = await loadFixture(fixture)
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-10-12 17:50:25 +02:00
it('should configure', async () => {
const { tornadoPool, amb } = await loadFixture(fixture)
const newWithdrawalLimit = utils.parseEther('0.01337')
const newDepositLimit = utils.parseEther('1337')
const { data } = await tornadoPool.populateTransaction.configureLimits(
newWithdrawalLimit,
newDepositLimit,
)
await amb.execute(tornadoPool.address, data)
expect(await tornadoPool.maximumDepositAmount()).to.be.equal(newDepositLimit)
expect(await tornadoPool.minimalWithdrawalAmount()).to.be.equal(newWithdrawalLimit)
})
2021-08-05 09:29:49 +02:00
})
2021-06-09 12:56:33 +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()
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 })
tornadoPool = tornadoPool.connect(sender)
await registerAndTransact({
tornadoPool,
outputs: [aliceDepositUtxo],
2021-10-04 18:28:08 +02:00
account: {
owner: sender.address,
publicKey: aliceDepositUtxo.keypair.address(),
},
2021-07-22 16:01:22 +02:00
})
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())
})
2021-06-15 22:02:59 +02:00
it('should deposit, transact and withdraw', async function () {
2021-09-29 21:39:30 +02:00
const { tornadoPool, token } = await loadFixture(fixture)
2021-08-21 14:46:17 +02:00
2021-06-15 22:02:59 +02:00
// Alice deposits into tornado pool
const aliceDepositAmount = utils.parseEther('0.1')
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount })
await transaction({ tornadoPool, outputs: [aliceDepositUtxo] })
2021-06-15 22:02:59 +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
// Alice sends some funds to Bob
const bobSendAmount = utils.parseEther('0.06')
const bobSendUtxo = new Utxo({ amount: bobSendAmount, keypair: Keypair.fromString(bobAddress) })
2021-06-16 02:31:31 +02:00
const aliceChangeUtxo = new Utxo({
amount: aliceDepositAmount.sub(bobSendAmount),
2021-06-16 02:31:31 +02:00
keypair: aliceDepositUtxo.keypair,
})
await transaction({ tornadoPool, inputs: [aliceDepositUtxo], outputs: [bobSendUtxo, aliceChangeUtxo] })
2021-06-15 22:02:59 +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()
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)
}
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
const bobWithdrawAmount = utils.parseEther('0.05')
const bobEthAddress = '0xDeaD00000000000000000000000000000000BEEf'
const bobChangeUtxo = new Utxo({ amount: bobSendAmount.sub(bobWithdrawAmount), keypair: bobKeypair })
2021-06-16 02:31:31 +02:00
await transaction({
tornadoPool,
inputs: [bobReceiveUtxo],
outputs: [bobChangeUtxo],
recipient: bobEthAddress,
})
2021-09-29 21:39:30 +02:00
const bobBalance = await token.balanceOf(bobEthAddress)
expect(bobBalance).to.be.equal(bobWithdrawAmount)
2021-06-09 12:56:33 +02:00
})
2021-09-30 17:34:07 +02:00
it('should deposit from L1 and withdraw to L1', async function () {
const { tornadoPool, token, omniBridge } = await loadFixture(fixture)
2021-10-05 14:12:39 +02:00
const aliceKeypair = new Keypair() // contains private and public keys
2021-09-30 17:34:07 +02:00
// Alice deposits into tornado pool
const aliceDepositAmount = utils.parseEther('0.07')
2021-10-05 14:12:39 +02:00
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount, keypair: aliceKeypair })
2021-09-30 17:34:07 +02:00
const { args, extData } = await prepareTransaction({
tornadoPool,
outputs: [aliceDepositUtxo],
})
2021-10-05 14:12:39 +02:00
const onTokenBridgedData = encodeDataForBridge({
proof: args,
2021-10-04 18:28:08 +02:00
extData,
2021-10-05 14:12:39 +02:00
})
2021-09-30 17:34:07 +02:00
const onTokenBridgedTx = await tornadoPool.populateTransaction.onTokenBridged(
token.address,
aliceDepositUtxo.amount,
onTokenBridgedData,
)
// emulating bridge. first it sends tokens and then calls onTokenBridged method
await token.transfer(tornadoPool.address, aliceDepositAmount)
await omniBridge.execute(tornadoPool.address, onTokenBridgedTx.data)
// withdraws a part of his funds from the shielded pool
const aliceWithdrawAmount = utils.parseEther('0.06')
2021-09-30 17:34:07 +02:00
const recipient = '0xDeaD00000000000000000000000000000000BEEf'
const aliceChangeUtxo = new Utxo({
amount: aliceDepositAmount.sub(aliceWithdrawAmount),
2021-09-30 17:34:07 +02:00
keypair: aliceKeypair,
})
await transaction({
tornadoPool,
inputs: [aliceDepositUtxo],
outputs: [aliceChangeUtxo],
recipient: recipient,
isL1Withdrawal: true,
})
const recipientBalance = await token.balanceOf(recipient)
expect(recipientBalance).to.be.equal(0)
const omniBridgeBalance = await token.balanceOf(omniBridge.address)
expect(omniBridgeBalance).to.be.equal(aliceWithdrawAmount)
})
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)
const aliceDepositAmount = utils.parseEther('0.07')
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount })
await transaction({
tornadoPool,
inputs: [new Utxo(), new Utxo(), new Utxo()],
outputs: [aliceDepositUtxo],
})
2021-06-15 13:47:54 +02:00
})
2021-06-09 12:56:33 +02:00
})