Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SC-425] Adding Circle CCTP support #15

Merged
merged 8 commits into from
May 20, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
POLYGON_RPC_URL: ${{secrets.POLYGON_RPC_URL}}
run: FOUNDRY_PROFILE=ci forge test

coverage:
Expand All @@ -58,6 +59,7 @@ jobs:
ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
POLYGON_RPC_URL: ${{secrets.POLYGON_RPC_URL}}
run: forge coverage --report summary --report lcov

# To ignore coverage for certain directories modify the paths in this step as needed. The
Expand Down
52 changes: 52 additions & 0 deletions src/CCTPReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.0;

/**
* @title CCTPReceiver
* @notice Receive messages from CCTP-style bridge.
*/
abstract contract CCTPReceiver {

address public immutable destinationMessenger;
uint32 public immutable sourceDomainId;
address public immutable sourceAuthority;

constructor(
address _destinationMessenger,
uint32 _sourceDomainId,
address _sourceAuthority
) {
destinationMessenger = _destinationMessenger;
sourceDomainId = _sourceDomainId;
sourceAuthority = _sourceAuthority;
}

function _onlyCrossChainMessage() internal view {
require(msg.sender == address(this), "Receiver/invalid-sender");
}

modifier onlyCrossChainMessage() {
_onlyCrossChainMessage();
_;
}

function handleReceiveMessage(
uint32 sourceDomain,
bytes32 sender,
bytes calldata messageBody
) external returns (bool) {
require(msg.sender == destinationMessenger, "Receiver/invalid-sender");
require(sourceDomainId == sourceDomain, "Receiver/invalid-sourceDomain");
require(sender == bytes32(uint256(uint160(sourceAuthority))), "Receiver/invalid-sourceAuthority");

(bool success, bytes memory ret) = address(this).call(messageBody);
if (!success) {
assembly {
revert(add(ret, 0x20), mload(ret))
}
}

return true;
}

}
63 changes: 63 additions & 0 deletions src/XChainForwarders.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ interface ICrossDomainZkEVM {
) external payable;
}

interface ICrossDomainCircleCCTP {
function sendMessage(
uint32 destinationDomain,
bytes32 recipient,
bytes calldata messageBody
) external;
}

/**
* @title XChainForwarders
* @notice Helper functions to abstract over L1 -> L2 message passing.
Expand Down Expand Up @@ -196,4 +204,59 @@ library XChainForwarders {
);
}

/// ================================ CCTP ================================

function sendMessageCCTP(
address sourceMessenger,
uint32 destinationDomainId,
bytes32 recipient,
bytes memory messageBody
) internal {
ICrossDomainCircleCCTP(sourceMessenger).sendMessage(
destinationDomainId,
recipient,
messageBody
);
}

function sendMessageCCTP(
address sourceMessenger,
uint32 destinationDomainId,
address recipient,
bytes memory messageBody
) internal {
sendMessageCCTP(
sourceMessenger,
destinationDomainId,
bytes32(uint256(uint160(recipient))),
messageBody
);
}

function sendMessageCircleCCTP(
uint32 destinationDomainId,
bytes32 recipient,
bytes memory messageBody
) internal {
sendMessageCCTP(
0x0a992d191DEeC32aFe36203Ad87D7d289a738F81,
destinationDomainId,
recipient,
messageBody
);
}

function sendMessageCircleCCTP(
uint32 destinationDomainId,
address recipient,
bytes memory messageBody
) internal {
sendMessageCCTP(
0x0a992d191DEeC32aFe36203Ad87D7d289a738F81,
destinationDomainId,
bytes32(uint256(uint160(recipient))),
messageBody
);
}

}
106 changes: 106 additions & 0 deletions src/testing/CircleCCTPDomain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.0;

import { StdChains } from "forge-std/StdChains.sol";
import { Vm } from "forge-std/Vm.sol";

import { Domain, BridgedDomain } from "./BridgedDomain.sol";
import { RecordedLogs } from "./RecordedLogs.sol";

interface MessengerLike {
function receiveMessage(bytes calldata message, bytes calldata attestation) external returns (bool success);
}

contract CircleCCTPDomain is BridgedDomain {

bytes32 private constant SENT_MESSAGE_TOPIC = keccak256("MessageSent(bytes)");

MessengerLike public SOURCE_MESSENGER;
MessengerLike public DESTINATION_MESSENGER;

uint256 internal lastFromHostLogIndex;
uint256 internal lastToHostLogIndex;
barrutko marked this conversation as resolved.
Show resolved Hide resolved

constructor(StdChains.Chain memory _chain, Domain _hostDomain) Domain(_chain) BridgedDomain(_hostDomain) {
barrutko marked this conversation as resolved.
Show resolved Hide resolved
SOURCE_MESSENGER = MessengerLike(_getMessengerFromChainAlias(_hostDomain.details().chainAlias));
DESTINATION_MESSENGER = MessengerLike(_getMessengerFromChainAlias(_chain.chainAlias));

// Set minimum required signatures to zero for both domains
selectFork();
vm.store(
address(DESTINATION_MESSENGER),
bytes32(uint256(4)),
0
);
hostDomain.selectFork();
vm.store(
address(SOURCE_MESSENGER),
bytes32(uint256(4)),
0
);

vm.recordLogs();
}

function _getMessengerFromChainAlias(string memory chainAlias) internal pure returns (address) {
bytes32 name = keccak256(bytes(chainAlias));
if (name == keccak256("mainnet")) {
return 0x0a992d191DEeC32aFe36203Ad87D7d289a738F81;
} else if (name == keccak256("avalanche")) {
return 0x8186359aF5F57FbB40c6b14A588d2A59C0C29880;
} else if (name == keccak256("optimism")) {
return 0x4D41f22c5a0e5c74090899E5a8Fb597a8842b3e8;
} else if (name == keccak256("arbitrum_one")) {
return 0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca;
} else if (name == keccak256("base")) {
return 0xAD09780d193884d503182aD4588450C416D6F9D4;
} else if (name == keccak256("polygon")) {
return 0xF3be9355363857F3e001be68856A2f96b4C39Ba9;
} else {
revert("Unsupported chain");
}
}

function relayFromHost(bool switchToGuest) external override {
selectFork();

// Read all L1 -> L2 messages and relay them under CCTP fork
Vm.Log[] memory logs = RecordedLogs.getLogs();
for (; lastFromHostLogIndex < logs.length; lastFromHostLogIndex++) {
Vm.Log memory log = logs[lastFromHostLogIndex];
if (log.topics[0] == SENT_MESSAGE_TOPIC && log.emitter == address(SOURCE_MESSENGER)) {
DESTINATION_MESSENGER.receiveMessage(removeFirst64Bytes(log.data), "");
}
}

if (!switchToGuest) {
hostDomain.selectFork();
}
}

function relayToHost(bool switchToHost) external override {
hostDomain.selectFork();

// Read all L2 -> L1 messages and relay them under host fork
Vm.Log[] memory logs = RecordedLogs.getLogs();
for (; lastToHostLogIndex < logs.length; lastToHostLogIndex++) {
Vm.Log memory log = logs[lastToHostLogIndex];
if (log.topics[0] == SENT_MESSAGE_TOPIC && log.emitter == address(DESTINATION_MESSENGER)) {
SOURCE_MESSENGER.receiveMessage(removeFirst64Bytes(log.data), "");
}
}

if (!switchToHost) {
selectFork();
}
}

function removeFirst64Bytes(bytes memory inputData) public pure returns (bytes memory) {
bytes memory returnValue = new bytes(inputData.length - 64);
for (uint256 i = 0; i < inputData.length - 64; i++) {
returnValue[i] = inputData[i + 64];
}
return returnValue;
}

}
Loading
Loading