From b4dbc42bec6820ef1c96e7d72ac5d2a1632d7a89 Mon Sep 17 00:00:00 2001 From: madhurMongia Date: Tue, 17 Sep 2024 15:16:44 +0530 Subject: [PATCH] test(arbToGno): added more unit test and concurrency in deployment scripts --- .../01-outbox/01-arb-to-gnosis-outbox.ts | 15 +- .../deploy/02-inbox/02-arb-to-gnosis-inbox.ts | 13 +- .../03-routers/03-arb-to-gnosis-router.ts | 22 +- contracts/test/integration/ArbToGnosis.ts | 201 +++++++++++++++++- 4 files changed, 229 insertions(+), 22 deletions(-) diff --git a/contracts/deploy/01-outbox/01-arb-to-gnosis-outbox.ts b/contracts/deploy/01-outbox/01-arb-to-gnosis-outbox.ts index 23e3dcb8..2b584579 100644 --- a/contracts/deploy/01-outbox/01-arb-to-gnosis-outbox.ts +++ b/contracts/deploy/01-outbox/01-arb-to-gnosis-outbox.ts @@ -36,7 +36,7 @@ const paramsByChainId = { sequencerLimit: 86400, // 24 hours }, HARDHAT: { - deposit: parseEther("10"), // 120 xDAI budget for timeout + deposit: parseEther("10"), // Average happy path wait time is 22.5 mins, assume no censorship epochPeriod: 600, // 10 min minChallengePeriod: 600, // 10 min (assume no sequencer backdating) @@ -56,8 +56,15 @@ const deployOutbox: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { providers } = ethers; // fallback to hardhat node signers on local network - const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; - const chainId = Number(await getChainId()); + const [namedAccounts, signers, rawChainId] = await Promise.all([ + getNamedAccounts(), + hre.ethers.getSigners(), + getChainId(), + ]); + + const deployer = namedAccounts.deployer ?? signers[0].address; + const chainId = Number(rawChainId); + console.log("deploying to chainId %s with deployer %s", chainId, deployer); const routerNetworks = { @@ -89,7 +96,7 @@ const deployOutbox: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { console.log("calculated future router for nonce %d: %s", nonce + 10, routerAddress); const senderGatewayAddress = getContractAddress(deployer, nonce + 6); // with the current order of transaction ,nonce for sender gateway would be 14. - console.log("calculated future SenderGatewayToGnosis address for nonce %d: %s", nonce, senderGatewayAddress); + console.log("calculated future SenderGatewayToGnosis address for nonce %d: %s", nonce + 6, senderGatewayAddress); const ambMock = await deploy("MockAMB", { from: deployer, diff --git a/contracts/deploy/02-inbox/02-arb-to-gnosis-inbox.ts b/contracts/deploy/02-inbox/02-arb-to-gnosis-inbox.ts index 629a2032..55a44bfe 100644 --- a/contracts/deploy/02-inbox/02-arb-to-gnosis-inbox.ts +++ b/contracts/deploy/02-inbox/02-arb-to-gnosis-inbox.ts @@ -18,7 +18,6 @@ const paramsByChainId = { }, HARDHAT: { epochPeriod: 600, // 10 minutes - routerAddress: ethers.constants.AddressZero, }, }; @@ -27,8 +26,16 @@ const deployInbox: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deploy } = deployments; // fallback to hardhat node signers on local network - const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; - const chainId = Number(await getChainId()); + const [namedAccounts, signers, rawChainId] = await Promise.all([ + getNamedAccounts(), + hre.ethers.getSigners(), + getChainId(), + ]); + + const deployer = namedAccounts.deployer ?? signers[0].address; + const chainId = Number(rawChainId); + + console.log("deploying to chainId %s with deployer %s", chainId, deployer); const { epochPeriod } = paramsByChainId[SenderChains[chainId]]; diff --git a/contracts/deploy/03-routers/03-arb-to-gnosis-router.ts b/contracts/deploy/03-routers/03-arb-to-gnosis-router.ts index a0198009..0572747b 100644 --- a/contracts/deploy/03-routers/03-arb-to-gnosis-router.ts +++ b/contracts/deploy/03-routers/03-arb-to-gnosis-router.ts @@ -27,20 +27,28 @@ const paramsByChainId = { const deployRouter: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deployments, getNamedAccounts, getChainId } = hre; const { deploy } = deployments; - const chainId = Number(await getChainId()); // fallback to hardhat node signers on local network - const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; - console.log("deployer: %s", deployer); + const [namedAccounts, signers, rawChainId] = await Promise.all([ + getNamedAccounts(), + hre.ethers.getSigners(), + getChainId(), + ]); + + const deployer = namedAccounts.deployer ?? signers[0].address; + const chainId = Number(rawChainId); + + console.log("deploying to chainId %s with deployer %s", chainId, deployer); const { arbitrumBridge, amb } = paramsByChainId[RouterChains[chainId]]; // ---------------------------------------------------------------------------------------------- const hardhatDeployer = async () => { - const veaOutbox = await deployments.get("VeaOutboxArbToGnosis"); - const veaInbox = await deployments.get("VeaInboxArbToGnosis"); - const amb = await deployments.get("MockAMB"); - //const ArbSysMock = await deployments.get('arbSysMock'); + const [veaOutbox, veaInbox, amb] = await Promise.all([ + deployments.get("VeaOutboxArbToGnosis"), + deployments.get("VeaInboxArbToGnosis"), + deployments.get("MockAMB"), + ]); const sequencerInbox = await deploy("SequencerInboxMock", { from: deployer, diff --git a/contracts/test/integration/ArbToGnosis.ts b/contracts/test/integration/ArbToGnosis.ts index ca91525e..3eef5488 100644 --- a/contracts/test/integration/ArbToGnosis.ts +++ b/contracts/test/integration/ArbToGnosis.ts @@ -369,12 +369,12 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { await network.provider.send("evm_mine"); // Ensure bridger and challenger have enough WETH - await weth.transfer(bridger.address, TEN_ETH.mul(2)); - await weth.transfer(challenger.address, TEN_ETH.mul(2)); + await weth.transfer(bridger.address, TEN_ETH.mul(10)); + await weth.transfer(challenger.address, TEN_ETH.mul(10)); // Approve WETH spending for both - await weth.connect(bridger).approve(veaOutbox.address, TEN_ETH.mul(2)); - await weth.connect(challenger).approve(veaOutbox.address, TEN_ETH.mul(2)); + await weth.connect(bridger).approve(veaOutbox.address, TEN_ETH.mul(10)); + await weth.connect(challenger).approve(veaOutbox.address, TEN_ETH.mul(10)); await amb.setMaxGasPerTx(100000); }); @@ -447,10 +447,102 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { const verifiedEvent = verifiedEvents[0]; expect(verifiedEvent.args._epoch).to.equal(epoch, "Verified event epoch mismatch"); + const expectedClaim = { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: claimBlock.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 1, + challenger: challenger.address, + }; + + const expectedClaimHash = await veaOutbox.hashClaim(expectedClaim); + const storedClaimHash = await veaOutbox.claimHashes(epoch); + + expect(storedClaimHash).to.equal(expectedClaimHash, "Stored claim hash does not match expected"); expect(await veaOutbox.stateRoot()).to.equal(batchMerkleRoot, "VeaOutbox stateRoot should be updated"); expect(await veaOutbox.latestVerifiedEpoch()).to.equal(epoch, "VeaOutbox latestVerifiedEpoch should be updated"); }); + it("should not update latestEpoch and stateRoot when resolving older dispute", async () => { + const { claimBlock } = await setupClaimAndChallenge(epoch, batchMerkleRoot, 0); + + // Create and verify newer epochs + const newEpoch1 = epoch + 1; + const newMerkleRoot1 = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("newer1")); + + // Advance time to the next epoch + await network.provider.send("evm_increaseTime", [EPOCH_PERIOD]); + await network.provider.send("evm_mine"); + + const newClaimTxOne = await veaOutbox.connect(bridger).claim(newEpoch1, newMerkleRoot1); + const newClaimTxOneBlock = await ethers.provider.getBlock(newClaimTxOne.blockNumber!); + + const sequencerDelayLimit = await veaOutbox.sequencerDelayLimit(); + const maxL2StateSyncDelay = sequencerDelayLimit.add(EPOCH_PERIOD); + await network.provider.send("evm_increaseTime", [maxL2StateSyncDelay.toNumber()]); + await network.provider.send("evm_mine"); + + const newVerifyTxOne = await veaOutbox.startVerification( + newEpoch1, + createClaim(newMerkleRoot1, bridger.address, newClaimTxOneBlock.timestamp) + ); + const newVerifyTxOneBlock = await ethers.provider.getBlock(newVerifyTxOne.blockNumber!); + + await network.provider.send("evm_increaseTime", [CHALLENGE_PERIOD]); + await network.provider.send("evm_mine"); + + await veaOutbox.connect(bridger).verifySnapshot(newEpoch1, { + ...createClaim(newMerkleRoot1, bridger.address, newClaimTxOneBlock.timestamp), + blocknumberVerification: newVerifyTxOne.blockNumber!, + timestampVerification: newVerifyTxOneBlock.timestamp, + }); + + // Advance time to the next epoch + await network.provider.send("evm_increaseTime", [EPOCH_PERIOD]); + await network.provider.send("evm_mine"); + + const newEpoch2 = (await veaOutbox.epochNow()).toNumber() - 1; + const newMerkleRoot2 = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("newer2")); + const newClaimTxTwo = await veaOutbox.connect(bridger).claim(newEpoch2, newMerkleRoot2); + + const newClaimTxTwoBlock = await ethers.provider.getBlock(newClaimTxTwo.blockNumber!); + + await network.provider.send("evm_increaseTime", [maxL2StateSyncDelay.toNumber()]); + await network.provider.send("evm_mine"); + + const newVerifyTxTwo = await veaOutbox.startVerification( + newEpoch2, + createClaim(newMerkleRoot2, bridger.address, newClaimTxTwoBlock.timestamp) + ); + const newVerifyTxTwoBlock = await ethers.provider.getBlock(newVerifyTxTwo.blockNumber!); + + await network.provider.send("evm_increaseTime", [CHALLENGE_PERIOD]); + await network.provider.send("evm_mine"); + + await veaOutbox.connect(bridger).verifySnapshot(newEpoch2, { + ...createClaim(newMerkleRoot2, bridger.address, newClaimTxTwoBlock.timestamp), + timestampVerification: newVerifyTxTwoBlock.timestamp!, + blocknumberVerification: newVerifyTxTwo.blockNumber!, + }); + + // Resolve the dispute for the old epoch + await simulateDisputeResolution(epoch, { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: claimBlock.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: challenger.address, + }); + + // Check that latestEpoch and stateRoot weren't updated to the old epoch's data + expect(await veaOutbox.latestVerifiedEpoch()).to.equal(newEpoch2, "Latest verified epoch should not change"); + expect(await veaOutbox.stateRoot()).to.equal(newMerkleRoot2, "State root should not change"); + }); + it("should allow bridger to withdraw deposit plus reward", async () => { const { claimBlock } = await setupClaimAndChallenge(epoch, batchMerkleRoot, 0); @@ -558,12 +650,12 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { await network.provider.send("evm_mine"); // Ensure bridger and challenger have enough WETH - await weth.transfer(bridger.address, TEN_ETH.mul(2)); - await weth.transfer(challenger.address, TEN_ETH.mul(2)); + await weth.transfer(bridger.address, TEN_ETH.mul(10)); + await weth.transfer(challenger.address, TEN_ETH.mul(10)); // Approve WETH spending for both - await weth.connect(bridger).approve(veaOutbox.address, TEN_ETH.mul(2)); - await weth.connect(challenger).approve(veaOutbox.address, TEN_ETH.mul(2)); + await weth.connect(bridger).approve(veaOutbox.address, TEN_ETH.mul(10)); + await weth.connect(challenger).approve(veaOutbox.address, TEN_ETH.mul(10)); }); it("should allow challenger to submit a challenge to a dishonest claim", async () => { @@ -613,6 +705,21 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { challenger: challenger.address, }); + const expectedClaim = { + stateRoot: dishonestMerkleRoot, + claimer: bridger.address, + timestampClaimed: claimBlock.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 2, + challenger: challenger.address, + }; + + const expectedClaimHash = await veaOutbox.hashClaim(expectedClaim); + const storedClaimHash = await veaOutbox.claimHashes(epoch); + + expect(storedClaimHash).to.equal(expectedClaimHash, "Stored claim hash does not match expected"); + expect(await veaOutbox.stateRoot()).to.equal(honestMerkleRoot, "State root should be updated to honest root"); }); @@ -718,6 +825,84 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => { expect(await veaOutbox.stateRoot()).to.equal(honestMerkleRoot, "State root should be updated to honest root"); }); + it("should not update latestEpoch and stateRoot when resolving older dispute", async () => { + const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0); + + // Create and verify newer epochs + const newEpoch1 = epoch + 1; + const newMerkleRoot1 = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("newer1")); + + // Advance time to the next epoch + await network.provider.send("evm_increaseTime", [EPOCH_PERIOD]); + await network.provider.send("evm_mine"); + + const newClaimTxOne = await veaOutbox.connect(bridger).claim(newEpoch1, newMerkleRoot1); + const newClaimTxOneBlock = await ethers.provider.getBlock(newClaimTxOne.blockNumber!); + + const sequencerDelayLimit = await veaOutbox.sequencerDelayLimit(); + const maxL2StateSyncDelay = sequencerDelayLimit.add(EPOCH_PERIOD); + await network.provider.send("evm_increaseTime", [maxL2StateSyncDelay.toNumber()]); + await network.provider.send("evm_mine"); + + const newVerifyTxOne = await veaOutbox.startVerification( + newEpoch1, + createClaim(newMerkleRoot1, bridger.address, newClaimTxOneBlock.timestamp) + ); + const newVerifyTxOneBlock = await ethers.provider.getBlock(newVerifyTxOne.blockNumber!); + + await network.provider.send("evm_increaseTime", [CHALLENGE_PERIOD]); + await network.provider.send("evm_mine"); + + await veaOutbox.connect(bridger).verifySnapshot(newEpoch1, { + ...createClaim(newMerkleRoot1, bridger.address, newClaimTxOneBlock.timestamp), + blocknumberVerification: newVerifyTxOne.blockNumber!, + timestampVerification: newVerifyTxOneBlock.timestamp, + }); + + // Advance time to the next epoch + await network.provider.send("evm_increaseTime", [EPOCH_PERIOD]); + await network.provider.send("evm_mine"); + + const newEpoch2 = (await veaOutbox.epochNow()).toNumber() - 1; + const newMerkleRoot2 = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("newer2")); + const newClaimTxTwo = await veaOutbox.connect(bridger).claim(newEpoch2, newMerkleRoot2); + + const newClaimTxTwoBlock = await ethers.provider.getBlock(newClaimTxTwo.blockNumber!); + + await network.provider.send("evm_increaseTime", [maxL2StateSyncDelay.toNumber()]); + await network.provider.send("evm_mine"); + + const newVerifyTxTwo = await veaOutbox.startVerification( + newEpoch2, + createClaim(newMerkleRoot2, bridger.address, newClaimTxTwoBlock.timestamp) + ); + const newVerifyTxTwoBlock = await ethers.provider.getBlock(newVerifyTxTwo.blockNumber!); + + await network.provider.send("evm_increaseTime", [CHALLENGE_PERIOD]); + await network.provider.send("evm_mine"); + + await veaOutbox.connect(bridger).verifySnapshot(newEpoch2, { + ...createClaim(newMerkleRoot2, bridger.address, newClaimTxTwoBlock.timestamp), + timestampVerification: newVerifyTxTwoBlock.timestamp!, + blocknumberVerification: newVerifyTxTwo.blockNumber!, + }); + + // Resolve the dispute for the old epoch + await simulateDisputeResolution(epoch, { + stateRoot: dishonestMerkleRoot, + claimer: bridger.address, + timestampClaimed: claimBlock.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: challenger.address, + }); + + // Check that latestEpoch and stateRoot weren't updated to the old epoch's data + expect(await veaOutbox.latestVerifiedEpoch()).to.equal(newEpoch2, "Latest verified epoch should not change"); + expect(await veaOutbox.stateRoot()).to.equal(newMerkleRoot2, "State root should not change"); + }); + it("should not allow multiple withdrawals for the same challenge", async () => { const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);