Skip to content

Commit

Permalink
feat: add support for wormhole + cctp
Browse files Browse the repository at this point in the history
  • Loading branch information
sujithsomraaj committed Jun 2, 2024
1 parent 68c8c4d commit e9e11de
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 0 deletions.
91 changes: 91 additions & 0 deletions src/wormhole/automatic-relayer/WormholeHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@ interface IWormholeReceiver {
) external payable;
}

interface IMessageTransmitter {
function attesterManager() external view returns (address);
function enableAttester(address newAttester) external;
function setSignatureThreshold(uint256 newSignatureThreshold) external;
function receiveMessage(bytes calldata message, bytes calldata attestation) external;
}

/// @title WormholeHelper
/// @notice supports only automatic relayer (not specialized relayers)
/// MORE INFO: https://docs.wormhole.com/wormhole/quick-start/cross-chain-dev/automatic-relayer
contract WormholeHelper is Test {
/// @dev is the default event selector if not specified by the user
bytes32 constant MESSAGE_EVENT_SELECTOR = 0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2;
bytes32 constant CCTP_MESSAGE_EVENT_SELECTOR = 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036;

//////////////////////////////////////////////////////////////
// EXTERNAL FUNCTIONS //
Expand Down Expand Up @@ -128,6 +136,89 @@ contract WormholeHelper is Test {
}
}

struct LocalCCTPVars {
uint256 prevForkId;
bytes cctpMessage;
bytes[] additionalMessage;
bytes32 digest;
uint8 v;
bytes32 r;
bytes32 s;
Vm.Log log;
uint64 sequence;
uint32 nonce;
bytes payload;
address dstAddress;
}

/// @dev is a helper for https://docs.wormhole.com/wormhole/quick-start/tutorials/cctp
/// @param srcChainId represents the wormhole identifier for the source chain
/// @param dstForkId represents the dst fork id to deliver the message
/// @param expDstAddress represents the expected dst chain receiver of wormhole message
/// @param dstRelayer represents the wormhole dst relayer address
/// @param dstTransmitter represents the cctp dst transmitter address
/// @param logs represents the logs after message dispatch using sendToEvm
/// @notice supports only one CCTP transfer and sendToEvm per log
function helpWithCctpAndWormhole(
uint16 srcChainId,
uint256 dstForkId,
address expDstAddress,
address dstRelayer,
address dstTransmitter,
Vm.Log[] calldata logs
) external {
LocalCCTPVars memory v;
v.prevForkId = vm.activeFork();
v.additionalMessage = new bytes[](1);
vm.selectFork(dstForkId);

/// @dev identifies the cctp transfer
for (uint256 i; i < logs.length; ++i) {
v.log = logs[i];
if (v.log.topics[0] == CCTP_MESSAGE_EVENT_SELECTOR) {
v.cctpMessage = abi.decode(logs[i].data, (bytes));
/// @dev prepare circle transmitter on dst chain
IMessageTransmitter messageTransmitter = IMessageTransmitter(dstTransmitter);

vm.startPrank(messageTransmitter.attesterManager());
messageTransmitter.enableAttester(vm.addr(420));
messageTransmitter.setSignatureThreshold(1);
vm.stopPrank();

v.digest = keccak256(v.cctpMessage);
(v.v, v.r, v.s) = vm.sign(420, v.digest);
v.additionalMessage[0] = abi.encode(v.cctpMessage, abi.encodePacked(v.r, v.s, v.v));
}
}

/// @dev identifies and delivers the wormhole message
vm.startBroadcast(dstRelayer);
for (uint256 j; j < logs.length; ++j) {
v.log = logs[j];

if (v.log.topics[0] == MESSAGE_EVENT_SELECTOR) {
(v.sequence, v.nonce, v.payload,) = abi.decode(v.log.data, (uint64, uint32, bytes, uint8));

DeliveryInstruction memory instruction = PayloadDecoder.decodeDeliveryInstruction(v.payload);

v.dstAddress = TypeCasts.bytes32ToAddress(instruction.targetAddress);

if (expDstAddress == address(0) || expDstAddress == v.dstAddress) {
IWormholeReceiver(v.dstAddress).receiveWormholeMessages(
instruction.payload,
v.additionalMessage,
instruction.senderAddress,
srcChainId,
/// @dev generating some random hash
keccak256(abi.encodePacked(v.sequence, v.nonce))
);
}
}
}
vm.stopBroadcast();
vm.selectFork(v.prevForkId);
}

/// @dev helps find logs of `length` for default event selector
/// @param logs represents the logs after message dispatch on src chain
/// @param length represents the expected number of logs
Expand Down
114 changes: 114 additions & 0 deletions test/Wormhole.AutomaticRelayer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ import "forge-std/Test.sol";
/// local imports
import "src/wormhole/automatic-relayer/WormholeHelper.sol";
import "src/wormhole/specialized-relayer/lib/IWormhole.sol";
import "solady/src/tokens/ERC20.sol";

interface IWormholeRelayerSend {
struct MessageKey {
uint8 keyType;
bytes encodedKey;
}

function sendPayloadToEvm(
uint16 targetChain,
address targetAddress,
Expand Down Expand Up @@ -36,14 +42,40 @@ interface IWormholeRelayerSend {
VaaKey[] memory vaaKeys
) external payable returns (uint64 sequence);

function sendToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 paymentForExtraReceiverValue,
uint256 gasLimit,
uint16 refundChain,
address refundAddress,
address deliveryProviderAddress,
MessageKey[] memory messageKeys,
uint8 consistencyLevel
) external payable returns (uint64 sequence);

function quoteEVMDeliveryPrice(uint16 targetChain, uint256 receiverValue, uint256 gasLimit)
external
view
returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused);

function getDefaultDeliveryProvider() external view returns (address);
}

interface IWormholeRelayer is IWormholeRelayerSend {}

interface ITokenManager {
function depositForBurnWithCaller(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
bytes32 destinationCaller
) external returns (uint64 nonce);
}

contract Target is IWormholeReceiver {
uint256 public value;

Expand Down Expand Up @@ -97,11 +129,35 @@ contract AnotherTarget {
}
}

contract CCTPTarget {
IMessageTransmitter transmitter;

constructor(IMessageTransmitter transmitter_) {
transmitter = transmitter_;
}

function receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalVaas,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external payable {
(bytes memory message, bytes memory attestation) = abi.decode(additionalVaas[0], (bytes, bytes));
transmitter.receiveMessage(message, attestation);
}
}

contract WormholeAutomaticRelayerHelperTest is Test {
IWormhole wormhole = IWormhole(0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B);
ITokenManager tokenMessenger = ITokenManager(0xBd3fa81B58Ba92a82136038B25aDec7066af3155);
ERC20 USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
ERC20 USDC_POLYGON = ERC20(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359);

WormholeHelper wormholeHelper;
Target target;
Target altTarget;
CCTPTarget cctpTarget;

AnotherTarget anotherTarget;
AdditionalVAATarget addVaaTarget;
Expand All @@ -120,6 +176,8 @@ contract WormholeAutomaticRelayerHelperTest is Test {
address constant L2_1_RELAYER = 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911;
address constant L2_2_RELAYER = 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911;

address constant MESSAGE_TRANSMITTER_POLYGON = 0xF3be9355363857F3e001be68856A2f96b4C39Ba9;

address[] public allDstRelayers;
uint16[] public allDstChainIds;
uint256[] public allDstForks;
Expand All @@ -139,6 +197,7 @@ contract WormholeAutomaticRelayerHelperTest is Test {
target = new Target();
addVaaTarget = new AdditionalVAATarget();
anotherTarget = new AnotherTarget(L1_CHAIN_ID);
cctpTarget = new CCTPTarget(IMessageTransmitter(MESSAGE_TRANSMITTER_POLYGON));

ARBITRUM_FORK_ID = vm.createSelectFork(RPC_ARBITRUM_MAINNET, 38063686);
altTarget = new Target();
Expand Down Expand Up @@ -258,6 +317,61 @@ contract WormholeAutomaticRelayerHelperTest is Test {
assertEq(addVaaTarget.vaalen(), 1);
}

/// @dev test single dst cctp transfers with wormhole
function testCctpWormhole() external {
vm.selectFork(L1_FORK_ID);
address bridgoor = address(32145);

vm.deal(bridgoor, 2 ether);
deal(address(USDC), bridgoor, 100e6);
vm.startPrank(bridgoor);

USDC.approve(address(tokenMessenger), 100e6);

vm.recordLogs();
uint64 nonce = tokenMessenger.depositForBurnWithCaller(
100e6,
7,
bytes32(uint256(uint160(address(cctpTarget)))),
address(USDC),
bytes32(uint256(uint160(address(cctpTarget))))
);

IWormholeRelayer relayer = IWormholeRelayer(L1_RELAYER);

IWormholeRelayerSend.MessageKey[] memory messageKeys = new IWormholeRelayerSend.MessageKey[](1);
messageKeys[0] = IWormholeRelayerSend.MessageKey(2, abi.encodePacked(uint32(7), nonce));

(uint256 msgValue,) = relayer.quoteEVMDeliveryPrice(L2_1_CHAIN_ID, 0, 500000);

relayer.sendToEvm{value: msgValue}(
L2_1_CHAIN_ID,
address(cctpTarget),
bytes(""),
0,
0,
500000,
L2_1_CHAIN_ID,
address(0),
relayer.getDefaultDeliveryProvider(),
messageKeys,
1
);

wormholeHelper.helpWithCctpAndWormhole(
L1_CHAIN_ID,
POLYGON_FORK_ID,
address(cctpTarget),
L2_1_RELAYER,
0xF3be9355363857F3e001be68856A2f96b4C39Ba9,
vm.getRecordedLogs()
);
vm.stopPrank();

vm.selectFork(POLYGON_FORK_ID);
assertEq(USDC_POLYGON.balanceOf(address(cctpTarget)), 100e6);
}

function _aMostFancyCrossChainFunctionInYourContract(uint16 dstChainId, address receiver) internal {
IWormholeRelayer relayer = IWormholeRelayer(L1_RELAYER);

Expand Down

0 comments on commit e9e11de

Please sign in to comment.