Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
124 changes: 114 additions & 10 deletions solidity/contracts/mock/MockCircleMessageTransmitter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ pragma solidity ^0.8.13;

import {IMessageTransmitter} from "../interfaces/cctp/IMessageTransmitter.sol";
import {IMessageTransmitterV2} from "../interfaces/cctp/IMessageTransmitterV2.sol";
import {IMessageHandler} from "../interfaces/cctp/IMessageHandler.sol";
import {IMessageHandlerV2} from "../interfaces/cctp/IMessageHandlerV2.sol";
import {MockToken} from "./MockToken.sol";
import {TypedMemView} from "../libs/TypedMemView.sol";
import {CctpMessageV1} from "../libs/CctpMessageV1.sol";
import {CctpMessageV2} from "../libs/CctpMessageV2.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";

contract MockCircleMessageTransmitter is
IMessageTransmitter,
IMessageTransmitterV2
{
using TypedMemView for bytes;
using TypedMemView for bytes29;
using CctpMessageV1 for bytes29;
using CctpMessageV2 for bytes29;
using TypeCasts for address;

mapping(bytes32 => bool) processedNonces;
MockToken token;
uint32 public version;
Expand All @@ -26,10 +38,66 @@ contract MockCircleMessageTransmitter is
}

function receiveMessage(
bytes memory,
bytes memory message,
bytes calldata
) external pure returns (bool success) {
success = true;
) external returns (bool success) {
bytes29 cctpMessage = TypedMemView.ref(message, 0);

// Extract nonce and source domain to check if message was already processed
uint32 sourceDomain;
bytes32 nonceId;
if (version == 0) {
sourceDomain = cctpMessage._sourceDomain();
uint64 nonce = cctpMessage._nonce();
nonceId = hashSourceAndNonce(sourceDomain, nonce);
} else {
sourceDomain = cctpMessage._getSourceDomain();
bytes32 nonce = cctpMessage._getNonce();
// For V2, use the nonce directly as the nonceId (it's already a bytes32)
nonceId = keccak256(abi.encodePacked(sourceDomain, nonce));
}

require(!processedNonces[nonceId], "Message already processed");
processedNonces[nonceId] = true;

// Extract recipient based on version
address recipient;
bytes32 sender;
bytes memory messageBody;

if (version == 0) {
// V1
recipient = _bytes32ToAddress(cctpMessage._recipient());
sender = cctpMessage._sender();
messageBody = cctpMessage._messageBody().clone();
} else {
// V2
recipient = _bytes32ToAddress(cctpMessage._getRecipient());
sender = cctpMessage._getSender();
messageBody = cctpMessage._getMessageBody().clone();
}

if (version == 0) {
// V1: Call handleReceiveMessage
success = IMessageHandler(recipient).handleReceiveMessage(
sourceDomain,
sender,
messageBody
);
} else {
// V2: Call handleReceiveUnfinalizedMessage
success = IMessageHandlerV2(recipient)
.handleReceiveUnfinalizedMessage(
sourceDomain,
sender,
1000, // mock finality threshold
messageBody
);
}
}

function _bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
return address(uint160(uint256(_buf)));
}

function hashSourceAndNonce(
Expand Down Expand Up @@ -70,11 +138,36 @@ contract MockCircleMessageTransmitter is
}

function sendMessage(
uint32,
bytes32,
bytes calldata message
uint32 destinationDomain,
bytes32 recipient,
bytes calldata messageBody
) public returns (uint64) {
emit MessageSent(message);
// Format a complete CCTP message for the event based on version
bytes memory cctpMessage;
if (version == 0) {
cctpMessage = CctpMessageV1._formatMessage(
version,
0, // sourceDomain (mock localDomain returns 0)
destinationDomain,
0, // nonce
address(this).addressToBytes32(),
recipient,
bytes32(0), // destinationCaller (anyone can relay)
messageBody
);
} else {
cctpMessage = CctpMessageV2._formatMessageForRelay(
version,
0, // sourceDomain (mock localDomain returns 0)
destinationDomain,
address(this).addressToBytes32(),
recipient,
bytes32(0), // destinationCaller (anyone can relay)
1000, // mock finality threshold
messageBody
);
}
emit MessageSent(cctpMessage);
return 0;
}

Expand All @@ -90,10 +183,21 @@ contract MockCircleMessageTransmitter is
function sendMessage(
uint32 destinationDomain,
bytes32 recipient,
bytes32,
uint32,
bytes32 destinationCaller,
uint32 minFinalityThreshold,
bytes calldata messageBody
) external {
sendMessage(destinationDomain, recipient, messageBody);
// V2 sendMessage: format a complete CCTP V2 message
bytes memory cctpMessage = CctpMessageV2._formatMessageForRelay(
version,
0, // sourceDomain (mock localDomain returns 0)
destinationDomain,
address(this).addressToBytes32(),
recipient,
destinationCaller,
minFinalityThreshold,
messageBody
);
emit MessageSent(cctpMessage);
}
}
37 changes: 36 additions & 1 deletion solidity/contracts/mock/MockCircleTokenMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ pragma solidity ^0.8.13;

import {ITokenMessenger, ITokenMessengerV1} from "../interfaces/cctp/ITokenMessenger.sol";
import {ITokenMessengerV2} from "../interfaces/cctp/ITokenMessengerV2.sol";
import {IMessageHandler} from "../interfaces/cctp/IMessageHandler.sol";
import {IMessageHandlerV2} from "../interfaces/cctp/IMessageHandlerV2.sol";
import {MockToken} from "./MockToken.sol";

contract MockCircleTokenMessenger is ITokenMessengerV1, ITokenMessengerV2 {
contract MockCircleTokenMessenger is
ITokenMessengerV1,
ITokenMessengerV2,
IMessageHandler,
IMessageHandlerV2
{
uint64 public nextNonce = 0;
MockToken token;
uint32 public version;
Expand Down Expand Up @@ -56,4 +63,32 @@ contract MockCircleTokenMessenger is ITokenMessengerV1, ITokenMessengerV2 {
) external {
depositForBurn(_amount, 0, 0, _burnToken);
}

// V1 handler
function handleReceiveMessage(
uint32,
bytes32,
bytes calldata
) external pure override returns (bool) {
return true;
}

// V2 handlers
function handleReceiveFinalizedMessage(
uint32,
bytes32,
uint32,
bytes calldata
) external pure override returns (bool) {
return true;
}

function handleReceiveUnfinalizedMessage(
uint32,
bytes32,
uint32,
bytes calldata
) external pure override returns (bool) {
return true;
}
}
Loading
Loading