Skip to content

Commit

Permalink
[SC-425] Adding Circle CCTP support (#15)
Browse files Browse the repository at this point in the history
* adding cctp support

* complete circle cctp

* remove get sender function as its pointless in this style of callback

* add polygon and avalanche support

* use more general language for cctp domain

* rename some of the variables

* more var renaming; use l2 authority

* review fixes
  • Loading branch information
hexonaut authored May 20, 2024
1 parent 855dcf7 commit d71b629
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 0 deletions.
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;

constructor(StdChains.Chain memory _chain, Domain _hostDomain) Domain(_chain) BridgedDomain(_hostDomain) {
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

0 comments on commit d71b629

Please sign in to comment.