From 056b4835a96aa9264c1121475d5b559a97cf8514 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Wed, 4 Aug 2021 15:06:11 -0400 Subject: [PATCH 01/18] Integration tests with native verifier Nonce is now updated upon and emmited upon anchor deposit --- contracts/anchors/bridged/AnchorPoseidon2.sol | 7 +- test/bridge/executeUpdateProposal.js | 9 +- test/integration/twoChainIntegration.js | 790 ++++++++++++++++++ 3 files changed, 796 insertions(+), 10 deletions(-) create mode 100644 test/integration/twoChainIntegration.js diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 2c9821389..23c6464dd 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -20,6 +20,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { address public bridge; address public admin; address public handler; + uint64 public updateNonce; IVerifier public immutable verifier; uint256 public immutable denomination; @@ -49,7 +50,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { uint latestHistoryIndex; // currency events - event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp); + event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint64 updateNonce, uint256 timestamp); event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee); // bridge events event EdgeAddition(uint256 chainID, uint256 height, bytes32 merkleRoot); @@ -78,6 +79,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { // TODO: Handle pruning length in function signature pruningLength = 100; latestHistoryIndex = 0; + updateNonce = 0; // TODO: Parameterize max roots (length of array should be max roots) rootHistory[latestHistoryIndex] = new bytes32[](1); } @@ -92,8 +94,9 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { uint32 insertedIndex = _insert(_commitment); commitments[_commitment] = true; _processDeposit(); + updateNonce++; + emit Deposit(_commitment, insertedIndex, updateNonce, block.timestamp); - emit Deposit(_commitment, insertedIndex, block.timestamp); } /** @dev this function is defined in a child contract */ diff --git a/test/bridge/executeUpdateProposal.js b/test/bridge/executeUpdateProposal.js index 6a64dfda4..0d74be9d2 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/integration/twoChainIntegration.js b/test/integration/twoChainIntegration.js new file mode 100644 index 000000000..9ec886919 --- /dev/null +++ b/test/integration/twoChainIntegration.js @@ -0,0 +1,790 @@ +const TruffleAssert = require('truffle-assertions'); +const Ethers = require('ethers'); +const Helpers = require('../helpers'); + +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 { toBN, randomHex } = require('web3-utils') +const Poseidon = artifacts.require('PoseidonT3'); +const { NATIVE_AMOUNT, MERKLE_TREE_HEIGHT } = process.env +let prefix = 'poseidon-test' +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) { + var n = num.toString(16).replace(/^0x/, ''); + while (n.length < (digits * 2)) { + n = "0" + n; + } + 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) { + 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 +} + +contract('E2E LinkableAnchors - Two EVM Chains', async accounts => { + const relayerThreshold = 2; + const originChainID = 1; + const destChainID = 2; + const relayer1Address = accounts[3]; + const relayer2Address = accounts[4]; + const relayer1Bit = 1 << 0; + + const depositerAddress = accounts[1]; + const recipientAddress = accounts[2]; + const initialTokenAmount = 10000000000000; + const levels = MERKLE_TREE_HEIGHT || 30 + 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 = getRandomRecipient(); + const relayer = accounts[6]; + + + let originMerkleRoot; + let destMerkleRoot; + let originBlockHeight = 1; + let destBlockHeight = 1; + let originUpdateNonce = 1; + let destUpdateNonce = 1; + let hasher, verifier; + let originChainToken; + let destChainToken; + let tokenDenomination = '1000'; + 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 (this follows create2 scheme) + 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), + ]); + + await originChainToken.mint(sender, initialTokenAmount); + await destChainToken.mint(sender, initialTokenAmount); + // increase allowance and set resources for bridge + await Promise.all([ + originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), + destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), + OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), + DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) + ]); + + // deposit on both chains and define nonces based on events emmited + let { logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(42), {from: sender}); + originUpdateNonce = logs[0].args.updateNonce; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + ({logs} = await DestChainLinkableAnchorInstance.deposit(toFixedHex(40), {from: sender})); + destUpdateNonce = logs[0].args.updateNonce; + destMerkleRoot = await DestChainLinkableAnchorInstance.getLastRoot(); + // set bridge and handler permissions for anchors + await OriginChainLinkableAnchorInstance.setHandler(OriginAnchorHandlerInstance.address, {from: sender}); + await OriginChainLinkableAnchorInstance.setBridge(OriginBridgeInstance.address, {from: sender}); + await DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}); + await DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}); + // create correct update proposal data for the deposits on both chains + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot,); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + destDepositData = Helpers.createUpdateProposalData(destChainID, destBlockHeight, destMerkleRoot); + destDepositDataHash = Ethers.utils.keccak256(OriginAnchorHandlerInstance.address + destDepositData.substr(2)); + + createWitness = async (data) => { + const wtns = {type: "mem"}; + await snarkjs.wtns.calculate(data, path.join( + "artifacts/circuits", + "bridge", + "poseidon_bridge_2.wasm" + ), wtns); + return wtns; + } + + tree = new MerkleTree(levels, null, prefix) + zkey_final = fs.readFileSync('build/bridge2/circuit_final.zkey').buffer; + }); + + it('[sanity] origin chain bridge 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') + }) + + it('[sanity] deposit 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('[sanity] updateProposal on origin chain should be created with expected values', async () => { + await TruffleAssert.passes(OriginBridgeInstance.voteProposal( + destChainID, originUpdateNonce, resourceID, destDepositDataHash, {from: relayer1Address})); + + const expectedUpdateProposal = { + _yesVotes: relayer1Bit.toString(), + _yesVotesTotal: '1', + _status: '1' // Active + }; + + const updateProposal = await OriginBridgeInstance.getProposal( + destChainID, originUpdateNonce, destDepositDataHash); + + assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); + }); + + it('[sanity] updateProposal on destination chain should be created with expected values', async () => { + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, destUpdateNonce, resourceID, originDepositDataHash, {from: relayer1Address})); + + const expectedUpdateProposal = { + _yesVotes: relayer1Bit.toString(), + _yesVotesTotal: '1', + _status: '1' // Active + }; + + const updateProposal = await DestBridgeInstance.getProposal( + originChainID, destUpdateNonce, originDepositDataHash); + + assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); + }); + + it("E2E: deposit on origin chain withdraw on destination chain", async () => { + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // checking edge is added + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + + // native verification + const deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + const { root, path_elements, path_index } = await tree.path(0); + const roots = [root, 0]; + const diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + const input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + const wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let tempSignals = publicSignals; + const vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + }); + + it("E2E: deposit and withdraw on both chains", async () => { + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // same process but for a deposit on dest chain leading to update on origin chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer2 will execute the deposit proposal + TruffleAssert.passes(await OriginBridgeInstance.executeProposal( + destChainID, + originUpdateNonce, + destDepositData, + resourceID, + { from: relayer2Address } + )); + // checking edges are added on both destChain and originChain + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(originChainRoots.length, maxRoots); + assert.strictEqual(originChainRoots[0], destMerkleRoot); + + // native verification for dest chain + let deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + let { root, path_elements, path_index } = await tree.path(0); + let roots = [root, 0]; + let diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + let input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + let wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let tempSignals = publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // native verification for origin chain + // reset tree + tree = new MerkleTree(levels, null, prefix) + deposit = generateDeposit(originChainID); + await tree.insert(deposit.commitment); + ({ root, path_elements, path_index } = await tree.path(0)); + roots = [root, 0]; + diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + tempSignals = publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + }); + + it("E2E: adding edges on both chains and updating an edge on one", async () => { + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // same process but for a deposit on dest chain leading to update on origin chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer2 will execute the deposit proposal + TruffleAssert.passes(await OriginBridgeInstance.executeProposal( + destChainID, + originUpdateNonce, + destDepositData, + resourceID, + { from: relayer2Address } + )); + + // new deposit on originChain changes root + ({ logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(46), {from: sender})); + destUpdateNonce = logs[0].args.updateNonce; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + + // 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 + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + + + // checking edge is updted correctly + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(originChainRoots.length, maxRoots); + assert.strictEqual(originChainRoots[0], destMerkleRoot); + + // native verification for dest chain + let deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + let { root, path_elements, path_index } = await tree.path(0); + let roots = [root, 0]; + let diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + let input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + let wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let tempSignals = publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // native verification for origin chain + // reset tree + tree = new MerkleTree(levels, null, prefix) + deposit = generateDeposit(originChainID); + await tree.insert(deposit.commitment); + ({ root, path_elements, path_index } = await tree.path(0)); + roots = [root, 0]; + diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + tempSignals = publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + }); + + afterEach(async () => { + tree = new MerkleTree(levels, null, prefix) + }) +}) + From 9fb590b689a71078abe7655beec817cfd89a060e Mon Sep 17 00:00:00 2001 From: KeltonMad <51988730+KeltonMad@users.noreply.github.com> Date: Thu, 5 Aug 2021 01:55:26 -0400 Subject: [PATCH 02/18] Update contracts/anchors/bridged/AnchorPoseidon2.sol Co-authored-by: drewstone --- contracts/anchors/bridged/AnchorPoseidon2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 67d10adc2..4767de181 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -50,7 +50,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { uint latestHistoryIndex; // currency events - event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint64 updateNonce, uint256 timestamp); + event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint64 nonce, uint256 timestamp); event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee); // bridge events event EdgeAddition(uint256 chainID, uint256 height, bytes32 merkleRoot); From 3a2dcf0a90de0bf369bd4e5357ffe3da327aaf4a Mon Sep 17 00:00:00 2001 From: KeltonMad <51988730+KeltonMad@users.noreply.github.com> Date: Thu, 5 Aug 2021 01:56:36 -0400 Subject: [PATCH 03/18] Update test/integration/twoChainIntegration.js Co-authored-by: drewstone --- test/integration/twoChainIntegration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/twoChainIntegration.js b/test/integration/twoChainIntegration.js index 9ec886919..8414b9fb2 100644 --- a/test/integration/twoChainIntegration.js +++ b/test/integration/twoChainIntegration.js @@ -785,6 +785,6 @@ contract('E2E LinkableAnchors - Two EVM Chains', async accounts => { afterEach(async () => { tree = new MerkleTree(levels, null, prefix) - }) + }) }) From 5258642965ab8df1570d2f618eb4d10b1187f802 Mon Sep 17 00:00:00 2001 From: KeltonMad <51988730+KeltonMad@users.noreply.github.com> Date: Thu, 5 Aug 2021 02:02:52 -0400 Subject: [PATCH 04/18] Update AnchorPoseidon2.sol --- contracts/anchors/bridged/AnchorPoseidon2.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 4767de181..f14702b36 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -20,7 +20,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { address public bridge; address public admin; address public handler; - uint64 public updateNonce; + uint64 public depositNonce; IVerifier public immutable verifier; uint256 public immutable denomination; @@ -94,7 +94,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { uint32 insertedIndex = _insert(_commitment); commitments[_commitment] = true; _processDeposit(); - updateNonce++; + depositNonce++; emit Deposit(_commitment, insertedIndex, updateNonce, block.timestamp); } From ae8a895d51c827a5a5c09e19f9eb2e34731e71bc Mon Sep 17 00:00:00 2001 From: KeltonMad <51988730+KeltonMad@users.noreply.github.com> Date: Thu, 5 Aug 2021 15:08:42 -0400 Subject: [PATCH 05/18] Update AnchorPoseidon2.sol --- contracts/anchors/bridged/AnchorPoseidon2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index f14702b36..9c6ec991a 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -79,7 +79,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { // TODO: Handle pruning length in function signature pruningLength = 100; latestHistoryIndex = 0; - updateNonce = 0; + depositNonce = 0; // TODO: Parameterize max roots (length of array should be max roots) rootHistory[latestHistoryIndex] = new bytes32[](1); } From eb9ba3133af71a14e68450b02f0ac032ea925641 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Thu, 5 Aug 2021 16:07:16 -0400 Subject: [PATCH 06/18] Fix nonce --- contracts/anchors/bridged/AnchorPoseidon2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 9c6ec991a..fb8a425fd 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -95,7 +95,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { commitments[_commitment] = true; _processDeposit(); depositNonce++; - emit Deposit(_commitment, insertedIndex, updateNonce, block.timestamp); + emit Deposit(_commitment, insertedIndex, depositNonce, block.timestamp); } From 76d3e39979aef6f49aa127cd41caa4e457881c72 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Thu, 5 Aug 2021 17:32:40 -0400 Subject: [PATCH 07/18] Formatting --- contracts/anchors/bridged/AnchorPoseidon2.sol | 10 +- .../bridged/LinkableAnchorPoseidon2.sol | 1 - test/integration/twoChainIntegration.js | 1461 +++++++++-------- 3 files changed, 733 insertions(+), 739 deletions(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index c866f2971..a3b775bb5 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -127,15 +127,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { 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)); diff --git a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol index cfe8cca5a..93fc30734 100644 --- a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol +++ b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol @@ -63,7 +63,6 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { // emit update event bytes32[1] memory neighbors = getLatestNeighborRoots(); emit RootHistoryUpdate(block.timestamp, neighbors); - } function updateEdge( diff --git a/test/integration/twoChainIntegration.js b/test/integration/twoChainIntegration.js index 8414b9fb2..5a783c7ce 100644 --- a/test/integration/twoChainIntegration.js +++ b/test/integration/twoChainIntegration.js @@ -1,19 +1,24 @@ +/** + * Copyright 2021 Webb Technologies + * SPDX-License-Identifier: GPL-3.0-or-later + */ + const TruffleAssert = require('truffle-assertions'); const Ethers = require('ethers'); const Helpers = require('../helpers'); 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 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 { toBN, randomHex } = require('web3-utils') const Poseidon = artifacts.require('PoseidonT3'); -const { NATIVE_AMOUNT, MERKLE_TREE_HEIGHT } = process.env +const { NATIVE_AMOUNT } = process.env let prefix = 'poseidon-test' const snarkjs = require('snarkjs') const bigInt = require('big-integer'); @@ -33,10 +38,10 @@ const { const PoseidonHasher = require('../../lib/Poseidon'); const MerkleTree = require('../../lib/MerkleTree'); -function bigNumberToPaddedBytes(num, digits = 32) { +function bigNumberToPaddedBytes(num, digits = 32) { var n = num.toString(16).replace(/^0x/, ''); while (n.length < (digits * 2)) { - n = "0" + n; + n = "0" + n; } return "0x" + n; } @@ -47,15 +52,15 @@ const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenH const toFixedHex = (number, length = 32) => '0x' + BigInt(`${number}`) - .toString(16) - .padStart(length * 2, '0') + .toString(16) + .padStart(length * 2, '0') const getRandomRecipient = () => rbigint(20) -function generateDeposit(targetChainID) { +function generateDeposit(targetChainID = 0) { let deposit = { - chainID: BigInt(targetChainID), - secret: rbigint(31), - nullifier: rbigint(31), + chainID: BigInt(targetChainID), + secret: rbigint(31), + nullifier: rbigint(31), } deposit.commitment = poseidonHasher.hash3([deposit.chainID, deposit.nullifier, deposit.secret]); @@ -64,727 +69,725 @@ function generateDeposit(targetChainID) { } contract('E2E LinkableAnchors - Two EVM Chains', async accounts => { - const relayerThreshold = 2; - const originChainID = 1; - const destChainID = 2; - const relayer1Address = accounts[3]; - const relayer2Address = accounts[4]; - const relayer1Bit = 1 << 0; - - const depositerAddress = accounts[1]; - const recipientAddress = accounts[2]; - const initialTokenAmount = 10000000000000; - const levels = MERKLE_TREE_HEIGHT || 30 - 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 = getRandomRecipient(); - const relayer = accounts[6]; - - - let originMerkleRoot; - let destMerkleRoot; - let originBlockHeight = 1; - let destBlockHeight = 1; - let originUpdateNonce = 1; - let destUpdateNonce = 1; - let hasher, verifier; - let originChainToken; - let destChainToken; - let tokenDenomination = '1000'; - 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 (this follows create2 scheme) - 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), - ]); - - await originChainToken.mint(sender, initialTokenAmount); - await destChainToken.mint(sender, initialTokenAmount); - // increase allowance and set resources for bridge - await Promise.all([ - originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), - destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), - OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), - DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) - ]); - - // deposit on both chains and define nonces based on events emmited - let { logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(42), {from: sender}); - originUpdateNonce = logs[0].args.updateNonce; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); - ({logs} = await DestChainLinkableAnchorInstance.deposit(toFixedHex(40), {from: sender})); - destUpdateNonce = logs[0].args.updateNonce; - destMerkleRoot = await DestChainLinkableAnchorInstance.getLastRoot(); - // set bridge and handler permissions for anchors - await OriginChainLinkableAnchorInstance.setHandler(OriginAnchorHandlerInstance.address, {from: sender}); - await OriginChainLinkableAnchorInstance.setBridge(OriginBridgeInstance.address, {from: sender}); - await DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}); - await DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}); - // create correct update proposal data for the deposits on both chains - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot,); - originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); - destDepositData = Helpers.createUpdateProposalData(destChainID, destBlockHeight, destMerkleRoot); - destDepositDataHash = Ethers.utils.keccak256(OriginAnchorHandlerInstance.address + destDepositData.substr(2)); - - createWitness = async (data) => { - const wtns = {type: "mem"}; - await snarkjs.wtns.calculate(data, path.join( - "artifacts/circuits", - "bridge", - "poseidon_bridge_2.wasm" - ), wtns); - return wtns; - } + const relayerThreshold = 2; + const originChainID = 1; + const destChainID = 2; + const relayer1Address = accounts[3]; + const relayer2Address = accounts[4]; + const relayer1Bit = 1 << 0; + + const initialBalanceToMint = 10000000000000; + 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 = getRandomRecipient(); + + let originMerkleRoot; + let destMerkleRoot; + let originBlockHeight = 1; + let destBlockHeight = 1; + let originUpdateNonce = 1; + let destUpdateNonce = 1; + let hasher, verifier; + let originChainToken; + let destChainToken; + let tokenDenomination = BigInt(1e21); + 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 (this follows create2 scheme) + resourceID = Helpers.createResourceID(OriginChainLinkableAnchorInstance.address, 0); + initialResourceIDs = [resourceID]; + originInitialContractAddresses = [DestChainLinkableAnchorInstance.address]; + destInitialContractAddresses = [OriginChainLinkableAnchorInstance.address]; - tree = new MerkleTree(levels, null, prefix) - zkey_final = fs.readFileSync('build/bridge2/circuit_final.zkey').buffer; + // 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), + ]); + + // mint initial balances for sender + await originChainToken.mint(sender, initialBalanceToMint); + await destChainToken.mint(sender, initialBalanceToMint); + // increase allowance and set resources for bridge + await Promise.all([ + originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialBalanceToMint, { from: sender }), + destChainToken.approve(DestChainLinkableAnchorInstance.address, initialBalanceToMint, { from: sender }), + OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), + DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) + ]); + + // set bridge and handler permissions for anchors + await OriginChainLinkableAnchorInstance.setHandler(OriginAnchorHandlerInstance.address, {from: sender}); + await OriginChainLinkableAnchorInstance.setBridge(OriginBridgeInstance.address, {from: sender}); + await DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}); + await DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}); + + // deposit on both chains and define nonces based on events emitted + let { logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(42), {from: sender}); + originUpdateNonce = logs[0].args.updateNonce; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + ({logs} = await DestChainLinkableAnchorInstance.deposit(toFixedHex(40), {from: sender})); + destUpdateNonce = logs[0].args.updateNonce; + destMerkleRoot = await DestChainLinkableAnchorInstance.getLastRoot(); + + // create correct update proposal data for the deposits on both chains + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot,); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + destDepositData = Helpers.createUpdateProposalData(destChainID, destBlockHeight, destMerkleRoot); + destDepositDataHash = Ethers.utils.keccak256(OriginAnchorHandlerInstance.address + destDepositData.substr(2)); + + 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('test/fixtures/circuit_final.zkey').buffer; + }); + + it('[sanity] origin chain bridge 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') + }) + + it('[sanity] deposit 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('[sanity] updateProposal on origin chain should be created with expected values', async () => { + await TruffleAssert.passes(OriginBridgeInstance.voteProposal( + destChainID, originUpdateNonce, resourceID, destDepositDataHash, {from: relayer1Address})); + + const expectedUpdateProposal = { + _yesVotes: relayer1Bit.toString(), + _yesVotesTotal: '1', + _status: '1' // Active + }; + + const updateProposal = await OriginBridgeInstance.getProposal( + destChainID, originUpdateNonce, destDepositDataHash); + + assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); + }); + + it('[sanity] updateProposal on destination chain should be created with expected values', async () => { + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, destUpdateNonce, resourceID, originDepositDataHash, {from: relayer1Address})); + + const expectedUpdateProposal = { + _yesVotes: relayer1Bit.toString(), + _yesVotesTotal: '1', + _status: '1' // Active + }; + + const updateProposal = await DestBridgeInstance.getProposal( + originChainID, destUpdateNonce, originDepositDataHash); + + assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); + }); + + it("E2E: deposit on origin chain withdraw on destination chain", async () => { + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // checking edge is added + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + + // native verification + const deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + const { root, path_elements, path_index } = await tree.path(0); + const roots = [root, 0]; + const diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); }); - - it('[sanity] origin chain bridge 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') - }) - - it('[sanity] deposit 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('[sanity] updateProposal on origin chain should be created with expected values', async () => { - await TruffleAssert.passes(OriginBridgeInstance.voteProposal( - destChainID, originUpdateNonce, resourceID, destDepositDataHash, {from: relayer1Address})); - - const expectedUpdateProposal = { - _yesVotes: relayer1Bit.toString(), - _yesVotesTotal: '1', - _status: '1' // Active - }; - - const updateProposal = await OriginBridgeInstance.getProposal( - destChainID, originUpdateNonce, destDepositDataHash); - - assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + const input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer: relayer1Address, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + const wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let tempSignals = publicSignals; + const vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + }); + + it("E2E: deposit and withdraw on both chains", async () => { + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // same process but for a deposit on dest chain leading to update on origin chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer2 will execute the deposit proposal + TruffleAssert.passes(await OriginBridgeInstance.executeProposal( + destChainID, + originUpdateNonce, + destDepositData, + resourceID, + { from: relayer2Address } + )); + // checking edges are added on both destChain and originChain + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(originChainRoots.length, maxRoots); + assert.strictEqual(originChainRoots[0], destMerkleRoot); + + // native verification for dest chain + let deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + let { root, path_elements, path_index } = await tree.path(0); + let roots = [root, 0]; + let diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); }); - - it('[sanity] updateProposal on destination chain should be created with expected values', async () => { - await TruffleAssert.passes(DestBridgeInstance.voteProposal( - originChainID, destUpdateNonce, resourceID, originDepositDataHash, {from: relayer1Address})); - - const expectedUpdateProposal = { - _yesVotes: relayer1Bit.toString(), - _yesVotesTotal: '1', - _status: '1' // Active - }; - - const updateProposal = await DestBridgeInstance.getProposal( - originChainID, destUpdateNonce, originDepositDataHash); - - assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + let input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer: relayer1Address, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).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 tempSignals = publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // native verification for origin chain + // reset tree + tree = new MerkleTree(merkleTreeHeight, null, prefix) + deposit = generateDeposit(originChainID); + await tree.insert(deposit.commitment); + ({ root, path_elements, path_index } = await tree.path(0)); + roots = [root, 0]; + diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); }); - - it("E2E: deposit on origin chain withdraw on destination chain", async () => { - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // checking edge is added - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - - // native verification - const deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - const { root, path_elements, path_index } = await tree.path(0); - const roots = [root, 0]; - const diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - const input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - const wtns = await createWitness(input); - - let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - let tempSignals = publicSignals; - const vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - }); - - it("E2E: deposit and withdraw on both chains", async () => { - // deposit on origin chain leads to update proposal on dest chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // same process but for a deposit on dest chain leading to update on origin chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer2Address } - )); - - // relayer2 will execute the deposit proposal - TruffleAssert.passes(await OriginBridgeInstance.executeProposal( - destChainID, - originUpdateNonce, - destDepositData, - resourceID, - { from: relayer2Address } - )); - // checking edges are added on both destChain and originChain - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(originChainRoots.length, maxRoots); - assert.strictEqual(originChainRoots[0], destMerkleRoot); - - // native verification for dest chain - let deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - let { root, path_elements, path_index } = await tree.path(0); - let roots = [root, 0]; - let diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - let input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - let wtns = await createWitness(input); - - let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - let tempSignals = publicSignals; - let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // native verification for origin chain - // reset tree - tree = new MerkleTree(levels, null, prefix) - deposit = generateDeposit(originChainID); - await tree.insert(deposit.commitment); - ({ root, path_elements, path_index } = await tree.path(0)); - roots = [root, 0]; - diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - wtns = await createWitness(input); - - res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - tempSignals = publicSignals; - vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer: relayer1Address, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + tempSignals = publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + }); + + it("E2E: adding edges on both chains and updating an edge on one", async () => { + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // same process but for a deposit on dest chain leading to update on origin chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer2 will execute the deposit proposal + TruffleAssert.passes(await OriginBridgeInstance.executeProposal( + destChainID, + originUpdateNonce, + destDepositData, + resourceID, + { from: relayer2Address } + )); + + // new deposit on originChain changes root + ({ logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(46), {from: sender})); + destUpdateNonce = logs[0].args.updateNonce; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + + // 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 + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + + + // checking edge is updted correctly + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(originChainRoots.length, maxRoots); + assert.strictEqual(originChainRoots[0], destMerkleRoot); + + // native verification for dest chain + let deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + let { root, path_elements, path_index } = await tree.path(0); + let roots = [root, 0]; + let diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); }); - - it("E2E: adding edges on both chains and updating an edge on one", async () => { - // deposit on origin chain leads to update proposal on dest chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // same process but for a deposit on dest chain leading to update on origin chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer2Address } - )); - - // relayer2 will execute the deposit proposal - TruffleAssert.passes(await OriginBridgeInstance.executeProposal( - destChainID, - originUpdateNonce, - destDepositData, - resourceID, - { from: relayer2Address } - )); - - // new deposit on originChain changes root - ({ logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(46), {from: sender})); - destUpdateNonce = logs[0].args.updateNonce; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); - originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); - - // 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 - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - - - // checking edge is updted correctly - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(originChainRoots.length, maxRoots); - assert.strictEqual(originChainRoots[0], destMerkleRoot); - - // native verification for dest chain - let deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - let { root, path_elements, path_index } = await tree.path(0); - let roots = [root, 0]; - let diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - let input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - let wtns = await createWitness(input); - - let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - let tempSignals = publicSignals; - let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // native verification for origin chain - // reset tree - tree = new MerkleTree(levels, null, prefix) - deposit = generateDeposit(originChainID); - await tree.insert(deposit.commitment); - ({ root, path_elements, path_index } = await tree.path(0)); - roots = [root, 0]; - diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - wtns = await createWitness(input); - - res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - tempSignals = publicSignals; - vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + let input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer: relayer1Address, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).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 tempSignals = publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // native verification for origin chain + // reset tree + tree = new MerkleTree(merkleTreeHeight, null, prefix) + deposit = generateDeposit(originChainID); + await tree.insert(deposit.commitment); + ({ root, path_elements, path_index } = await tree.path(0)); + roots = [root, 0]; + diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); }); - - afterEach(async () => { - tree = new MerkleTree(levels, null, prefix) - }) + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer: relayer1Address, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + tempSignals = publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + }); + + afterEach(async () => { + tree = new MerkleTree(merkleTreeHeight, null, prefix) + }) }) - + From ed48415769de990ebfa7dc4393c0c062c3f156c7 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Thu, 5 Aug 2021 13:40:46 -0400 Subject: [PATCH 08/18] stash --- test/integration/twoChainIntegration.js | 1455 +++++++++++------------ 1 file changed, 726 insertions(+), 729 deletions(-) diff --git a/test/integration/twoChainIntegration.js b/test/integration/twoChainIntegration.js index 5a783c7ce..d7761b5ae 100644 --- a/test/integration/twoChainIntegration.js +++ b/test/integration/twoChainIntegration.js @@ -1,8 +1,3 @@ -/** - * Copyright 2021 Webb Technologies - * SPDX-License-Identifier: GPL-3.0-or-later - */ - const TruffleAssert = require('truffle-assertions'); const Ethers = require('ethers'); const Helpers = require('../helpers'); @@ -18,7 +13,7 @@ const fs = require('fs') const path = require('path'); const { toBN, randomHex } = require('web3-utils') const Poseidon = artifacts.require('PoseidonT3'); -const { NATIVE_AMOUNT } = process.env +const { NATIVE_AMOUNT, MERKLE_TREE_HEIGHT } = process.env let prefix = 'poseidon-test' const snarkjs = require('snarkjs') const bigInt = require('big-integer'); @@ -38,10 +33,10 @@ const { const PoseidonHasher = require('../../lib/Poseidon'); const MerkleTree = require('../../lib/MerkleTree'); -function bigNumberToPaddedBytes(num, digits = 32) { +function bigNumberToPaddedBytes(num, digits = 32) { var n = num.toString(16).replace(/^0x/, ''); while (n.length < (digits * 2)) { - n = "0" + n; + n = "0" + n; } return "0x" + n; } @@ -52,15 +47,15 @@ const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenH const toFixedHex = (number, length = 32) => '0x' + BigInt(`${number}`) - .toString(16) - .padStart(length * 2, '0') + .toString(16) + .padStart(length * 2, '0') const getRandomRecipient = () => rbigint(20) -function generateDeposit(targetChainID = 0) { +function generateDeposit(targetChainID) { let deposit = { - chainID: BigInt(targetChainID), - secret: rbigint(31), - nullifier: rbigint(31), + chainID: BigInt(targetChainID), + secret: rbigint(31), + nullifier: rbigint(31), } deposit.commitment = poseidonHasher.hash3([deposit.chainID, deposit.nullifier, deposit.secret]); @@ -69,725 +64,727 @@ function generateDeposit(targetChainID = 0) { } contract('E2E LinkableAnchors - Two EVM Chains', async accounts => { - const relayerThreshold = 2; - const originChainID = 1; - const destChainID = 2; - const relayer1Address = accounts[3]; - const relayer2Address = accounts[4]; - const relayer1Bit = 1 << 0; - - const initialBalanceToMint = 10000000000000; - 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 = getRandomRecipient(); - - let originMerkleRoot; - let destMerkleRoot; - let originBlockHeight = 1; - let destBlockHeight = 1; - let originUpdateNonce = 1; - let destUpdateNonce = 1; - let hasher, verifier; - let originChainToken; - let destChainToken; - let tokenDenomination = BigInt(1e21); - 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 (this follows create2 scheme) - resourceID = Helpers.createResourceID(OriginChainLinkableAnchorInstance.address, 0); - initialResourceIDs = [resourceID]; - originInitialContractAddresses = [DestChainLinkableAnchorInstance.address]; - destInitialContractAddresses = [OriginChainLinkableAnchorInstance.address]; + const relayerThreshold = 2; + const originChainID = 1; + const destChainID = 2; + const relayer1Address = accounts[3]; + const relayer2Address = accounts[4]; + const relayer1Bit = 1 << 0; + + const depositerAddress = accounts[1]; + const recipientAddress = accounts[2]; + const initialTokenAmount = 10000000000000; + const levels = MERKLE_TREE_HEIGHT || 30 + 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 = getRandomRecipient(); + const relayer = accounts[6]; + + + let originMerkleRoot; + let destMerkleRoot; + let originBlockHeight = 1; + let destBlockHeight = 1; + let originUpdateNonce = 1; + let destUpdateNonce = 1; + let hasher, verifier; + let originChainToken; + let destChainToken; + let tokenDenomination = '1000'; + 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 (this follows create2 scheme) + 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), + ]); + + await originChainToken.mint(sender, initialTokenAmount); + await destChainToken.mint(sender, initialTokenAmount); + // increase allowance and set resources for bridge + await Promise.all([ + originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), + destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), + OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), + DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) + ]); + + // deposit on both chains and define nonces based on events emmited + let { logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(42), {from: sender}); + originUpdateNonce = logs[0].args.updateNonce; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + ({logs} = await DestChainLinkableAnchorInstance.deposit(toFixedHex(40), {from: sender})); + destUpdateNonce = logs[0].args.updateNonce; + destMerkleRoot = await DestChainLinkableAnchorInstance.getLastRoot(); + // set bridge and handler permissions for anchors + await OriginChainLinkableAnchorInstance.setHandler(OriginAnchorHandlerInstance.address, {from: sender}); + await OriginChainLinkableAnchorInstance.setBridge(OriginBridgeInstance.address, {from: sender}); + await DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}); + await DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}); + // create correct update proposal data for the deposits on both chains + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot,); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + destDepositData = Helpers.createUpdateProposalData(destChainID, destBlockHeight, destMerkleRoot); + destDepositDataHash = Ethers.utils.keccak256(OriginAnchorHandlerInstance.address + destDepositData.substr(2)); + + createWitness = async (data) => { + const wtns = {type: "mem"}; + await snarkjs.wtns.calculate(data, path.join( + "artifacts/circuits", + "bridge", + "poseidon_bridge_2.wasm" + ), wtns); + return wtns; + } - // 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), - ]); - - // mint initial balances for sender - await originChainToken.mint(sender, initialBalanceToMint); - await destChainToken.mint(sender, initialBalanceToMint); - // increase allowance and set resources for bridge - await Promise.all([ - originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialBalanceToMint, { from: sender }), - destChainToken.approve(DestChainLinkableAnchorInstance.address, initialBalanceToMint, { from: sender }), - OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), - DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) - ]); - - // set bridge and handler permissions for anchors - await OriginChainLinkableAnchorInstance.setHandler(OriginAnchorHandlerInstance.address, {from: sender}); - await OriginChainLinkableAnchorInstance.setBridge(OriginBridgeInstance.address, {from: sender}); - await DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}); - await DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}); - - // deposit on both chains and define nonces based on events emitted - let { logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(42), {from: sender}); - originUpdateNonce = logs[0].args.updateNonce; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); - ({logs} = await DestChainLinkableAnchorInstance.deposit(toFixedHex(40), {from: sender})); - destUpdateNonce = logs[0].args.updateNonce; - destMerkleRoot = await DestChainLinkableAnchorInstance.getLastRoot(); - - // create correct update proposal data for the deposits on both chains - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot,); - originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); - destDepositData = Helpers.createUpdateProposalData(destChainID, destBlockHeight, destMerkleRoot); - destDepositDataHash = Ethers.utils.keccak256(OriginAnchorHandlerInstance.address + destDepositData.substr(2)); - - 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('test/fixtures/circuit_final.zkey').buffer; - }); - - it('[sanity] origin chain bridge 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') - }) - - it('[sanity] deposit 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('[sanity] updateProposal on origin chain should be created with expected values', async () => { - await TruffleAssert.passes(OriginBridgeInstance.voteProposal( - destChainID, originUpdateNonce, resourceID, destDepositDataHash, {from: relayer1Address})); - - const expectedUpdateProposal = { - _yesVotes: relayer1Bit.toString(), - _yesVotesTotal: '1', - _status: '1' // Active - }; - - const updateProposal = await OriginBridgeInstance.getProposal( - destChainID, originUpdateNonce, destDepositDataHash); - - assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); - }); - - it('[sanity] updateProposal on destination chain should be created with expected values', async () => { - await TruffleAssert.passes(DestBridgeInstance.voteProposal( - originChainID, destUpdateNonce, resourceID, originDepositDataHash, {from: relayer1Address})); - - const expectedUpdateProposal = { - _yesVotes: relayer1Bit.toString(), - _yesVotesTotal: '1', - _status: '1' // Active - }; - - const updateProposal = await DestBridgeInstance.getProposal( - originChainID, destUpdateNonce, originDepositDataHash); - - assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); - }); - - it("E2E: deposit on origin chain withdraw on destination chain", async () => { - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // checking edge is added - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - - // native verification - const deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - const { root, path_elements, path_index } = await tree.path(0); - const roots = [root, 0]; - const diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); + tree = new MerkleTree(levels, null, prefix) + zkey_final = fs.readFileSync('build/bridge2/circuit_final.zkey').buffer; }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - const input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer: relayer1Address, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - const wtns = await createWitness(input); - - let res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - let tempSignals = publicSignals; - const vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - }); - - it("E2E: deposit and withdraw on both chains", async () => { - // deposit on origin chain leads to update proposal on dest chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // same process but for a deposit on dest chain leading to update on origin chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer2Address } - )); - - // relayer2 will execute the deposit proposal - TruffleAssert.passes(await OriginBridgeInstance.executeProposal( - destChainID, - originUpdateNonce, - destDepositData, - resourceID, - { from: relayer2Address } - )); - // checking edges are added on both destChain and originChain - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(originChainRoots.length, maxRoots); - assert.strictEqual(originChainRoots[0], destMerkleRoot); - - // native verification for dest chain - let deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - let { root, path_elements, path_index } = await tree.path(0); - let roots = [root, 0]; - let diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); + + it('[sanity] origin chain bridge 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') + }) + + it('[sanity] deposit 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('[sanity] updateProposal on origin chain should be created with expected values', async () => { + await TruffleAssert.passes(OriginBridgeInstance.voteProposal( + destChainID, originUpdateNonce, resourceID, destDepositDataHash, {from: relayer1Address})); + + const expectedUpdateProposal = { + _yesVotes: relayer1Bit.toString(), + _yesVotesTotal: '1', + _status: '1' // Active + }; + + const updateProposal = await OriginBridgeInstance.getProposal( + destChainID, originUpdateNonce, destDepositDataHash); + + assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - let input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer: relayer1Address, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).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 tempSignals = publicSignals; - let vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // native verification for origin chain - // reset tree - tree = new MerkleTree(merkleTreeHeight, null, prefix) - deposit = generateDeposit(originChainID); - await tree.insert(deposit.commitment); - ({ root, path_elements, path_index } = await tree.path(0)); - roots = [root, 0]; - diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); + + it('[sanity] updateProposal on destination chain should be created with expected values', async () => { + await TruffleAssert.passes(DestBridgeInstance.voteProposal( + originChainID, destUpdateNonce, resourceID, originDepositDataHash, {from: relayer1Address})); + + const expectedUpdateProposal = { + _yesVotes: relayer1Bit.toString(), + _yesVotesTotal: '1', + _status: '1' // Active + }; + + const updateProposal = await DestBridgeInstance.getProposal( + originChainID, destUpdateNonce, originDepositDataHash); + + assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer: relayer1Address, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - wtns = await createWitness(input); - - res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - tempSignals = publicSignals; - vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - }); - - it("E2E: adding edges on both chains and updating an edge on one", async () => { - // deposit on origin chain leads to update proposal on dest chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // same process but for a deposit on dest chain leading to update on origin chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer2Address } - )); - - // relayer2 will execute the deposit proposal - TruffleAssert.passes(await OriginBridgeInstance.executeProposal( - destChainID, - originUpdateNonce, - destDepositData, - resourceID, - { from: relayer2Address } - )); - - // new deposit on originChain changes root - ({ logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(46), {from: sender})); - destUpdateNonce = logs[0].args.updateNonce; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); - originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); - - // 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 - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - - - // checking edge is updted correctly - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(originChainRoots.length, maxRoots); - assert.strictEqual(originChainRoots[0], destMerkleRoot); - - // native verification for dest chain - let deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - let { root, path_elements, path_index } = await tree.path(0); - let roots = [root, 0]; - let diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); + + it("E2E: deposit on origin chain withdraw on destination chain", async () => { + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // checking edge is added + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + + // native verification + const deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + const { root, path_elements, path_index } = await tree.path(0); + const roots = [root, 0]; + const diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + const input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + const wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let tempSignals = publicSignals; + const vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + }); + + it("E2E: deposit and withdraw on both chains", async () => { + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // same process but for a deposit on dest chain leading to update on origin chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer2 will execute the deposit proposal + TruffleAssert.passes(await OriginBridgeInstance.executeProposal( + destChainID, + originUpdateNonce, + destDepositData, + resourceID, + { from: relayer2Address } + )); + // checking edges are added on both destChain and originChain + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(originChainRoots.length, maxRoots); + assert.strictEqual(originChainRoots[0], destMerkleRoot); + + // native verification for dest chain + let deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + let { root, path_elements, path_index } = await tree.path(0); + let roots = [root, 0]; + let diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + let input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + let wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let tempSignals = publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // native verification for origin chain + // reset tree + tree = new MerkleTree(levels, null, prefix) + deposit = generateDeposit(originChainID); + await tree.insert(deposit.commitment); + ({ root, path_elements, path_index } = await tree.path(0)); + roots = [root, 0]; + diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + tempSignals = publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - let input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer: relayer1Address, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).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 tempSignals = publicSignals; - let vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // native verification for origin chain - // reset tree - tree = new MerkleTree(merkleTreeHeight, null, prefix) - deposit = generateDeposit(originChainID); - await tree.insert(deposit.commitment); - ({ root, path_elements, path_index } = await tree.path(0)); - roots = [root, 0]; - diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); + + it("E2E: adding edges on both chains and updating an edge on one", async () => { + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // same process but for a deposit on dest chain leading to update on origin chain + // relayer1 creates the deposit proposal for the deposit that occured in the before each loop + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + originUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer2 will execute the deposit proposal + TruffleAssert.passes(await OriginBridgeInstance.executeProposal( + destChainID, + originUpdateNonce, + destDepositData, + resourceID, + { from: relayer2Address } + )); + + // new deposit on originChain changes root + ({ logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(46), {from: sender})); + destUpdateNonce = logs[0].args.updateNonce; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + + // 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 + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer1Address } + )); + + // relayer2 votes in favor of the update proposal + // because the relayerThreshold is 2, the deposit proposal will become passed + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + destUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + destUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + + + // checking edge is updted correctly + const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(destChainRoots.length, maxRoots); + assert.strictEqual(destChainRoots[0], originMerkleRoot); + const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); + assert.strictEqual(originChainRoots.length, maxRoots); + assert.strictEqual(originChainRoots[0], destMerkleRoot); + + // native verification for dest chain + let deposit = generateDeposit(destChainID); + await tree.insert(deposit.commitment); + let { root, path_elements, path_index } = await tree.path(0); + let roots = [root, 0]; + let diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + let input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + let wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + let tempSignals = publicSignals; + let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // native verification for origin chain + // reset tree + tree = new MerkleTree(levels, null, prefix) + deposit = generateDeposit(originChainID); + await tree.insert(deposit.commitment); + ({ root, path_elements, path_index } = await tree.path(0)); + roots = [root, 0]; + diffs = roots.map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }); + // mock set membership gadget computation + for (var i = 0; i < roots.length; i++) { + assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); + } + + input = { + // public + nullifierHash: deposit.nullifierHash, + recipient, + relayer, + fee, + refund, + chainID: deposit.chainID, + roots: [root, 0], + // private + nullifier: deposit.nullifier, + secret: deposit.secret, + pathElements: path_elements, + pathIndices: path_index, + diffs: [root, 0].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${root}`), + ).toString(); + }), + }; + + wtns = await createWitness(input); + + res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + tempSignals = publicSignals; + vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); + + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, true); + + // nullifier + publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // try to cheat with recipient + publicSignals[1] = '133738360804642228759657445999390850076318544422' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; + + // fee + publicSignals[2] = '1337100000000000000000' + res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + assert.strictEqual(res, false) + publicSignals = tempSignals; }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer: relayer1Address, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - wtns = await createWitness(input); - - res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - tempSignals = publicSignals; - vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - }); - - afterEach(async () => { - tree = new MerkleTree(merkleTreeHeight, null, prefix) - }) + + afterEach(async () => { + tree = new MerkleTree(levels, null, prefix) + }) }) - + From 22b71ab4484977a2ecb93cd1e4ae50bd0e312ec6 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Sat, 7 Aug 2021 13:34:09 -0600 Subject: [PATCH 09/18] Integration Tests --- contracts/anchors/bridged/AnchorPoseidon2.sol | 2 +- test/anchor/anchor.js | 226 +++-- test/helpers.js | 61 +- test/integration/multipleDepositWithdraw.js | 326 ++++++++ test/integration/simpleWithdrawals.js | 434 ++++++++++ test/integration/twoChainIntegration.js | 790 ------------------ test/merkleTree/MerkleTreePoseidon.test.js | 47 +- 7 files changed, 922 insertions(+), 964 deletions(-) create mode 100644 test/integration/multipleDepositWithdraw.js create mode 100644 test/integration/simpleWithdrawals.js delete mode 100644 test/integration/twoChainIntegration.js diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index a3b775bb5..5579e87f4 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -123,7 +123,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { ) external payable nonReentrant { 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(_root), "Cannot find your merkle root"); address rec = address(_recipient); address rel = address(_relayer); bytes32[1] memory neighbors = getLatestNeighborRoots(); diff --git a/test/anchor/anchor.js b/test/anchor/anchor.js index 120c02e93..df38298e2 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]; @@ -240,7 +213,7 @@ contract('AnchorPoseidon2', (accounts) => { describe('#withdraw', () => { it.only('should work', async () => { - const deposit = generateDeposit(chainID); + 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.toFixedHex(root), + 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); @@ -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); @@ -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); @@ -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); @@ -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,24 +615,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 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 = [ @@ -676,12 +648,12 @@ 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([ @@ -697,12 +669,12 @@ 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 = [ @@ -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) @@ -762,12 +734,12 @@ contract('AnchorPoseidon2', (accounts) => { 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 = [ @@ -785,16 +757,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,12 +799,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), ] let proofHex = helpers.toSolidityInput(proof); let proofEncoded = [ @@ -843,8 +815,8 @@ contract('AnchorPoseidon2', (accounts) => { ]; 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 +825,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/helpers.js b/test/helpers.js index 725858e0c..308cdaec3 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -3,29 +3,54 @@ * 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 getRandomRecipient = () => rbigint(20) + +function generateDeposit(targetChainID = 0, secret = 31) { + let deposit = { + chainID: BigInt(targetChainID), + secret: rbigint(secret), + nullifier: rbigint(31) + } - const abiEncode = (valueTypes, values) => { + deposit.commitment = poseidonHasher.hash3([deposit.chainID, deposit.nullifier, deposit.secret]); + deposit.nullifierHash = poseidonHasher.hash(null, deposit.nullifier, deposit.nullifier); + return deposit +} + +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) @@ -146,8 +171,11 @@ module.exports = { advanceBlock, blankFunctionSig, blankFunctionDepositerOffset, + getRandomRecipient, + toFixedHex, toHex, abiEncode, + generateDeposit, getFunctionSignature, createERCDepositData, createUpdateProposalData, @@ -157,4 +185,5 @@ module.exports = { toSolidityInput, p256, groth16ExportSolidityCallData, + }; diff --git a/test/integration/multipleDepositWithdraw.js b/test/integration/multipleDepositWithdraw.js new file mode 100644 index 000000000..047720586 --- /dev/null +++ b/test/integration/multipleDepositWithdraw.js @@ -0,0 +1,326 @@ +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 with multiple deposits', 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 (this follows create2 scheme) + 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 Promise.all([ + originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), + destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), + 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( + "artifacts/circuits", + "bridge", + "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.only('withdrawing across bridge after two deposits should work', async () => { + // minting Tokens + await originChainToken.mint(sender, initialTokenMintAmount); + // deposit on both chains and define nonces based on events emmited + let { logs } = await OriginChainLinkableAnchorInstance.deposit( + Helpers.toFixedHex(Helpers.generateDeposit(destChainID).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)); + + // 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 + TruffleAssert.passes(await 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 + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + originUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + + // 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; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + // create correct update proposal data for the deposit on origin chain + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + + // 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 + TruffleAssert.passes(await 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 + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await DestBridgeInstance.executeProposal( + originChainID, + originUpdateNonce, + originDepositData, + resourceID, + { from: relayer1Address } + )); + // check roots + 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)); + let balanceDestAnchorAfterDeposits = await destChainToken.balanceOf(DestChainLinkableAnchorInstance.address); + // insert two commitments into the tree + await tree.insert(Helpers.generateDeposit(destChainID).commitment); + await tree.insert(originDeposit.commitment); + + const { root, path_elements, path_index } = await tree.path(1); + + // verification fails (this discrepency is likley one of the reasons) + assert.strictEqual(destNeighborRoots[0], Helpers.toFixedHex(root)) + + const destNativeRoot = await DestChainLinkableAnchorInstance.getLastRoot(); + const 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}`), + ).toString(); + }), + }; + + const wtns = await createWitness(input); + + let res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + proof = res.proof; + publicSignals = res.publicSignals; + const 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) + const args = [ + Helpers.toFixedHex(await DestChainLinkableAnchorInstance.getLastRoot()), + 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); + const fullProof = JSON.parse("[" + result + "]"); + const pi_a = fullProof[0]; + const pi_b = fullProof[1]; + const pi_c = fullProof[2]; + const 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(''); + await destChainToken.mint(DestChainLinkableAnchorInstance.address, initialTokenMintAmount); + + ({ 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()) + console.log('balanceDestAnchorAfterDeposits: ', balanceDestAnchorAfterDeposits.toString()); + console.log('balanceOperatorBefore: ', balanceOperatorBefore.toString()); + console.log('balanceReceiverBefore: ', balanceReceiverBefore.toString()); + console.log('balanceDestAnchorAfter: ', balanceDestAnchorAfter.toString()); + console.log('balanceOperatorAfter: ', balanceOperatorAfter.toString()); + console.log('balanceReceiverAfter: ', balanceReceiverAfter.toString()); + console.log('feeBN: ', feeBN.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); + + }) +}) diff --git a/test/integration/simpleWithdrawals.js b/test/integration/simpleWithdrawals.js new file mode 100644 index 000000000..da786cb33 --- /dev/null +++ b/test/integration/simpleWithdrawals.js @@ -0,0 +1,434 @@ +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 (this follows create2 scheme) + 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([ + originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), + destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), + 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( + "artifacts/circuits", + "bridge", + "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 () => { + // minting Tokens + await originChainToken.mint(sender, initialTokenMintAmount); + // generate deposit commitment + 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)); + + // deposit on origin chain leads to update proposal on dest chain + // relayer1 creates the deposit proposal for the deposit + TruffleAssert.passes(await 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 + TruffleAssert.passes(await DestBridgeInstance.voteProposal( + originChainID, + originUpdateNonce, + resourceID, + originDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the deposit proposal + TruffleAssert.passes(await 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)); + + + 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}`), + ).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.toFixedHex(await DestChainLinkableAnchorInstance.getLastRoot()), + 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(''); + + 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); + + // minting Tokens + await destChainToken.mint(helpers.toFixedHex(sender, 20), initialTokenMintAmount); + // 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)); + + // deposit on dest chain leads to update proposal on origin chain + // relayer1 creates the deposit proposal + TruffleAssert.passes(await 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 + TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + destChainID, + destUpdateNonce, + resourceID, + destDepositDataHash, + { from: relayer2Address } + )); + + // relayer1 will execute the update proposal + TruffleAssert.passes(await 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)); + + 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].map(r => { + return F.sub( + Scalar.fromString(`${r}`), + Scalar.fromString(`${originNeighborRoots}`), + ).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.toFixedHex(await OriginChainLinkableAnchorInstance.getLastRoot()), + 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(''); + + 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/integration/twoChainIntegration.js b/test/integration/twoChainIntegration.js deleted file mode 100644 index d7761b5ae..000000000 --- a/test/integration/twoChainIntegration.js +++ /dev/null @@ -1,790 +0,0 @@ -const TruffleAssert = require('truffle-assertions'); -const Ethers = require('ethers'); -const Helpers = require('../helpers'); - -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 { toBN, randomHex } = require('web3-utils') -const Poseidon = artifacts.require('PoseidonT3'); -const { NATIVE_AMOUNT, MERKLE_TREE_HEIGHT } = process.env -let prefix = 'poseidon-test' -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) { - var n = num.toString(16).replace(/^0x/, ''); - while (n.length < (digits * 2)) { - n = "0" + n; - } - 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) { - 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 -} - -contract('E2E LinkableAnchors - Two EVM Chains', async accounts => { - const relayerThreshold = 2; - const originChainID = 1; - const destChainID = 2; - const relayer1Address = accounts[3]; - const relayer2Address = accounts[4]; - const relayer1Bit = 1 << 0; - - const depositerAddress = accounts[1]; - const recipientAddress = accounts[2]; - const initialTokenAmount = 10000000000000; - const levels = MERKLE_TREE_HEIGHT || 30 - 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 = getRandomRecipient(); - const relayer = accounts[6]; - - - let originMerkleRoot; - let destMerkleRoot; - let originBlockHeight = 1; - let destBlockHeight = 1; - let originUpdateNonce = 1; - let destUpdateNonce = 1; - let hasher, verifier; - let originChainToken; - let destChainToken; - let tokenDenomination = '1000'; - 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 (this follows create2 scheme) - 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), - ]); - - await originChainToken.mint(sender, initialTokenAmount); - await destChainToken.mint(sender, initialTokenAmount); - // increase allowance and set resources for bridge - await Promise.all([ - originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), - destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenAmount, { from: sender }), - OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), - DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) - ]); - - // deposit on both chains and define nonces based on events emmited - let { logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(42), {from: sender}); - originUpdateNonce = logs[0].args.updateNonce; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); - ({logs} = await DestChainLinkableAnchorInstance.deposit(toFixedHex(40), {from: sender})); - destUpdateNonce = logs[0].args.updateNonce; - destMerkleRoot = await DestChainLinkableAnchorInstance.getLastRoot(); - // set bridge and handler permissions for anchors - await OriginChainLinkableAnchorInstance.setHandler(OriginAnchorHandlerInstance.address, {from: sender}); - await OriginChainLinkableAnchorInstance.setBridge(OriginBridgeInstance.address, {from: sender}); - await DestChainLinkableAnchorInstance.setHandler(DestAnchorHandlerInstance.address, {from: sender}); - await DestChainLinkableAnchorInstance.setBridge(DestBridgeInstance.address, {from: sender}); - // create correct update proposal data for the deposits on both chains - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot,); - originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); - destDepositData = Helpers.createUpdateProposalData(destChainID, destBlockHeight, destMerkleRoot); - destDepositDataHash = Ethers.utils.keccak256(OriginAnchorHandlerInstance.address + destDepositData.substr(2)); - - createWitness = async (data) => { - const wtns = {type: "mem"}; - await snarkjs.wtns.calculate(data, path.join( - "artifacts/circuits", - "bridge", - "poseidon_bridge_2.wasm" - ), wtns); - return wtns; - } - - tree = new MerkleTree(levels, null, prefix) - zkey_final = fs.readFileSync('build/bridge2/circuit_final.zkey').buffer; - }); - - it('[sanity] origin chain bridge 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') - }) - - it('[sanity] deposit 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('[sanity] updateProposal on origin chain should be created with expected values', async () => { - await TruffleAssert.passes(OriginBridgeInstance.voteProposal( - destChainID, originUpdateNonce, resourceID, destDepositDataHash, {from: relayer1Address})); - - const expectedUpdateProposal = { - _yesVotes: relayer1Bit.toString(), - _yesVotesTotal: '1', - _status: '1' // Active - }; - - const updateProposal = await OriginBridgeInstance.getProposal( - destChainID, originUpdateNonce, destDepositDataHash); - - assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); - }); - - it('[sanity] updateProposal on destination chain should be created with expected values', async () => { - await TruffleAssert.passes(DestBridgeInstance.voteProposal( - originChainID, destUpdateNonce, resourceID, originDepositDataHash, {from: relayer1Address})); - - const expectedUpdateProposal = { - _yesVotes: relayer1Bit.toString(), - _yesVotesTotal: '1', - _status: '1' // Active - }; - - const updateProposal = await DestBridgeInstance.getProposal( - originChainID, destUpdateNonce, originDepositDataHash); - - assert.deepInclude(Object.assign({}, updateProposal), expectedUpdateProposal); - }); - - it("E2E: deposit on origin chain withdraw on destination chain", async () => { - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // checking edge is added - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - - // native verification - const deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - const { root, path_elements, path_index } = await tree.path(0); - const roots = [root, 0]; - const diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - const input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - const wtns = await createWitness(input); - - let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - let tempSignals = publicSignals; - const vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - }); - - it("E2E: deposit and withdraw on both chains", async () => { - // deposit on origin chain leads to update proposal on dest chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // same process but for a deposit on dest chain leading to update on origin chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer2Address } - )); - - // relayer2 will execute the deposit proposal - TruffleAssert.passes(await OriginBridgeInstance.executeProposal( - destChainID, - originUpdateNonce, - destDepositData, - resourceID, - { from: relayer2Address } - )); - // checking edges are added on both destChain and originChain - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(originChainRoots.length, maxRoots); - assert.strictEqual(originChainRoots[0], destMerkleRoot); - - // native verification for dest chain - let deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - let { root, path_elements, path_index } = await tree.path(0); - let roots = [root, 0]; - let diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - let input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - let wtns = await createWitness(input); - - let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - let tempSignals = publicSignals; - let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // native verification for origin chain - // reset tree - tree = new MerkleTree(levels, null, prefix) - deposit = generateDeposit(originChainID); - await tree.insert(deposit.commitment); - ({ root, path_elements, path_index } = await tree.path(0)); - roots = [root, 0]; - diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - wtns = await createWitness(input); - - res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - tempSignals = publicSignals; - vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - }); - - it("E2E: adding edges on both chains and updating an edge on one", async () => { - // deposit on origin chain leads to update proposal on dest chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - // same process but for a deposit on dest chain leading to update on origin chain - // relayer1 creates the deposit proposal for the deposit that occured in the before each loop - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( - destChainID, - originUpdateNonce, - resourceID, - destDepositDataHash, - { from: relayer2Address } - )); - - // relayer2 will execute the deposit proposal - TruffleAssert.passes(await OriginBridgeInstance.executeProposal( - destChainID, - originUpdateNonce, - destDepositData, - resourceID, - { from: relayer2Address } - )); - - // new deposit on originChain changes root - ({ logs } = await OriginChainLinkableAnchorInstance.deposit(toFixedHex(46), {from: sender})); - destUpdateNonce = logs[0].args.updateNonce; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); - originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); - - // 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 - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer1Address } - )); - - // relayer2 votes in favor of the update proposal - // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( - originChainID, - destUpdateNonce, - resourceID, - originDepositDataHash, - { from: relayer2Address } - )); - - // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( - originChainID, - destUpdateNonce, - originDepositData, - resourceID, - { from: relayer1Address } - )); - - - // checking edge is updted correctly - const destChainRoots = await DestChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(destChainRoots.length, maxRoots); - assert.strictEqual(destChainRoots[0], originMerkleRoot); - const originChainRoots = await OriginChainLinkableAnchorInstance.getLatestNeighborRoots(); - assert.strictEqual(originChainRoots.length, maxRoots); - assert.strictEqual(originChainRoots[0], destMerkleRoot); - - // native verification for dest chain - let deposit = generateDeposit(destChainID); - await tree.insert(deposit.commitment); - let { root, path_elements, path_index } = await tree.path(0); - let roots = [root, 0]; - let diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - let input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - let wtns = await createWitness(input); - - let res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - let tempSignals = publicSignals; - let vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // native verification for origin chain - // reset tree - tree = new MerkleTree(levels, null, prefix) - deposit = generateDeposit(originChainID); - await tree.insert(deposit.commitment); - ({ root, path_elements, path_index } = await tree.path(0)); - roots = [root, 0]; - diffs = roots.map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }); - // mock set membership gadget computation - for (var i = 0; i < roots.length; i++) { - assert.strictEqual(Scalar.fromString(roots[i]), F.add(Scalar.fromString(diffs[i]), Scalar.fromString(root))); - } - - input = { - // public - nullifierHash: deposit.nullifierHash, - recipient, - relayer, - fee, - refund, - chainID: deposit.chainID, - roots: [root, 0], - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, - diffs: [root, 0].map(r => { - return F.sub( - Scalar.fromString(`${r}`), - Scalar.fromString(`${root}`), - ).toString(); - }), - }; - - wtns = await createWitness(input); - - res = await snarkjs.groth16.prove('build/bridge2/circuit_final.zkey', wtns); - proof = res.proof; - publicSignals = res.publicSignals; - tempSignals = publicSignals; - vKey = await snarkjs.zKey.exportVerificationKey('build/bridge2/circuit_final.zkey'); - - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, true); - - // nullifier - publicSignals[0] = '133792158246920651341275668520530514036799294649489851421007411546007850802' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // try to cheat with recipient - publicSignals[1] = '133738360804642228759657445999390850076318544422' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - - // fee - publicSignals[2] = '1337100000000000000000' - res = await snarkjs.groth16.verify(vKey, publicSignals, proof); - assert.strictEqual(res, false) - publicSignals = tempSignals; - }); - - afterEach(async () => { - tree = new MerkleTree(levels, null, prefix) - }) -}) - 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)); }); }); }); From 40d0bee84d5822cff5c2505cb3bb50cecaf23d68 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Sat, 7 Aug 2021 20:52:40 -0600 Subject: [PATCH 10/18] Fixes --- contracts/anchors/bridged/AnchorPoseidon2.sol | 7 ++----- test/anchor/anchor.js | 2 +- test/integration/multipleDepositWithdraw.js | 6 +++--- test/integration/simpleWithdrawals.js | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 5579e87f4..316da7f12 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -22,7 +22,6 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { address public bridge; address public admin; address public handler; - uint64 public depositNonce; IVerifier public immutable verifier; uint256 public immutable denomination; @@ -52,7 +51,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { uint latestHistoryIndex; // currency events - event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint64 nonce, uint256 timestamp); + event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp); event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee); // bridge events event EdgeAddition(uint256 chainID, uint256 height, bytes32 merkleRoot); @@ -81,7 +80,6 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { // TODO: Handle pruning length in function signature pruningLength = 100; latestHistoryIndex = 0; - depositNonce = 0; // TODO: Parameterize max roots (length of array should be max roots) rootHistory[latestHistoryIndex] = new bytes32[](1); } @@ -96,8 +94,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { uint32 insertedIndex = _insert(_commitment); commitments[_commitment] = true; _processDeposit(); - depositNonce++; - emit Deposit(_commitment, insertedIndex, depositNonce, block.timestamp); + emit Deposit(_commitment, insertedIndex, block.timestamp); } diff --git a/test/anchor/anchor.js b/test/anchor/anchor.js index df38298e2..b1cf8b15c 100644 --- a/test/anchor/anchor.js +++ b/test/anchor/anchor.js @@ -212,7 +212,7 @@ contract('AnchorPoseidon2', (accounts) => { }) describe('#withdraw', () => { - it.only('should work', async () => { + it('should work', async () => { const deposit = helpers.generateDeposit(chainID); const user = accounts[4] await tree.insert(deposit.commitment) diff --git a/test/integration/multipleDepositWithdraw.js b/test/integration/multipleDepositWithdraw.js index 047720586..4ae0a8abd 100644 --- a/test/integration/multipleDepositWithdraw.js +++ b/test/integration/multipleDepositWithdraw.js @@ -126,7 +126,7 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') }) - it.only('withdrawing across bridge after two deposits should work', async () => { + it('withdrawing across bridge after two deposits should work', async () => { // minting Tokens await originChainToken.mint(sender, initialTokenMintAmount); // deposit on both chains and define nonces based on events emmited @@ -231,13 +231,13 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as fee, refund, chainID: originDeposit.chainID, - roots: [destNativeRoot, destNeighborRoots], + roots: [destNativeRoot, ...destNeighborRoots], // private nullifier: originDeposit.nullifier, secret: originDeposit.secret, pathElements: path_elements, pathIndices: path_index, - diffs: [destNativeRoot, destNeighborRoots].map(r => { + diffs: [destNativeRoot, destNeighborRoots[0]].map(r => { return F.sub( Scalar.fromString(`${r}`), Scalar.fromString(`${destNeighborRoots}`), diff --git a/test/integration/simpleWithdrawals.js b/test/integration/simpleWithdrawals.js index da786cb33..229dec7ce 100644 --- a/test/integration/simpleWithdrawals.js +++ b/test/integration/simpleWithdrawals.js @@ -145,7 +145,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') }) - it.only('withdrawals on both chains integration', async () => { + it('withdrawals on both chains integration', async () => { // minting Tokens await originChainToken.mint(sender, initialTokenMintAmount); // generate deposit commitment From 69172fafa952baac1b90a6e5c78514280b7b01a4 Mon Sep 17 00:00:00 2001 From: KeltonMad <51988730+KeltonMad@users.noreply.github.com> Date: Sun, 8 Aug 2021 21:16:57 -0400 Subject: [PATCH 11/18] Update multipleDepositWithdraw.js --- test/integration/multipleDepositWithdraw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/multipleDepositWithdraw.js b/test/integration/multipleDepositWithdraw.js index 047720586..1eaea1c75 100644 --- a/test/integration/multipleDepositWithdraw.js +++ b/test/integration/multipleDepositWithdraw.js @@ -85,7 +85,7 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as destChainID, destChainToken.address, {from: sender}); - // create resource ID using anchor address (this follows create2 scheme) + // create resource ID using anchor address resourceID = Helpers.createResourceID(OriginChainLinkableAnchorInstance.address, 0); initialResourceIDs = [resourceID]; destInitialContractAddresses = [OriginChainLinkableAnchorInstance.address]; From 5fff5a68bc008050477edda25a4751ec8341a658 Mon Sep 17 00:00:00 2001 From: KeltonMad <51988730+KeltonMad@users.noreply.github.com> Date: Sun, 8 Aug 2021 21:18:06 -0400 Subject: [PATCH 12/18] Update simpleWithdrawals.js --- test/integration/simpleWithdrawals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/simpleWithdrawals.js b/test/integration/simpleWithdrawals.js index da786cb33..30feaacce 100644 --- a/test/integration/simpleWithdrawals.js +++ b/test/integration/simpleWithdrawals.js @@ -95,7 +95,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { destChainID, destChainToken.address, {from: sender}); - // create resource ID using anchor address (this follows create2 scheme) + // create resource ID using anchor address resourceID = helpers.createResourceID(OriginChainLinkableAnchorInstance.address, 0); initialResourceIDs = [resourceID]; originInitialContractAddresses = [DestChainLinkableAnchorInstance.address]; From 24934f02dfeee192e5c2716212f52574c574cb5f Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Mon, 9 Aug 2021 09:08:28 -0600 Subject: [PATCH 13/18] tests now pass after fixes --- test/integration/multipleDepositWithdraw.js | 15 +++++++++------ test/integration/simpleWithdrawals.js | 14 +++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/test/integration/multipleDepositWithdraw.js b/test/integration/multipleDepositWithdraw.js index e1edf0c9d..9c36674f4 100644 --- a/test/integration/multipleDepositWithdraw.js +++ b/test/integration/multipleDepositWithdraw.js @@ -126,12 +126,13 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') }) - it('withdrawing across bridge after two deposits should work', async () => { + it.only('withdrawing across bridge after two deposits should work', async () => { // minting Tokens await originChainToken.mint(sender, initialTokenMintAmount); // deposit on both chains and define nonces based on events emmited + let firstOriginDeposit = Helpers.generateDeposit(destChainID); let { logs } = await OriginChainLinkableAnchorInstance.deposit( - Helpers.toFixedHex(Helpers.generateDeposit(destChainID).commitment), {from: sender}); + Helpers.toFixedHex(firstOriginDeposit.commitment), {from: sender}); originUpdateNonce = logs[0].args.leafIndex; originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); // create correct update proposal data for the deposit on origin chain @@ -212,9 +213,9 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as // check initial balances let balanceOperatorBefore = await destChainToken.balanceOf(operator); let balanceReceiverBefore = await destChainToken.balanceOf(Helpers.toFixedHex(recipient, 20)); - let balanceDestAnchorAfterDeposits = await destChainToken.balanceOf(DestChainLinkableAnchorInstance.address); + // insert two commitments into the tree - await tree.insert(Helpers.generateDeposit(destChainID).commitment); + await tree.insert(firstOriginDeposit.commitment); await tree.insert(originDeposit.commitment); const { root, path_elements, path_index } = await tree.path(1); @@ -240,7 +241,7 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as diffs: [destNativeRoot, destNeighborRoots[0]].map(r => { return F.sub( Scalar.fromString(`${r}`), - Scalar.fromString(`${destNeighborRoots}`), + Scalar.fromString(`${destNeighborRoots[0]}`), ).toString(); }), }; @@ -294,8 +295,10 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as ] .map(elt => elt.substr(2)) .join(''); + // 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' })); diff --git a/test/integration/simpleWithdrawals.js b/test/integration/simpleWithdrawals.js index 3d4f65913..45515adf9 100644 --- a/test/integration/simpleWithdrawals.js +++ b/test/integration/simpleWithdrawals.js @@ -145,7 +145,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') }) - it('withdrawals on both chains integration', async () => { + it.only('withdrawals on both chains integration', async () => { // minting Tokens await originChainToken.mint(sender, initialTokenMintAmount); // generate deposit commitment @@ -207,16 +207,16 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { fee, refund, chainID: originDeposit.chainID, - roots: [destNativeRoot, destNeighborRoots], + roots: [destNativeRoot, ...destNeighborRoots], // private nullifier: originDeposit.nullifier, secret: originDeposit.secret, pathElements: path_elements, pathIndices: path_index, - diffs: [destNativeRoot, destNeighborRoots].map(r => { + diffs: [destNativeRoot, destNeighborRoots[0]].map(r => { return F.sub( Scalar.fromString(`${r}`), - Scalar.fromString(`${destNeighborRoots}`), + Scalar.fromString(`${destNeighborRoots[0]}`), ).toString(); }), }; @@ -348,16 +348,16 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { fee, refund, chainID: destDeposit.chainID, - roots: [originNativeRoot, originNeighborRoots], + roots: [originNativeRoot, ...originNeighborRoots], // private nullifier: destDeposit.nullifier, secret: destDeposit.secret, pathElements: path_elements, pathIndices: path_index, - diffs: [originNativeRoot, originNeighborRoots].map(r => { + diffs: [originNativeRoot, originNeighborRoots[0]].map(r => { return F.sub( Scalar.fromString(`${r}`), - Scalar.fromString(`${originNeighborRoots}`), + Scalar.fromString(`${originNeighborRoots[0]}`), ).toString(); }), }; From cd9db5a8d10edbfde564a89082703eb4dc7139a1 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Mon, 9 Aug 2021 11:10:37 -0600 Subject: [PATCH 14/18] test fixes --- test/integration/multipleDepositWithdraw.js | 55 ++++++++++---------- test/integration/simpleWithdrawals.js | 56 ++++++++++++++------- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/test/integration/multipleDepositWithdraw.js b/test/integration/multipleDepositWithdraw.js index 9c36674f4..73d2f8654 100644 --- a/test/integration/multipleDepositWithdraw.js +++ b/test/integration/multipleDepositWithdraw.js @@ -95,22 +95,18 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as .then(instance => DestAnchorHandlerInstance = instance), ]); // increase allowance and set resources for bridge - await Promise.all([ - originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), - destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), - DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) - ]); + await DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) // set bridge and handler permissions for anchor - await Promise.all([ + 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( - "artifacts/circuits", - "bridge", + "test", + "fixtures", "poseidon_bridge_2.wasm" ), wtns); return wtns; @@ -126,9 +122,14 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') }) - it.only('withdrawing across bridge after two deposits should work', async () => { + 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( @@ -167,15 +168,17 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as resourceID, { from: relayer1Address } )); - - // 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; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); - // create correct update proposal data for the deposit on origin chain - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); - originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); + /* + * 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; + originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + // create correct update proposal data for the deposit on origin chain + originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); + originDepositDataHash = Ethers.utils.keccak256(DestAnchorHandlerInstance.address + originDepositData.substr(2)); // 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 @@ -213,7 +216,9 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as // check initial balances let balanceOperatorBefore = await destChainToken.balanceOf(operator); let balanceReceiverBefore = await destChainToken.balanceOf(Helpers.toFixedHex(recipient, 20)); - + /* + * generate proof + */ // insert two commitments into the tree await tree.insert(firstOriginDeposit.commitment); await tree.insert(originDeposit.commitment); @@ -295,6 +300,9 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as ] .map(elt => elt.substr(2)) .join(''); + /* + * withdraw + */ // mint to anchor and track balance await destChainToken.mint(DestChainLinkableAnchorInstance.address, initialTokenMintAmount); let balanceDestAnchorAfterDeposits = await destChainToken.balanceOf(DestChainLinkableAnchorInstance.address); @@ -306,13 +314,6 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as let balanceOperatorAfter = await destChainToken.balanceOf(input.relayer); let balanceReceiverAfter = await destChainToken.balanceOf(Helpers.toFixedHex(recipient, 20)); const feeBN = toBN(fee.toString()) - console.log('balanceDestAnchorAfterDeposits: ', balanceDestAnchorAfterDeposits.toString()); - console.log('balanceOperatorBefore: ', balanceOperatorBefore.toString()); - console.log('balanceReceiverBefore: ', balanceReceiverBefore.toString()); - console.log('balanceDestAnchorAfter: ', balanceDestAnchorAfter.toString()); - console.log('balanceOperatorAfter: ', balanceOperatorAfter.toString()); - console.log('balanceReceiverAfter: ', balanceReceiverAfter.toString()); - console.log('feeBN: ', feeBN.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()); diff --git a/test/integration/simpleWithdrawals.js b/test/integration/simpleWithdrawals.js index 45515adf9..59f952d7f 100644 --- a/test/integration/simpleWithdrawals.js +++ b/test/integration/simpleWithdrawals.js @@ -109,24 +109,22 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { ]); // increase allowance and set resources for bridge await Promise.all([ - originChainToken.approve(OriginChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), - destChainToken.approve(DestChainLinkableAnchorInstance.address, initialTokenMintAmount, { from: sender }), OriginBridgeInstance.adminSetResource(OriginAnchorHandlerInstance.address, resourceID, OriginChainLinkableAnchorInstance.address), DestBridgeInstance.adminSetResource(DestAnchorHandlerInstance.address, resourceID, DestChainLinkableAnchorInstance.address) ]); // set bridge and handler permissions for anchors - await Promise.all([ + 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( - "artifacts/circuits", - "bridge", + "test", + "fixtures", "poseidon_bridge_2.wasm" ), wtns); return wtns; @@ -145,19 +143,26 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') }) - it.only('withdrawals on both chains integration', async () => { + it('withdrawals on both chains integration', async () => { + /* + * Desposit on origin chain + */ // minting Tokens await originChainToken.mint(sender, initialTokenMintAmount); - // generate deposit commitment + // 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,); + 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 TruffleAssert.passes(await DestBridgeInstance.voteProposal( @@ -193,8 +198,9 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { // 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); @@ -213,7 +219,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { secret: originDeposit.secret, pathElements: path_elements, pathIndices: path_index, - diffs: [destNativeRoot, destNeighborRoots[0]].map(r => { + diffs: [destNativeRoot, ...destNeighborRoots].map(r => { return F.sub( Scalar.fromString(`${r}`), Scalar.fromString(`${destNeighborRoots[0]}`), @@ -267,7 +273,9 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { ] .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 @@ -287,9 +295,13 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { 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(helpers.toFixedHex(sender, 20), initialTokenMintAmount); + 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 @@ -299,7 +311,9 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { // 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 TruffleAssert.passes(await OriginBridgeInstance.voteProposal( @@ -334,7 +348,9 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { // 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); @@ -408,7 +424,9 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { ] .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 From dd3c85f201d98c0933b0327e099b3b447b943a30 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Tue, 10 Aug 2021 12:40:28 -0600 Subject: [PATCH 15/18] Neighbor Root History Integration --- contracts/anchors/bridged/AnchorPoseidon2.sol | 46 +++- .../bridged/LinkableAnchorPoseidon2.sol | 9 +- test/anchor/anchor.js | 21 +- test/helpers.js | 31 ++- ...tWithdraw.js => historicalRootWithdraw.js} | 258 +++++++++++++----- test/integration/simpleWithdrawals.js | 16 +- 6 files changed, 271 insertions(+), 110 deletions(-) rename test/integration/{multipleDepositWithdraw.js => historicalRootWithdraw.js} (64%) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 316da7f12..6faec23b6 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,7 +113,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { */ function withdraw( bytes calldata _proof, - bytes32 _root, + bytes32[2] calldata _roots, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, @@ -120,10 +122,14 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { ) external payable nonReentrant { require(_fee <= denomination, "Fee exceeds transfer value"); require(!nullifierHashes[_nullifierHash], "The note has been already spent"); - require(isKnownRoot(_root), "Cannot find your merkle root"); + 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(); uint256[8] memory inputs; inputs[0] = uint256(_nullifierHash); @@ -132,8 +138,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"); @@ -205,6 +211,7 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { } } } + /** @dev */ function getLatestNeighborRoots() public view returns (bytes32[1] memory roots) { for (uint256 i = 0; i < 1; i++) { @@ -216,6 +223,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 93fc30734..59d950bda 100644 --- a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol +++ b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol @@ -40,7 +40,7 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { bytes32[1] memory history = getLatestNeighborRoots(); rootHistory[latestHistoryIndex] = history; // set the next history index modulo pruning length - latestHistoryIndex = latestHistoryIndex % pruningLength; + latestHistoryIndex = latestHistoryIndex % ROOT_HISTORY_SIZE; emit RootHistoryRecorded(block.timestamp, history); } @@ -59,6 +59,9 @@ 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(); @@ -79,6 +82,10 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { root: root, height: height }); + // add to root histories + currentNeighborRootIndex[sourceChainID] = (currentNeighborRootIndex[sourceChainID] + 1) % 30; + uint32 neighborRootIndex = currentNeighborRootIndex[sourceChainID]; + neighborRoots[sourceChainID][neighborRootIndex] = root; emit EdgeUpdate(sourceChainID, height, root); // emit update event bytes32[1] memory neighbors = getLatestNeighborRoots(); diff --git a/test/anchor/anchor.js b/test/anchor/anchor.js index b1cf8b15c..ffc244829 100644 --- a/test/anchor/anchor.js +++ b/test/anchor/anchor.js @@ -278,7 +278,7 @@ contract('AnchorPoseidon2', (accounts) => { // gas = await anchor.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' }) // console.log('withdraw gas:', gas) const args = [ - helpers.toFixedHex(root), + helpers.arrayToFixedHex(input.roots), helpers.toFixedHex(input.nullifierHash), helpers.toFixedHex(input.recipient, 20), helpers.toFixedHex(input.relayer, 20), @@ -385,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' })); @@ -449,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( @@ -509,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( @@ -569,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( @@ -639,7 +639,7 @@ contract('AnchorPoseidon2', (accounts) => { ...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' }), @@ -660,7 +660,7 @@ contract('AnchorPoseidon2', (accounts) => { ...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' }), @@ -681,7 +681,7 @@ contract('AnchorPoseidon2', (accounts) => { ...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' }), @@ -732,7 +732,6 @@ contract('AnchorPoseidon2', (accounts) => { proof = res.proof; publicSignals = res.publicSignals; - const args = [ helpers.toFixedHex(root), helpers.toFixedHex(input.nullifierHash), @@ -746,7 +745,7 @@ contract('AnchorPoseidon2', (accounts) => { ...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' }), @@ -811,7 +810,7 @@ contract('AnchorPoseidon2', (accounts) => { ...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' }) diff --git a/test/helpers.js b/test/helpers.js index 308cdaec3..fcc6c3820 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -28,19 +28,14 @@ const toFixedHex = (number, length = 32) => .toString(16) .padStart(length * 2, '0') -const getRandomRecipient = () => rbigint(20) - -function generateDeposit(targetChainID = 0, secret = 31) { - let deposit = { - chainID: BigInt(targetChainID), - secret: rbigint(secret), - nullifier: rbigint(31) +const arrayToFixedHex = (array) => { + for (let i = 0; i < array.length; i++) { + array[i] = toFixedHex(array[i]); } + return array; +}; - deposit.commitment = poseidonHasher.hash3([deposit.chainID, deposit.nullifier, deposit.secret]); - deposit.nullifierHash = poseidonHasher.hash(null, deposit.nullifier, deposit.nullifier); - return deposit -} +const getRandomRecipient = () => rbigint(20) const abiEncode = (valueTypes, values) => { return AbiCoder.encode(valueTypes, values) @@ -111,6 +106,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); @@ -173,6 +180,7 @@ module.exports = { blankFunctionDepositerOffset, getRandomRecipient, toFixedHex, + arrayToFixedHex, toHex, abiEncode, generateDeposit, @@ -185,5 +193,4 @@ module.exports = { toSolidityInput, p256, groth16ExportSolidityCallData, - }; diff --git a/test/integration/multipleDepositWithdraw.js b/test/integration/historicalRootWithdraw.js similarity index 64% rename from test/integration/multipleDepositWithdraw.js rename to test/integration/historicalRootWithdraw.js index 73d2f8654..8a0760311 100644 --- a/test/integration/multipleDepositWithdraw.js +++ b/test/integration/historicalRootWithdraw.js @@ -21,7 +21,7 @@ const Scalar = require("ffjavascript").Scalar; const MerkleTree = require('../../lib/MerkleTree'); -contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', async accounts => { +contract('E2E LinkableAnchors - Cross chain withdraw using historical root should work', async accounts => { const relayerThreshold = 2; const originChainID = 1; const destChainID = 2; @@ -135,14 +135,14 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as let { logs } = await OriginChainLinkableAnchorInstance.deposit( Helpers.toFixedHex(firstOriginDeposit.commitment), {from: sender}); originUpdateNonce = logs[0].args.leafIndex; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + firstWithdrawlMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); // create correct update proposal data for the deposit on origin chain - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight, originMerkleRoot); + 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 - TruffleAssert.passes(await DestBridgeInstance.voteProposal( + await TruffleAssert.passes(DestBridgeInstance.voteProposal( originChainID, originUpdateNonce, resourceID, @@ -152,7 +152,7 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as // relayer2 votes in favor of the update proposal // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( + await TruffleAssert.passes(DestBridgeInstance.voteProposal( originChainID, originUpdateNonce, resourceID, @@ -161,13 +161,96 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as )); // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( + 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.arrayToFixedHex(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 */ @@ -175,14 +258,16 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as originDeposit = Helpers.generateDeposit(destChainID, 30); ({ logs } = await OriginChainLinkableAnchorInstance.deposit(Helpers.toFixedHex(originDeposit.commitment), {from: sender})); originUpdateNonce = logs[0].args.leafIndex; - originMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); + secondWithdrawalMerkleRoot = await OriginChainLinkableAnchorInstance.getLastRoot(); // create correct update proposal data for the deposit on origin chain - originDepositData = Helpers.createUpdateProposalData(originChainID, originBlockHeight + 10, originMerkleRoot); + 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 - TruffleAssert.passes(await DestBridgeInstance.voteProposal( + await TruffleAssert.passes(DestBridgeInstance.voteProposal( originChainID, originUpdateNonce, resourceID, @@ -192,7 +277,7 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as // relayer2 votes in favor of the update proposal // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( + await TruffleAssert.passes(DestBridgeInstance.voteProposal( originChainID, originUpdateNonce, resourceID, @@ -201,35 +286,51 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as )); // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( + await TruffleAssert.passes(DestBridgeInstance.executeProposal( originChainID, originUpdateNonce, originDepositData, resourceID, { from: relayer1Address } )); - // check roots - 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 + * withdraw */ - // insert two commitments into the tree - await tree.insert(firstOriginDeposit.commitment); - await tree.insert(originDeposit.commitment); - - const { root, path_elements, path_index } = await tree.path(1); + // 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()); - // verification fails (this discrepency is likley one of the reasons) - assert.strictEqual(destNeighborRoots[0], Helpers.toFixedHex(root)) + isSpent = await DestChainLinkableAnchorInstance.isSpent(Helpers.toFixedHex(input.nullifierHash)); + assert(isSpent); - const destNativeRoot = await DestChainLinkableAnchorInstance.getLastRoot(); - const input = { + /* + * 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, @@ -237,37 +338,34 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as fee, refund, chainID: originDeposit.chainID, - roots: [destNativeRoot, ...destNeighborRoots], + roots: [destNativeRoot, ...secondWithdrawalNeighborRoots], // private nullifier: originDeposit.nullifier, secret: originDeposit.secret, pathElements: path_elements, pathIndices: path_index, - diffs: [destNativeRoot, destNeighborRoots[0]].map(r => { + diffs: [destNativeRoot, secondWithdrawalNeighborRoots[0]].map(r => { return F.sub( Scalar.fromString(`${r}`), - Scalar.fromString(`${destNeighborRoots[0]}`), + Scalar.fromString(`${secondWithdrawalNeighborRoots[0]}`), ).toString(); }), }; - const wtns = await createWitness(input); + wtns = await createWitness(input); - let res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); + res = await snarkjs.groth16.prove('test/fixtures/circuit_final.zkey', wtns); proof = res.proof; publicSignals = res.publicSignals; - const vKey = await snarkjs.zKey.exportVerificationKey('test/fixtures/circuit_final.zkey'); + 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)); + 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) - const args = [ - Helpers.toFixedHex(await DestChainLinkableAnchorInstance.getLastRoot()), + args = [ + Helpers.arrayToFixedHex(input.roots), Helpers.toFixedHex(input.nullifierHash), Helpers.toFixedHex(input.recipient, 20), Helpers.toFixedHex(input.relayer, 20), @@ -275,12 +373,12 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as Helpers.toFixedHex(input.refund), ]; - const result = await Helpers.groth16ExportSolidityCallData(proof, publicSignals); - const fullProof = JSON.parse("[" + result + "]"); - const pi_a = fullProof[0]; - const pi_b = fullProof[1]; - const pi_c = fullProof[2]; - const inputs = fullProof[3]; + 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, @@ -300,31 +398,55 @@ contract('E2E LinkableAnchors - Cross chain withdraw with multiple deposits', as ] .map(elt => elt.substr(2)) .join(''); + /* - * withdraw + * create 30 new deposits on chain so history wraps around and forgets second deposit */ - // 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); + 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 index 59f952d7f..2b794d1ae 100644 --- a/test/integration/simpleWithdrawals.js +++ b/test/integration/simpleWithdrawals.js @@ -165,7 +165,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { */ // deposit on origin chain leads to update proposal on dest chain // relayer1 creates the deposit proposal for the deposit - TruffleAssert.passes(await DestBridgeInstance.voteProposal( + await TruffleAssert.passes(DestBridgeInstance.voteProposal( originChainID, originUpdateNonce, resourceID, @@ -175,7 +175,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { // relayer2 votes in favor of the update proposal // because the relayerThreshold is 2, the deposit proposal will become passed - TruffleAssert.passes(await DestBridgeInstance.voteProposal( + await TruffleAssert.passes(DestBridgeInstance.voteProposal( originChainID, originUpdateNonce, resourceID, @@ -184,7 +184,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { )); // relayer1 will execute the deposit proposal - TruffleAssert.passes(await DestBridgeInstance.executeProposal( + await TruffleAssert.passes(DestBridgeInstance.executeProposal( originChainID, originUpdateNonce, originDepositData, @@ -240,7 +240,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.strictEqual(isSpent, false); let args = [ - helpers.toFixedHex(await DestChainLinkableAnchorInstance.getLastRoot()), + helpers.arrayToFixedHex(input.roots), helpers.toFixedHex(input.nullifierHash), helpers.toFixedHex(input.recipient, 20), helpers.toFixedHex(input.relayer, 20), @@ -316,7 +316,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { */ // deposit on dest chain leads to update proposal on origin chain // relayer1 creates the deposit proposal - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + await TruffleAssert.passes(OriginBridgeInstance.voteProposal( destChainID, destUpdateNonce, resourceID, @@ -326,7 +326,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { // relayer2 votes in favor of the update proposal // because the relayerThreshold is 2, the update proposal will become passed - TruffleAssert.passes(await OriginBridgeInstance.voteProposal( + await TruffleAssert.passes(OriginBridgeInstance.voteProposal( destChainID, destUpdateNonce, resourceID, @@ -335,7 +335,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { )); // relayer1 will execute the update proposal - TruffleAssert.passes(await OriginBridgeInstance.executeProposal( + await TruffleAssert.passes(OriginBridgeInstance.executeProposal( destChainID, destUpdateNonce, destDepositData, @@ -391,7 +391,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.strictEqual(isSpent, false); args = [ - helpers.toFixedHex(await OriginChainLinkableAnchorInstance.getLastRoot()), + helpers.arrayToFixedHex(input.roots), helpers.toFixedHex(input.nullifierHash), helpers.toFixedHex(input.recipient, 20), helpers.toFixedHex(input.relayer, 20), From 42a6847f2c76fed0870b188bfe9031e028638141 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Tue, 10 Aug 2021 12:45:18 -0600 Subject: [PATCH 16/18] spacing --- contracts/anchors/bridged/AnchorPoseidon2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 6faec23b6..9b3e0b560 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -96,8 +96,8 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { uint32 insertedIndex = _insert(_commitment); commitments[_commitment] = true; _processDeposit(); - emit Deposit(_commitment, insertedIndex, block.timestamp); + emit Deposit(_commitment, insertedIndex, block.timestamp); } /** @dev this function is defined in a child contract */ From ef3a4bcb2f225b2e09c5e3d226a631ab54743db3 Mon Sep 17 00:00:00 2001 From: KeltonMad <51988730+KeltonMad@users.noreply.github.com> Date: Wed, 11 Aug 2021 09:58:03 -0400 Subject: [PATCH 17/18] Update contracts/anchors/bridged/LinkableAnchorPoseidon2.sol Co-authored-by: nepoche --- contracts/anchors/bridged/LinkableAnchorPoseidon2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol index 59d950bda..6fca9717d 100644 --- a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol +++ b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol @@ -83,7 +83,7 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { height: height }); // add to root histories - currentNeighborRootIndex[sourceChainID] = (currentNeighborRootIndex[sourceChainID] + 1) % 30; + currentNeighborRootIndex[sourceChainID] = (currentNeighborRootIndex[sourceChainID] + 1) % ROOT_HISTORY_SIZE; uint32 neighborRootIndex = currentNeighborRootIndex[sourceChainID]; neighborRoots[sourceChainID][neighborRootIndex] = root; emit EdgeUpdate(sourceChainID, height, root); From 1963af5cc14c96dfafd63d5ee34b241b24246435 Mon Sep 17 00:00:00 2001 From: KeltonMad Date: Wed, 11 Aug 2021 06:07:07 -0600 Subject: [PATCH 18/18] Fixes --- contracts/anchors/bridged/AnchorPoseidon2.sol | 47 ++++++++++--------- .../bridged/LinkableAnchorPoseidon2.sol | 13 +---- contracts/interfaces/ILinkableAnchor.sol | 3 +- test/anchor/anchor.js | 2 +- test/helpers.js | 13 ++++- test/integration/historicalRootWithdraw.js | 4 +- test/integration/simpleWithdrawals.js | 6 +-- 7 files changed, 44 insertions(+), 44 deletions(-) diff --git a/contracts/anchors/bridged/AnchorPoseidon2.sol b/contracts/anchors/bridged/AnchorPoseidon2.sol index 9b3e0b560..9d780e96d 100644 --- a/contracts/anchors/bridged/AnchorPoseidon2.sol +++ b/contracts/anchors/bridged/AnchorPoseidon2.sol @@ -113,21 +113,22 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { */ function withdraw( bytes calldata _proof, - bytes32[2] calldata _roots, + 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(_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"); - } + 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); @@ -138,8 +139,8 @@ abstract contract AnchorPoseidon2 is MerkleTreePoseidon, ReentrancyGuard { inputs[3] = uint256(_fee); inputs[4] = uint256(_refund); inputs[5] = uint256(chainID); - inputs[6] = uint256(_roots[0]); - inputs[7] = uint256(_roots[1]); + inputs[6] = uint256(roots[0]); + inputs[7] = uint256(roots[1]); bytes memory encodedInputs = abi.encodePacked(inputs); require(verify(_proof, encodedInputs), "Invalid withdraw proof"); @@ -225,21 +226,21 @@ 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; + if (_root == 0) { + return false; } - i--; - } while (i != _currentRootIndex); - 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() { diff --git a/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol b/contracts/anchors/bridged/LinkableAnchorPoseidon2.sol index 6fca9717d..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 % ROOT_HISTORY_SIZE; - emit RootHistoryRecorded(block.timestamp, history); - } - function addEdge( uint256 sourceChainID, bytes32 root, @@ -83,8 +74,8 @@ abstract contract LinkableAnchorPoseidon2 is AnchorPoseidon2, ILinkableAnchor { height: height }); // add to root histories - currentNeighborRootIndex[sourceChainID] = (currentNeighborRootIndex[sourceChainID] + 1) % ROOT_HISTORY_SIZE; - uint32 neighborRootIndex = currentNeighborRootIndex[sourceChainID]; + uint32 neighborRootIndex = (currentNeighborRootIndex[sourceChainID] + 1) % ROOT_HISTORY_SIZE; + currentNeighborRootIndex[sourceChainID] = neighborRootIndex; neighborRoots[sourceChainID][neighborRootIndex] = root; emit EdgeUpdate(sourceChainID, height, root); // emit update event 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 ffc244829..fca412780 100644 --- a/test/anchor/anchor.js +++ b/test/anchor/anchor.js @@ -278,7 +278,7 @@ contract('AnchorPoseidon2', (accounts) => { // gas = await anchor.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' }) // console.log('withdraw gas:', gas) const args = [ - helpers.arrayToFixedHex(input.roots), + helpers.createRootsBytes(input.roots), helpers.toFixedHex(input.nullifierHash), helpers.toFixedHex(input.recipient, 20), helpers.toFixedHex(input.relayer, 20), diff --git a/test/helpers.js b/test/helpers.js index fcc6c3820..d2d260457 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -48,8 +48,8 @@ const getFunctionSignature = (contractInstance, functionName) => { 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) => { @@ -59,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]) @@ -187,6 +195,7 @@ module.exports = { getFunctionSignature, createERCDepositData, createUpdateProposalData, + createRootsBytes, createResourceID, assertObjectsMatch, nonceAndId, diff --git a/test/integration/historicalRootWithdraw.js b/test/integration/historicalRootWithdraw.js index 8a0760311..37f52b038 100644 --- a/test/integration/historicalRootWithdraw.js +++ b/test/integration/historicalRootWithdraw.js @@ -217,7 +217,7 @@ contract('E2E LinkableAnchors - Cross chain withdraw using historical root shoul // gas = await anchor.withdraw.estimateGas(proof, publicSignals, { from: relayer, gasPrice: '0' }) // console.log('withdraw gas:', gas) let args = [ - Helpers.arrayToFixedHex(input.roots), + Helpers.createRootsBytes(input.roots), Helpers.toFixedHex(input.nullifierHash), Helpers.toFixedHex(input.recipient, 20), Helpers.toFixedHex(input.relayer, 20), @@ -365,7 +365,7 @@ contract('E2E LinkableAnchors - Cross chain withdraw using historical root shoul assert.strictEqual(isSpent, false); args = [ - Helpers.arrayToFixedHex(input.roots), + Helpers.createRootsBytes(input.roots), Helpers.toFixedHex(input.nullifierHash), Helpers.toFixedHex(input.recipient, 20), Helpers.toFixedHex(input.relayer, 20), diff --git a/test/integration/simpleWithdrawals.js b/test/integration/simpleWithdrawals.js index 2b794d1ae..cda37cd93 100644 --- a/test/integration/simpleWithdrawals.js +++ b/test/integration/simpleWithdrawals.js @@ -143,7 +143,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.equal((await DestBridgeInstance._totalRelayers()).toString(), '2') }) - it('withdrawals on both chains integration', async () => { + it.only('withdrawals on both chains integration', async () => { /* * Desposit on origin chain */ @@ -240,7 +240,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.strictEqual(isSpent, false); let args = [ - helpers.arrayToFixedHex(input.roots), + helpers.createRootsBytes(input.roots), helpers.toFixedHex(input.nullifierHash), helpers.toFixedHex(input.recipient, 20), helpers.toFixedHex(input.relayer, 20), @@ -391,7 +391,7 @@ contract('E2E LinkableAnchors - Cross chain withdrawals', async accounts => { assert.strictEqual(isSpent, false); args = [ - helpers.arrayToFixedHex(input.roots), + helpers.createRootsBytes(input.roots), helpers.toFixedHex(input.nullifierHash), helpers.toFixedHex(input.recipient, 20), helpers.toFixedHex(input.relayer, 20),