diff --git a/src/CCIPHelpers.sol b/src/CCIPHelpers.sol index 89cced3..de86ebb 100644 --- a/src/CCIPHelpers.sol +++ b/src/CCIPHelpers.sol @@ -14,9 +14,10 @@ library CCIPHelpers { address gydAddress, address recipient, uint256 amount, + bytes memory data, uint256 gasLimit ) internal pure returns (Client.EVM2AnyMessage memory) { - bytes memory messageData = abi.encode(recipient, amount); + bytes memory messageData = abi.encode(recipient, amount, data); return Client.EVM2AnyMessage({ receiver: abi.encode(gydAddress), data: messageData, diff --git a/src/GydL1CCIPEscrow.sol b/src/GydL1CCIPEscrow.sol index d53a89e..4cc6bec 100644 --- a/src/GydL1CCIPEscrow.sol +++ b/src/GydL1CCIPEscrow.sol @@ -25,6 +25,7 @@ contract GydL1CCIPEscrow is CCIPReceiverUpgradeable { using SafeERC20 for IERC20; + using Address for address; using Address for address payable; struct ChainMetadata { @@ -155,16 +156,26 @@ contract GydL1CCIPEscrow is emit GasLimitUpdated(chainSelector, gasLimit); } + function bridgeToken( + uint64 destinationChainSelector, + address recipient, + uint256 amount + ) external payable virtual { + bridgeToken(destinationChainSelector, recipient, amount, ""); + } + /** * @notice Bridge GYD from Ethereum mainnet to the specified chain * @param recipient The recipient of the bridged token * @param amount GYD amount + * @param data calldata for the recipient on the destination chain */ function bridgeToken( uint64 destinationChainSelector, address recipient, - uint256 amount - ) external payable virtual { + uint256 amount, + bytes memory data + ) public payable virtual { gyd.safeTransferFrom(msg.sender, address(this), amount); ChainMetadata memory chainMeta = chainsMetadata[destinationChainSelector]; @@ -173,7 +184,7 @@ contract GydL1CCIPEscrow is } Client.EVM2AnyMessage memory evm2AnyMessage = CCIPHelpers.buildCCIPMessage( - chainMeta.gydAddress, recipient, amount, chainMeta.gasLimit + chainMeta.gydAddress, recipient, amount, data, chainMeta.gasLimit ); uint256 fees = router.getFee(destinationChainSelector, evm2AnyMessage); CCIPHelpers.sendCCIPMessage( @@ -191,13 +202,22 @@ contract GydL1CCIPEscrow is address recipient, uint256 amount ) external view returns (uint256) { + return getFee(destinationChainSelector, recipient, amount, ""); + } + + function getFee( + uint64 destinationChainSelector, + address recipient, + uint256 amount, + bytes memory data + ) public view returns (uint256) { ChainMetadata memory chainMeta = chainsMetadata[destinationChainSelector]; if (chainMeta.gydAddress == address(0)) { revert ChainNotSupported(destinationChainSelector); } Client.EVM2AnyMessage memory evm2AnyMessage = CCIPHelpers.buildCCIPMessage( - chainMeta.gydAddress, recipient, amount, chainMeta.gasLimit + chainMeta.gydAddress, recipient, amount, data, chainMeta.gasLimit ); return router.getFee(destinationChainSelector, evm2AnyMessage); } @@ -229,13 +249,16 @@ contract GydL1CCIPEscrow is revert MessageInvalid(); } - (address recipient, uint256 amount) = - abi.decode(any2EvmMessage.data, (address, uint256)); + (address recipient, uint256 amount, bytes memory data) = + abi.decode(any2EvmMessage.data, (address, uint256, bytes)); uint256 bridged = totalBridgedGYD[any2EvmMessage.sourceChainSelector]; bridged -= amount; totalBridgedGYD[any2EvmMessage.sourceChainSelector] = bridged; gyd.safeTransfer(recipient, amount); + if (data.length > 0) { + recipient.functionCall(data); + } emit GYDClaimed( any2EvmMessage.sourceChainSelector, recipient, amount, bridged diff --git a/src/L2Gyd.sol b/src/L2Gyd.sol index 5f0516d..b0efd6c 100644 --- a/src/L2Gyd.sol +++ b/src/L2Gyd.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; +import {Address} from "oz/utils/Address.sol"; import {Initializable} from "upgradeable/proxy/utils/Initializable.sol"; import {UUPSUpgradeable} from "upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from @@ -24,6 +25,8 @@ contract L2Gyd is ERC20Upgradeable, CCIPReceiverUpgradeable { + using Address for address; + /// @notice The CCIP router contract IRouterClient public router; @@ -98,19 +101,23 @@ contract L2Gyd is revert RenounceInvalid(); } + function bridgeToken(address recipient, uint256 amount) public payable { + bridgeToken(recipient, amount, ""); + } + /** * @notice Bridge GYD from the current chain to Ethereum mainnet * @param recipient The recipient of the bridged token * @param amount GYD amount */ - function bridgeToken(address recipient, uint256 amount) + function bridgeToken(address recipient, uint256 amount, bytes memory data) public payable virtual { _burn(msg.sender, amount); Client.EVM2AnyMessage memory evm2AnyMessage = CCIPHelpers.buildCCIPMessage( - destAddress, recipient, amount, bridgeGasLimit + destAddress, recipient, amount, data, bridgeGasLimit ); uint256 fees = router.getFee(mainnetChainSelector, evm2AnyMessage); CCIPHelpers.sendCCIPMessage( @@ -124,9 +131,17 @@ contract L2Gyd is external view returns (uint256) + { + return getFee(recipient, amount, ""); + } + + function getFee(address recipient, uint256 amount, bytes memory data) + public + view + returns (uint256) { Client.EVM2AnyMessage memory evm2AnyMessage = CCIPHelpers.buildCCIPMessage( - destAddress, recipient, amount, bridgeGasLimit + destAddress, recipient, amount, data, bridgeGasLimit ); return router.getFee(mainnetChainSelector, evm2AnyMessage); } @@ -154,9 +169,12 @@ contract L2Gyd is revert MessageInvalid(); } - (address recipient, uint256 amount) = - abi.decode(any2EvmMessage.data, (address, uint256)); + (address recipient, uint256 amount, bytes memory data) = + abi.decode(any2EvmMessage.data, (address, uint256, bytes)); _mint(recipient, amount); + if (data.length > 0) { + recipient.functionCall(data); + } emit GYDClaimed(recipient, amount, totalSupply()); } diff --git a/test/GydL1Escrow.t.sol b/test/GydL1Escrow.t.sol index bf85b49..e6badb8 100644 --- a/test/GydL1Escrow.t.sol +++ b/test/GydL1Escrow.t.sol @@ -210,7 +210,7 @@ contract GydL1EscrowTest is Test { address routerAddress = address(proxyV1.router()); (address originAddress,) = proxyV1.chainsMetadata(arbitrumChainSelector); uint64 chainSelector = arbitrumChainSelector; - bytes memory data = abi.encode(bob, 1 ether); + bytes memory data = abi.encode(bob, 1 ether, ""); // Invalid caller vm.startPrank(bob); @@ -261,7 +261,7 @@ contract GydL1EscrowTest is Test { address routerAddress = address(proxyV1.router()); (address originAddress,) = proxyV1.chainsMetadata(arbitrumChainSelector); uint64 chainSelector = arbitrumChainSelector; - bytes memory messageData = abi.encode(bob, bridgeAmount); + bytes memory messageData = abi.encode(bob, bridgeAmount, ""); vm.startPrank(routerAddress); proxyV1.ccipReceive( diff --git a/test/L2Gyd.t.sol b/test/L2Gyd.t.sol index 1c91158..572c0e2 100644 --- a/test/L2Gyd.t.sol +++ b/test/L2Gyd.t.sol @@ -137,7 +137,7 @@ contract L2GydTest is Test { // Mint test NativeGYD vm.startPrank(address(router)); - bytes memory data = abi.encode(alice, bridgeAmount); + bytes memory data = abi.encode(alice, bridgeAmount, ""); mockedProxyV1.ccipReceive( _receivedMessage(mainnetChainSelector, destAddress, data) ); @@ -166,7 +166,7 @@ contract L2GydTest is Test { // Mint test NativeGYD vm.startPrank(routerAddress); - bytes memory data = abi.encode(alice, bridgeAmount); + bytes memory data = abi.encode(alice, bridgeAmount, ""); proxyV1.ccipReceive( _receivedMessage(mainnetChainSelector, destAddress, data) ); @@ -198,7 +198,7 @@ contract L2GydTest is Test { // Mint test NativeGYD vm.startPrank(routerAddress); - bytes memory data = abi.encode(alice, bridgeAmount); + bytes memory data = abi.encode(alice, bridgeAmount, ""); proxyV1.ccipReceive( _receivedMessage(mainnetChainSelector, destAddress, data) ); @@ -213,7 +213,7 @@ contract L2GydTest is Test { address currentRouterAddress = address(proxyV1.router()); address originAddress = proxyV1.destAddress(); uint64 chainSelector = proxyV1.mainnetChainSelector(); - bytes memory metadata = abi.encode(bob, 1 ether); + bytes memory metadata = abi.encode(bob, 1 ether, ""); // Invalid caller vm.startPrank(bob); @@ -253,7 +253,7 @@ contract L2GydTest is Test { // Mint test NativeGYD vm.startPrank(routerAddress); - bytes memory data = abi.encode(alice, bridgeAmount); + bytes memory data = abi.encode(alice, bridgeAmount, ""); proxyV1.ccipReceive( _receivedMessage(mainnetChainSelector, destAddress, data) ); @@ -268,7 +268,7 @@ contract L2GydTest is Test { address currentRouterAddress = address(proxyV1.router()); address originAddress = proxyV1.destAddress(); uint64 chainSelector = proxyV1.mainnetChainSelector(); - bytes memory messageData = abi.encode(bob, bridgeAmount); + bytes memory messageData = abi.encode(bob, bridgeAmount, ""); vm.startPrank(currentRouterAddress); proxyV1.ccipReceive(