Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ contract CCTPMessageTransmitterMock is ICCTPMessageTransmitter {
Message[] public messages;
// map of encoded messages to the corresponding message structs
mapping(bytes32 => Message) public encodedMessages;
// tracks relayed messages to mirror CCTP replay protection in tests
mapping(bytes32 => bool) public processedMessages;

constructor(address _usdc) {
usdc = IERC20(_usdc);
Expand Down Expand Up @@ -120,7 +122,11 @@ contract CCTPMessageTransmitterMock is ICCTPMessageTransmitter {
override
returns (bool)
{
Message memory storedMsg = encodedMessages[keccak256(message)];
bytes32 messageHash = keccak256(message);
require(!processedMessages[messageHash], "Message already processed");
processedMessages[messageHash] = true;

Message memory storedMsg = encodedMessages[messageHash];
AbstractCCTPIntegrator recipient = AbstractCCTPIntegrator(
address(uint160(uint256(storedMsg.recipient)))
);
Expand Down
38 changes: 37 additions & 1 deletion contracts/test/strategies/crosschain/cross-chain-strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ const {
const { setERC20TokenBalance } = require("../../_fund");
const { units, usdcUnits } = require("../../helpers");
const { impersonateAndFund } = require("../../../utils/signers");
const { encodeBalanceCheckMessageBody } = require("./_crosschain-helpers");
const {
encodeBalanceCheckMessageBody,
encodeCCTPMessage,
} = require("./_crosschain-helpers");

const loadFixture = createFixtureLoader(crossChainFixtureUnit);
const DAY_IN_SECONDS = 86400;
Expand Down Expand Up @@ -456,6 +459,39 @@ describe("ForkTest: CrossChainRemoteStrategy", function () {
await assertVaultTotalValue("1000");
});

it("Should reject direct replay of an already-relayed balance update message", async function () {
const { messageTransmitter } = fixture;
await sendBalanceUpdateToMaster();

const nonce = await crossChainMasterStrategy.lastTransferNonce();
const balance = await crossChainRemoteStrategy.checkBalance(usdc.address);
const latestBlock = await ethers.provider.getBlock("latest");
const body = encodeBalanceCheckMessageBody(
nonce,
balance,
false,
latestBlock.timestamp
);
const encodedMessage = encodeCCTPMessage(
await crossChainMasterStrategy.peerDomainID(),
crossChainRemoteStrategy.address,
crossChainMasterStrategy.address,
body
);

await expect(messageTransmitter.processFront())
.to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated")
.withArgs(balance);
await expect(await messageTransmitter.messagesInQueue()).to.eq(0);

const transmitterSigner = await impersonateAndFund(messageTransmitter.address);
await expect(
crossChainMasterStrategy
.connect(transmitterSigner)
.relay(encodedMessage, "0x")
).to.be.revertedWith("Message already processed");
});

it("Should emit a BalanceCheckIgnored event if balance update message is too old", async function () {
const { messageTransmitter, crossChainMasterStrategy } = fixture;
await sendBalanceUpdateToMaster();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,18 @@ describe("ForkTest: CrossChainMasterStrategy", function () {
const strategyBalanceAfter = await crossChainMasterStrategy.checkBalance(
usdc.address
);
// Pending bridged deposits remain counted in the master strategy's total
// value until the remote confirmation updates the cached remote balance.
expect(strategyBalanceAfter).to.eq(strategyBalanceBefore);

const lastTransferNonce = await crossChainMasterStrategy.lastTransferNonce();
expect(await crossChainMasterStrategy.pendingAmount()).to.eq(
usdcUnits("1000")
);
expect(await crossChainMasterStrategy.isTransferPending()).to.eq(true);
expect(
await crossChainMasterStrategy.isNonceProcessed(lastTransferNonce)
).to.eq(false);

// Check for message sent event
const receipt = await tx.wait();
Expand Down Expand Up @@ -262,6 +269,11 @@ describe("ForkTest: CrossChainMasterStrategy", function () {
payload
);

expect(await crossChainMasterStrategy.pendingAmount()).to.eq(
usdcUnits("1000")
);
expect(await crossChainMasterStrategy.isTransferPending()).to.eq(true);

// Relay the message with fake attestation
await crossChainMasterStrategy.connect(relayer).relay(message, "0x");

Expand All @@ -273,6 +285,10 @@ describe("ForkTest: CrossChainMasterStrategy", function () {
expect(await crossChainMasterStrategy.pendingAmount()).to.eq(
usdcUnits("0")
);
expect(await crossChainMasterStrategy.isTransferPending()).to.eq(false);
expect(await crossChainMasterStrategy.checkBalance(usdc.address)).to.eq(
usdcUnits("10000")
);
});

it("Should accept tokens for a pending withdrawal", async function () {
Expand Down Expand Up @@ -391,7 +407,12 @@ describe("ForkTest: CrossChainMasterStrategy", function () {
// Relay the message with fake attestation
await crossChainMasterStrategy.connect(relayer).relay(message, "0x");

// Should've ignore the message
// The out-of-order non-confirmation balance update must not clear the
// pending transfer or cached remote balance. pendingAmount tracks pending
// deposits only, so a pending withdrawal keeps it at zero.
expect(await crossChainMasterStrategy.pendingAmount()).to.eq(0);
expect(await crossChainMasterStrategy.isTransferPending()).to.eq(true);

const remoteStrategyBalance =
await crossChainMasterStrategy.remoteStrategyBalance();
expect(remoteStrategyBalance).to.eq(remoteStrategyBalanceBefore);
Expand Down Expand Up @@ -493,6 +514,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () {
const remoteStrategyBalanceAfter =
await crossChainMasterStrategy.remoteStrategyBalance();
expect(remoteStrategyBalanceAfter).to.eq(remoteStrategyBalanceBefore);
expect(await crossChainMasterStrategy.isTransferPending()).to.eq(false);
});

it("Should revert if the burn token is not peer USDC", async function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () {

const nonceAfter = await crossChainRemoteStrategy.lastTransferNonce();
expect(nonceAfter).to.eq(nextNonce);
expect(await crossChainRemoteStrategy.isTransferPending()).to.eq(false);

const balanceAfter = await crossChainRemoteStrategy.checkBalance(
usdc.address
Expand Down Expand Up @@ -242,6 +243,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () {

const nonceAfter = await crossChainRemoteStrategy.lastTransferNonce();
expect(nonceAfter).to.eq(nextNonce);
expect(await crossChainRemoteStrategy.isTransferPending()).to.eq(false);

const balanceAfter = await crossChainRemoteStrategy.checkBalance(
usdc.address
Expand Down
Loading