diff --git a/makefile-external b/makefile-external index 5b47c7c..81604fd 100644 --- a/makefile-external +++ b/makefile-external @@ -54,3 +54,12 @@ fct-vrf-cancel-subscription: fct-check-rpc-url $(call check_set_parameter,RECEIVING_ADDRESS,receivingAddress) \ forge script ${FCT_PLUGIN_PATH}/script/vrf/VRF.CLI.s.sol --sig "cancelSubscription(address,uint64,address)" $$functionsRouterAddress $$subId $$receivingAddress --rpc-url ${RPC_URL} --broadcast --private-key ${PRIVATE_KEY} --out ${FCT_PLUGIN_PATH}/out +# CCIP +.PHONY: fcc-ccip-estimate-receive +fcc-ccip-estimate-receive: + $(call check_set_parameter,DESTINATION_RPC_URL,destinationRpcUrl) \ + $(call check_set_parameter,CCIP_ROUTER_ADDRESS,ccipReceiverAddress) \ + $(call check_set_parameter,SOURCE_CHAIN_SELECTOR,sourceChainSelector) \ + $(call check_set_parameter,CCIP_MESSAGE_SENDER,ccipMessageSender) \ + $(call check_set_parameter,CCIP_MESSAGE_DATA,ccipMessageData) \ + forge script ${FCT_PLUGIN_PATH}/script/ccip/CCIPReceiver.CLI.s.sol --sig "ccipReceive(address,uint64,address,bytes)" $$ccipReceiverAddress $$sourceChainSelector $$ccipMessageSender $$ccipMessageData --rpc-url $$destinationRpcUrl --out ${FCT_PLUGIN_PATH}/out diff --git a/script/automation/Automation.CLI.s.sol b/script/automation/Automation.CLI.s.sol index 6b2ec95..8630a4e 100644 --- a/script/automation/Automation.CLI.s.sol +++ b/script/automation/Automation.CLI.s.sol @@ -16,7 +16,7 @@ contract AutomationCLIScript is BaseScript { address upkeepAddress, uint32 gasLimit, bytes calldata checkData - ) nestedScriptContext public returns (bytes32 requestHash) { + ) nestedScriptContext external returns (bytes32 requestHash) { AutomationScript automationScript = new AutomationScript(keeperRegistryAddress); return automationScript.registerUpkeep( amountInJuels, diff --git a/script/ccip/CCIPReceiver.CLI.s.sol b/script/ccip/CCIPReceiver.CLI.s.sol new file mode 100644 index 0000000..86b11c4 --- /dev/null +++ b/script/ccip/CCIPReceiver.CLI.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Script.sol"; + +import "./CCIPReceiver.s.sol"; +import "../helpers/BaseScript.s.sol"; + +contract CCIPReceiverCLIScript is BaseScript { + function ccipReceive( + address ccipReceiverAddress, + uint64 sourceChainSelector, + address sender, + bytes memory data + ) external { + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](0); + CCIPReceiverScript ccipReceiverScript = new CCIPReceiverScript(ccipReceiverAddress); + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: bytes32(0), + sourceChainSelector: sourceChainSelector, + sender: abi.encode(sender), + data: data, + destTokenAmounts: destTokenAmounts + }); + ccipReceiverScript.ccipReceive(message); + } +} diff --git a/script/ccip/CCIPReceiver.s.sol b/script/ccip/CCIPReceiver.s.sol new file mode 100644 index 0000000..71c1575 --- /dev/null +++ b/script/ccip/CCIPReceiver.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Script.sol"; + +import "../helpers/BaseScript.s.sol"; +import "src/interfaces/ccip/IAny2EVMMessageReceiver.sol"; +import "src/libraries/CCIPClient.sol"; + +contract CCIPReceiverScript is BaseScript { + address public ccipReceiverAddress; + + constructor (address _ccipReceiverAddress) { + ccipReceiverAddress = _ccipReceiverAddress; + } + + function ccipReceive( + Client.Any2EVMMessage memory message + ) external { + IAny2EVMMessageReceiver ccipReceiver = IAny2EVMMessageReceiver(ccipReceiverAddress); + ccipReceiver.ccipReceive(message); + } +} diff --git a/script/ccip/CCIPRouter.CLI.s.sol b/script/ccip/CCIPRouter.CLI.s.sol new file mode 100644 index 0000000..8675671 --- /dev/null +++ b/script/ccip/CCIPRouter.CLI.s.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Script.sol"; + +import "./CCIPRouter.s.sol"; +import "../helpers/BaseScript.s.sol"; +import "src/libraries/CCIPClient.sol"; + +contract CCIPRouterCLIScript is BaseScript { + function getFee( + address ccipRouterAddress, + uint64 destinationChainSelector, + Client.EVM2AnyMessage memory message + ) external returns (uint256 fee) { + CCIPRouterScript ccipRouterScript = new CCIPRouterScript(ccipRouterAddress); + return ccipRouterScript.getFee(destinationChainSelector, message); + } + + function getSupportedTokens( + address ccipRouterAddress, + uint64 chainSelector + ) external returns (address[] memory) { + CCIPRouterScript ccipRouterScript = new CCIPRouterScript(ccipRouterAddress); + return ccipRouterScript.getSupportedTokens(chainSelector); + } + + function isChainSupported( + address ccipRouterAddress, + uint64 chainSelector + ) external returns (bool) { + CCIPRouterScript ccipRouterScript = new CCIPRouterScript(ccipRouterAddress); + return ccipRouterScript.isChainSupported(chainSelector); + } + + function getOnRamp( + address ccipRouterAddress, + uint64 destChainSelector + ) external returns (address) { + CCIPRouterScript ccipRouterScript = new CCIPRouterScript(ccipRouterAddress); + return ccipRouterScript.getOnRamp(destChainSelector); + } + + function isOffRamp( + address ccipRouterAddress, + uint64 sourceChainSelector, + address offRamp + ) external returns (bool) { + CCIPRouterScript ccipRouterScript = new CCIPRouterScript(ccipRouterAddress); + return ccipRouterScript.isOffRamp(sourceChainSelector, offRamp); + } + + function getWrappedNative(address ccipRouterAddress) external returns (address) { + CCIPRouterScript ccipRouterScript = new CCIPRouterScript(ccipRouterAddress); + return ccipRouterScript.getWrappedNative(); + } +} diff --git a/script/ccip/CCIPRouter.s.sol b/script/ccip/CCIPRouter.s.sol new file mode 100644 index 0000000..7021b79 --- /dev/null +++ b/script/ccip/CCIPRouter.s.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "forge-std/Script.sol"; + +import "../helpers/BaseScript.s.sol"; +import "../helpers/TypeAndVersion.s.sol"; +import "src/interfaces/ccip/CCIPRouterInterface.sol"; +import "src/libraries/CCIPClient.sol"; + +contract CCIPRouterScript is BaseScript, TypeAndVersionScript { + address public ccipRouterAddress; + + constructor (address _ccipRouterAddress) { + ccipRouterAddress = _ccipRouterAddress; + } + + function getFee( + uint64 destinationChainSelector, + Client.EVM2AnyMessage memory message + ) external view returns (uint256 fee) { + CCIPRouterInterface ccipRouter = CCIPRouterInterface(ccipRouterAddress); + return ccipRouter.getFee(destinationChainSelector, message); + } + + function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory) { + CCIPRouterInterface ccipRouter = CCIPRouterInterface(ccipRouterAddress); + return ccipRouter.getSupportedTokens(chainSelector); + } + + function isChainSupported(uint64 chainSelector) public view returns (bool) { + CCIPRouterInterface ccipRouter = CCIPRouterInterface(ccipRouterAddress); + return ccipRouter.isChainSupported(chainSelector); + } + + function getOnRamp(uint64 destChainSelector) external view returns (address) { + CCIPRouterInterface ccipRouter = CCIPRouterInterface(ccipRouterAddress); + return ccipRouter.getOnRamp(destChainSelector); + } + + function isOffRamp(uint64 sourceChainSelector, address offRamp) public view returns (bool) { + CCIPRouterInterface ccipRouter = CCIPRouterInterface(ccipRouterAddress); + return ccipRouter.isOffRamp(sourceChainSelector, offRamp); + } + + function getWrappedNative() external view returns (address) { + CCIPRouterInterface ccipRouter = CCIPRouterInterface(ccipRouterAddress); + return ccipRouter.getWrappedNative(); + } +} diff --git a/src/interfaces/ccip/CCIPRouterInterface.sol b/src/interfaces/ccip/CCIPRouterInterface.sol new file mode 100644 index 0000000..de50028 --- /dev/null +++ b/src/interfaces/ccip/CCIPRouterInterface.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "../shared/TypeAndVersionInterface.sol"; +import "src/libraries/CCIPClient.sol"; + +interface CCIPRouterInterface is TypeAndVersionInterface { + function getFee( + uint64 destinationChainSelector, + Client.EVM2AnyMessage memory message + ) external view returns (uint256 fee); + function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory); + function isChainSupported(uint64 chainSelector) external view returns (bool); + function getOnRamp(uint64 destChainSelector) external view returns (address); + function isOffRamp(uint64 sourceChainSelector, address offRamp) external view returns (bool); + function getWrappedNative() external view returns (address); +} diff --git a/src/interfaces/ccip/IAny2EVMMessageReceiver.sol b/src/interfaces/ccip/IAny2EVMMessageReceiver.sol new file mode 100644 index 0000000..dde6df8 --- /dev/null +++ b/src/interfaces/ccip/IAny2EVMMessageReceiver.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "src/libraries/CCIPClient.sol"; + +/// @notice Application contracts that intend to receive messages from +/// the router should implement this interface. +interface IAny2EVMMessageReceiver { + /// @notice Called by the Router to deliver a message. + /// If this reverts, any token transfers also revert. The message + /// will move to a FAILED state and become available for manual execution. + /// @param message CCIP Message + /// @dev Note ensure you check the msg.sender is the OffRampRouter + function ccipReceive(Client.Any2EVMMessage calldata message) external; +} diff --git a/src/libraries/CCIPClient.sol b/src/libraries/CCIPClient.sol new file mode 100644 index 0000000..a3d81c6 --- /dev/null +++ b/src/libraries/CCIPClient.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +/// @title Library of common constants and enums for the CCIP Router contract +library Client { + /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers. + struct EVMTokenAmount { + address token; // token address on the local chain. + uint256 amount; // Amount of tokens. + } + + // If extraArgs is empty bytes, the default is 200k gas limit. + struct EVM2AnyMessage { + bytes receiver; // abi.encode(receiver address) for dest EVM chains + bytes data; // Data payload + EVMTokenAmount[] tokenAmounts; // Token transfers + address feeToken; // Address of feeToken. address(0) means you will send msg.value. + bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1) + } + + struct Any2EVMMessage { + bytes32 messageId; // MessageId corresponding to ccipSend on source. + uint64 sourceChainSelector; // Source chain selector. + bytes sender; // abi.decode(sender) if coming from an EVM chain. + bytes data; // payload sent in original message. + EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation. + } +}