Skip to content

Commit 2c3c514

Browse files
committed
test(arbToGno): dishonest claim - honest challenge - bridger deposit forfeited, challenger paid
1 parent 658e0b6 commit 2c3c514

File tree

1 file changed

+221
-1
lines changed

1 file changed

+221
-1
lines changed

contracts/test/integration/ArbToGnosis.ts

Lines changed: 221 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from "chai";
22
import { deployments, ethers, network } from "hardhat";
3-
import { BigNumber } from "ethers";
3+
import { BigNumber, Contract } from "ethers";
44
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
55
import { MerkleTree } from "../merkle/MerkleTree";
66
const { mine } = require("@nomicfoundation/hardhat-network-helpers");
@@ -536,4 +536,224 @@ describe("Arbitrum to Gnosis Bridge Tests", async () => {
536536
await expect(relayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0);
537537
});
538538
});
539+
540+
describe("Dishonest Claim - Honest Challenge - Bridger deposit forfeited, Challenger paid", async () => {
541+
let epoch: number;
542+
let dishonestMerkleRoot: string;
543+
let honestMerkleRoot: string;
544+
545+
beforeEach(async () => {
546+
// Setup: Send message and save snapshot on Arbitrum
547+
await senderGateway.connect(sender).sendMessage(1121);
548+
await veaInbox.connect(bridger).saveSnapshot();
549+
550+
const BatchOutgoing = veaInbox.filters.SnapshotSaved();
551+
const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing);
552+
epoch = Math.floor((await batchOutGoingEvent[0].getBlock()).timestamp / EPOCH_PERIOD);
553+
honestMerkleRoot = await veaInbox.snapshots(epoch);
554+
dishonestMerkleRoot = ethers.utils.keccak256("0x123456"); // Simulating a dishonest state root
555+
556+
// Advance time to next epoch
557+
await network.provider.send("evm_increaseTime", [EPOCH_PERIOD]);
558+
await network.provider.send("evm_mine");
559+
560+
// Ensure bridger and challenger have enough WETH
561+
await weth.transfer(bridger.address, TEN_ETH.mul(2));
562+
await weth.transfer(challenger.address, TEN_ETH.mul(2));
563+
564+
// Approve WETH spending for both
565+
await weth.connect(bridger).approve(veaOutbox.address, TEN_ETH.mul(2));
566+
await weth.connect(challenger).approve(veaOutbox.address, TEN_ETH.mul(2));
567+
});
568+
569+
it("should allow challenger to submit a challenge to a dishonest claim", async () => {
570+
const { claimBlock, challengeTx } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
571+
572+
await expect(challengeTx).to.emit(veaOutbox, "Challenged").withArgs(epoch, challenger.address);
573+
});
574+
575+
it("should initiate cross-chain dispute resolution for dishonest claim", async () => {
576+
const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
577+
578+
const sendSnapshotTx = await veaInbox.connect(bridger).sendSnapshot(
579+
epoch,
580+
100000,
581+
{
582+
stateRoot: dishonestMerkleRoot,
583+
claimer: bridger.address,
584+
timestampClaimed: claimBlock.timestamp,
585+
timestampVerification: 0,
586+
blocknumberVerification: 0,
587+
honest: 0,
588+
challenger: challenger.address,
589+
},
590+
{ gasLimit: 100000 }
591+
);
592+
593+
await expect(sendSnapshotTx)
594+
.to.emit(veaInbox, "SnapshotSent")
595+
.withArgs(epoch, ethers.utils.formatBytes32String(""));
596+
597+
const routerEvents = await router.queryFilter(router.filters.Routed(), sendSnapshotTx.blockNumber);
598+
expect(routerEvents.length).to.equal(1, "Expected one Routed event");
599+
const routedEvent = routerEvents[0];
600+
expect(routedEvent.args._epoch).to.equal(epoch, "Routed event epoch mismatch");
601+
});
602+
603+
it("should resolve dispute in favor of the challenger", async () => {
604+
const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
605+
606+
await simulateDisputeResolution(epoch, {
607+
stateRoot: dishonestMerkleRoot,
608+
claimer: bridger.address,
609+
timestampClaimed: claimBlock.timestamp,
610+
timestampVerification: 0,
611+
blocknumberVerification: 0,
612+
honest: 0,
613+
challenger: challenger.address,
614+
});
615+
616+
expect(await veaOutbox.stateRoot()).to.equal(honestMerkleRoot, "State root should be updated to honest root");
617+
});
618+
619+
it("should not allow dishonest bridger to withdraw deposit", async () => {
620+
const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
621+
622+
await simulateDisputeResolution(epoch, {
623+
stateRoot: dishonestMerkleRoot,
624+
claimer: bridger.address,
625+
timestampClaimed: claimBlock.timestamp,
626+
timestampVerification: 0,
627+
blocknumberVerification: 0,
628+
honest: 0,
629+
challenger: challenger.address,
630+
});
631+
632+
await expect(
633+
veaOutbox.connect(bridger).withdrawClaimDeposit(epoch, {
634+
stateRoot: dishonestMerkleRoot,
635+
claimer: bridger.address,
636+
timestampClaimed: claimBlock.timestamp,
637+
timestampVerification: 0,
638+
blocknumberVerification: 0,
639+
honest: 2,
640+
challenger: challenger.address,
641+
})
642+
).to.be.revertedWith("Claim failed.");
643+
});
644+
645+
it("should allow challenger to withdraw deposit plus reward", async () => {
646+
const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
647+
648+
await simulateDisputeResolution(epoch, {
649+
stateRoot: dishonestMerkleRoot,
650+
claimer: bridger.address,
651+
timestampClaimed: claimBlock.timestamp,
652+
timestampVerification: 0,
653+
blocknumberVerification: 0,
654+
honest: 0,
655+
challenger: challenger.address,
656+
});
657+
658+
const challengerInitialBalance = await weth.balanceOf(challenger.address);
659+
await veaOutbox.connect(challenger).withdrawChallengeDeposit(epoch, {
660+
stateRoot: dishonestMerkleRoot,
661+
claimer: bridger.address,
662+
timestampClaimed: claimBlock.timestamp,
663+
timestampVerification: 0,
664+
blocknumberVerification: 0,
665+
honest: 2,
666+
challenger: challenger.address,
667+
});
668+
const challengerFinalBalance = await weth.balanceOf(challenger.address);
669+
expect(challengerFinalBalance.sub(challengerInitialBalance)).to.equal(
670+
TEN_ETH.add(TEN_ETH.div(2)),
671+
"Incorrect withdrawal amount"
672+
);
673+
});
674+
675+
it("should allow message relay with correct state root after dispute resolution", async () => {
676+
const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
677+
678+
await simulateDisputeResolution(epoch, {
679+
stateRoot: dishonestMerkleRoot,
680+
claimer: bridger.address,
681+
timestampClaimed: claimBlock.timestamp,
682+
timestampVerification: 0,
683+
blocknumberVerification: 0,
684+
honest: 0,
685+
challenger: challenger.address,
686+
});
687+
688+
const MessageSent = veaInbox.filters.MessageSent();
689+
const MessageSentEvent = await veaInbox.queryFilter(MessageSent);
690+
const msg = MessageSentEvent[0].args._nodeData;
691+
const nonce = "0x" + msg.slice(2, 18);
692+
const to = "0x" + msg.slice(18, 58);
693+
const msgData = "0x" + msg.slice(58);
694+
695+
let nodes: string[] = [];
696+
nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData));
697+
const mt = new MerkleTree(nodes);
698+
const proof = mt.getHexProof(nodes[0]);
699+
700+
const relayTx = await veaOutbox.connect(receiver).sendMessage(proof, 0, receiverGateway.address, msgData);
701+
await expect(relayTx).to.emit(veaOutbox, "MessageRelayed").withArgs(0);
702+
});
703+
704+
it("should update latest verified epoch and state root correctly after dispute resolution", async () => {
705+
const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
706+
707+
await simulateDisputeResolution(epoch, {
708+
stateRoot: dishonestMerkleRoot,
709+
claimer: bridger.address,
710+
timestampClaimed: claimBlock.timestamp,
711+
timestampVerification: 0,
712+
blocknumberVerification: 0,
713+
honest: 0,
714+
challenger: challenger.address,
715+
});
716+
717+
expect(await veaOutbox.latestVerifiedEpoch()).to.equal(epoch, "Latest verified epoch should be updated");
718+
expect(await veaOutbox.stateRoot()).to.equal(honestMerkleRoot, "State root should be updated to honest root");
719+
});
720+
721+
it("should not allow multiple withdrawals for the same challenge", async () => {
722+
const { claimBlock } = await setupClaimAndChallenge(epoch, dishonestMerkleRoot, 0);
723+
724+
await simulateDisputeResolution(epoch, {
725+
stateRoot: dishonestMerkleRoot,
726+
claimer: bridger.address,
727+
timestampClaimed: claimBlock.timestamp,
728+
timestampVerification: 0,
729+
blocknumberVerification: 0,
730+
honest: 0,
731+
challenger: challenger.address,
732+
});
733+
734+
// First withdrawal should succeed
735+
await veaOutbox.connect(challenger).withdrawChallengeDeposit(epoch, {
736+
stateRoot: dishonestMerkleRoot,
737+
claimer: bridger.address,
738+
timestampClaimed: claimBlock.timestamp,
739+
timestampVerification: 0,
740+
blocknumberVerification: 0,
741+
honest: 2,
742+
challenger: challenger.address,
743+
});
744+
745+
// Second withdrawal should fail
746+
await expect(
747+
veaOutbox.connect(challenger).withdrawChallengeDeposit(epoch, {
748+
stateRoot: dishonestMerkleRoot,
749+
claimer: bridger.address,
750+
timestampClaimed: claimBlock.timestamp,
751+
timestampVerification: 0,
752+
blocknumberVerification: 0,
753+
honest: 2,
754+
challenger: challenger.address,
755+
})
756+
).to.be.revertedWith("Invalid claim.");
757+
});
758+
});
539759
});

0 commit comments

Comments
 (0)