From 8b1710625b708ee8ad116069b90ce53641c15a60 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:35:15 +0100 Subject: [PATCH 01/22] add ccip contracts package and update copyArtifacts script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c1117b..db1af54 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "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", + "copyArtifacts": "copyfiles -a -s -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", @@ -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", From 0fdd2420e57e8c9e4f7be8b2d2e5868508a8f154 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:35:38 +0100 Subject: [PATCH 02/22] add CCIP Router artifact as external artifact --- hardhat.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/hardhat.config.ts b/hardhat.config.ts index 0bc4673..fd404ca 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -43,6 +43,7 @@ const config: HardhatUserConfig = { "./chainlink-artifacts/TypeAndVersionInterface.json", "./chainlink-artifacts/OptimismSequencerUptimeFeed.json", "./chainlink-artifacts/VRFCoordinatorV2.json", + "./chainlink-artifacts/Router.json", ], }, }; From ed72913157c76f00d81d40cf882b8f6d40de4c14 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:36:19 +0100 Subject: [PATCH 03/22] add CCIP routers to the registry --- src/registries/ccipRoutersRegistry.ts | 58 +++++++++++++++++++ src/registries/helpers/build.ts | 14 +++++ src/registries/index.ts | 1 + .../interfaces/ccipRouter.interface.ts | 4 ++ src/registries/interfaces/index.ts | 1 + src/registries/json/CCIPRouters.json | 58 +++++++++++++++++++ 6 files changed, 136 insertions(+) create mode 100644 src/registries/ccipRoutersRegistry.ts create mode 100644 src/registries/interfaces/ccipRouter.interface.ts create mode 100644 src/registries/json/CCIPRouters.json 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/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" + } +] From 91a50468eee7c147740391286f4ad70383f6b2b2 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:36:49 +0100 Subject: [PATCH 04/22] update networks registry: add ccip chain selector, add new networks --- .../interfaces/network.interface.ts | 1 + src/registries/json/Networks.json | 100 +++++++++++++---- src/registries/networksRegistry.ts | 104 ++++++++++++++++++ 3 files changed, 181 insertions(+), 24 deletions(-) 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/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", }, }; From 45a89e40465c73c966d1197004ecd0e0dce80c24 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:37:21 +0100 Subject: [PATCH 05/22] add wrapper for CCIP router contract --- src/ccip/ccipRouter.ts | 174 +++++++++++++++++++++++++++++++++++++++++ src/ccip/index.ts | 1 + 2 files changed, 175 insertions(+) create mode 100644 src/ccip/ccipRouter.ts create mode 100644 src/ccip/index.ts 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"; From 077837729ead90adbf203b129746db695f735390 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:38:23 +0100 Subject: [PATCH 06/22] update HRE with CCIP router class, code refactoring --- src/HardhatChainlink.ts | 131 +++++++++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 15 deletions(-) diff --git a/src/HardhatChainlink.ts b/src/HardhatChainlink.ts index 2b47be5..b9a2a23 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"; @@ -46,6 +49,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 +75,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); } @@ -88,7 +93,7 @@ class Sandbox { this.operator = new Operator(hre); this.directRequestConsumer = new DirectRequestConsumer(hre); this.linkToken = new LinkToken(hre); - this.functionsSimulation = new FunctionsSimulation(hre); + this.functionsSimulation = new FunctionsSimulation(); } } @@ -1364,7 +1369,7 @@ class Functions { version ); } - + // utils public fetchRequestCommitment( functionsRouterAddress: string, @@ -1386,6 +1391,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 +1536,7 @@ class Utils { ): Promise { return utils.deleteGist(githubApiToken, gistURL); } - + public async buildFunctionsRequestCBOR( codeLocation: functionsToolkit.Location, codeLanguage: functionsToolkit.CodeLanguage, @@ -1552,7 +1659,10 @@ class DirectRequestConsumer { public async getLatestAnswer( directRequestConsumerAddress: string ): Promise { - return directRequestConsumer.getLatestAnswer(this.hre, directRequestConsumerAddress); + return directRequestConsumer.getLatestAnswer( + this.hre, + directRequestConsumerAddress + ); } } @@ -1572,12 +1682,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 +1721,7 @@ class LinkToken { } class FunctionsSimulation { - private hre: HardhatRuntimeEnvironment; - - constructor(hre: HardhatRuntimeEnvironment) { - this.hre = hre; - } + constructor() {} public async simulateRequest( source: string, From 210d7a68ee3b4a8ab7d09bfb6d5c9469ece931f3 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:39:47 +0100 Subject: [PATCH 07/22] update service files with CCIP router functionality --- src/shared/enums.ts | 12 ++++++++++++ src/shared/types.ts | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/shared/enums.ts b/src/shared/enums.ts index e500072..769ac28 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", @@ -130,6 +131,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 +148,7 @@ export enum PluginRegistriesSubtask { getKeeperRegistry = "getKeeperRegistry", getL2Sequencer = "getL2Sequencer", getFunctionRouter = "getFunctionRouter", + getCCIPRouter = "getCCIPRouter", getDenomination = "getDenomination", } @@ -193,6 +204,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..1ba6ebc 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>; From afbedfede4a2937839f020e305f6a7d386b1a7af Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:40:10 +0100 Subject: [PATCH 08/22] update inquirers and interactive registries --- src/helpers/inquirers.ts | 77 +++++++++++++++++++++++++++++++++++ src/tasks/registries/index.ts | 4 ++ 2 files changed, 81 insertions(+) diff --git a/src/helpers/inquirers.ts b/src/helpers/inquirers.ts index 1a4f27c..393faae 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, @@ -50,6 +51,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: @@ -695,6 +698,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/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(); }; From 79b06fa9a6c0008134b7e4de352f2295d374fea6 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:40:37 +0100 Subject: [PATCH 09/22] update hardhat tasks and subtasks with CCIP router functionality --- src/index.ts | 15 +++++++ src/subtasks/index.ts | 89 +++++++++++++++++++++++++++++++++++++++++ src/tasks/ccip/index.ts | 62 ++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 src/tasks/ccip/index.ts diff --git a/src/index.ts b/src/index.ts index 69a0d5d..b27718e 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") diff --git a/src/subtasks/index.ts b/src/subtasks/index.ts index 3d86d5a..68fe9fd 100644 --- a/src/subtasks/index.ts +++ b/src/subtasks/index.ts @@ -2,6 +2,7 @@ import { camelToFlat } from "../helpers/utils"; import { AutomationRegistrarSubtask, AutomationRegistrySubtask, + CCIPSubtask, DataFeedProxySubtask, DataFeedSubtask, DirectRequestConsumerSubtask, @@ -21,6 +22,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"; @@ -1410,6 +1412,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 +1530,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), 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); +}; From 8eb0a6fe941af210d31826a2b1e19c5e4dc25f3e Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 27 Feb 2024 09:40:52 +0100 Subject: [PATCH 10/22] bump package version to 0.0.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db1af54..856d59f 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", From b87fffe877a957595699187a60c740462388a0cb Mon Sep 17 00:00:00 2001 From: koteld Date: Wed, 13 Mar 2024 18:03:31 +0100 Subject: [PATCH 11/22] add gas estimation for CCIP Receiver --- hardhat.config.ts | 1 + package.json | 5 ++--- src/index.ts | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index fd404ca..5a741e8 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -44,6 +44,7 @@ const config: HardhatUserConfig = { "./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 856d59f..4693708 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "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 -s -f ./node_modules/@chainlink/contracts/abi/**/* ./node_modules/@chainlink/contracts-ccip/abi/**/* chainlink-artifacts", + "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", @@ -55,7 +55,6 @@ "chai-as-promised": "^7.1.1", "copyfiles": "^2.4.1", "hardhat": "^2.17.3", - "hardhat-gas-reporter": "^1.0.9", "mocha": "^10.2.0", "mocha-suppress-logs": "^0.3.1", "prettier": "^2.8.8", @@ -69,7 +68,7 @@ }, "dependencies": { "@chainlink/contracts": "0.8.0", - "@chainlink/contracts-ccip": "^1.2.1", + "@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/index.ts b/src/index.ts index b27718e..aee6ef4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,8 @@ import { HardhatUserConfig, } from "hardhat/types"; +import { CCIPReceiver__factory } from "../types"; + import { HardhatChainlink } from "./HardhatChainlink"; import { PACKAGE_NAME } from "./shared/constants"; import { Task } from "./shared/enums"; @@ -218,6 +220,46 @@ task( printSubtasks(Task.ccip); }); +task( + `${PACKAGE_NAME}:${Task.ccip}:estimateReceiverGas`, + "CCIP Module: Estimate CCIP Receiver Gas" +).setAction( + async (taskArgs: { + destinationRPCUrl: string; + ccipMessageId: string; + sourceChainSelector: string; + ccipMessageSender: string; + ccipMessageData: string; + ccipReceiverAddress: string; + gasLimit: string; + }) => { + const { ethers } = require("ethers"); + + // Initialize an ethers instance + const provider = new ethers.providers.JsonRpcProvider( + `${taskArgs.destinationRPCUrl}` + ); + + const ccipReceiverInterface = CCIPReceiver__factory.createInterface(); + const data = ccipReceiverInterface.encodeFunctionData("ccipReceive", [ + { + messageId: `${taskArgs.ccipMessageId}`, + sourceChainSelector: `${taskArgs.sourceChainSelector}`, + sender: `${taskArgs.ccipMessageSender}`, + data: `${taskArgs.ccipMessageData}`, + destTokenAmounts: [], + }, + ]); + + // Query the blockchain (replace example parameters) + return provider.estimateGas({ + to: `${taskArgs.ccipReceiverAddress}`, + data, + gasLimit: `${taskArgs.gasLimit}`, + }); + } +); + // REGISTRIES task(`${PACKAGE_NAME}:${Task.registries}`, "Plugin Registries Module") .addOptionalPositionalParam("subtask", "Subtask") @@ -264,7 +306,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) => { From b4f774a7af2be6035bb152a1e9c0a49260290ff2 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:13:11 +0100 Subject: [PATCH 12/22] add hardhat-toolbox to project dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4693708..550c6ce 100644 --- a/package.json +++ b/package.json @@ -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", From 6bc4e2dcf369c2ff6a2bd2fd44f7a259e4ad77e0 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:13:46 +0100 Subject: [PATCH 13/22] add SimpleCCIPReceiver.sol as example CCIP Receiver contract --- contracts/SimpleCCIPReceiver.sol | 89 ++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 contracts/SimpleCCIPReceiver.sol 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 + ); + } +} From f4e3c3c8837942ff817f515d585c17cb39365708 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:15:19 +0100 Subject: [PATCH 14/22] introduce CCIP Receiver module in sandbox --- src/sandbox/ccipReceiver/index.ts | 130 ++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/sandbox/ccipReceiver/index.ts 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 = []; + } +}; From 6da7e60152d7088acbe558bb432d19ec56a4c522 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:15:51 +0100 Subject: [PATCH 15/22] add types associated with CCIP Receiver --- src/shared/types.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/shared/types.ts b/src/shared/types.ts index 1ba6ebc..b9a87a5 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -62,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; +}; From d449de56a59022f8b5200dfd7c619cc4a7c62a9c Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:16:45 +0100 Subject: [PATCH 16/22] update inquirers so it can handle boolean variables in more convenient way --- src/helpers/inquirers.ts | 15 ++++++++++++--- src/subtasks/interfaces/index.ts | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/helpers/inquirers.ts b/src/helpers/inquirers.ts index 393faae..a93a52f 100644 --- a/src/helpers/inquirers.ts +++ b/src/helpers/inquirers.ts @@ -30,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: @@ -65,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, 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 { From 8d555ea33dba4983de7d711df4deed91bccb4949 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:17:41 +0100 Subject: [PATCH 17/22] add CCIP Receiver module to Hardhat Extended Environment as Sandbox submodule --- src/HardhatChainlink.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/HardhatChainlink.ts b/src/HardhatChainlink.ts index b9a2a23..e3b1b64 100644 --- a/src/HardhatChainlink.ts +++ b/src/HardhatChainlink.ts @@ -20,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"; @@ -87,6 +90,7 @@ class Sandbox { public directRequestConsumer: DirectRequestConsumer; public linkToken: LinkToken; public functionsSimulation: FunctionsSimulation; + public ccipReceiver: CCIPReceiver; constructor(hre: HardhatRuntimeEnvironment) { this.node = new Node(hre); @@ -94,6 +98,7 @@ class Sandbox { this.directRequestConsumer = new DirectRequestConsumer(hre); this.linkToken = new LinkToken(hre); this.functionsSimulation = new FunctionsSimulation(); + this.ccipReceiver = new CCIPReceiver(hre); } } @@ -1737,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 + ); + } +} From 11f45709a730725906148fc3bc173448a78cd6be Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:18:06 +0100 Subject: [PATCH 18/22] introduce tasks for CCIP Receiver module --- src/tasks/sandbox/ccipReceiver/index.ts | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/tasks/sandbox/ccipReceiver/index.ts 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(); +}; From 6411ba4daa5cb3b6a6822b2c876deee5c2468aaf Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:18:36 +0100 Subject: [PATCH 19/22] update tasks helpers in order to handle boolean values --- src/tasks/helpers/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ); } } From a3c40a90317df0ed853a729c1c7a65843dc2fbc8 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:19:07 +0100 Subject: [PATCH 20/22] add CCIP Receiver tasks description as subtasks --- src/shared/enums.ts | 7 ++++++ src/subtasks/index.ts | 53 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/shared/enums.ts b/src/shared/enums.ts index 769ac28..a09057e 100644 --- a/src/shared/enums.ts +++ b/src/shared/enums.ts @@ -16,6 +16,7 @@ export enum Task { directRequestConsumer = "sandbox:directRequestConsumer", linkToken = "sandbox:linkToken", functionsSimulation = "sandbox:functionsSimulation", + ccipReceiver = "sandbox:ccipReceiver", } export enum DataFeedSubtask { @@ -186,6 +187,12 @@ export enum FunctionsSimulationSubtask { simulateRequest = "simulateRequest", } +export enum CCIPReceiverSubtask { + deploy = "deploy", + getRouterAddress = "getRouterAddress", + estimateGas = "estimateGas", +} + export enum UtilsSubtask { getRoundId = "getRoundId", parseRoundId = "parseRoundId", diff --git a/src/subtasks/index.ts b/src/subtasks/index.ts index 68fe9fd..52da994 100644 --- a/src/subtasks/index.ts +++ b/src/subtasks/index.ts @@ -2,6 +2,7 @@ import { camelToFlat } from "../helpers/utils"; import { AutomationRegistrarSubtask, AutomationRegistrySubtask, + CCIPReceiverSubtask, CCIPSubtask, DataFeedProxySubtask, DataFeedSubtask, @@ -30,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"; @@ -1839,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, + }, + ], + }, + }, }; From aa85bc91862fefc4e150f8bd1f79c5a65e274e13 Mon Sep 17 00:00:00 2001 From: koteld Date: Tue, 26 Mar 2024 18:19:47 +0100 Subject: [PATCH 21/22] register CCIP Receiver module as subtasks --- src/index.ts | 57 ++++++++++++++-------------------------------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/src/index.ts b/src/index.ts index aee6ef4..c33d7f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,8 +6,6 @@ import { HardhatUserConfig, } from "hardhat/types"; -import { CCIPReceiver__factory } from "../types"; - import { HardhatChainlink } from "./HardhatChainlink"; import { PACKAGE_NAME } from "./shared/constants"; import { Task } from "./shared/enums"; @@ -220,46 +218,6 @@ task( printSubtasks(Task.ccip); }); -task( - `${PACKAGE_NAME}:${Task.ccip}:estimateReceiverGas`, - "CCIP Module: Estimate CCIP Receiver Gas" -).setAction( - async (taskArgs: { - destinationRPCUrl: string; - ccipMessageId: string; - sourceChainSelector: string; - ccipMessageSender: string; - ccipMessageData: string; - ccipReceiverAddress: string; - gasLimit: string; - }) => { - const { ethers } = require("ethers"); - - // Initialize an ethers instance - const provider = new ethers.providers.JsonRpcProvider( - `${taskArgs.destinationRPCUrl}` - ); - - const ccipReceiverInterface = CCIPReceiver__factory.createInterface(); - const data = ccipReceiverInterface.encodeFunctionData("ccipReceive", [ - { - messageId: `${taskArgs.ccipMessageId}`, - sourceChainSelector: `${taskArgs.sourceChainSelector}`, - sender: `${taskArgs.ccipMessageSender}`, - data: `${taskArgs.ccipMessageData}`, - destTokenAmounts: [], - }, - ]); - - // Query the blockchain (replace example parameters) - return provider.estimateGas({ - to: `${taskArgs.ccipReceiverAddress}`, - data, - gasLimit: `${taskArgs.gasLimit}`, - }); - } -); - // REGISTRIES task(`${PACKAGE_NAME}:${Task.registries}`, "Plugin Registries Module") .addOptionalPositionalParam("subtask", "Subtask") @@ -356,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") From 522a08843d3daba4400c93d1c171cf8444402c2c Mon Sep 17 00:00:00 2001 From: koteld Date: Thu, 2 May 2024 10:28:14 +0200 Subject: [PATCH 22/22] update package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 550c6ce..ee82bea 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "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", + "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", @@ -55,6 +55,7 @@ "chai-as-promised": "^7.1.1", "copyfiles": "^2.4.1", "hardhat": "^2.17.3", + "hardhat-gas-reporter": "^1.0.8", "mocha": "^10.2.0", "mocha-suppress-logs": "^0.3.1", "prettier": "^2.8.8",