Skip to content

Commit de49a8a

Browse files
author
buy doge
committed
test: cover CCTP replay rejection in cross-chain mock
1 parent 219c222 commit de49a8a

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ contract CCTPMessageTransmitterMock is ICCTPMessageTransmitter {
4444
Message[] public messages;
4545
// map of encoded messages to the corresponding message structs
4646
mapping(bytes32 => Message) public encodedMessages;
47+
// tracks relayed messages to mirror CCTP replay protection in tests
48+
mapping(bytes32 => bool) public processedMessages;
4749

4850
constructor(address _usdc) {
4951
usdc = IERC20(_usdc);
@@ -120,7 +122,11 @@ contract CCTPMessageTransmitterMock is ICCTPMessageTransmitter {
120122
override
121123
returns (bool)
122124
{
123-
Message memory storedMsg = encodedMessages[keccak256(message)];
125+
bytes32 messageHash = keccak256(message);
126+
require(!processedMessages[messageHash], "Message already processed");
127+
processedMessages[messageHash] = true;
128+
129+
Message memory storedMsg = encodedMessages[messageHash];
124130
AbstractCCTPIntegrator recipient = AbstractCCTPIntegrator(
125131
address(uint160(uint256(storedMsg.recipient)))
126132
);

contracts/test/strategies/crosschain/cross-chain-strategy.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ const {
77
const { setERC20TokenBalance } = require("../../_fund");
88
const { units, usdcUnits } = require("../../helpers");
99
const { impersonateAndFund } = require("../../../utils/signers");
10-
const { encodeBalanceCheckMessageBody } = require("./_crosschain-helpers");
10+
const {
11+
encodeBalanceCheckMessageBody,
12+
encodeCCTPMessage,
13+
} = require("./_crosschain-helpers");
1114

1215
const loadFixture = createFixtureLoader(crossChainFixtureUnit);
1316
const DAY_IN_SECONDS = 86400;
@@ -456,6 +459,39 @@ describe("ForkTest: CrossChainRemoteStrategy", function () {
456459
await assertVaultTotalValue("1000");
457460
});
458461

462+
it("Should reject direct replay of an already-relayed balance update message", async function () {
463+
const { messageTransmitter } = fixture;
464+
await sendBalanceUpdateToMaster();
465+
466+
const nonce = await crossChainMasterStrategy.lastTransferNonce();
467+
const balance = await crossChainRemoteStrategy.checkBalance(usdc.address);
468+
const latestBlock = await ethers.provider.getBlock("latest");
469+
const body = encodeBalanceCheckMessageBody(
470+
nonce,
471+
balance,
472+
false,
473+
latestBlock.timestamp
474+
);
475+
const encodedMessage = encodeCCTPMessage(
476+
await crossChainMasterStrategy.peerDomainID(),
477+
crossChainRemoteStrategy.address,
478+
crossChainMasterStrategy.address,
479+
body
480+
);
481+
482+
await expect(messageTransmitter.processFront())
483+
.to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated")
484+
.withArgs(balance);
485+
await expect(await messageTransmitter.messagesInQueue()).to.eq(0);
486+
487+
const transmitterSigner = await impersonateAndFund(messageTransmitter.address);
488+
await expect(
489+
crossChainMasterStrategy
490+
.connect(transmitterSigner)
491+
.relay(encodedMessage, "0x")
492+
).to.be.revertedWith("Message already processed");
493+
});
494+
459495
it("Should emit a BalanceCheckIgnored event if balance update message is too old", async function () {
460496
const { messageTransmitter, crossChainMasterStrategy } = fixture;
461497
await sendBalanceUpdateToMaster();

0 commit comments

Comments
 (0)