diff --git a/contracts/SimpleCCIPReceiver.sol b/contracts/SimpleCCIPReceiver.sol new file mode 100644 index 0000000..bde41c6 --- /dev/null +++ b/contracts/SimpleCCIPReceiver.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; +import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; +import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; + +/** + * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. + * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. + * DO NOT USE THIS CODE IN PRODUCTION. + */ + +/// @title - A simple contract for receiving string data across chains. +contract SimpleCCIPReceiver is CCIPReceiver, OwnerIsCreator { + error SourceChainNotAllowed(uint64 sourceChainSelector); // Used when the source chain has not been allowlisted by the contract owner. + error SenderNotAllowed(address sender); // Used when the sender has not been allowlisted by the contract owner. + + // Event emitted when a message is received from another chain. + event MessageReceived( + bytes32 indexed messageId, // The unique ID of the message. + uint64 indexed sourceChainSelector, // The chain selector of the source chain. + address sender, // The address of the sender from the source chain. + uint256 iterationsInput, // The number of iterations to be executed. + uint256 iterationsDone, // The number of iterations executed. + uint256 result // The result of the iterations. + ); + + // Mapping to keep track of allowlisted source chains. + mapping(uint64 => bool) public allowlistedSourceChains; + + // Mapping to keep track of allowlisted senders. + mapping(address => bool) public allowlistedSenders; + + /// @dev Modifier that checks if the chain with the given sourceChainSelector is allowlisted and if the sender is allowlisted. + /// @param _sourceChainSelector The selector of the destination chain. + /// @param _sender The address of the sender. + modifier onlyAllowlisted(uint64 _sourceChainSelector, address _sender) { + if (!allowlistedSourceChains[_sourceChainSelector]) + revert SourceChainNotAllowed(_sourceChainSelector); + if (!allowlistedSenders[_sender]) revert SenderNotAllowed(_sender); + _; + } + + /// @notice Constructor initializes the contract with the router address. + /// @param router The address of the router contract. + constructor(address router) CCIPReceiver(router) {} + + /// @dev Updates the allowlist status of a source chain + /// @notice This function can only be called by the owner. + /// @param _sourceChainSelector The selector of the source chain to be updated. + /// @param allowed The allowlist status to be set for the source chain. + function allowlistSourceChain( + uint64 _sourceChainSelector, + bool allowed + ) external onlyOwner { + allowlistedSourceChains[_sourceChainSelector] = allowed; + } + + /// @dev Updates the allowlist status of a sender for transactions. + /// @notice This function can only be called by the owner. + /// @param _sender The address of the sender to be updated. + /// @param allowed The allowlist status to be set for the sender. + function allowlistSender(address _sender, bool allowed) external onlyOwner { + allowlistedSenders[_sender] = allowed; + } + + /// handle a received message + function _ccipReceive( + Client.Any2EVMMessage memory any2EvmMessage + ) internal override { + uint256 iterations = abi.decode(any2EvmMessage.data, (uint256)); // abi-decoding of the receiver number of iterations + + uint256 result = iterations; + uint256 maxIterations = iterations % 100; + for (uint256 i = 0; i < maxIterations; i++) { + result += i; + } + + emit MessageReceived( + any2EvmMessage.messageId, // fetch the message id + any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector) + abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address, + iterations, // the number of iterations + maxIterations, // the number of iterations executed + result // the result of the iterations + ); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 0bc4673..5a741e8 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -43,6 +43,8 @@ const config: HardhatUserConfig = { "./chainlink-artifacts/TypeAndVersionInterface.json", "./chainlink-artifacts/OptimismSequencerUptimeFeed.json", "./chainlink-artifacts/VRFCoordinatorV2.json", + "./chainlink-artifacts/Router.json", + "./chainlink-artifacts/CCIPReceiver.json", ], }, }; diff --git a/package.json b/package.json index 3c1117b..ee82bea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chainlink/hardhat-chainlink", - "version": "0.0.4", + "version": "0.0.5", "description": "Hardhat plugin that adds interaction with Chainlink services to Hardhat projects", "repository": "https://github.com/smartcontractkit/hardhat-chainlink.git", "license": "MIT", @@ -26,8 +26,8 @@ "test:automationRegistry": "mocha --exit 'test/automationRegistry.test.ts'", "test:functions": "mocha --exit 'test/functionsRouter.test.ts'", "test:functionsSimulation": "mocha --exit 'test/functionsSimulation.test.ts'", - "test": "yarn test:prepare && yarn test:dataFeed && yarn test:dataFeedProxy && yarn test:ensFeedsResolver && yarn test:feedRegistry && yarn test:l2FeedUptimeSequencer && yarn test:vrfCoordinator && yarn test:automationRegistrar && yarn test:automationRegistry && yarn test:functions", - "copyArtifacts": "copyfiles -a -f ./node_modules/@chainlink/contracts/abi/**/* chainlink-artifacts", + "test": "yarn test:prepare && yarn test:dataFeed && yarn test:dataFeedProxy && yarn test:ensFeedsResolver && yarn test:feedRegistry && yarn test:vrfCoordinator && yarn test:automationRegistrar && yarn test:automationRegistry && yarn test:functions", + "copyArtifacts": "copyfiles -a -f ./node_modules/@chainlink/contracts/abi/**/* ./node_modules/@chainlink/contracts-ccip/abi/**/* chainlink-artifacts", "removeArtifacts": "rm -rf chainlink-artifacts", "compile": "hardhat compile", "prebuild": "npm run copyArtifacts && npm run compile && npm run removeArtifacts", @@ -44,7 +44,7 @@ "devDependencies": { "@nomicfoundation/hardhat-chai-matchers": "1.0.6", "@nomicfoundation/hardhat-network-helpers": "^1.0.8", - "@nomicfoundation/hardhat-toolbox": "2.0.2", + "@nomicfoundation/hardhat-toolbox": "^2.0.2", "@nomiclabs/hardhat-etherscan": "^3.1.7", "@typechain/ethers-v5": "^10.1.1", "@typechain/hardhat": "^6.1.6", @@ -55,7 +55,7 @@ "chai-as-promised": "^7.1.1", "copyfiles": "^2.4.1", "hardhat": "^2.17.3", - "hardhat-gas-reporter": "^1.0.9", + "hardhat-gas-reporter": "^1.0.8", "mocha": "^10.2.0", "mocha-suppress-logs": "^0.3.1", "prettier": "^2.8.8", @@ -69,6 +69,7 @@ }, "dependencies": { "@chainlink/contracts": "0.8.0", + "@chainlink/contracts-ccip": "1.2.1", "@chainlink/functions-toolkit": "0.2.6", "@inquirer/confirm": "^2.0.6", "@inquirer/input": "^1.2.5", diff --git a/src/HardhatChainlink.ts b/src/HardhatChainlink.ts index 2b47be5..e3b1b64 100644 --- a/src/HardhatChainlink.ts +++ b/src/HardhatChainlink.ts @@ -3,8 +3,11 @@ import "@nomiclabs/hardhat-ethers"; import { BigNumber, BigNumberish, BytesLike } from "ethers"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { Client } from "../types/chainlink-artifacts/Router"; + import * as automationRegistrar from "./automation/keepersRegistrar"; import * as automationRegistry from "./automation/keepersRegistry"; +import * as ccip from "./ccip"; import * as dataFeed from "./feeds/dataFeed"; import * as dataFeedProxy from "./feeds/dataFeedProxy"; import * as ensFeedsResolver from "./feeds/ensFeedsResolver"; @@ -17,9 +20,12 @@ import * as functionsSimulations from "./sandbox/functionsSimulations"; import * as linkToken from "./sandbox/linkToken"; import * as node from "./sandbox/node"; import * as operator from "./sandbox/operator"; +import * as ccipReceiver from "./sandbox/ccipReceiver"; import { + CCIPMessage, DockerOutput, FunctionsSubscriptionDetails, + GasEstimationOptions, Overrides, VRFSubscriptionDetails, } from "./shared/types"; @@ -46,6 +52,7 @@ export class HardhatChainlink { public automationRegistrar: AutomationRegistrar; public automationRegistry: AutomationRegistry; public functions: Functions; + public ccip: CCIP; public utils: Utils; public sandbox: Sandbox; private hre: HardhatRuntimeEnvironment; @@ -71,6 +78,7 @@ export class HardhatChainlink { this.automationRegistrar = new AutomationRegistrar(this.hre); this.automationRegistry = new AutomationRegistry(this.hre); this.functions = new Functions(this.hre); + this.ccip = new CCIP(this.hre); this.utils = new Utils(this.hre); this.sandbox = new Sandbox(this.hre); } @@ -82,13 +90,15 @@ class Sandbox { public directRequestConsumer: DirectRequestConsumer; public linkToken: LinkToken; public functionsSimulation: FunctionsSimulation; + public ccipReceiver: CCIPReceiver; constructor(hre: HardhatRuntimeEnvironment) { this.node = new Node(hre); this.operator = new Operator(hre); this.directRequestConsumer = new DirectRequestConsumer(hre); this.linkToken = new LinkToken(hre); - this.functionsSimulation = new FunctionsSimulation(hre); + this.functionsSimulation = new FunctionsSimulation(); + this.ccipReceiver = new CCIPReceiver(hre); } } @@ -1364,7 +1374,7 @@ class Functions { version ); } - + // utils public fetchRequestCommitment( functionsRouterAddress: string, @@ -1386,6 +1396,108 @@ class Functions { } } +class CCIP { + private hre: HardhatRuntimeEnvironment; + + constructor(hre: HardhatRuntimeEnvironment) { + this.hre = hre; + } + + public initializeRouter( + ccipRouterAddress: string, + overrides?: Overrides + ): Promise { + return ccip.CCIPRouter.initialize({ + hre: this.hre, + ccipRouterAddress, + overrides, + }); + } + + public getFee( + ccipRouterAddress: string, + destinationChainSelector: BigNumberish, + message: Client.EVM2AnyMessageStruct, + overrides?: Overrides + ): Promise { + return ccip.getFee( + this.hre, + ccipRouterAddress, + destinationChainSelector, + message, + overrides + ); + } + + public getSupportedTokens( + ccipRouterAddress: string, + chainSelector: BigNumberish, + overrides?: Overrides + ): Promise { + return ccip.getSupportedTokens( + this.hre, + ccipRouterAddress, + chainSelector, + overrides + ); + } + + public isChainSupported( + ccipRouterAddress: string, + chainSelector: BigNumberish, + overrides?: Overrides + ): Promise { + return ccip.isChainSupported( + this.hre, + ccipRouterAddress, + chainSelector, + overrides + ); + } + + public getOnRamp( + ccipRouterAddress: string, + chainSelector: BigNumberish, + overrides?: Overrides + ): Promise { + return ccip.getOnRamp( + this.hre, + ccipRouterAddress, + chainSelector, + overrides + ); + } + + public isOffRamp( + ccipRouterAddress: string, + sourceChainSelector: BigNumberish, + offRampAddress: string, + overrides?: Overrides + ): Promise { + return ccip.isOffRamp( + this.hre, + ccipRouterAddress, + sourceChainSelector, + offRampAddress, + overrides + ); + } + + public getWrappedNative( + ccipRouterAddress: string, + overrides?: Overrides + ): Promise { + return ccip.getWrappedNative(this.hre, ccipRouterAddress, overrides); + } + + public getTypeAndVersion( + ccipRouterAddress: string, + overrides?: Overrides + ): Promise { + return ccip.getTypeAndVersion(this.hre, ccipRouterAddress, overrides); + } +} + class Utils { private hre: HardhatRuntimeEnvironment; @@ -1429,7 +1541,7 @@ class Utils { ): Promise { return utils.deleteGist(githubApiToken, gistURL); } - + public async buildFunctionsRequestCBOR( codeLocation: functionsToolkit.Location, codeLanguage: functionsToolkit.CodeLanguage, @@ -1552,7 +1664,10 @@ class DirectRequestConsumer { public async getLatestAnswer( directRequestConsumerAddress: string ): Promise { - return directRequestConsumer.getLatestAnswer(this.hre, directRequestConsumerAddress); + return directRequestConsumer.getLatestAnswer( + this.hre, + directRequestConsumerAddress + ); } } @@ -1572,12 +1687,7 @@ class LinkToken { recipient: string, amount: BigNumberish ): Promise<{ transactionHash: string }> { - return linkToken.transfer( - this.hre, - linkTokenAddress, - recipient, - amount - ); + return linkToken.transfer(this.hre, linkTokenAddress, recipient, amount); } public async getAllowance( @@ -1616,11 +1726,7 @@ class LinkToken { } class FunctionsSimulation { - private hre: HardhatRuntimeEnvironment; - - constructor(hre: HardhatRuntimeEnvironment) { - this.hre = hre; - } + constructor() {} public async simulateRequest( source: string, @@ -1636,3 +1742,32 @@ class FunctionsSimulation { ); } } + +class CCIPReceiver { + private hre: HardhatRuntimeEnvironment; + + constructor(hre: HardhatRuntimeEnvironment) { + this.hre = hre; + } + + public deploy(ccipRouterAddress: string): Promise { + return ccipReceiver.deploy(this.hre, ccipRouterAddress); + } + + public getRouterAddress(ccipReceiverAddress: string): Promise { + return ccipReceiver.getRouterAddress(this.hre, ccipReceiverAddress); + } + + public estimateGas( + ccipReceiverAddress: string, + ccipMessage: CCIPMessage, + gasEstimationOptions: GasEstimationOptions, + ): Promise { + return ccipReceiver.estimateGas( + this.hre, + ccipReceiverAddress, + ccipMessage, + gasEstimationOptions + ); + } +} diff --git a/src/ccip/ccipRouter.ts b/src/ccip/ccipRouter.ts new file mode 100644 index 0000000..9c9fb49 --- /dev/null +++ b/src/ccip/ccipRouter.ts @@ -0,0 +1,174 @@ +import { BigNumber, BigNumberish, Signer } from "ethers"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +import { Router__factory, type Router } from "../../types"; +import { Client } from "../../types/chainlink-artifacts/Router"; +import { Overrides } from "../shared/types"; + +export const getFee = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string, + destinationChainSelector: BigNumberish, + message: Client.EVM2AnyMessageStruct, + overrides?: Overrides +): Promise => { + const ccipRouter = await CCIPRouter.initialize({ + hre, + ccipRouterAddress, + overrides, + }); + + return ccipRouter.getFee(destinationChainSelector, message); +}; + +export const getSupportedTokens = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string, + chainSelector: BigNumberish, + overrides?: Overrides +): Promise => { + const ccipRouter = await CCIPRouter.initialize({ + hre, + ccipRouterAddress, + overrides, + }); + + return ccipRouter.getSupportedTokens(chainSelector); +}; + +export const isChainSupported = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string, + chainSelector: BigNumberish, + overrides?: Overrides +): Promise => { + const ccipRouter = await CCIPRouter.initialize({ + hre, + ccipRouterAddress, + overrides, + }); + + return ccipRouter.isChainSupported(chainSelector); +}; + +export const getOnRamp = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string, + chainSelector: BigNumberish, + overrides?: Overrides +): Promise => { + const ccipRouter = await CCIPRouter.initialize({ + hre, + ccipRouterAddress, + overrides, + }); + + return ccipRouter.getOnRamp(chainSelector); +}; + +export const isOffRamp = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string, + sourceChainSelector: BigNumberish, + offRampAddress: string, + overrides?: Overrides +): Promise => { + const ccipRouter = await CCIPRouter.initialize({ + hre, + ccipRouterAddress, + overrides, + }); + + return ccipRouter.isOffRamp(sourceChainSelector, offRampAddress); +}; + +export const getWrappedNative = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string, + overrides?: Overrides +): Promise => { + const ccipRouter = await CCIPRouter.initialize({ + hre, + ccipRouterAddress, + overrides, + }); + + return ccipRouter.getWrappedNative(); +}; + +export const getTypeAndVersion = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string, + overrides?: Overrides +): Promise => { + const ccipRouter = await CCIPRouter.initialize({ + hre, + ccipRouterAddress, + overrides, + }); + + return ccipRouter.getTypeAndVersion(); +}; + +export class CCIPRouter { + private hre: HardhatRuntimeEnvironment; + private ccipRouter: Router; + + private constructor( + hre: HardhatRuntimeEnvironment, + signer: Signer, + ccipRouterAddress: string + ) { + this.hre = hre; + this.ccipRouter = Router__factory.connect(ccipRouterAddress, signer); + } + + // static async class factory + static async initialize(args: { + hre: HardhatRuntimeEnvironment; + ccipRouterAddress: string; + overrides?: Overrides; + }): Promise { + const { hre, ccipRouterAddress, overrides } = args; + const accounts = await hre.ethers.getSigners(); + return new CCIPRouter( + hre, + overrides?.signer || accounts[0], + ccipRouterAddress + ); + } + + async getFee( + destinationChainSelector: BigNumberish, + message: Client.EVM2AnyMessageStruct + ): Promise { + return this.ccipRouter.getFee(destinationChainSelector, message); + } + + async getSupportedTokens(chainSelector: BigNumberish): Promise { + return this.ccipRouter.getSupportedTokens(chainSelector); + } + + async isChainSupported(chainSelector: BigNumberish): Promise { + return this.ccipRouter.isChainSupported(chainSelector); + } + + async getOnRamp(chainSelector: BigNumberish): Promise { + return this.ccipRouter.getOnRamp(chainSelector); + } + + async isOffRamp( + sourceChainSelector: BigNumberish, + offRampAddress: string + ): Promise { + return this.ccipRouter.isOffRamp(sourceChainSelector, offRampAddress); + } + + async getWrappedNative(): Promise { + return this.ccipRouter.getWrappedNative(); + } + + async getTypeAndVersion(): Promise { + return this.ccipRouter.typeAndVersion(); + } +} diff --git a/src/ccip/index.ts b/src/ccip/index.ts new file mode 100644 index 0000000..1acbe09 --- /dev/null +++ b/src/ccip/index.ts @@ -0,0 +1 @@ +export * from "./ccipRouter"; diff --git a/src/helpers/inquirers.ts b/src/helpers/inquirers.ts index 1a4f27c..a93a52f 100644 --- a/src/helpers/inquirers.ts +++ b/src/helpers/inquirers.ts @@ -10,6 +10,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import * as registries from "../registries"; import { InquirableParameter, Task } from "../shared/enums"; import { + CCIPRoutersRegistry, Choice, DataFeedsRegistry, DenominationsRegistry, @@ -29,7 +30,8 @@ import { camelToFlat, kebabToCamelCase } from "./utils"; export const inquire = async ( hre: HardhatRuntimeEnvironment, parameterName: string, - defaultValue: any + defaultValue: any, + isBoolean: boolean = false ) => { switch (parameterName) { case InquirableParameter.dataFeedAddress: @@ -50,6 +52,8 @@ export const inquire = async ( return inquireL2SequencerAddress(hre); case InquirableParameter.functionsRouterAddress: return inquireFunctionsRouterAddress(hre); + case InquirableParameter.ccipRouterAddress: + return inquireCCIPRouterAddress(hre); case InquirableParameter.donId: return inquireDonId(hre); case InquirableParameter.codeLocation: @@ -62,14 +66,22 @@ export const inquire = async ( case InquirableParameter.feedRegistryQuoteTick: return inquireFeedRegistryQuoteTick(); default: - return inquireInput(camelToFlat(parameterName), defaultValue); + return inquireInput(camelToFlat(parameterName), defaultValue, isBoolean); } }; export const inquireInput = async ( parameterName: string, - defaultValue: any + defaultValue: any, + isBoolean: boolean = false ) => { + if (isBoolean) { + const result = await confirm({ + message: `${parameterName}`, + }); + + return result.toString(); + } return input({ message: `Provide a ${parameterName}`, default: defaultValue, @@ -695,6 +707,80 @@ export const inquireFunctionsRouterAddress = async ( return functionsRouterAddress; }; +export const inquireCCIPRouter = async ( + hre: HardhatRuntimeEnvironment, + useHardhatNetwork: boolean = true +) => { + const networksRegistry: NetworksRegistry = + registries.networksRegistry as NetworksRegistry; + + const ccipRoutersRegistry: CCIPRoutersRegistry = + registries.ccipRoutersRegistry as CCIPRoutersRegistry; + + let chainSlug = ""; + if (useHardhatNetwork) { + const chainId = hre.network.config.chainId; + if (!chainId) { + console.log( + "Could not identify network, chainId is not specified in hardhat.config." + ); + return undefined; + } + + chainSlug = networksRegistry[chainId].chainSlug; + } else { + chainSlug = await select({ + message: "Select a network", + choices: Object.values(Object.keys(ccipRoutersRegistry)).reduce( + (agg, networkName) => { + agg.push({ + name: networksRegistry[networkName].name, + value: kebabToCamelCase(networksRegistry[networkName].chainSlug), + description: networksRegistry[networkName].name, + }); + return agg; + }, + [] as Choice[] + ), + }); + } + + if (!ccipRoutersRegistry[chainSlug]) { + console.log( + `There is no CCIP Router in the plugin registry for the selected chain: ${hre.network.name}` + ); + return undefined; + } + + return ccipRoutersRegistry[chainSlug]; +}; + +export const inquireCCIPRouterAddress = async ( + hre: HardhatRuntimeEnvironment, + useHardhatNetwork: boolean = true +) => { + const ccipRouter = await inquireCCIPRouter(hre, useHardhatNetwork); + if (!ccipRouter) { + return input({ + message: "Provide a valid CCIP Router address", + }); + } + + const ccipRouterAddress = ccipRouter.contractAddress; + + const answer: boolean = await confirm({ + message: `CCIP Router found in the plugin registry: ${ccipRouterAddress}. Do you want to proceed with it?`, + }); + + if (!answer) { + return input({ + message: "Provide a valid CCIP Router address", + }); + } + + return ccipRouterAddress; +}; + export const inquireDonId = async ( hre: HardhatRuntimeEnvironment, useHardhatNetwork: boolean = true diff --git a/src/index.ts b/src/index.ts index 69a0d5d..c33d7f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -203,6 +203,21 @@ task( printSubtasks(Task.functions); }); +// CCIP +task(`${PACKAGE_NAME}:${Task.ccip}`, "CCIP Module") + .addOptionalPositionalParam("subtask", "Subtask") + .addOptionalParam("args", "Subtask args") + .setAction(async (taskArgs, hre) => { + return resolveTask(hre, Task.ccip, taskArgs); + }); + +task( + `${PACKAGE_NAME}:${Task.ccip}:subtasks`, + "CCIP Module: Subtasks List" +).setAction(async () => { + printSubtasks(Task.ccip); +}); + // REGISTRIES task(`${PACKAGE_NAME}:${Task.registries}`, "Plugin Registries Module") .addOptionalPositionalParam("subtask", "Subtask") @@ -249,7 +264,10 @@ task( }); // DIRECT REQUEST CONSUMER -task(`${PACKAGE_NAME}:${Task.directRequestConsumer}`, "Direct Request Consumer Module") +task( + `${PACKAGE_NAME}:${Task.directRequestConsumer}`, + "Direct Request Consumer Module" +) .addOptionalPositionalParam("subtask", "Subtask") .addOptionalParam("args", "Subtask args") .setAction(async (taskArgs, hre) => { @@ -296,6 +314,21 @@ task( printSubtasks(Task.functionsSimulation); }); +// CCIP RECEIVER +task(`${PACKAGE_NAME}:${Task.ccipReceiver}`, "CCIP Receiver Module") + .addOptionalPositionalParam("subtask", "Subtask") + .addOptionalParam("args", "Subtask args") + .setAction(async (taskArgs, hre) => { + return resolveTask(hre, Task.ccipReceiver, taskArgs); + }); + +task( + `${PACKAGE_NAME}:${Task.ccipReceiver}:subtasks`, + "CCIP Receiver Module: Subtasks List" +).setAction(async () => { + printSubtasks(Task.ccipReceiver); +}); + // UTILS task(`${PACKAGE_NAME}:${Task.utils}`, "Plugin Utils Module") .addOptionalPositionalParam("subtask", "Subtask") diff --git a/src/registries/ccipRoutersRegistry.ts b/src/registries/ccipRoutersRegistry.ts new file mode 100644 index 0000000..194f0d0 --- /dev/null +++ b/src/registries/ccipRoutersRegistry.ts @@ -0,0 +1,58 @@ +export const ccipRoutersRegistry = { + ethereum: { + contractAddress: "0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D", + chainId: "1", + }, + optimism: { + contractAddress: "0x3206695CaE29952f4b0c22a169725a865bc8Ce0f", + chainId: "10", + }, + arbitrum: { + contractAddress: "0x141fa059441E0ca23ce184B6A78bafD2A517DdE8", + chainId: "42161", + }, + polygon: { + contractAddress: "0x849c5ED5a80F5B408Dd4969b78c2C8fdf0565Bfe", + chainId: "137", + }, + avalanche: { + contractAddress: "0xF4c7E640EdA248ef95972845a62bdC74237805dB", + chainId: "43114", + }, + bsc: { + contractAddress: "0x34B03Cb9086d7D758AC55af71584F81A598759FE", + chainId: "56", + }, + base: { + contractAddress: "0x881e3A65B4d4a04dD529061dd0071cf975F58bCD", + chainId: "8453", + }, + sepolia: { + contractAddress: "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59", + chainId: "11155111", + }, + optimismSepolia: { + contractAddress: "0x114a20a10b43d4115e5aeef7345a1a71d2a60c57", + chainId: "11155420", + }, + mumbai: { + contractAddress: "0x1035CabC275068e0F4b745A29CEDf38E13aF41b1", + chainId: "80001", + }, + fuji: { + contractAddress: "0xF694E193200268f9a4868e4Aa017A0118C9a8177", + chainId: "43113", + }, + bnbChainTestnet: { + contractAddress: "0xE1053aE1857476f36A3C62580FF9b016E8EE8F6f", + chainId: "97", + }, + arbitrumSepolia: { + contractAddress: "0x2a9C5afB0d0e4BAb2BCdaE109EC4b0c4Be15a165", + chainId: "421614", + }, + baseSepolia: { + contractAddress: "0xD3b06cEbF099CE7DA4AcCf578aaebFDBd6e88a93", + chainId: "84532", + }, +}; diff --git a/src/registries/helpers/build.ts b/src/registries/helpers/build.ts index 7f99b1e..8ed77f1 100644 --- a/src/registries/helpers/build.ts +++ b/src/registries/helpers/build.ts @@ -2,6 +2,7 @@ import fs from "fs"; import { kebabToCamelCase } from "../../helpers/utils"; import { + CCIPRoutersRegistry, DataFeedsRegistry, FeedRegistriesRegistry, FunctionsRoutersRegistry, @@ -12,6 +13,7 @@ import { VRFCoordinatorsRegistry, } from "../../shared/types"; import { + CCIPRouter, DataFeed, FeedRegistry, FunctionsRouter, @@ -21,6 +23,7 @@ import { Network, VRFCoordinator, } from "../interfaces"; +import ccipRoutersJSON from "../json/CCIPRouters.json"; import dataFeedsJSON from "../json/DataFeeds.json"; import feedRegistriesJSON from "../json/FeedRegistries.json"; import functionsRoutersJSON from "../json/FunctionsRouters.json"; @@ -30,6 +33,7 @@ import linkTokensJSON from "../json/LinkTokens.json"; import networksJSON from "../json/Networks.json"; import vrfCoordinatorsJSON from "../json/VRFCoordinators.json"; +const ccipRouters: CCIPRouter[] = ccipRoutersJSON as CCIPRouter[]; const dataFeeds: DataFeed[] = dataFeedsJSON as DataFeed[]; const feedRegistries: FeedRegistry[] = feedRegistriesJSON as FeedRegistry[]; const networks: Network[] = networksJSON as Network[]; @@ -43,6 +47,7 @@ const keeperRegistries: KeeperRegistry[] = const l2Sequencers: L2Sequencer[] = l2SequencersJson as L2Sequencer[]; const networksMap: NetworksRegistry = {}; +const ccipRoutersMap: CCIPRoutersRegistry = {}; const dataFeedsMap: DataFeedsRegistry = {}; const feedRegistriesMap: FeedRegistriesRegistry = {}; const vrfCoordinatorsMap: VRFCoordinatorsRegistry = {}; @@ -83,6 +88,11 @@ functionsRouters.forEach((functionsRouter: FunctionsRouter) => { functionsRoutersMap[kebabToCamelCase(chainSlug)] = functionsRouter; }); +ccipRouters.forEach((ccipRouter: CCIPRouter) => { + const chainSlug = networksMap[ccipRouter.chainId].chainSlug; + ccipRoutersMap[kebabToCamelCase(chainSlug)] = ccipRouter; +}); + linkTokens.forEach((linkToken: LinkToken) => { const chainSlug = networksMap[linkToken.chainId].chainSlug; linkTokensMap[kebabToCamelCase(chainSlug)] = linkToken; @@ -113,6 +123,9 @@ const tsCodeVRFCoordinators = `export const vrfCoordinatorsRegistry = ${JSON.str const tsCodeFunctionsRouters = `export const functionsRoutersRegistry = ${JSON.stringify( functionsRoutersMap )};`; +const tsCodeCCIPRouters = `export const ccipRoutersRegistry = ${JSON.stringify( + ccipRoutersMap +)};`; const tsCodeLinkTokens = `export const linkTokensRegistry = ${JSON.stringify( linkTokensMap )};`; @@ -128,6 +141,7 @@ fs.writeFileSync("../dataFeedsRegistry.ts", tsCodeDataFeeds); fs.writeFileSync("../feedRegistriesRegistry.ts", tsCodeFeedRegistries); fs.writeFileSync("../vrfCoordinatorsRegistry.ts", tsCodeVRFCoordinators); fs.writeFileSync("../functionsRoutersRegistry.ts", tsCodeFunctionsRouters); +fs.writeFileSync("../ccipRoutersRegistry.ts", tsCodeCCIPRouters); fs.writeFileSync("../linkTokensRegistry.ts", tsCodeLinkTokens); fs.writeFileSync("../keeperRegistriesRegistry.ts", tsCodeKeeperRegistries); fs.writeFileSync("../l2SequencersRegistry.ts", tsCodeL2Sequencers); diff --git a/src/registries/index.ts b/src/registries/index.ts index e0dfcb5..d010044 100644 --- a/src/registries/index.ts +++ b/src/registries/index.ts @@ -3,6 +3,7 @@ export * from "./dataFeedsRegistry"; export * from "./feedRegistriesRegistry"; export * from "./vrfCoordinatorsRegistry"; export * from "./functionsRoutersRegistry"; +export * from "./ccipRoutersRegistry"; export * from "./linkTokensRegistry"; export * from "./keeperRegistriesRegistry"; export * from "./l2SequencersRegistry"; diff --git a/src/registries/interfaces/ccipRouter.interface.ts b/src/registries/interfaces/ccipRouter.interface.ts new file mode 100644 index 0000000..19f81bf --- /dev/null +++ b/src/registries/interfaces/ccipRouter.interface.ts @@ -0,0 +1,4 @@ +export interface CCIPRouter { + contractAddress: string; + chainId: string; +} diff --git a/src/registries/interfaces/index.ts b/src/registries/interfaces/index.ts index 67ec2a7..56f7ed0 100644 --- a/src/registries/interfaces/index.ts +++ b/src/registries/interfaces/index.ts @@ -1,3 +1,4 @@ +export * from "./ccipRouter.interface"; export * from "./dataFeed.interface"; export * from "./feedRegistry.interface"; export * from "./functionRouter.interface"; diff --git a/src/registries/interfaces/network.interface.ts b/src/registries/interfaces/network.interface.ts index c1031f3..0a7b2f8 100644 --- a/src/registries/interfaces/network.interface.ts +++ b/src/registries/interfaces/network.interface.ts @@ -3,4 +3,5 @@ export interface Network { chainSlug: string; networkSlug: string; chainId: string; + ccipChainSelector: string; } diff --git a/src/registries/json/CCIPRouters.json b/src/registries/json/CCIPRouters.json new file mode 100644 index 0000000..f44f879 --- /dev/null +++ b/src/registries/json/CCIPRouters.json @@ -0,0 +1,58 @@ +[ + { + "contractAddress": "0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D", + "chainId": "1" + }, + { + "contractAddress": "0x3206695CaE29952f4b0c22a169725a865bc8Ce0f", + "chainId": "10" + }, + { + "contractAddress": "0x141fa059441E0ca23ce184B6A78bafD2A517DdE8", + "chainId": "42161" + }, + { + "contractAddress": "0x849c5ED5a80F5B408Dd4969b78c2C8fdf0565Bfe", + "chainId": "137" + }, + { + "contractAddress": "0xF4c7E640EdA248ef95972845a62bdC74237805dB", + "chainId": "43114" + }, + { + "contractAddress": "0x34B03Cb9086d7D758AC55af71584F81A598759FE", + "chainId": "56" + }, + { + "contractAddress": "0x881e3A65B4d4a04dD529061dd0071cf975F58bCD", + "chainId": "8453" + }, + { + "contractAddress": "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59", + "chainId": "11155111" + }, + { + "contractAddress": "0x114a20a10b43d4115e5aeef7345a1a71d2a60c57", + "chainId": "11155420" + }, + { + "contractAddress": "0x1035CabC275068e0F4b745A29CEDf38E13aF41b1", + "chainId": "80001" + }, + { + "contractAddress": "0xF694E193200268f9a4868e4Aa017A0118C9a8177", + "chainId": "43113" + }, + { + "contractAddress": "0xE1053aE1857476f36A3C62580FF9b016E8EE8F6f", + "chainId": "97" + }, + { + "contractAddress": "0x2a9C5afB0d0e4BAb2BCdaE109EC4b0c4Be15a165", + "chainId": "421614" + }, + { + "contractAddress": "0xD3b06cEbF099CE7DA4AcCf578aaebFDBd6e88a93", + "chainId": "84532" + } +] diff --git a/src/registries/json/Networks.json b/src/registries/json/Networks.json index 7f1bc6f..84432c8 100644 --- a/src/registries/json/Networks.json +++ b/src/registries/json/Networks.json @@ -3,144 +3,196 @@ "name": "Ethereum Mainnet", "chainSlug": "ethereum", "networkSlug": "mainnet", - "chainId": "1" + "chainId": "1", + "ccipChainSelector": "5009297550715157269" }, { "name": "Ethereum Goerli", "chainSlug": "goerli", "networkSlug": "testnet", - "chainId": "5" + "chainId": "5", + "ccipChainSelector": "" }, { "name": "Ethereum Sepolia", "chainSlug": "sepolia", "networkSlug": "testnet", - "chainId": "11155111" + "chainId": "11155111", + "ccipChainSelector": "16015286601757825753" }, { "name": "BNB Chain Mainnet", "chainSlug": "bsc", "networkSlug": "mainnet", - "chainId": "56" + "chainId": "56", + "ccipChainSelector": "11344663589394136015" }, { "name": "BNB Chain Testnet", "chainSlug": "bnb-chain-testnet", "networkSlug": "testnet", - "chainId": "97" + "chainId": "97", + "ccipChainSelector": "13264668187771770619" }, { "name": "Polygon Mainnet", "chainSlug": "polygon", "networkSlug": "mainnet", - "chainId": "137" + "chainId": "137", + "ccipChainSelector": "4051577828743386545" }, { "name": "Polygon Mumbai", "chainSlug": "mumbai", "networkSlug": "testnet", - "chainId": "80001" + "chainId": "80001", + "ccipChainSelector": "12532609583862916517" }, { "name": "Arbitrum Mainnet", "chainSlug": "arbitrum", "networkSlug": "mainnet", - "chainId": "42161" + "chainId": "42161", + "ccipChainSelector": "4949039107694359620" }, { "name": "Arbitrum Goerli", "chainSlug": "arbitrum-goerli", "networkSlug": "testnet", - "chainId": "421613" + "chainId": "421613", + "ccipChainSelector": "" + }, + { + "name": "Arbitrum Sepolia", + "chainSlug": "arbitrum-sepolia", + "networkSlug": "testnet", + "chainId": "421614", + "ccipChainSelector": "3478487238524512106" }, { "name": "Optimism Mainnet", "chainSlug": "optimism", "networkSlug": "mainnet", - "chainId": "10" + "chainId": "10", + "ccipChainSelector": "3734403246176062136" }, { "name": "Optimism Goerli", "chainSlug": "optimism-goerli", "networkSlug": "testnet", - "chainId": "420" + "chainId": "420", + "ccipChainSelector": "" + }, + { + "name": "Optimism Sepolia", + "chainSlug": "optimism-sepolia", + "networkSlug": "testnet", + "chainId": "11155420", + "ccipChainSelector": "5224473277236331295" }, { "name": "Avalanche Mainnet", "chainSlug": "avalanche", "networkSlug": "mainnet", - "chainId": "43114" + "chainId": "43114", + "ccipChainSelector": "6433500567565415381" }, { "name": "Avalanche Fuji", "chainSlug": "fuji", "networkSlug": "testnet", - "chainId": "43113" + "chainId": "43113", + "ccipChainSelector": "14767482510784806043" }, { "name": "Moonriver Mainnet", "chainSlug": "moonriver", "networkSlug": "mainnet", - "chainId": "1285" + "chainId": "1285", + "ccipChainSelector": "" }, { "name": "Fantom Opera", "chainSlug": "fantom", "networkSlug": "mainnet", - "chainId": "250" + "chainId": "250", + "ccipChainSelector": "" }, { "name": "Fantom Testnet", "chainSlug": "fantom-testnet", "networkSlug": "testnet", - "chainId": "4002" + "chainId": "4002", + "ccipChainSelector": "" }, { "name": "Harmony Mainnet", "chainSlug": "harmony", "networkSlug": "mainnet", - "chainId": "1666600000" + "chainId": "1666600000", + "ccipChainSelector": "" }, { "name": "Moonbeam Mainnet", "chainSlug": "moonbeam", "networkSlug": "mainnet", - "chainId": "1284" + "chainId": "1284", + "ccipChainSelector": "" }, { "name": "Metis Mainnet", "chainSlug": "metis", "networkSlug": "mainnet", - "chainId": "1088" + "chainId": "1088", + "ccipChainSelector": "" }, { "name": "RSK Mainnet", "chainSlug": "rsk", "networkSlug": "mainnet", - "chainId": "30" + "chainId": "30", + "ccipChainSelector": "" }, { "name": "Gnosis Chain Mainnet", "chainSlug": "xdai", "networkSlug": "mainnet", - "chainId": "100" + "chainId": "100", + "ccipChainSelector": "" }, { "name": "Base Goerli", "chainSlug": "base-goerli", "networkSlug": "testnet", - "chainId": "84531" + "chainId": "84531", + "ccipChainSelector": "" }, { "name": "Celo Mainnet", "chainSlug": "celo", "networkSlug": "mainnet", - "chainId": "42220" + "chainId": "42220", + "ccipChainSelector": "" }, { "name": "Celo Alfajores", "chainSlug": "alfajores", "networkSlug": "testnet", - "chainId": "44787" + "chainId": "44787", + "ccipChainSelector": "" + }, + { + "name": "Base Mainnet", + "chainSlug": "base", + "networkSlug": "mainnet", + "chainId": "8453", + "ccipChainSelector": "15971525489660198786" + }, + { + "name": "Base Sepolia", + "chainSlug": "base-sepolia", + "networkSlug": "testnet", + "chainId": "84532", + "ccipChainSelector": "10344971235874465080" } ] diff --git a/src/registries/networksRegistry.ts b/src/registries/networksRegistry.ts index 865d940..f57e68e 100644 --- a/src/registries/networksRegistry.ts +++ b/src/registries/networksRegistry.ts @@ -4,287 +4,391 @@ export const networksRegistry = { chainSlug: "ethereum", networkSlug: "mainnet", chainId: "1", + ccipChainSelector: "5009297550715157269", }, "5": { name: "Ethereum Goerli", chainSlug: "goerli", networkSlug: "testnet", chainId: "5", + ccipChainSelector: "", }, "10": { name: "Optimism Mainnet", chainSlug: "optimism", networkSlug: "mainnet", chainId: "10", + ccipChainSelector: "3734403246176062136", }, "30": { name: "RSK Mainnet", chainSlug: "rsk", networkSlug: "mainnet", chainId: "30", + ccipChainSelector: "", }, "56": { name: "BNB Chain Mainnet", chainSlug: "bsc", networkSlug: "mainnet", chainId: "56", + ccipChainSelector: "11344663589394136015", }, "97": { name: "BNB Chain Testnet", chainSlug: "bnb-chain-testnet", networkSlug: "testnet", chainId: "97", + ccipChainSelector: "13264668187771770619", }, "100": { name: "Gnosis Chain Mainnet", chainSlug: "xdai", networkSlug: "mainnet", chainId: "100", + ccipChainSelector: "", }, "137": { name: "Polygon Mainnet", chainSlug: "polygon", networkSlug: "mainnet", chainId: "137", + ccipChainSelector: "4051577828743386545", }, "250": { name: "Fantom Opera", chainSlug: "fantom", networkSlug: "mainnet", chainId: "250", + ccipChainSelector: "", }, "420": { name: "Optimism Goerli", chainSlug: "optimism-goerli", networkSlug: "testnet", chainId: "420", + ccipChainSelector: "", }, "1088": { name: "Metis Mainnet", chainSlug: "metis", networkSlug: "mainnet", chainId: "1088", + ccipChainSelector: "", }, "1284": { name: "Moonbeam Mainnet", chainSlug: "moonbeam", networkSlug: "mainnet", chainId: "1284", + ccipChainSelector: "", }, "1285": { name: "Moonriver Mainnet", chainSlug: "moonriver", networkSlug: "mainnet", chainId: "1285", + ccipChainSelector: "", }, "4002": { name: "Fantom Testnet", chainSlug: "fantom-testnet", networkSlug: "testnet", chainId: "4002", + ccipChainSelector: "", + }, + "8453": { + name: "Base Mainnet", + chainSlug: "base", + networkSlug: "mainnet", + chainId: "8453", + ccipChainSelector: "15971525489660198786", }, "42161": { name: "Arbitrum Mainnet", chainSlug: "arbitrum", networkSlug: "mainnet", chainId: "42161", + ccipChainSelector: "4949039107694359620", }, "42220": { name: "Celo Mainnet", chainSlug: "celo", networkSlug: "mainnet", chainId: "42220", + ccipChainSelector: "", }, "43113": { name: "Avalanche Fuji", chainSlug: "fuji", networkSlug: "testnet", chainId: "43113", + ccipChainSelector: "14767482510784806043", }, "43114": { name: "Avalanche Mainnet", chainSlug: "avalanche", networkSlug: "mainnet", chainId: "43114", + ccipChainSelector: "6433500567565415381", }, "44787": { name: "Celo Alfajores", chainSlug: "alfajores", networkSlug: "testnet", chainId: "44787", + ccipChainSelector: "", }, "80001": { name: "Polygon Mumbai", chainSlug: "mumbai", networkSlug: "testnet", chainId: "80001", + ccipChainSelector: "12532609583862916517", }, "84531": { name: "Base Goerli", chainSlug: "base-goerli", networkSlug: "testnet", chainId: "84531", + ccipChainSelector: "", + }, + "84532": { + name: "Base Sepolia", + chainSlug: "base-sepolia", + networkSlug: "testnet", + chainId: "84532", + ccipChainSelector: "10344971235874465080", }, "421613": { name: "Arbitrum Goerli", chainSlug: "arbitrum-goerli", networkSlug: "testnet", chainId: "421613", + ccipChainSelector: "", + }, + "421614": { + name: "Arbitrum Sepolia", + chainSlug: "arbitrum-sepolia", + networkSlug: "testnet", + chainId: "421614", + ccipChainSelector: "3478487238524512106", }, "11155111": { name: "Ethereum Sepolia", chainSlug: "sepolia", networkSlug: "testnet", chainId: "11155111", + ccipChainSelector: "16015286601757825753", + }, + "11155420": { + name: "Optimism Sepolia", + chainSlug: "optimism-sepolia", + networkSlug: "testnet", + chainId: "11155420", + ccipChainSelector: "5224473277236331295", }, "1666600000": { name: "Harmony Mainnet", chainSlug: "harmony", networkSlug: "mainnet", chainId: "1666600000", + ccipChainSelector: "", }, ethereum: { name: "Ethereum Mainnet", chainSlug: "ethereum", networkSlug: "mainnet", chainId: "1", + ccipChainSelector: "5009297550715157269", }, goerli: { name: "Ethereum Goerli", chainSlug: "goerli", networkSlug: "testnet", chainId: "5", + ccipChainSelector: "", }, sepolia: { name: "Ethereum Sepolia", chainSlug: "sepolia", networkSlug: "testnet", chainId: "11155111", + ccipChainSelector: "16015286601757825753", }, bsc: { name: "BNB Chain Mainnet", chainSlug: "bsc", networkSlug: "mainnet", chainId: "56", + ccipChainSelector: "11344663589394136015", }, bnbChainTestnet: { name: "BNB Chain Testnet", chainSlug: "bnb-chain-testnet", networkSlug: "testnet", chainId: "97", + ccipChainSelector: "13264668187771770619", }, polygon: { name: "Polygon Mainnet", chainSlug: "polygon", networkSlug: "mainnet", chainId: "137", + ccipChainSelector: "4051577828743386545", }, mumbai: { name: "Polygon Mumbai", chainSlug: "mumbai", networkSlug: "testnet", chainId: "80001", + ccipChainSelector: "12532609583862916517", }, arbitrum: { name: "Arbitrum Mainnet", chainSlug: "arbitrum", networkSlug: "mainnet", chainId: "42161", + ccipChainSelector: "4949039107694359620", }, arbitrumGoerli: { name: "Arbitrum Goerli", chainSlug: "arbitrum-goerli", networkSlug: "testnet", chainId: "421613", + ccipChainSelector: "", + }, + arbitrumSepolia: { + name: "Arbitrum Sepolia", + chainSlug: "arbitrum-sepolia", + networkSlug: "testnet", + chainId: "421614", + ccipChainSelector: "3478487238524512106", }, optimism: { name: "Optimism Mainnet", chainSlug: "optimism", networkSlug: "mainnet", chainId: "10", + ccipChainSelector: "3734403246176062136", }, optimismGoerli: { name: "Optimism Goerli", chainSlug: "optimism-goerli", networkSlug: "testnet", chainId: "420", + ccipChainSelector: "", + }, + optimismSepolia: { + name: "Optimism Sepolia", + chainSlug: "optimism-sepolia", + networkSlug: "testnet", + chainId: "11155420", + ccipChainSelector: "5224473277236331295", }, avalanche: { name: "Avalanche Mainnet", chainSlug: "avalanche", networkSlug: "mainnet", chainId: "43114", + ccipChainSelector: "6433500567565415381", }, fuji: { name: "Avalanche Fuji", chainSlug: "fuji", networkSlug: "testnet", chainId: "43113", + ccipChainSelector: "14767482510784806043", }, moonriver: { name: "Moonriver Mainnet", chainSlug: "moonriver", networkSlug: "mainnet", chainId: "1285", + ccipChainSelector: "", }, fantom: { name: "Fantom Opera", chainSlug: "fantom", networkSlug: "mainnet", chainId: "250", + ccipChainSelector: "", }, fantomTestnet: { name: "Fantom Testnet", chainSlug: "fantom-testnet", networkSlug: "testnet", chainId: "4002", + ccipChainSelector: "", }, harmony: { name: "Harmony Mainnet", chainSlug: "harmony", networkSlug: "mainnet", chainId: "1666600000", + ccipChainSelector: "", }, moonbeam: { name: "Moonbeam Mainnet", chainSlug: "moonbeam", networkSlug: "mainnet", chainId: "1284", + ccipChainSelector: "", }, metis: { name: "Metis Mainnet", chainSlug: "metis", networkSlug: "mainnet", chainId: "1088", + ccipChainSelector: "", }, rsk: { name: "RSK Mainnet", chainSlug: "rsk", networkSlug: "mainnet", chainId: "30", + ccipChainSelector: "", }, xdai: { name: "Gnosis Chain Mainnet", chainSlug: "xdai", networkSlug: "mainnet", chainId: "100", + ccipChainSelector: "", }, baseGoerli: { name: "Base Goerli", chainSlug: "base-goerli", networkSlug: "testnet", chainId: "84531", + ccipChainSelector: "", }, celo: { name: "Celo Mainnet", chainSlug: "celo", networkSlug: "mainnet", chainId: "42220", + ccipChainSelector: "", }, alfajores: { name: "Celo Alfajores", chainSlug: "alfajores", networkSlug: "testnet", chainId: "44787", + ccipChainSelector: "", + }, + base: { + name: "Base Mainnet", + chainSlug: "base", + networkSlug: "mainnet", + chainId: "8453", + ccipChainSelector: "15971525489660198786", + }, + baseSepolia: { + name: "Base Sepolia", + chainSlug: "base-sepolia", + networkSlug: "testnet", + chainId: "84532", + ccipChainSelector: "10344971235874465080", }, }; diff --git a/src/sandbox/ccipReceiver/index.ts b/src/sandbox/ccipReceiver/index.ts new file mode 100644 index 0000000..57a1da8 --- /dev/null +++ b/src/sandbox/ccipReceiver/index.ts @@ -0,0 +1,130 @@ +import * as networkHelpers from "@nomicfoundation/hardhat-network-helpers"; +import { BigNumber, constants, providers, utils } from "ethers"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +import { + CCIPReceiver__factory, + SimpleCCIPReceiver__factory, +} from "../../../types"; +import { + CCIPMessage, + GasEstimationOptions, + Overrides, +} from "../../shared/types"; + +export const deploy = async ( + hre: HardhatRuntimeEnvironment, + ccipRouterAddress: string +): Promise => { + const [signer] = await hre.ethers.getSigners(); + + const ccipReceiver = await new SimpleCCIPReceiver__factory() + .connect(signer) + .deploy(ccipRouterAddress); + await ccipReceiver.deployed(); + + return ccipReceiver.address; +}; + +export const getRouterAddress = async ( + hre: HardhatRuntimeEnvironment, + ccipReceiverAddress: string, + overrides?: Overrides +): Promise => { + const provider = overrides?.provider + ? overrides.provider + : hre.ethers.provider; + const ccipReceiver = CCIPReceiver__factory.connect( + ccipReceiverAddress, + provider + ); + return ccipReceiver.getRouter(); +}; + +export const estimateGas = async ( + hre: HardhatRuntimeEnvironment, + ccipReceiverAddress: string, + ccipMessage: CCIPMessage, + gasEstimationOptions: GasEstimationOptions = {} +): Promise => { + validateCCIPMessage(ccipMessage); + + const provider = await getProvider(hre, gasEstimationOptions); + const ccipRouterAddress = await getRouterAddress(hre, ccipReceiverAddress, { + provider, + }); + const encodedMessageData = await encodeCCIPMessageData(ccipMessage); + + return provider.estimateGas({ + from: ccipRouterAddress, + to: ccipReceiverAddress, + data: encodedMessageData, + }); +}; + +const getProvider = async ( + hre: HardhatRuntimeEnvironment, + estimationOptions: GasEstimationOptions +): Promise => { + if (!estimationOptions.destinationChainRpcUrl) { + return hre.ethers.provider; + } + + if (estimationOptions.isForking) { + await networkHelpers.reset( + estimationOptions.destinationChainRpcUrl, + estimationOptions.destinationChainBlockId + ); + return hre.ethers.provider; + } + + return new hre.ethers.providers.JsonRpcProvider( + estimationOptions.destinationChainRpcUrl + ); +}; + +const encodeCCIPMessageData = async ( + any2EVMMessage: CCIPMessage +): Promise => { + const ccipReceiverInterface = CCIPReceiver__factory.createInterface(); + return ccipReceiverInterface.encodeFunctionData("ccipReceive", [ + any2EVMMessage, + ]); +}; + +const validateCCIPMessage = (ccipMessage: CCIPMessage) => { + if (ccipMessage.messageId) { + if (!utils.isBytesLike(ccipMessage.messageId)) { + throw new Error("Invalid messageId: must be a bytes-like value"); + } + // Pad messageId to 32 bytes if it is less than 32 bytes + ccipMessage.messageId = utils.hexZeroPad(ccipMessage.messageId, 32); + } else { + ccipMessage.messageId = constants.HashZero; + } + + if (!ccipMessage.sender) { + throw new Error("Invalid sender: sender is required"); + } + + if (utils.isAddress(ccipMessage.sender)) { + ccipMessage.sender = utils.defaultAbiCoder.encode( + ["address"], + [ccipMessage.sender] + ); + } + + if (!ccipMessage.data) { + ccipMessage.data = utils.hexValue(0); + } + + if (ccipMessage.destTokenAmounts) { + ccipMessage.destTokenAmounts.forEach((destTokenAmount) => { + if (!utils.isAddress(destTokenAmount.token)) { + throw new Error("Invalid destTokenAmounts: token must be an address"); + } + }); + } else { + ccipMessage.destTokenAmounts = []; + } +}; diff --git a/src/shared/enums.ts b/src/shared/enums.ts index e500072..a09057e 100644 --- a/src/shared/enums.ts +++ b/src/shared/enums.ts @@ -8,6 +8,7 @@ export enum Task { automationRegistrar = "automationRegistrar", vrf = "vrf", functions = "functions", + ccip = "ccip", registries = "registries", utils = "utils", node = "sandbox:node", @@ -15,6 +16,7 @@ export enum Task { directRequestConsumer = "sandbox:directRequestConsumer", linkToken = "sandbox:linkToken", functionsSimulation = "sandbox:functionsSimulation", + ccipReceiver = "sandbox:ccipReceiver", } export enum DataFeedSubtask { @@ -130,6 +132,15 @@ export enum FunctionsSubtask { estimateRequestCost = "estimateRequestCost", } +export enum CCIPSubtask { + getSupportedTokens = "getSupportedTokens", + isChainSupported = "isChainSupported", + getOnRamp = "getOnRamp", + isOffRamp = "isOffRamp", + getWrappedNative = "getWrappedNative", + getTypeAndVersion = "getTypeAndVersion", +} + export enum PluginRegistriesSubtask { getDataFeed = "getDataFeed", getFeedRegistry = "getFeedRegistry", @@ -138,6 +149,7 @@ export enum PluginRegistriesSubtask { getKeeperRegistry = "getKeeperRegistry", getL2Sequencer = "getL2Sequencer", getFunctionRouter = "getFunctionRouter", + getCCIPRouter = "getCCIPRouter", getDenomination = "getDenomination", } @@ -175,6 +187,12 @@ export enum FunctionsSimulationSubtask { simulateRequest = "simulateRequest", } +export enum CCIPReceiverSubtask { + deploy = "deploy", + getRouterAddress = "getRouterAddress", + estimateGas = "estimateGas", +} + export enum UtilsSubtask { getRoundId = "getRoundId", parseRoundId = "parseRoundId", @@ -193,6 +211,7 @@ export const enum InquirableParameter { keeperRegistrarAddress = "keeperRegistrarAddress", l2SequencerAddress = "l2SequencerAddress", functionsRouterAddress = "functionsRouterAddress", + ccipRouterAddress = "ccipRouterAddress", donId = "donId", secretsLocation = "secretsLocation", codeLocation = "codeLocation", diff --git a/src/shared/types.ts b/src/shared/types.ts index c1f06e2..b9a87a5 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,6 +1,7 @@ import { BigNumber, providers, Signer } from "ethers"; import { + CCIPRouter, DataFeed, FeedRegistry, FunctionsRouter, @@ -24,6 +25,7 @@ export type KeeperRegistriesRegistry = Record; export type L2SequencersRegistry = Record; export type DenominationsRegistry = Record; export type FunctionsRoutersRegistry = Record; +export type CCIPRoutersRegistry = Record; export type Subtasks = Record>; @@ -60,3 +62,17 @@ export type Overrides = { signer?: Signer; provider?: providers.JsonRpcProvider; }; + +export type CCIPMessage = { + messageId: string; + sourceChainSelector: string; + sender: string; + data: string; + destTokenAmounts: { token: string; amount: string }[]; +}; + +export type GasEstimationOptions = { + destinationChainRpcUrl?: string; + destinationChainBlockId?: string; + isForking?: boolean; +}; diff --git a/src/subtasks/index.ts b/src/subtasks/index.ts index 3d86d5a..52da994 100644 --- a/src/subtasks/index.ts +++ b/src/subtasks/index.ts @@ -2,6 +2,8 @@ import { camelToFlat } from "../helpers/utils"; import { AutomationRegistrarSubtask, AutomationRegistrySubtask, + CCIPReceiverSubtask, + CCIPSubtask, DataFeedProxySubtask, DataFeedSubtask, DirectRequestConsumerSubtask, @@ -21,6 +23,7 @@ import { import { Subtasks } from "../shared/types"; import * as automationRegistrarActions from "../tasks/automation/keeperRegistrar"; import * as automationRegistryActions from "../tasks/automation/keeperRegistry"; +import * as ccipActions from "../tasks/ccip"; import * as dataFeedActions from "../tasks/feeds/dataFeed"; import * as dataFeedProxyActions from "../tasks/feeds/dataFeedProxy"; import * as ensFeedsResolverActions from "../tasks/feeds/ensFeedsResolver"; @@ -28,6 +31,7 @@ import * as feedRegistryActions from "../tasks/feeds/feedRegistry"; import * as l2FeedUptimeSequencerActions from "../tasks/feeds/l2FeedUptimeSequencer"; import * as functionsActions from "../tasks/functions"; import * as registriesActions from "../tasks/registries"; +import * as ccipReceiverActions from "../tasks/sandbox/ccipReceiver"; import * as directRequestConsumerActions from "../tasks/sandbox/directRequestConsumer"; import * as functionsSimulationActions from "../tasks/sandbox/functionsSimulation"; import * as linkTokenActions from "../tasks/sandbox/linkToken"; @@ -1410,6 +1414,88 @@ export const subtasks: Subtasks = { ], }, }, + [Task.ccip]: { + [CCIPSubtask.getSupportedTokens]: { + action: ccipActions.getSupportedTokens, + description: camelToFlat(CCIPSubtask.getSupportedTokens), + args: [ + { + name: "ccipRouterAddress", + description: "Address of CCIP Router", + }, + { + name: "chainSelector", + description: "CCIP chain selector", + }, + ], + }, + [CCIPSubtask.isChainSupported]: { + action: ccipActions.isChainSupported, + description: camelToFlat(CCIPSubtask.isChainSupported), + args: [ + { + name: "ccipRouterAddress", + description: "Address of CCIP Router", + }, + { + name: "chainSelector", + description: "CCIP chain selector", + }, + ], + }, + [CCIPSubtask.getOnRamp]: { + action: ccipActions.getOnRamp, + description: camelToFlat(CCIPSubtask.getOnRamp), + args: [ + { + name: "ccipRouterAddress", + description: "Address of CCIP Router", + }, + { + name: "chainSelector", + description: "CCIP chain selector", + }, + ], + }, + [CCIPSubtask.isOffRamp]: { + action: ccipActions.isOffRamp, + description: camelToFlat(CCIPSubtask.isOffRamp), + args: [ + { + name: "ccipRouterAddress", + description: "Address of CCIP Router", + }, + { + name: "sourceChainSelector", + description: "CCIP source chain selector", + }, + { + name: "offRampAddress", + description: "Address of a contract to be checked", + }, + ], + }, + [CCIPSubtask.getWrappedNative]: { + action: ccipActions.getWrappedNative, + description: camelToFlat(CCIPSubtask.getWrappedNative), + args: [ + { + name: "ccipRouterAddress", + description: "Address of CCIP Router", + }, + ], + }, + [CCIPSubtask.getTypeAndVersion]: { + action: ccipActions.getTypeAndVersion, + description: camelToFlat(CCIPSubtask.getTypeAndVersion), + args: [ + { + name: "ccipRouterAddress", + description: "Address of CCIP Router", + }, + ], + }, + }, [Task.registries]: { [PluginRegistriesSubtask.getDataFeed]: { action: registriesActions.getDataFeed, @@ -1446,6 +1532,11 @@ export const subtasks: Subtasks = { description: camelToFlat(PluginRegistriesSubtask.getFunctionRouter), args: [], }, + [PluginRegistriesSubtask.getCCIPRouter]: { + action: registriesActions.getCCIPRouter, + description: camelToFlat(PluginRegistriesSubtask.getCCIPRouter), + args: [], + }, [PluginRegistriesSubtask.getDenomination]: { action: registriesActions.getDenomination, description: camelToFlat(PluginRegistriesSubtask.getDenomination), @@ -1750,4 +1841,55 @@ export const subtasks: Subtasks = { ], }, }, + [Task.ccipReceiver]: { + [CCIPReceiverSubtask.deploy]: { + action: ccipReceiverActions.deploy, + description: "Deploy Simple CCIP Receiver", + args: [ + { + name: "ccipRouterAddress", + description: "Address of CCIP Router", + }, + ], + }, + [CCIPReceiverSubtask.getRouterAddress]: { + action: ccipReceiverActions.getRouterAddress, + description: "Get CCIP Router address", + args: [ + { + name: "ccipReceiverAddress", + description: "Address of CCIP Receiver", + }, + ], + }, + [CCIPReceiverSubtask.estimateGas]: { + action: ccipReceiverActions.estimateGas, + description: "Estimate CCIP Receiver gas usage", + args: [ + { + name: "ccipReceiverAddress", + description: "Address of CCIP Receiver", + }, + { + name: "ccipMessageJsonPath", + description: "JSON path to CCIP message", + }, + { + name: "destinationChainRpcUrl", + description: "Destination chain RPC URL", + defaultValue: "", + }, + { + name: "destinationChainBlockId", + description: "Destination chain block ID", + defaultValue: "", + }, + { + name: "isForking", + description: "Fork destination chain while estimating gas", + isBoolean: true, + }, + ], + }, + }, }; diff --git a/src/subtasks/interfaces/index.ts b/src/subtasks/interfaces/index.ts index 5371d78..5c0ffc4 100644 --- a/src/subtasks/interfaces/index.ts +++ b/src/subtasks/interfaces/index.ts @@ -4,6 +4,7 @@ interface SubtaskArg { name: string; description?: string; defaultValue?: any; + isBoolean?: boolean; } export interface SubtaskProperties { diff --git a/src/tasks/ccip/index.ts b/src/tasks/ccip/index.ts new file mode 100644 index 0000000..da8dc97 --- /dev/null +++ b/src/tasks/ccip/index.ts @@ -0,0 +1,62 @@ +import { BigNumberish } from "ethers"; +import { ActionType } from "hardhat/types"; + +import * as ccip from "../../ccip"; + +export const getSupportedTokens: ActionType<{ + ccipRouterAddress: string; + chainSelector: BigNumberish; +}> = async (taskArgs, hre) => { + return ccip.getSupportedTokens( + hre, + taskArgs.ccipRouterAddress, + taskArgs.chainSelector + ); +}; + +export const isChainSupported: ActionType<{ + ccipRouterAddress: string; + chainSelector: BigNumberish; +}> = async (taskArgs, hre) => { + return ccip.isChainSupported( + hre, + taskArgs.ccipRouterAddress, + taskArgs.chainSelector + ); +}; + +export const getOnRamp: ActionType<{ + ccipRouterAddress: string; + chainSelector: BigNumberish; +}> = async (taskArgs, hre) => { + return ccip.getOnRamp( + hre, + taskArgs.ccipRouterAddress, + taskArgs.chainSelector + ); +}; + +export const isOffRamp: ActionType<{ + ccipRouterAddress: string; + sourceChainSelector: BigNumberish; + offRampAddress: string; +}> = async (taskArgs, hre) => { + return ccip.isOffRamp( + hre, + taskArgs.ccipRouterAddress, + taskArgs.sourceChainSelector, + taskArgs.offRampAddress + ); +}; + +export const getWrappedNative: ActionType<{ + ccipRouterAddress: string; +}> = async (taskArgs, hre) => { + return ccip.getWrappedNative(hre, taskArgs.ccipRouterAddress); +}; + +export const getTypeAndVersion: ActionType<{ + ccipRouterAddress: string; +}> = async (taskArgs, hre) => { + return ccip.getTypeAndVersion(hre, taskArgs.ccipRouterAddress); +}; diff --git a/src/tasks/helpers/index.ts b/src/tasks/helpers/index.ts index 45717d6..081adf2 100644 --- a/src/tasks/helpers/index.ts +++ b/src/tasks/helpers/index.ts @@ -29,7 +29,8 @@ export const resolveTask = async ( subtaskArgs[subtaskArg.name] = await inquire( hre, subtaskArg.name, - subtaskArg.defaultValue + subtaskArg.defaultValue, + subtaskArg.isBoolean ); } } diff --git a/src/tasks/registries/index.ts b/src/tasks/registries/index.ts index 0e0c799..e7fd6a4 100644 --- a/src/tasks/registries/index.ts +++ b/src/tasks/registries/index.ts @@ -30,6 +30,10 @@ export const getFunctionRouter: ActionType<{}> = async (taskArgs, hre) => { return inquirers.inquireFunctionsRouter(hre, false); }; +export const getCCIPRouter: ActionType<{}> = async (taskArgs, hre) => { + return inquirers.inquireCCIPRouter(hre, false); +}; + export const getDenomination: ActionType<{}> = async () => { return inquirers.inquireDenomination(); }; diff --git a/src/tasks/sandbox/ccipReceiver/index.ts b/src/tasks/sandbox/ccipReceiver/index.ts new file mode 100644 index 0000000..00cd231 --- /dev/null +++ b/src/tasks/sandbox/ccipReceiver/index.ts @@ -0,0 +1,48 @@ +import fs from "fs"; +import { ActionType } from "hardhat/types"; +import path from "path"; + +import * as ccipReceiver from "../../../sandbox/ccipReceiver"; +import { CCIPMessage } from "../../../shared/types"; + +export const deploy: ActionType<{ + ccipRouterAddress: string; +}> = async (taskArgs, hre): Promise => { + return ccipReceiver.deploy(hre, taskArgs.ccipRouterAddress); +}; + +export const getRouterAddress: ActionType<{ + ccipReceiverAddress: string; +}> = async (taskArgs, hre): Promise => { + return ccipReceiver.getRouterAddress(hre, taskArgs.ccipReceiverAddress); +}; + +export const estimateGas: ActionType<{ + ccipReceiverAddress: string; + ccipMessageJsonPath: string; + destinationChainRpcUrl: string; + destinationChainBlockId: string; + isForking: string; +}> = async (taskArgs, hre): Promise => { + const ccipMessageRaw = fs.readFileSync( + path.resolve(taskArgs.ccipMessageJsonPath) + ); + const ccipMessage: CCIPMessage = JSON.parse(ccipMessageRaw.toString()); + const result = await ccipReceiver.estimateGas( + hre, + taskArgs.ccipReceiverAddress, + ccipMessage, + { + destinationChainRpcUrl: + taskArgs.destinationChainRpcUrl === "" + ? undefined + : taskArgs.destinationChainRpcUrl, + destinationChainBlockId: + taskArgs.destinationChainBlockId === "" + ? undefined + : taskArgs.destinationChainBlockId, + isForking: taskArgs.isForking === "true", + } + ); + return result.toString(); +};