Skip to content
Merged
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 @@ -50,6 +50,7 @@ abstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {
// Ref: https://developers.circle.com/cctp/technical-guide#message-body
// Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol
uint8 private constant BURN_MESSAGE_V2_VERSION_INDEX = 0;
uint8 private constant BURN_MESSAGE_V2_BURN_TOKEN_INDEX = 4;
uint8 private constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36;
uint8 private constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68;
uint8 private constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;
Expand Down Expand Up @@ -81,6 +82,9 @@ abstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {
/// @notice USDC address on local chain
address public immutable usdcToken;

/// @notice USDC address on remote chain
address public immutable peerUsdcToken;

/// @notice Domain ID of the chain from which messages are accepted
uint32 public immutable peerDomainID;

Expand Down Expand Up @@ -141,10 +145,15 @@ abstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {
uint32 peerDomainID;
address peerStrategy;
address usdcToken;
address peerUsdcToken;
}

constructor(CCTPIntegrationConfig memory _config) {
require(_config.usdcToken != address(0), "Invalid USDC address");
require(
_config.peerUsdcToken != address(0),
"Invalid peer USDC address"
);
require(
_config.cctpTokenMessenger != address(0),
"Invalid CCTP config"
Expand Down Expand Up @@ -182,6 +191,9 @@ abstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {
keccak256(abi.encodePacked("USDC")),
"Token symbol must be USDC"
);

// USDC address on remote chain
peerUsdcToken = _config.peerUsdcToken;
}

/**
Expand Down Expand Up @@ -472,6 +484,12 @@ abstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {
"Invalid burn message"
);

// Ensure the burn token is USDC
address burnToken = messageBody.extractAddress(
BURN_MESSAGE_V2_BURN_TOKEN_INDEX
);
require(burnToken == peerUsdcToken, "Invalid burn token");

// Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain
sender = messageBody.extractAddress(
BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX
Expand Down
1 change: 1 addition & 0 deletions contracts/deploy/base/041_crosschain_strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = deployOnBase(
cctpDomainIds.Ethereum,
crossChainStrategyProxyAddress,
addresses.base.USDC,
addresses.mainnet.USDC,
"CrossChainRemoteStrategy",
addresses.CCTPTokenMessengerV2,
addresses.CCTPMessageTransmitterV2,
Expand Down
6 changes: 6 additions & 0 deletions contracts/deploy/deployActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,7 @@ const deployCrossChainMasterStrategyImpl = async (
targetDomainId,
remoteStrategyAddress,
baseToken,
peerBaseToken,
vaultAddress,
implementationName = "CrossChainMasterStrategy",
skipInitialize = false,
Expand Down Expand Up @@ -1860,6 +1861,7 @@ const deployCrossChainMasterStrategyImpl = async (
targetDomainId,
remoteStrategyAddress,
baseToken,
peerBaseToken,
],
]);
const dCrossChainMasterStrategy = await ethers.getContract(
Expand Down Expand Up @@ -1894,6 +1896,7 @@ const deployCrossChainRemoteStrategyImpl = async (
targetDomainId,
remoteStrategyAddress,
baseToken,
peerBaseToken,
implementationName = "CrossChainRemoteStrategy",
tokenMessengerAddress = addresses.CCTPTokenMessengerV2,
messageTransmitterAddress = addresses.CCTPMessageTransmitterV2,
Expand All @@ -1920,6 +1923,7 @@ const deployCrossChainRemoteStrategyImpl = async (
targetDomainId,
remoteStrategyAddress,
baseToken,
peerBaseToken,
],
]);
const dCrossChainRemoteStrategy = await ethers.getContract(
Expand Down Expand Up @@ -1977,6 +1981,7 @@ const deployCrossChainUnitTestStrategy = async (usdcAddress) => {
// unit tests differ from mainnet where remote strategy has a different address
dRemoteProxy.address,
usdcAddress,
usdcAddress, // Assume both are same on unit tests
cVaultProxy.address,
"CrossChainMasterStrategy",
false,
Expand All @@ -1991,6 +1996,7 @@ const deployCrossChainUnitTestStrategy = async (usdcAddress) => {
0, // Ethereum domain id
dMasterProxy.address,
usdcAddress,
usdcAddress, // Assume both are same on unit tests
"CrossChainRemoteStrategy",
tokenMessenger.address,
messageTransmitter.address,
Expand Down
1 change: 1 addition & 0 deletions contracts/deploy/mainnet/166_crosschain_strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module.exports = deploymentWithGovernanceProposal(
// Same address for both master and remote strategy
crossChainStrategyProxyAddress,
addresses.mainnet.USDC,
addresses.base.USDC,
cVaultProxy.address,
"CrossChainMasterStrategy",
false,
Expand Down
13 changes: 11 additions & 2 deletions contracts/test/strategies/crosschain/_crosschain-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,27 @@ const encodeCCTPMessage = (
return `0x${versionStr}${sourceDomainStr}${empty18Bytes}${senderStr}${recipientStr}${empty20Bytes}${messageBodyStr}`;
};

const encodeBurnMessageBody = (sender, recipient, amount, hookData) => {
const encodeBurnMessageBody = (
sender,
recipient,
burnToken,
amount,
hookData
) => {
const senderEncoded = ethers.utils.defaultAbiCoder
.encode(["address"], [sender])
.slice(2);
const recipientEncoded = ethers.utils.defaultAbiCoder
.encode(["address"], [recipient])
.slice(2);
const burnTokenEncoded = ethers.utils.defaultAbiCoder
.encode(["address"], [burnToken])
.slice(2);
const amountEncoded = ethers.utils.defaultAbiCoder
.encode(["uint256"], [amount])
.slice(2);
const encodedHookData = hookData.slice(2);
return `0x00000001${empty16Bytes}${recipientEncoded}${amountEncoded}${senderEncoded}${empty16Bytes.repeat(
return `0x00000001${burnTokenEncoded}${recipientEncoded}${amountEncoded}${senderEncoded}${empty16Bytes.repeat(
3
)}${encodedHookData}`;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () {
const burnPayload = encodeBurnMessageBody(
crossChainMasterStrategy.address,
crossChainMasterStrategy.address,
addresses.base.USDC,
usdcUnits("2342"),
balancePayload
);
Expand Down Expand Up @@ -493,5 +494,57 @@ describe("ForkTest: CrossChainMasterStrategy", function () {
await crossChainMasterStrategy.remoteStrategyBalance();
expect(remoteStrategyBalanceAfter).to.eq(remoteStrategyBalanceBefore);
});

it("Should revert if the burn token is not peer USDC", async function () {
const { crossChainMasterStrategy, strategist } = fixture;

if (await crossChainMasterStrategy.isTransferPending()) {
// Skip if there's a pending transfer
console.log(
"Skipping balance check message fork test because there's a pending transfer"
);
return;
}

// set an arbitrary remote strategy balance
await setRemoteStrategyBalance(
crossChainMasterStrategy,
usdcUnits("123456")
);

const lastNonce = (
await crossChainMasterStrategy.lastTransferNonce()
).toNumber();

// Replace transmitter to mock transmitter
await replaceMessageTransmitter();

// Build check balance payload
const balancePayload = encodeBalanceCheckMessageBody(
lastNonce,
usdcUnits("12345"),
true // withdrawal confirmation
);
const burnPayload = encodeBurnMessageBody(
crossChainMasterStrategy.address,
crossChainMasterStrategy.address,
addresses.mainnet.WETH, // Not peer USDC
usdcUnits("2342"),
balancePayload
);
const message = encodeCCTPMessage(
6,
addresses.CCTPTokenMessengerV2,
addresses.CCTPTokenMessengerV2,
burnPayload
);

// Relay the message with fake attestation
const tx = crossChainMasterStrategy
.connect(strategist)
.relay(message, "0x");

await expect(tx).to.be.revertedWith("Invalid burn token");
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () {
const burnPayload = encodeBurnMessageBody(
crossChainRemoteStrategy.address,
crossChainRemoteStrategy.address,
addresses.mainnet.USDC,
depositAmount,
depositPayload
);
Expand Down Expand Up @@ -246,4 +247,40 @@ describe("ForkTest: CrossChainRemoteStrategy", function () {
);
expect(balanceAfter).to.approxEqual(expectedBalance);
});

it("Should revert if the burn token is not peer USDC", async function () {
const { crossChainRemoteStrategy, strategist } = fixture;

const nonceBefore = await crossChainRemoteStrategy.lastTransferNonce();

const depositAmount = usdcUnits("1234.56");

// Replace transmitter to mock transmitter
await replaceMessageTransmitter();

const nextNonce = nonceBefore.toNumber() + 1;

// Build deposit message
const depositPayload = encodeDepositMessageBody(nextNonce, depositAmount);
const burnPayload = encodeBurnMessageBody(
crossChainRemoteStrategy.address,
crossChainRemoteStrategy.address,
addresses.base.WETH, // Not peer USDC
depositAmount,
depositPayload
);
const message = encodeCCTPMessage(
0,
addresses.CCTPTokenMessengerV2,
addresses.CCTPTokenMessengerV2,
burnPayload
);

// Relay the message
const tx = crossChainRemoteStrategy
.connect(strategist)
.relay(message, "0x");

await expect(tx).to.be.revertedWith("Invalid burn token");
});
});
Loading