diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 736016e6f..9d780e96d 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -38,6 +38,11 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { mapping(uint256 => bool) public edgeExistsForChain; Edge[] public edgeList; + // map to store chainID => (rootIndex => root) to track neighbor histories + mapping(uint256 => mapping(uint32 => bytes32)) public neighborRoots; + // map to store the current historical root index for a chainID + mapping(uint256 => uint32) public currentNeighborRootIndex; + // map to store used nullifier hashes mapping(bytes32 => bool) public nullifierHashes; // map to store all commitments to prevent accidental deposits with the same commitment @@ -45,9 +50,8 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { // map to store the history of root updates mapping(uint => bytes32[]) public rootHistory; - // pruning length for root history (i.e. the # of history items to persist) - uint pruningLength; - // the latest history index that represents the next index to store history at % pruningLength + + // the latest history index that represents the next index to store history uint latestHistoryIndex; // currency events @@ -77,8 +81,6 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { verifier = _verifier; denomination = _denomination; chainID = _chainID; - // TODO: Handle pruning length in function signature - pruningLength = 100; latestHistoryIndex = 0; // TODO: Parameterize max roots (length of array should be max roots) rootHistory[latestHistoryIndex] = new bytes32[](1); @@ -111,28 +113,25 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { */ function withdraw( bytes calldata _proof, - bytes32 _root, + bytes calldata _roots, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund ) external payable nonReentrant { + bytes32[2] memory roots = abi.decode(_roots, (bytes32[2])); require(_fee <= denomination, "Fee exceeds transfer value"); require(!nullifierHashes[_nullifierHash], "The note has been already spent"); - require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one + require(isKnownRoot(roots[0]), "Cannot find your merkle root"); + require(roots.length >= edgeList.length + 1, "Incorrect root array length"); + for (uint i = 0; i < edgeList.length; i++) { + Edge memory _edge = edgeList[i]; + require(isKnownNeighborRoot(_edge.chainID, roots[i+1]), "Neighbor root not found"); + } address rec = address(_recipient); address rel = address(_relayer); - bytes32[1] memory neighbors = getLatestNeighborRoots(); - // console.log(uint256(_nullifierHash)); - // console.log(uint256(uint160(rec))); - // console.log(uint256(uint160(rel))); - // console.log(_fee); - // console.log(_refund); - // console.log(uint256(chainID)); - // console.log(uint256(_root)); - // console.log(uint256(neighbors[0])); - // console.logBytes(_proof); + uint256[8] memory inputs; inputs[0] = uint256(_nullifierHash); inputs[1] = uint256(uint160(rec)); @@ -140,8 +139,8 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { inputs[3] = uint256(_fee); inputs[4] = uint256(_refund); inputs[5] = uint256(chainID); - inputs[6] = uint256(_root); - inputs[7] = uint256(neighbors[0]); + inputs[6] = uint256(roots[0]); + inputs[7] = uint256(roots[1]); bytes memory encodedInputs = abi.encodePacked(inputs); require(verify(_proof, encodedInputs), "Invalid withdraw proof"); @@ -213,6 +212,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { } } } + /** @dev */ function getLatestNeighborRoots() public view returns (bytes32[1] memory roots) { for (uint256 i = 0; i < 1; i++) { @@ -224,6 +224,25 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { } } + /** @dev */ + function isKnownNeighborRoot(uint256 neighborChainID, bytes32 _root) public view returns (bool) { + if (_root == 0) { + return false; + } + uint32 _currentRootIndex = currentNeighborRootIndex[neighborChainID]; + uint32 i = _currentRootIndex; + do { + if (_root == neighborRoots[neighborChainID][i]) { + return true; + } + if (i == 0) { + i = ROOT_HISTORY_SIZE; + } + i--; + } while (i != _currentRootIndex); + return false; + } + modifier onlyAdmin() { require(msg.sender == admin, 'sender is not the admin'); _; diff --git a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol index cfe8cca5a..807d3c816 100644 --- a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol +++ b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol @@ -35,15 +35,6 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { return edgeExistsForChain[_chainID]; } - function recordHistory() override external { - // add a new historical record by snapshotting the Anchor's current neighbors - bytes32[1] memory history = getLatestNeighborRoots(); - rootHistory[latestHistoryIndex] = history; - // set the next history index modulo pruning length - latestHistoryIndex = latestHistoryIndex % pruningLength; - emit RootHistoryRecorded(block.timestamp, history); - } - function addEdge( uint256 sourceChainID, bytes32 root, @@ -59,11 +50,13 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { }); edgeList.push(edge); edgeIndex[sourceChainID] = index; + // add to root histories + uint32 neighborRootIndex = 0; + neighborRoots[sourceChainID][neighborRootIndex] = root; emit EdgeAddition(sourceChainID, height, root); // emit update event bytes32[1] memory neighbors = getLatestNeighborRoots(); emit RootHistoryUpdate(block.timestamp, neighbors); - } function updateEdge( @@ -80,6 +73,10 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { root: root, height: height }); + // add to root histories + uint32 neighborRootIndex = (currentNeighborRootIndex[sourceChainID] + 1) % ROOT_HISTORY_SIZE; + currentNeighborRootIndex[sourceChainID] = neighborRootIndex; + neighborRoots[sourceChainID][neighborRootIndex] = root; emit EdgeUpdate(sourceChainID, height, root); // emit update event bytes32[1] memory neighbors = getLatestNeighborRoots(); diff --git a/contracts/interfaces/ILinkableAnchor.sol b/contracts/interfaces/ILinkableAnchor.sol index 58ed77feb..584f6dd2e 100644 --- a/contracts/interfaces/ILinkableAnchor.sol +++ b/contracts/interfaces/ILinkableAnchor.sol @@ -8,7 +8,6 @@ pragma solidity ^0.8.0; interface ILinkableAnchor { function setHandler(address _handler) external; function setBridge(address _bridge) external; - function recordHistory() external; function hasEdge(uint256 _chainID) external view returns (bool); function addEdge( uint256 sourceChainID, @@ -20,4 +19,4 @@ interface ILinkableAnchor { bytes32 root, uint256 height ) external payable; -} +} \ No newline at end of file diff --git a/test/anchor/anchor.js b/test/anchor/anchor.js index 120c02e93..fca412780 100644 --- a/test/anchor/anchor.js +++ b/test/anchor/anchor.js @@ -17,19 +17,11 @@ const { NATIVE_AMOUNT, MERKLE_TREE_HEIGHT } = process.env const snarkjs = require('snarkjs') const bigInt = require('big-integer'); const BN = require('bn.js'); -const crypto = require('crypto') const circomlib = require('circomlib'); const F = require('circomlib').babyJub.F; const Scalar = require("ffjavascript").Scalar; const helpers = require('../helpers'); -const utils = require("ffjavascript").utils; -const { - leBuff2int, - leInt2Buff, - stringifyBigInts, -} = utils; -const PoseidonHasher = require('../../lib/Poseidon'); const MerkleTree = require('../../lib/MerkleTree'); function bigNumberToPaddedBytes(num, digits = 32) { @@ -40,27 +32,8 @@ function bigNumberToPaddedBytes(num, digits = 32) { return "0x" + n; } -const poseidonHasher = new PoseidonHasher(); -const rbigint = (nbytes) => leBuff2int(crypto.randomBytes(nbytes)) -const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0] -const toFixedHex = (number, length = 32) => - '0x' + - BigInt(`${number}`) - .toString(16) - .padStart(length * 2, '0') -const getRandomRecipient = () => rbigint(20) - -function generateDeposit(targetChainID = 0) { - let deposit = { - chainID: BigInt(targetChainID), - secret: rbigint(31), - nullifier: rbigint(31), - } - deposit.commitment = poseidonHasher.hash3([deposit.chainID, deposit.nullifier, deposit.secret]); - deposit.nullifierHash = poseidonHasher.hash(null, deposit.nullifier, deposit.nullifier); - return deposit -} +const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0] contract('AnchorPoseidon2', (accounts) => { let anchor @@ -72,7 +45,7 @@ contract('AnchorPoseidon2', (accounts) => { let tree const fee = BigInt((new BN(`${NATIVE_AMOUNT}`).shrn(1)).toString()) || BigInt((new BN(`${1e17}`)).toString()) const refund = BigInt((new BN('0')).toString()) - const recipient = getRandomRecipient() + const recipient = helpers.getRandomRecipient() const relayer = accounts[1] let verifier; let tokenDenomination = '1000000000000000000' // 1 ether @@ -143,7 +116,7 @@ contract('AnchorPoseidon2', (accounts) => { describe('#deposit', () => { it('should emit event', async () => { - let commitment = toFixedHex(42); + let commitment = helpers.toFixedHex(42); await token.approve(anchor.address, tokenDenomination) let { logs } = await anchor.deposit(commitment, { from: sender }) @@ -156,7 +129,7 @@ contract('AnchorPoseidon2', (accounts) => { }) it('should throw if there is a such commitment', async () => { - const commitment = toFixedHex(42) + const commitment = helpers.toFixedHex(42) await token.approve(anchor.address, tokenDenomination) await TruffleAssert.passes(anchor.deposit(commitment, { from: sender })); await TruffleAssert.reverts( @@ -169,7 +142,7 @@ contract('AnchorPoseidon2', (accounts) => { // Use Node version >=12 describe('snark proof verification on js side', () => { it('should detect tampering', async () => { - const deposit = generateDeposit(chainID); + const deposit = helpers.generateDeposit(chainID); await tree.insert(deposit.commitment); const { root, path_elements, path_index } = await tree.path(0); const roots = [root, 0]; @@ -239,8 +212,8 @@ contract('AnchorPoseidon2', (accounts) => { }) describe('#withdraw', () => { - it.only('should work', async () => { - const deposit = generateDeposit(chainID); + it('should work', async () => { + const deposit = helpers.generateDeposit(chainID); const user = accounts[4] await tree.insert(deposit.commitment) @@ -253,7 +226,7 @@ contract('AnchorPoseidon2', (accounts) => { // let gas = await anchor.deposit.estimateGas(toBN(deposit.commitment.toString()), { value, from: user }) // console.log('deposit gas:', gas) await TruffleAssert.passes(token.approve(anchor.address, tokenDenomination, { from: user })); - await TruffleAssert.passes(anchor.deposit(toFixedHex(deposit.commitment), { from: user })); + await TruffleAssert.passes(anchor.deposit(helpers.toFixedHex(deposit.commitment), { from: user })); const balanceUserAfterDeposit = await token.balanceOf(user) const balanceAnchorAfterDeposit = await token.balanceOf(anchor.address); console.log('balanceUserAfterDeposit: ', balanceUserAfterDeposit.toString()); @@ -296,21 +269,21 @@ contract('AnchorPoseidon2', (accounts) => { const balanceRelayerBefore = await token.balanceOf(relayer) const balanceOperatorBefore = await token.balanceOf(operator) - const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20)) + const balanceReceiverBefore = await token.balanceOf(helpers.toFixedHex(recipient, 20)) - let isSpent = await anchor.isSpent(toFixedHex(input.nullifierHash)) + let isSpent = await anchor.isSpent(helpers.toFixedHex(input.nullifierHash)) assert.strictEqual(isSpent, false) // Uncomment to measure gas usage // gas = await anchor.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' }) // console.log('withdraw gas:', gas) const args = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.createRootsBytes(input.roots), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ]; const result = await helpers.groth16ExportSolidityCallData(proof, publicSignals); @@ -338,12 +311,11 @@ contract('AnchorPoseidon2', (accounts) => { ] .map(elt => elt.substr(2)) .join(''); - const { logs } = await anchor.withdraw(`0x${proofEncoded}`, ...args, { from: relayer, gasPrice: '0' }); const balanceAnchorAfter = await token.balanceOf(anchor.address) const balanceRelayerAfter = await token.balanceOf(relayer) const balanceOperatorAfter = await token.balanceOf(operator) - const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20)) + const balanceReceiverAfter = await token.balanceOf(helpers.toFixedHex(recipient, 20)) const feeBN = toBN(fee.toString()) console.log('balanceAnchorAfter: ', balanceAnchorAfter.toString()); console.log('balanceRelayerAfter: ', balanceRelayerAfter.toString()); @@ -356,18 +328,18 @@ contract('AnchorPoseidon2', (accounts) => { assert.strictEqual(balanceReceiverAfter.toString(), toBN(balanceReceiverBefore).add(toBN(value)).sub(feeBN).toString()) assert.strictEqual(logs[0].event, 'Withdrawal') - assert.strictEqual(logs[0].args.nullifierHash, toFixedHex(input.nullifierHash)) + assert.strictEqual(logs[0].args.nullifierHash, helpers.toFixedHex(input.nullifierHash)) assert.strictEqual(logs[0].args.relayer, operator); assert.strictEqual(logs[0].args.fee.toString(), feeBN.toString()); - isSpent = await anchor.isSpent(toFixedHex(input.nullifierHash)) + isSpent = await anchor.isSpent(helpers.toFixedHex(input.nullifierHash)) assert(isSpent); }) it('should prevent double spend', async () => { - const deposit = generateDeposit(); + const deposit = helpers.generateDeposit(); await tree.insert(deposit.commitment); await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit.commitment), { from: sender }); + await anchor.deposit(helpers.toFixedHex(deposit.commitment), { from: sender }); const { root, path_elements, path_index } = await tree.path(0); @@ -400,12 +372,12 @@ contract('AnchorPoseidon2', (accounts) => { publicSignals = res.publicSignals; const args = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ]; proofHex = helpers.toSolidityInput(proof); @@ -413,7 +385,7 @@ contract('AnchorPoseidon2', (accounts) => { ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await TruffleAssert.passes(anchor.withdraw(proofEncoded, ...args, { from: relayer, gasPrice: '0' })); @@ -424,10 +396,10 @@ contract('AnchorPoseidon2', (accounts) => { }) it('should prevent double spend with overflow', async () => { - const deposit = generateDeposit() + const deposit = helpers.generateDeposit() await tree.insert(deposit.commitment) await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit.commitment), { from: sender }) + await anchor.deposit(helpers.toFixedHex(deposit.commitment), { from: sender }) const { root, path_elements, path_index } = await tree.path(0) @@ -460,16 +432,16 @@ contract('AnchorPoseidon2', (accounts) => { publicSignals = res.publicSignals; const args = [ - toFixedHex(root), - toFixedHex( + helpers.toFixedHex(root), + helpers.toFixedHex( toBN(input.nullifierHash).add( toBN('21888242871839275222246405745257275088548364400416034343698204186575808495617'), ), ), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ]; proofHex = helpers.toSolidityInput(proof); @@ -477,7 +449,7 @@ contract('AnchorPoseidon2', (accounts) => { ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await TruffleAssert.reverts( @@ -487,10 +459,10 @@ contract('AnchorPoseidon2', (accounts) => { }) it('fee should be less or equal transfer value', async () => { - const deposit = generateDeposit() + const deposit = helpers.generateDeposit() await tree.insert(deposit.commitment) await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit.commitment), { from: sender }) + await anchor.deposit(helpers.toFixedHex(deposit.commitment), { from: sender }) const { root, path_elements, path_index } = await tree.path(0) const largeFee = new BN(`${value}`).add(bigInt(1)) @@ -524,12 +496,12 @@ contract('AnchorPoseidon2', (accounts) => { publicSignals = res.publicSignals; const args = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ] proofHex = helpers.toSolidityInput(proof); @@ -537,7 +509,7 @@ contract('AnchorPoseidon2', (accounts) => { ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await TruffleAssert.reverts( @@ -547,10 +519,10 @@ contract('AnchorPoseidon2', (accounts) => { }) it('should throw for corrupted merkle tree root', async () => { - const deposit = generateDeposit() + const deposit = helpers.generateDeposit() await tree.insert(deposit.commitment) await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit.commitment), { from: sender }) + await anchor.deposit(helpers.toFixedHex(deposit.commitment), { from: sender }) const { root, path_elements, path_index } = await tree.path(0) @@ -584,12 +556,12 @@ contract('AnchorPoseidon2', (accounts) => { const args = [ - toFixedHex(randomHex(32)), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(randomHex(32)), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ] proofHex = helpers.toSolidityInput(proof); @@ -597,7 +569,7 @@ contract('AnchorPoseidon2', (accounts) => { ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await TruffleAssert.reverts( @@ -607,10 +579,10 @@ contract('AnchorPoseidon2', (accounts) => { }) it('should reject with tampered public inputs', async () => { - const deposit = generateDeposit() + const deposit = helpers.generateDeposit() await tree.insert(deposit.commitment) await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit.commitment), { from: sender }) + await anchor.deposit(helpers.toFixedHex(deposit.commitment), { from: sender }) let { root, path_elements, path_index } = await tree.path(0) @@ -643,31 +615,31 @@ contract('AnchorPoseidon2', (accounts) => { publicSignals = res.publicSignals; const args = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ] let incorrectArgs const originalProof = proof; // recipient incorrectArgs = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex('0x0000000000000000000000007a1f9131357404ef86d7c38dbffed2da70321337', 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex('0x0000000000000000000000007a1f9131357404ef86d7c38dbffed2da70321337', 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ]; let proofHex = helpers.toSolidityInput(proof); let proofEncoded = [ ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await TruffleAssert.reverts( anchor.withdraw(proofEncoded, ...incorrectArgs, { from: relayer, gasPrice: '0' }), @@ -676,19 +648,19 @@ contract('AnchorPoseidon2', (accounts) => { // fee incorrectArgs = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex('0x000000000000000000000000000000000000000000000000015345785d8a0000'), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex('0x000000000000000000000000000000000000000000000000015345785d8a0000'), + helpers.toFixedHex(input.refund), ]; proofHex = helpers.toSolidityInput(proof); proofEncoded = Buffer.concat([ ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ].map(elt => Buffer.from(elt.toString(16)))); await TruffleAssert.reverts( anchor.withdraw(proofEncoded, ...incorrectArgs, { from: relayer, gasPrice: '0' }), @@ -697,19 +669,19 @@ contract('AnchorPoseidon2', (accounts) => { // nullifier incorrectArgs = [ - toFixedHex(root), - toFixedHex('0x00abdfc78211f8807b9c6504a6e537e71b8788b2f529a95f1399ce124a8642ad'), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex('0x00abdfc78211f8807b9c6504a6e537e71b8788b2f529a95f1399ce124a8642ad'), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ]; proofHex = helpers.toSolidityInput(proof); proofEncoded = [ ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await TruffleAssert.reverts( anchor.withdraw(proofEncoded, ...incorrectArgs, { from: relayer, gasPrice: '0' }), @@ -725,10 +697,10 @@ contract('AnchorPoseidon2', (accounts) => { }) it('should reject with non zero refund', async () => { - const deposit = generateDeposit() + const deposit = helpers.generateDeposit() await tree.insert(deposit.commitment) await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit.commitment), { from: sender }) + await anchor.deposit(helpers.toFixedHex(deposit.commitment), { from: sender }) const { root, path_elements, path_index } = await tree.path(0) @@ -760,21 +732,20 @@ contract('AnchorPoseidon2', (accounts) => { proof = res.proof; publicSignals = res.publicSignals; - const args = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ]; proofHex = helpers.toSolidityInput(proof); proofEncoded = [ ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await TruffleAssert.reverts( anchor.withdraw(proofEncoded, ...args, { from: relayer, gasPrice: '0' }), @@ -785,16 +756,16 @@ contract('AnchorPoseidon2', (accounts) => { describe('#isSpent', () => { it('should work', async () => { - const deposit1 = generateDeposit(chainID) - const deposit2 = generateDeposit(chainID) + const deposit1 = helpers.generateDeposit(chainID) + const deposit2 = helpers.generateDeposit(chainID) await tree.insert(deposit1.commitment) await tree.insert(deposit2.commitment) await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit1.commitment)); + await anchor.deposit(helpers.toFixedHex(deposit1.commitment)); await token.approve(anchor.address, tokenDenomination) - await anchor.deposit(toFixedHex(deposit2.commitment)); + await anchor.deposit(helpers.toFixedHex(deposit2.commitment)); const { root, path_elements, path_index } = await tree.path(1) @@ -827,24 +798,24 @@ contract('AnchorPoseidon2', (accounts) => { publicSignals = res.publicSignals; const args = [ - toFixedHex(root), - toFixedHex(input.nullifierHash), - toFixedHex(input.recipient, 20), - toFixedHex(input.relayer, 20), - toFixedHex(input.fee), - toFixedHex(input.refund), + helpers.toFixedHex(root), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), ] let proofHex = helpers.toSolidityInput(proof); let proofEncoded = [ ...proof.pi_a, ...proof.pi_b[0], ...proof.pi_b[1], - ...proo.pi_c, + ...proof.pi_c, ]; await anchor.withdraw(proofEncoded, ...args, { from: relayer, gasPrice: '0' }) - const nullifierHash1 = toFixedHex(pedersenHash(bigNumberToPaddedBytes(deposit1.nullifier, 31))) - const nullifierHash2 = toFixedHex(pedersenHash(bigNumberToPaddedBytes(depisit2.nullifier, 31))) + const nullifierHash1 = helpers.toFixedHex(pedersenHash(bigNumberToPaddedBytes(deposit1.nullifier, 31))) + const nullifierHash2 = helpers.toFixedHex(pedersenHash(bigNumberToPaddedBytes(depisit2.nullifier, 31))) const spentArray = await anchor.isSpentArray([nullifierHash1, nullifierHash2]) assert.strictEqual(spentArray, [false, true]) }) @@ -853,8 +824,4 @@ contract('AnchorPoseidon2', (accounts) => { afterEach(async () => { tree = new MerkleTree(levels, null, prefix) }) -}) - -module.exports = { - generateDeposit, -}; +}) \ No newline at end of file diff --git a/test/bridge/executeUpdateProposal.js b/test/bridge/executeUpdateProposal.js index 4b9902e2d..78cadaee0 100644 --- a/test/bridge/executeUpdateProposal.js +++ b/test/bridge/executeUpdateProposal.js @@ -19,22 +19,15 @@ contract('Bridge - [executeUpdateProposal with relayerThreshold == 3]', async (a const sourceChainID = 1; const destinationChainID = 2; const thirdChainID = 3; - const fourthChainID = 4; const relayer1Address = accounts[0]; const relayer2Address = accounts[1]; const relayer3Address = accounts[2]; const relayer4Address = accounts[3]; const relayer1Bit = 1 << 0; - const relayer2Bit = 1 << 1; - const relayer3Bit = 1 << 2; - const depositerAddress = accounts[4]; const relayerThreshold = 3; - const expectedFinalizedEventStatus = 2; - const expectedExecutedEventStatus = 3; const merkleTreeHeight = 31; const maxRoots = 1; const sender = accounts[5] - const operator = accounts[5] let ADMIN_ROLE; let merkleRoot; @@ -44,7 +37,7 @@ contract('Bridge - [executeUpdateProposal with relayerThreshold == 3]', async (a let LinkableAnchorDestChainInstance; let hasher, verifier; let token; - let tokenDenomination = '1000'; // 1 ether + let tokenDenomination = '1000'; let BridgeInstance; let DestinationAnchorHandlerInstance; diff --git a/test/helpers.js b/test/helpers.js index 725858e0c..d2d260457 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -3,33 +3,53 @@ * SPDX-License-Identifier: GPL-3.0-or-later-only */ - const Ethers = require('ethers'); +const Ethers = require('ethers'); +const crypto = require('crypto') +const PoseidonHasher = require('../lib/Poseidon'); +const utils = require("ffjavascript").utils; + +const { + leBuff2int, + unstringifyBigInts +} = utils; +const rbigint = (nbytes) => leBuff2int(crypto.randomBytes(nbytes)) +const poseidonHasher = new PoseidonHasher(); +const blankFunctionSig = '0x00000000'; +const blankFunctionDepositerOffset = 0; +const AbiCoder = new Ethers.utils.AbiCoder; + +const toHex = (covertThis, padding) => { + return Ethers.utils.hexZeroPad(Ethers.utils.hexlify(covertThis), padding); +}; - const blankFunctionSig = '0x00000000'; - const blankFunctionDepositerOffset = 0; - const AbiCoder = new Ethers.utils.AbiCoder; - const utils = require("ffjavascript").utils; - const { - unstringifyBigInts, - } = utils; +const toFixedHex = (number, length = 32) => + '0x' + + BigInt(`${number}`) + .toString(16) + .padStart(length * 2, '0') - const toHex = (covertThis, padding) => { - return Ethers.utils.hexZeroPad(Ethers.utils.hexlify(covertThis), padding); - }; +const arrayToFixedHex = (array) => { + for (let i = 0; i < array.length; i++) { + array[i] = toFixedHex(array[i]); + } + return array; +}; + +const getRandomRecipient = () => rbigint(20) - const abiEncode = (valueTypes, values) => { +const abiEncode = (valueTypes, values) => { return AbiCoder.encode(valueTypes, values) - }; +}; - const getFunctionSignature = (contractInstance, functionName) => { +const getFunctionSignature = (contractInstance, functionName) => { return contractInstance.abi.filter(abiProperty => abiProperty.name === functionName)[0].signature; - }; +}; - const createERCDepositData = (tokenAmountOrID, lenRecipientAddress, recipientAddress) => { +const createERCDepositData = (tokenAmountOrID, lenRecipientAddress, recipientAddress) => { return '0x' + toHex(tokenAmountOrID, 32).substr(2) + // Token amount or ID to deposit (32 bytes) - toHex(lenRecipientAddress, 32).substr(2) + // len(recipientAddress) (32 bytes) - recipientAddress.substr(2); // recipientAddress (?? bytes) + toHex(lenRecipientAddress, 32).substr(2) + // len(recipientAddress) (32 bytes) + recipientAddress.substr(2); // recipientAddress (?? bytes) }; const createUpdateProposalData = (sourceChainID, blockHeight, merkleRoot) => { @@ -39,6 +59,14 @@ const createUpdateProposalData = (sourceChainID, blockHeight, merkleRoot) => { toHex(merkleRoot, 32).substr(2); // Updated Merkle Root (32 bytes) }; +const createRootsBytes = (rootArray) => { + neighborBytes = "0x"; + for (let i = 0; i < rootArray.length; i++) { + neighborBytes += toFixedHex(rootArray[i]).substr(2); + } + return neighborBytes // root byte string (32 * array.length bytes) +}; + const advanceBlock = () => { const time = Math.floor(Date.now() / 1000); ethers.provider.send("evm_increaseTime", [time]) @@ -86,6 +114,18 @@ const nonceAndId = (nonce, id) => { return Ethers.utils.hexZeroPad(Ethers.utils.hexlify(nonce), 8) + Ethers.utils.hexZeroPad(Ethers.utils.hexlify(id), 1).substr(2) } +function generateDeposit(targetChainID = 0, secret = 31) { + let deposit = { + chainID: BigInt(targetChainID), + secret: rbigint(secret), + nullifier: rbigint(31) + } + + deposit.commitment = poseidonHasher.hash3([deposit.chainID, deposit.nullifier, deposit.secret]); + deposit.nullifierHash = poseidonHasher.hash(null, deposit.nullifier, deposit.nullifier); + return deposit +} + function hexifyBigInts(o) { if (typeof (o) === "bigint") { let str = o.toString(16); @@ -146,11 +186,16 @@ module.exports = { advanceBlock, blankFunctionSig, blankFunctionDepositerOffset, + getRandomRecipient, + toFixedHex, + arrayToFixedHex, toHex, abiEncode, + generateDeposit, getFunctionSignature, createERCDepositData, createUpdateProposalData, + createRootsBytes, createResourceID, assertObjectsMatch, nonceAndId, diff --git a/test/integration/historicalRootWithdraw.js b/test/integration/historicalRootWithdraw.js new file mode 100644 index 000000000..37f52b038 --- /dev/null +++ b/test/integration/historicalRootWithdraw.js @@ -0,0 +1,452 @@ +const TruffleAssert = require('truffle-assertions'); +const Ethers = require('ethers'); +const Helpers = require('../helpers'); +const { toBN } = require('web3-utils') +const assert = require('assert'); +const BridgeContract = artifacts.require("Bridge"); +const LinkableAnchorContract = artifacts.require("./LinkableERC20AnchorPoseidon2.sol"); +const Verifier = artifacts.require('./VerifierPoseidonBridge.sol'); +const Hasher = artifacts.require("PoseidonT3"); +const Token = artifacts.require("ERC20Mock"); +const AnchorHandlerContract = artifacts.require("AnchorHandler"); + +const fs = require('fs') +const path = require('path'); +const { NATIVE_AMOUNT } = process.env +let prefix = 'poseidon-test' +const snarkjs = require('snarkjs'); +const BN = require('bn.js'); +const F = require('circomlib').babyJub.F; +const Scalar = require("ffjavascript").Scalar; +const MerkleTree = require('../../lib/MerkleTree'); + + +contract('E2E LinkableAnchors - Cross chain withdraw using historical root should work', async accounts => { + const relayerThreshold = 2; + const originChainID = 1; + const destChainID = 2; + const relayer1Address = accounts[3]; + const relayer2Address = accounts[4]; + const operator = accounts[6]; + + const initialTokenMintAmount = BigInt(1e25); + const maxRoots = 1; + const merkleTreeHeight = 30; + const sender = accounts[5]; + + const fee = BigInt((new BN(`${NATIVE_AMOUNT}`).shrn(1)).toString()) || BigInt((new BN(`${1e17}`)).toString()); + const refund = BigInt((new BN('0')).toString()); + const recipient = Helpers.getRandomRecipient(); + + let originMerkleRoot; + let originBlockHeight = 1; + let originUpdateNonce; + let hasher, verifier; + let originChainToken; + let destChainToken; + let originDeposit; + let tokenDenomination = '1000000000000000000000'; + let tree; + let createWitness; + let OriginChainLinkableAnchorInstance; + let originDepositData; + let originDepositDataHash; + let resourceID; + let initialResourceIDs; + let DestBridgeInstance; + let DestChainLinkableAnchorInstance + let DestAnchorHandlerInstance; + let destInitialContractAddresses; + + beforeEach(async () => { + await Promise.all([ + // instantiate bridges on dest chain side + BridgeContract.new(destChainID, [relayer1Address, relayer2Address], relayerThreshold, 0, 100).then(instance => DestBridgeInstance = instance), + // create hasher, verifier, and tokens + Hasher.new().then(instance => hasher = instance), + Verifier.new().then(instance => verifier = instance), + Token.new().then(instance => originChainToken = instance), + Token.new().then(instance => destChainToken = instance), + ]); + // initialize anchors on both chains + OriginChainLinkableAnchorInstance = await LinkableAnchorContract.new( + verifier.address, + hasher.address, + tokenDenomination, + merkleTreeHeight, + originChainID, + originChainToken.address, + {from: sender}); + DestChainLinkableAnchorInstance = await LinkableAnchorContract.new( + verifier.address, + hasher.address, + tokenDenomination, + merkleTreeHeight, + destChainID, + destChainToken.address, + {from: sender}); + // create resource ID using anchor address + resourceID = Helpers.createResourceID(OriginChainLinkableAnchorInstance.address, 0); + initialResourceIDs = [resourceID]; + destInitialContractAddresses = [OriginChainLinkableAnchorInstance.address]; + // initialize anchorHanders + await Promise.all([ + AnchorHandlerContract.new(DestBridgeInstance.address, initialResourceIDs, destInitialContractAddresses) + .then(instance => DestAnchorHandlerInstance = instance), + ]); + // increase allowance and set resources for bridge + await DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) + // set bridge and handler permissions for anchor + await Promise.all([ + DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}), + DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}) + ]); + + createWitness = async (data) => { + const wtns = {type: "mem"}; + await snarkjs.wtns.calculate(data, path.join( + "test", + "fixtures", + "poseidon_bridge_2.wasm" + ), wtns); + return wtns; + } + + tree = new MerkleTree(merkleTreeHeight, null, prefix) + zkey_final = fs.readFileSync('build/bridge2/circuit_final.zkey').buffer; + }); + + it('[sanity] dest chain bridge configured with threshold and relayers', async () => { + assert.equal(await DestBridgeInstance._chainID(), destChainID) + assert.equal(await DestBridgeInstance._relayerThreshold(), relayerThreshold) + assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') + }) + + it('withdrawing across bridge after two deposits should work', async () => { + /* + * first deposit on origin chain + */ + // minting Tokens + await originChainToken.mint(sender, initialTokenMintAmount); + //increase allowance + originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }); + // deposit on both chains and define nonces based on events emmited + let firstOriginDeposit = Helpers.generateDeposit(destChainID); + let { logs } = await OriginChainLinkableAnchorInstance.deposit( + Helpers.toFixedHex(firstOriginDeposit.commitment), {from: sender}); + originUpdateNonce = logs[0].args.leafIndex; + firstWithdrawlMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + // create correct update proposal data for the deposit on origin chain + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, firstWithdrawlMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + + // deposit on origin chain leads to update addEdge proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + await TruffleAssert.passes(DestBridgeInstance.executeProposal( + originChainID, + originUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + + /* + * generate proof + */ + // insert two commitments into the tree + await tree.insert(firstOriginDeposit.commitment); + + let { root, path_elements, path_index } = await tree.path(0); + + const destNativeRoot = await DestChainLinkableAnchorInstance.getLastRoot(); + const firstWithdrawalNeighborRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + let input = { + // public + nullifierHash: firstOriginDeposit.nullifierHash, + recipient, + relayer: operator, + fee, + refund, + chainID: firstOriginDeposit.chainID, + roots: [destNativeRoot, ...firstWithdrawalNeighborRoots], + // private + nullifier: firstOriginDeposit.nullifier, + secret: firstOriginDeposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [destNativeRoot, firstWithdrawalNeighborRoots[0]].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${firstWithdrawalNeighborRoots[0]}`), + ).toString(); + }), + }; + + let wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + let isSpent = await DestChainLinkableAnchorInstance.isSpent(Helpers.toFixedHex(input.nullifierHash)); + assert.strictEqual(isSpent, false); + + // Uncomment to measure gas usage + // gas = await anchor.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' }) + // console.log('withdraw gas:', gas) + let args = [ + Helpers.createRootsBytes(input.roots), + Helpers.toFixedHex(input.nullifierHash), + Helpers.toFixedHex(input.recipient, 20), + Helpers.toFixedHex(input.relayer, 20), + Helpers.toFixedHex(input.fee), + Helpers.toFixedHex(input.refund), + ]; + + let result = await Helpers.groth16ExportSolidityCallData(proof, publicSignals); + let fullProof = JSON.parse("[" + result + "]"); + let pi_a = fullProof[0]; + let pi_b = fullProof[1]; + let pi_c = fullProof[2]; + let inputs = fullProof[3]; + assert.strictEqual(true, await verifier.verifyProof( + pi_a, + pi_b, + pi_c, + inputs, + )); + + proofEncoded = [ + pi_a[0], + pi_a[1], + pi_b[0][0], + pi_b[0][1], + pi_b[1][0], + pi_b[1][1], + pi_c[0], + pi_c[1], + ] + .map(elt => elt.substr(2)) + .join(''); + + /* + * second deposit on origin chain + */ + // deposit on origin chain and define nonce based on events emmited + originDeposit = Helpers.generateDeposit(destChainID, 30); + ({ logs } = await OriginChainLinkableAnchorInstance.deposit(Helpers.toFixedHex(originDeposit.commitment), {from: sender})); + originUpdateNonce = logs[0].args.leafIndex; + secondWithdrawalMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + // create correct update proposal data for the deposit on origin chain + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, secondWithdrawalMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + /* + * Relayers vote on dest chain + */ + // a second deposit on origin chain leads to update edge proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + await TruffleAssert.passes(DestBridgeInstance.executeProposal( + originChainID, + originUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + + // check initial balances + let balanceOperatorBefore = await destChainToken.balanceOf(operator); + let balanceReceiverBefore = await destChainToken.balanceOf(Helpers.toFixedHex(recipient, 20)); + /* + * withdraw + */ + // mint to anchor and track balance + await destChainToken.mint(DestChainLinkableAnchorInstance.address, initialTokenMintAmount); + let balanceDestAnchorAfterDeposits = await destChainToken.balanceOf(DestChainLinkableAnchorInstance.address); + // withdraw + ({ logs } = await DestChainLinkableAnchorInstance.withdraw + (`0x${proofEncoded}`, ...args, { from: input.relayer, gasPrice: '0' })); + + let balanceDestAnchorAfter = await destChainToken.balanceOf(DestChainLinkableAnchorInstance.address); + let balanceOperatorAfter = await destChainToken.balanceOf(input.relayer); + let balanceReceiverAfter = await destChainToken.balanceOf(Helpers.toFixedHex(recipient, 20)); + const feeBN = toBN(fee.toString()) + assert.strictEqual(balanceDestAnchorAfter.toString(), balanceDestAnchorAfterDeposits.sub(toBN(tokenDenomination)).toString()); + assert.strictEqual(balanceOperatorAfter.toString(), balanceOperatorBefore.add(feeBN).toString()); + assert.strictEqual(balanceReceiverAfter.toString(), balanceReceiverBefore.add(toBN(tokenDenomination)).sub(feeBN).toString()); + + assert.strictEqual(logs[0].event, 'Withdrawal'); + assert.strictEqual(logs[0].args.nullifierHash, Helpers.toFixedHex(input.nullifierHash)); + assert.strictEqual(logs[0].args.relayer, operator); + assert.strictEqual(logs[0].args.fee.toString(), feeBN.toString()); + + isSpent = await DestChainLinkableAnchorInstance.isSpent(Helpers.toFixedHex(input.nullifierHash)); + assert(isSpent); + + /* + * generate proof for second deposit + */ + // insert second deposit in tree and get path for withdrawal proof + await tree.insert(originDeposit.commitment); + ({ root, path_elements, path_index } = await tree.path(1)); + const secondWithdrawalNeighborRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + input = { + // public + nullifierHash: originDeposit.nullifierHash, + recipient, + relayer: operator, + fee, + refund, + chainID: originDeposit.chainID, + roots: [destNativeRoot, ...secondWithdrawalNeighborRoots], + // private + nullifier: originDeposit.nullifier, + secret: originDeposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [destNativeRoot, secondWithdrawalNeighborRoots[0]].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${secondWithdrawalNeighborRoots[0]}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + isSpent = await DestChainLinkableAnchorInstance.isSpent(Helpers.toFixedHex(input.nullifierHash)); + assert.strictEqual(isSpent, false); + + args = [ + Helpers.createRootsBytes(input.roots), + Helpers.toFixedHex(input.nullifierHash), + Helpers.toFixedHex(input.recipient, 20), + Helpers.toFixedHex(input.relayer, 20), + Helpers.toFixedHex(input.fee), + Helpers.toFixedHex(input.refund), + ]; + + result = await Helpers.groth16ExportSolidityCallData(proof, publicSignals); + fullProof = JSON.parse("[" + result + "]"); + pi_a = fullProof[0]; + pi_b = fullProof[1]; + pi_c = fullProof[2]; + inputs = fullProof[3]; + assert.strictEqual(true, await verifier.verifyProof( + pi_a, + pi_b, + pi_c, + inputs, + )); + + proofEncoded = [ + pi_a[0], + pi_a[1], + pi_b[0][0], + pi_b[0][1], + pi_b[1][0], + pi_b[1][1], + pi_c[0], + pi_c[1], + ] + .map(elt => elt.substr(2)) + .join(''); + + /* + * create 30 new deposits on chain so history wraps around and forgets second deposit + */ + let newBlockHeight = originBlockHeight + 100; + for (var i = 0; i < 30; i++) { + // deposit on origin chain and define nonce based on events emmited + originDeposit = Helpers.generateDeposit(destChainID, i); + ({ logs } = await OriginChainLinkableAnchorInstance.deposit(Helpers.toFixedHex(originDeposit.commitment), {from: sender})); + originUpdateNonce = logs[0].args.leafIndex; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + // create correct update proposal data for the deposit on origin chain + originDepositData = Helpers.createUpdateProposalData(originChainID, newBlockHeight + i, originMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + /* + * Relayers vote on dest chain + */ + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + await TruffleAssert.passes(DestBridgeInstance.executeProposal( + originChainID, + originUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + } + + // withdraw should revert as historical root does not exist + await TruffleAssert.reverts(DestChainLinkableAnchorInstance.withdraw + (`0x${proofEncoded}`, ...args, { from: input.relayer, gasPrice: '0' }), + "Neighbor root not found"); + }).timeout(0); +}) diff --git a/test/integration/simpleWithdrawals.js b/test/integration/simpleWithdrawals.js new file mode 100644 index 000000000..cda37cd93 --- /dev/null +++ b/test/integration/simpleWithdrawals.js @@ -0,0 +1,452 @@ +const TruffleAssert = require('truffle-assertions'); +const Ethers = require('ethers'); +const helpers = require('../helpers'); +const { toBN } = require('web3-utils') +const assert = require('assert'); +const BridgeContract = artifacts.require("Bridge"); +const LinkableAnchorContract = artifacts.require("./LinkableERC20AnchorPoseidon2.sol"); +const Verifier = artifacts.require('./VerifierPoseidonBridge.sol'); +const Hasher = artifacts.require("PoseidonT3"); +const Token = artifacts.require("ERC20Mock"); +const AnchorHandlerContract = artifacts.require("AnchorHandler"); + +const fs = require('fs') +const path = require('path'); +const { NATIVE_AMOUNT } = process.env +let prefix = 'poseidon-test' +const snarkjs = require('snarkjs'); +const BN = require('bn.js'); +const F = require('circomlib').babyJub.F; +const Scalar = require("ffjavascript").Scalar; +const MerkleTree = require('../../lib/MerkleTree'); + + +contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { + const relayerThreshold = 2; + const originChainID = 1; + const destChainID = 2; + const relayer1Address = accounts[3]; + const relayer2Address = accounts[4]; + const operator = accounts[6]; + + const initialTokenMintAmount = BigInt(1e25); + const maxRoots = 1; + const merkleTreeHeight = 30; + const sender = accounts[5]; + + const fee = BigInt((new BN(`${NATIVE_AMOUNT}`).shrn(1)).toString()) || BigInt((new BN(`${1e17}`)).toString()); + const refund = BigInt((new BN('0')).toString()); + const recipient = helpers.getRandomRecipient(); + + let originMerkleRoot; + let destMerkleRoot; + let originBlockHeight = 1; + let destBlockHeight = 1; + let originUpdateNonce; + let destUpdateNonce; + let hasher, verifier; + let originChainToken; + let destChainToken; + let originDeposit; + let destDeposit; + let tokenDenomination = '1000000000000000000000'; + let tree; + let createWitness; + let OriginBridgeInstance; + let OriginChainLinkableAnchorInstance; + let OriginAnchorHandlerInstance; + let originDepositData; + let originDepositDataHash; + let resourceID; + let initialResourceIDs; + let originInitialContractAddresses; + let DestBridgeInstance; + let DestChainLinkableAnchorInstance + let DestAnchorHandlerInstance; + let destDepositData; + let destDepositDataHash; + let destInitialContractAddresses; + + beforeEach(async () => { + await Promise.all([ + // instantiate bridges on both sides + BridgeContract.new(originChainID, [relayer1Address, relayer2Address], relayerThreshold, 0, 100).then(instance => OriginBridgeInstance = instance), + BridgeContract.new(destChainID, [relayer1Address, relayer2Address], relayerThreshold, 0, 100).then(instance => DestBridgeInstance = instance), + // create hasher, verifier, and tokens + Hasher.new().then(instance => hasher = instance), + Verifier.new().then(instance => verifier = instance), + Token.new().then(instance => originChainToken = instance), + Token.new().then(instance => destChainToken = instance), + ]); + // initialize anchors on both chains + OriginChainLinkableAnchorInstance = await LinkableAnchorContract.new( + verifier.address, + hasher.address, + tokenDenomination, + merkleTreeHeight, + originChainID, + originChainToken.address, + {from: sender}); + DestChainLinkableAnchorInstance = await LinkableAnchorContract.new( + verifier.address, + hasher.address, + tokenDenomination, + merkleTreeHeight, + destChainID, + destChainToken.address, + {from: sender}); + // create resource ID using anchor address + resourceID = helpers.createResourceID(OriginChainLinkableAnchorInstance.address, 0); + initialResourceIDs = [resourceID]; + originInitialContractAddresses = [DestChainLinkableAnchorInstance.address]; + destInitialContractAddresses = [OriginChainLinkableAnchorInstance.address]; + // initialize anchorHanders + await Promise.all([ + AnchorHandlerContract.new(OriginBridgeInstance.address, initialResourceIDs, originInitialContractAddresses) + .then(instance => OriginAnchorHandlerInstance = instance), + AnchorHandlerContract.new(DestBridgeInstance.address, initialResourceIDs, destInitialContractAddresses) + .then(instance => DestAnchorHandlerInstance = instance), + ]); + // increase allowance and set resources for bridge + await Promise.all([ + OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), + DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) + ]); + // set bridge and handler permissions for anchors + await Promise.all([ + OriginChainLinkableAnchorInstance.setHandler(OriginAnchorHandlerInstance.address, {from: sender}), + OriginChainLinkableAnchorInstance.setBridge(OriginBridgeInstance.address, {from: sender}), + DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}), + DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}) + ]); + + createWitness = async (data) => { + const wtns = {type: "mem"}; + await snarkjs.wtns.calculate(data, path.join( + "test", + "fixtures", + "poseidon_bridge_2.wasm" + ), wtns); + return wtns; + } + + tree = new MerkleTree(merkleTreeHeight, null, prefix) + zkey_final = fs.readFileSync('build/bridge2/circuit_final.zkey').buffer; + }); + + it('[sanity] bridges configured with threshold and relayers', async () => { + assert.equal(await OriginBridgeInstance._chainID(), originChainID); + assert.equal(await OriginBridgeInstance._relayerThreshold(), relayerThreshold) + assert.equal((await OriginBridgeInstance._totalRelayers()).toString(), '2') + assert.equal(await DestBridgeInstance._chainID(), destChainID) + assert.equal(await DestBridgeInstance._relayerThreshold(), relayerThreshold) + assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') + }) + + it.only('withdrawals on both chains integration', async () => { + /* + * Desposit on origin chain + */ + // minting Tokens + await originChainToken.mint(sender, initialTokenMintAmount); + // increasing allowance of anchors + await originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), + // generate deposit commitment targeting withdrawal on destination chain + originDeposit = helpers.generateDeposit(destChainID); + // deposit on origin chain and define nonce + let { logs } = await OriginChainLinkableAnchorInstance.deposit(helpers.toFixedHex(originDeposit.commitment), {from: sender}); + originUpdateNonce = logs[0].args.leafIndex; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + // create correct update proposal data for the deposit on origin chain + originDepositData = helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + /* + * relayers vote on dest chain + */ + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + await TruffleAssert.passes(DestBridgeInstance.executeProposal( + originChainID, + originUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + + const destNeighborRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destNeighborRoots.length, maxRoots); + assert.strictEqual(destNeighborRoots[0], originMerkleRoot); + // check initial balances + let balanceOperatorBefore = await destChainToken.balanceOf(operator); + let balanceReceiverBefore = await destChainToken.balanceOf(helpers.toFixedHex(recipient, 20)); + /* + * generate proof + */ + await tree.insert(originDeposit.commitment); + + let { root, path_elements, path_index } = await tree.path(0); + const destNativeRoot = await DestChainLinkableAnchorInstance.getLastRoot(); + let input = { + // public + nullifierHash: originDeposit.nullifierHash, + recipient, + relayer: operator, + fee, + refund, + chainID: originDeposit.chainID, + roots: [destNativeRoot, ...destNeighborRoots], + // private + nullifier: originDeposit.nullifier, + secret: originDeposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [destNativeRoot, ...destNeighborRoots].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${destNeighborRoots[0]}`), + ).toString(); + }), + }; + + let wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + let isSpent = await DestChainLinkableAnchorInstance.isSpent(helpers.toFixedHex(input.nullifierHash)); + assert.strictEqual(isSpent, false); + + let args = [ + helpers.createRootsBytes(input.roots), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), + ]; + + let result = await helpers.groth16ExportSolidityCallData(proof, publicSignals); + let fullProof = JSON.parse("[" + result + "]"); + let pi_a = fullProof[0]; + let pi_b = fullProof[1]; + let pi_c = fullProof[2]; + let inputs = fullProof[3]; + assert.strictEqual(true, await verifier.verifyProof( + pi_a, + pi_b, + pi_c, + inputs, + )); + + proofEncoded = [ + pi_a[0], + pi_a[1], + pi_b[0][0], + pi_b[0][1], + pi_b[1][0], + pi_b[1][1], + pi_c[0], + pi_c[1], + ] + .map(elt => elt.substr(2)) + .join(''); + /* + * withdraw on dest chain + */ + await destChainToken.mint(DestChainLinkableAnchorInstance.address, initialTokenMintAmount); + let balanceDestAnchorAfterDeposit = await destChainToken.balanceOf(DestChainLinkableAnchorInstance.address); + ({ logs } = await DestChainLinkableAnchorInstance.withdraw + (`0x${proofEncoded}`, ...args, { from: input.relayer, gasPrice: '0' })); + + let balanceDestAnchorAfter = await destChainToken.balanceOf(DestChainLinkableAnchorInstance.address); + let balanceOperatorAfter = await destChainToken.balanceOf(input.relayer); + let balanceReceiverAfter = await destChainToken.balanceOf(helpers.toFixedHex(recipient, 20)); + const feeBN = toBN(fee.toString()) + assert.strictEqual(balanceDestAnchorAfter.toString(), balanceDestAnchorAfterDeposit.sub(toBN(tokenDenomination)).toString()); + assert.strictEqual(balanceOperatorAfter.toString(), balanceOperatorBefore.add(feeBN).toString()); + assert.strictEqual(balanceReceiverAfter.toString(), balanceReceiverBefore.add(toBN(tokenDenomination)).sub(feeBN).toString()); + + assert.strictEqual(logs[0].event, 'Withdrawal'); + assert.strictEqual(logs[0].args.nullifierHash, helpers.toFixedHex(input.nullifierHash)); + assert.strictEqual(logs[0].args.relayer, operator); + assert.strictEqual(logs[0].args.fee.toString(), feeBN.toString()); + isSpent = await DestChainLinkableAnchorInstance.isSpent(helpers.toFixedHex(input.nullifierHash)); + assert(isSpent); + /* + * deposit on dest chain + */ + // minting Tokens + await destChainToken.mint(sender, initialTokenMintAmount); + // approval + await destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), + // generate deposit commitment + destDeposit = helpers.generateDeposit(originChainID); + // deposit on dest chain and define nonce + ({logs} = await DestChainLinkableAnchorInstance.deposit(helpers.toFixedHex(destDeposit.commitment), {from: sender})); + destUpdateNonce = logs[0].args.leafIndex; + destMerkleRoot = await DestChainLinkableAnchorInstance.getLastRoot(); + // create correct update proposal data for the deposit on dest chain + destDepositData = helpers.createUpdateProposalData(destChainID, destBlockHeight, destMerkleRoot); + destDepositDataHash = Ethers.utils.keccak256(OriginAnchorHandlerInstance.address + destDepositData.substr(2)); + /* + * relayers vote on origin chain + */ + // deposit on dest chain leads to update proposal on origin chain + // relayer1 creates the deposit proposal + await TruffleAssert.passes(OriginBridgeInstance.voteProposal( + destChainID, + destUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the update proposal will become passed + await TruffleAssert.passes(OriginBridgeInstance.voteProposal( + destChainID, + destUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the update proposal + await TruffleAssert.passes(OriginBridgeInstance.executeProposal( + destChainID, + destUpdateNonce, + destDepositData, + resourceID, + { from: relayer1Address } + )); + const originNeighborRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(originNeighborRoots.length, maxRoots); + assert.strictEqual(originNeighborRoots[0], destMerkleRoot); + // check initial balances + balanceOperatorBefore = await originChainToken.balanceOf(operator); + balanceReceiverBefore = await originChainToken.balanceOf(helpers.toFixedHex(recipient, 20)); + /* + * generate proof + */ + tree = new MerkleTree(merkleTreeHeight, null, prefix) + await tree.insert(destDeposit.commitment); + + ({ root, path_elements, path_index } = await tree.path(0)); + const originNativeRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + input = { + // public + nullifierHash: destDeposit.nullifierHash, + recipient, + relayer: operator, + fee, + refund, + chainID: destDeposit.chainID, + roots: [originNativeRoot, ...originNeighborRoots], + // private + nullifier: destDeposit.nullifier, + secret: destDeposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [originNativeRoot, originNeighborRoots[0]].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${originNeighborRoots[0]}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + isSpent = await DestChainLinkableAnchorInstance.isSpent(helpers.toFixedHex(input.nullifierHash)); + assert.strictEqual(isSpent, false); + + args = [ + helpers.createRootsBytes(input.roots), + helpers.toFixedHex(input.nullifierHash), + helpers.toFixedHex(input.recipient, 20), + helpers.toFixedHex(input.relayer, 20), + helpers.toFixedHex(input.fee), + helpers.toFixedHex(input.refund), + ]; + + result = await helpers.groth16ExportSolidityCallData(proof, publicSignals); + fullProof = JSON.parse("[" + result + "]"); + pi_a = fullProof[0]; + pi_b = fullProof[1]; + pi_c = fullProof[2]; + inputs = fullProof[3]; + assert.strictEqual(true, await verifier.verifyProof( + pi_a, + pi_b, + pi_c, + inputs, + )); + + proofEncoded = [ + pi_a[0], + pi_a[1], + pi_b[0][0], + pi_b[0][1], + pi_b[1][0], + pi_b[1][1], + pi_c[0], + pi_c[1], + ] + .map(elt => elt.substr(2)) + .join(''); + /* + * withdraw on origin chain + */ + await originChainToken.mint(OriginChainLinkableAnchorInstance.address, initialTokenMintAmount); + let balanceOriginAnchorAfterDeposit = await originChainToken.balanceOf(OriginChainLinkableAnchorInstance.address); + ({ logs } = await OriginChainLinkableAnchorInstance.withdraw + (`0x${proofEncoded}`, ...args, { from: input.relayer, gasPrice: '0' })); + + let balanceOriginAnchorAfter = await originChainToken.balanceOf(OriginChainLinkableAnchorInstance.address); + balanceOperatorAfter = await originChainToken.balanceOf(input.relayer); + balanceReceiverAfter = await originChainToken.balanceOf(helpers.toFixedHex(recipient, 20)); + + assert.strictEqual(balanceOriginAnchorAfter.toString(), balanceOriginAnchorAfterDeposit.sub(toBN(tokenDenomination)).toString()); + assert.strictEqual(balanceOperatorAfter.toString(), balanceOperatorBefore.add(feeBN).toString()); + assert.strictEqual(balanceReceiverAfter.toString(), balanceReceiverBefore.add(toBN(tokenDenomination)).sub(feeBN).toString()); + + assert.strictEqual(logs[0].event, 'Withdrawal'); + assert.strictEqual(logs[0].args.nullifierHash, helpers.toFixedHex(input.nullifierHash)); + assert.strictEqual(logs[0].args.relayer, operator); + assert.strictEqual(logs[0].args.fee.toString(), feeBN.toString()); + isSpent = await OriginChainLinkableAnchorInstance.isSpent(helpers.toFixedHex(input.nullifierHash)); + assert(isSpent); + + }) +}) + diff --git a/test/merkleTree/MerkleTreePoseidon.test.js b/test/merkleTree/MerkleTreePoseidon.test.js index e4f06eee4..2c67be4c3 100644 --- a/test/merkleTree/MerkleTreePoseidon.test.js +++ b/test/merkleTree/MerkleTreePoseidon.test.js @@ -5,7 +5,7 @@ const TruffleAssert = require('truffle-assertions'); const { ethers } = require('hardhat'); const BN = require('bn.js'); -const Helpers = require('../helpers'); +const helpers = require('../helpers'); const assert = require('assert'); const MerkleTreeWithHistory = artifacts.require('MerkleTreePoseidonMock') @@ -27,13 +27,6 @@ function BNArrayToStringArray(array) { return arrayToPrint } -function toFixedHex(number, length = 32) { - let str = BigInt(number).toString(16) - while (str.length < length * 2) str = '0' + str - str = '0x' + str - return str -} - contract('MerkleTreePoseidon', (accounts) => { let web3; let merkleTreeWithHistory; @@ -58,10 +51,10 @@ contract('MerkleTreePoseidon', (accounts) => { it('should initialize', async () => { const zeroValue = await merkleTreeWithHistory.ZERO_VALUE() const firstSubtree = await merkleTreeWithHistory.filledSubtrees(0) - assert.strictEqual(firstSubtree, toFixedHex(zeroValue)); + assert.strictEqual(firstSubtree, helpers.toFixedHex(zeroValue)); const firstZero = await merkleTreeWithHistory.zeros(0) - assert.strictEqual(firstZero, toFixedHex(zeroValue)); + assert.strictEqual(firstZero, helpers.toFixedHex(zeroValue)); }); }); @@ -73,7 +66,7 @@ contract('MerkleTreePoseidon', (accounts) => { it('tests insert', async () => { hasher = new hasherImpl() tree = new MerkleTree(2, null, prefix) - await tree.insert(toFixedHex('5')) + await tree.insert(helpers.toFixedHex('5')) let { root, path_elements } = await tree.path(0); const calculated_root = hasher.hash( null, @@ -175,11 +168,11 @@ contract('MerkleTreePoseidon', (accounts) => { let rootFromContract for (let i = 1; i < 11; i++) { - await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender }) + await merkleTreeWithHistory.insert(helpers.toFixedHex(i), { from: sender }) await tree.insert(i) let { root } = await tree.path(i - 1) rootFromContract = await merkleTreeWithHistory.getLastRoot(); - assert.strictEqual(toFixedHex(root), rootFromContract.toString()); + assert.strictEqual(helpers.toFixedHex(root), rootFromContract.toString()); } }); @@ -188,16 +181,16 @@ contract('MerkleTreePoseidon', (accounts) => { const merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, hasherInstance.address) for (let i = 0; i < 2 ** levels; i++) { - TruffleAssert.passes(await merkleTreeWithHistory.insert(toFixedHex(i + 42))) + TruffleAssert.passes(await merkleTreeWithHistory.insert(helpers.toFixedHex(i + 42))) } await TruffleAssert.reverts( - merkleTreeWithHistory.insert(toFixedHex(1337)), + merkleTreeWithHistory.insert(helpers.toFixedHex(1337)), 'Merkle tree is full. No more leaves can be added' ); await TruffleAssert.reverts( - merkleTreeWithHistory.insert(toFixedHex(1)), + merkleTreeWithHistory.insert(helpers.toFixedHex(1)), 'Merkle tree is full. No more leaves can be added' ); }) @@ -217,38 +210,36 @@ contract('MerkleTreePoseidon', (accounts) => { let path for (let i = 1; i < 5; i++) { - TruffleAssert.passes(await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender })) + TruffleAssert.passes(await merkleTreeWithHistory.insert(helpers.toFixedHex(i), { from: sender })) await tree.insert(i) path = await tree.path(i - 1) - let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(path.root)) + let isKnown = await merkleTreeWithHistory.isKnownRoot(helpers.toFixedHex(path.root)) assert(isKnown); } - TruffleAssert.passes(await merkleTreeWithHistory.insert(toFixedHex(42), { from: sender })); + TruffleAssert.passes(await merkleTreeWithHistory.insert(helpers.toFixedHex(42), { from: sender })); // check outdated root - let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(path.root)) + let isKnown = await merkleTreeWithHistory.isKnownRoot(helpers.toFixedHex(path.root)) assert(isKnown); }); it('should not return uninitialized roots', async () => { - TruffleAssert.passes(await merkleTreeWithHistory.insert(toFixedHex(42), { from: sender })); - let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(0)) + TruffleAssert.passes(await merkleTreeWithHistory.insert(helpers.toFixedHex(42), { from: sender })); + let isKnown = await merkleTreeWithHistory.isKnownRoot(helpers.toFixedHex(0)) assert(!isKnown); }); }); describe('#insertions using deposit commitments', async () => { - const generateDeposit = require('../anchor/anchor').generateDeposit; - it('should rebuild root correctly between native and contract', async () => { const merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, hasherInstance.address); - const deposit = generateDeposit(); + const deposit = helpers.generateDeposit(); const commitment = deposit.commitment; await tree.insert(commitment); const { root, path_elements, path_index } = await tree.path(0); - await merkleTreeWithHistory.insert(toFixedHex(commitment), { from: sender }); + await merkleTreeWithHistory.insert(helpers.toFixedHex(commitment), { from: sender }); rootFromContract = await merkleTreeWithHistory.getLastRoot(); - assert.strictEqual(toFixedHex(root), rootFromContract.toString()); + assert.strictEqual(helpers.toFixedHex(root), rootFromContract.toString()); let curr = deposit.commitment; for (var i = 0; i < path_elements.length; i++) { @@ -263,7 +254,7 @@ contract('MerkleTreePoseidon', (accounts) => { } } - assert.strictEqual(toFixedHex(curr), toFixedHex(root)); + assert.strictEqual(helpers.toFixedHex(curr), helpers.toFixedHex(root)); }); }); });