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

feat: add support for wormhole + cctp #39

Merged
merged 3 commits into from
Jun 3, 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
11 changes: 6 additions & 5 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ StargateHelperTest:testCustomOrderingSG() (gas: 798922)
StargateHelperTest:testFancySG() (gas: 591061)
StargateHelperTest:testSimpleSG() (gas: 538333)
StargateHelperTest:testSimpleSGWithEstimates() (gas: 538863)
WormholeAutomaticRelayerHelperTest:testCustomOrderingWormhole() (gas: 281916)
WormholeAutomaticRelayerHelperTest:testFancyWormhole() (gas: 224571)
WormholeAutomaticRelayerHelperTest:testMultiDstWormhole() (gas: 345623)
WormholeAutomaticRelayerHelperTest:testMultiDstWormholeWithAdditionalVAA() (gas: 372929)
WormholeAutomaticRelayerHelperTest:testSimpleWormhole() (gas: 177400)
WormholeAutomaticRelayerHelperTest:testCctpWormhole() (gas: 680853)
WormholeAutomaticRelayerHelperTest:testCustomOrderingWormhole() (gas: 282349)
WormholeAutomaticRelayerHelperTest:testFancyWormhole() (gas: 224754)
WormholeAutomaticRelayerHelperTest:testMultiDstWormhole() (gas: 345999)
WormholeAutomaticRelayerHelperTest:testMultiDstWormholeWithAdditionalVAA() (gas: 374447)
WormholeAutomaticRelayerHelperTest:testSimpleWormhole() (gas: 177619)
WormholeSpecializedRelayerHelperTest:testCustomOrderingWormhole() (gas: 599240)
WormholeSpecializedRelayerHelperTest:testFancyWormhole() (gas: 332394)
WormholeSpecializedRelayerHelperTest:testMultiDstWormhole() (gas: 635874)
Expand Down
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
Loading