From 6af9ae7b0d3c121a15889b394e1766fb77cd7313 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Fri, 7 Feb 2025 23:23:40 +0000 Subject: [PATCH 1/5] CCIP Hook --- solidity/contracts/isms/hook/CCIPIsm.sol | 2 +- typescript/sdk/src/consts/ccip.ts | 707 ++++++++++++++++++ .../src/hook/EvmHookModule.hardhat-test.ts | 8 + typescript/sdk/src/hook/EvmHookReader.test.ts | 34 + typescript/sdk/src/hook/EvmHookReader.ts | 53 +- .../sdk/src/hook/HyperlaneHookDeployer.ts | 33 + typescript/sdk/src/hook/contracts.ts | 2 + typescript/sdk/src/hook/types.ts | 9 + 8 files changed, 844 insertions(+), 4 deletions(-) create mode 100644 typescript/sdk/src/consts/ccip.ts diff --git a/solidity/contracts/isms/hook/CCIPIsm.sol b/solidity/contracts/isms/hook/CCIPIsm.sol index c5e1b1f489..cc10f05cf0 100644 --- a/solidity/contracts/isms/hook/CCIPIsm.sol +++ b/solidity/contracts/isms/hook/CCIPIsm.sol @@ -64,7 +64,7 @@ contract CCIPIsm is AbstractMessageIdAuthorizedIsm, CCIPReceiver { preVerifyMessage(messageId, msg.value); } - function _isAuthorized() internal pure override returns (bool) { + function _isAuthorized() internal view override returns (bool) { return msg.sender == getRouter(); } } diff --git a/typescript/sdk/src/consts/ccip.ts b/typescript/sdk/src/consts/ccip.ts new file mode 100644 index 0000000000..5a7821bf6e --- /dev/null +++ b/typescript/sdk/src/consts/ccip.ts @@ -0,0 +1,707 @@ +import { ChainMap } from '../types.js'; + +export type CCIPAddresses = { + armProxy: { + address: string; + version: string; + }; + chainSelector: string; + feeTokens: string[]; + registryModule: { + address: string; + version: string; + }; + router: { + address: string; + version: string; + }; + tokenAdminRegistry: { + address: string; + version: string; + }; +}; + +// Copied from chainlink docs repo +// https://github.com/smartcontractkit/documentation/blob/0ffd661733de2b946011dd3279011a79a25a31e0/src/config/data/ccip/v1_2_0/mainnet/chains.json +export const CCIP_NETWORKS: ChainMap = { + avalanche: { + armProxy: { + address: '0xcBD48A8eB077381c3c4Eb36b402d7283aB2b11Bc', + version: '1.0.0', + }, + chainSelector: '6433500567565415381', + feeTokens: ['LINK', 'WAVAX'], + registryModule: { + address: '0x9c093872cd5931D975C4d4B4a3a8c61a5767E5c1', + version: '1.5.0', + }, + router: { + address: '0xF4c7E640EdA248ef95972845a62bdC74237805dB', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xc8df5D618c6a59Cc6A311E96a39450381001464F', + version: '1.5.0', + }, + }, + bitlayer: { + armProxy: { + address: '0xcaa6131cEe85ba2F140cBa05F6825aC60B6CEA56', + version: '1.5.0', + }, + chainSelector: '7937294810946806131', + feeTokens: ['LINK', 'WBTC'], + registryModule: { + address: '0x907BF5A4489d2b14EBDf9C9BDEA60AAe2Da54ef4', + version: '1.5.0', + }, + router: { + address: '0x6c0aA29330c58dda07faD577fF5a0280823a910c', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xd999758aEB04BDa755Ae78344FFF5534947620CD', + version: '1.5.0', + }, + }, + bob: { + armProxy: { + address: '0xe4D8E0A02C61f6DDe95255E702fe1237428673D8', + version: '1.5.0', + }, + chainSelector: '3849287863852499584', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0x74C8B508BF3d22f811972625D9e055c2604d1021', + version: '1.5.0', + }, + router: { + address: '0x827716e74F769AB7b6bb374A29235d9c2156932C', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xa57d04119AFf4884F8602213E58d8AaAD18229cb', + version: '1.5.0', + }, + }, + bsquared: { + armProxy: { + address: '0x1C6Faa5762860261014a355a9efF2bEea2255851', + version: '1.5.0', + }, + chainSelector: '5406759801798337480', + feeTokens: ['LINK', 'WBTC'], + registryModule: { + address: '0x790b7770D12AdBa4d3F920d7A994E7a4f275037c', + version: '1.5.0', + }, + router: { + address: '0x9C34e9A192d7a4c2cf054668C1122C028C43026c', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x2e1543255119CfB9D3501E32d7f5B244E59A06F4', + version: '1.5.0', + }, + }, + bsc: { + armProxy: { + address: '0x9e09697842194f77d315E0907F1Bda77922e8f84', + version: '1.0.0', + }, + chainSelector: '11344663589394136015', + feeTokens: ['LINK', 'WBNB'], + registryModule: { + address: '0xfa4C3f58D2659AFe4F964C023e6AfD183C374435', + version: '1.5.0', + }, + router: { + address: '0x34B03Cb9086d7D758AC55af71584F81A598759FE', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x736Fd8660c443547a85e4Eaf70A49C1b7Bb008fc', + version: '1.5.0', + }, + }, + celo: { + armProxy: { + address: '0x56e0507d4E69D98bE7Eb4ada01d2315596F9f281', + version: '1.0.0', + }, + chainSelector: '1346049177634351622', + feeTokens: ['LINK', 'WCELO'], + registryModule: { + address: '0x858B064d15bD54fcdfaf087A4AE4BaabF724d9E9', + version: '1.5.0', + }, + router: { + address: '0xfB48f15480926A4ADf9116Dca468bDd2EE6C5F62', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xf19e0555fAA9051e277eeD5A0DcdB13CDaca39a9', + version: '1.5.0', + }, + }, + corn: { + armProxy: { + address: '0x91ca2Aa7429e5F702f1F750b317AB604d5a6a16e', + version: '1.5.0', + }, + chainSelector: '9043146809313071210', + feeTokens: ['LINK', 'WBTCN'], + registryModule: { + address: '0xD65b9D6eb4C6C387B9B43129aA4274Acc0010129', + version: '1.5.0', + }, + router: { + address: '0x183f6069A0D5c2DEC1Dd1eCF3B1581e12dEb4Efe', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xCd51e57cD26b9B5eecbfe3d96DAabF3d12A663DA', + version: '1.5.0', + }, + }, + metis: { + armProxy: { + address: '0xd99cc1d64027E07Cd2AaE871E16bb32b8F401998', + version: '1.0.0', + }, + chainSelector: '8805746078405598895', + feeTokens: ['LINK', 'WMETIS'], + registryModule: { + address: '0xE4B147224Db9B6E3776E4B3CEda31b3cE232e2FA', + version: '1.5.0', + }, + router: { + address: '0x7b9FB8717D306e2e08ce2e1Efa81F026bf9AD13c', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x3af897541eB03927c7431bF68884A6C2C23b683f', + version: '1.5.0', + }, + }, + arbitrum: { + armProxy: { + address: '0xC311a21e6fEf769344EB1515588B9d535662a145', + version: '1.0.0', + }, + chainSelector: '4949039107694359620', + feeTokens: ['GHO', 'LINK', 'WETH'], + registryModule: { + address: '0x818792C958Ac33C01c58D5026cEc91A86e9071d7', + version: '1.5.0', + }, + router: { + address: '0x141fa059441E0ca23ce184B6A78bafD2A517DdE8', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x39AE1032cF4B334a1Ed41cdD0833bdD7c7E7751E', + version: '1.5.0', + }, + }, + base: { + armProxy: { + address: '0xC842c69d54F83170C42C4d556B4F6B2ca53Dd3E8', + version: '1.0.0', + }, + chainSelector: '15971525489660198786', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0x1A5f2d0c090dDB7ee437051DA5e6f03b6bAE1A77', + version: '1.5.0', + }, + router: { + address: '0x881e3A65B4d4a04dD529061dd0071cf975F58bCD', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x6f6C373d09C07425BaAE72317863d7F6bb731e37', + version: '1.5.0', + }, + }, + blast: { + armProxy: { + address: '0x50dbd1e73ED032f42B5892E5F3689972FefAc880', + version: '1.0.0', + }, + chainSelector: '4411394078118774322', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0xa277610fF9A04364d2b80f26C9DFb32Be5e45D94', + version: '1.5.0', + }, + router: { + address: '0x12e0B8E349C6fb7E6E40713E8125C3cF1127ea8C', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x846Fccd01D4115FD1E81267495773aeB33bF1dC7', + version: '1.5.0', + }, + }, + hashkey: { + armProxy: { + address: '0x59F168858472c5ECC217588678F6c378951Bd524', + version: '1.5.0', + }, + chainSelector: '7613811247471741961', + feeTokens: ['LINK', 'WHSK'], + registryModule: { + address: '0xE9A76b7071F0bDaF5968583BEDF6CC537613A1F7', + version: '1.5.0', + }, + router: { + address: '0xf2Fd62c083F3BF324e99ce157D1a42d7EbA77f1d', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x4b238f757f842280FeA88A1c2B4186b71eF8BC5E', + version: '1.5.0', + }, + }, + ink: { + armProxy: { + address: '0x3A293fa336E118900AD0f2EcfeC0DAa6A4DeDaA1', + version: '1.5.0', + }, + chainSelector: '3461204551265785888', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0xDB51A5855d6a41f40D26591f843d6ac4c7CE5B73', + version: '1.5.0', + }, + router: { + address: '0xca7c90A52B44E301AC01Cb5EB99b2fD99339433A', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xEb062d21c713A3d940BB0FaECFdC387d6Ea23697', + version: '1.5.0', + }, + }, + kroma: { + armProxy: { + address: '0x6E4d2dBBF8a1A943412aD451422FE11A25C781DE', + version: '1.0.0', + }, + chainSelector: '3719320017875267166', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0xe87a456F364D1641F8123D4122Fc542282BFc0FA', + version: '1.5.0', + }, + router: { + address: '0xE93E8B0d1b1CEB44350C8758ed1E2799CCee31aB', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x447066676A5413682a881c63aed0F03f8ACf7E45', + version: '1.5.0', + }, + }, + linea: { + armProxy: { + address: '0x1F8fbCf559f08FE7c4076f0d68DB861e1E27f95b', + version: '1.0.0', + }, + chainSelector: '4627098889531055414', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0x2eab209352C0A5d71a79Cc889caAE6692520A891', + version: '1.5.0', + }, + router: { + address: '0x549FEB73F2348F6cD99b9fc8c69252034897f06C', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xBc933cEE67d2b1c08490ee8C51E2dF653a713534', + version: '1.5.0', + }, + }, + mantle: { + armProxy: { + address: '0x91E2186E93F0ECeDDCdf9850078F104daB085E79', + version: '1.5.0', + }, + chainSelector: '1556008542357238666', + feeTokens: ['LINK', 'WMNT'], + registryModule: { + address: '0x869c8c4e23668A83151267636f190F5A17A104FD', + version: '1.5.0', + }, + router: { + address: '0x670052635a9850bb45882Cb2eCcF66bCff0F41B7', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x000A744940eB5D857c0d61d97015DFc83107404F', + version: '1.5.0', + }, + }, + mode: { + armProxy: { + address: '0xA0876B45271615c737781185C2B5ada60ed2D2B9', + version: '1.0.0', + }, + chainSelector: '7264351850409363825', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0xF54d38E4844c5f6E5Aab0AF7557ef5cb1cA4253e', + version: '1.5.0', + }, + router: { + address: '0x24C40f13E77De2aFf37c280BA06c333531589bf1', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xB4b40c010A547dff6A22d94bC2C1c1e745b62aB2', + version: '1.5.0', + }, + }, + optimism: { + armProxy: { + address: '0x55b3FCa23EdDd28b1f5B4a3C7975f63EFd2d06CE', + version: '1.0.0', + }, + chainSelector: '3734403246176062136', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0x3E2f636Ff8e12728638C4c4b34d282a7fDF0e5B8', + version: '1.5.0', + }, + router: { + address: '0x3206695CaE29952f4b0c22a169725a865bc8Ce0f', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x657c42abE4CD8aa731Aec322f871B5b90cf6274F', + version: '1.5.0', + }, + }, + polygonzkevm: { + armProxy: { + address: '0x272fB92E5D43ffcCEb56bBE5b2D7B88a86235c48', + version: '1.0.0', + }, + chainSelector: '4348158687435793198', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0xE97273AD89a082950e7C17c4593d7743c987B8bb', + version: '1.5.0', + }, + router: { + address: '0xA9999937159B293c72e2367Ce314cb3544e7C1a3', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xe87fB6c46DCAADA001681819d2bD3c64f58D8963', + version: '1.5.0', + }, + }, + scroll: { + armProxy: { + address: '0x68B38980aD70650a6f3229BA156e5c1F88A21320', + version: '1.5.0', + }, + chainSelector: '13204309965629103672', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0x80E3946A4d3306c903545fdfCEDB57639C00A99d', + version: '1.5.0', + }, + router: { + address: '0x9a55E8Cab6564eb7bbd7124238932963B8Af71DC', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x846dEA1c1706FC35b4aa78B32d31F1599DAA47b4', + version: '1.5.0', + }, + }, + worldchain: { + armProxy: { + address: '0x7DE7Ef73cF001ff15b3aA558855D7eeC439d43ab', + version: '1.5.0', + }, + chainSelector: '2049429975587534727', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0x5c3511917797e01FB26E94fA3D30E71135d93826', + version: '1.5.0', + }, + router: { + address: '0x5fd9E4986187c56826A3064954Cfa2Cf250cfA0f', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x02Fe6ab4fb0943F58D9D925d1d2cbA9474997Ed0', + version: '1.5.0', + }, + }, + xlayer: { + armProxy: { + address: '0x326B01f673681dAd72cd386CCe12FFF717be32cD', + version: '1.0.0', + }, + chainSelector: '3016212468291539606', + feeTokens: ['LINK', 'WOKB'], + registryModule: { + address: '0x3c3B4DfEda43296dFf1b2C6e5a3e4E1e1a6D5766', + version: '1.5.0', + }, + router: { + address: '0xF2b6Cb7867EB5502C3249dD37D7bc1Cc148e5232', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xeCf1eAEE01E82F3388dECD7f4C3792374f3f72F3', + version: '1.5.0', + }, + }, + zircuit: { + armProxy: { + address: '0xf735667F2F3193d407089bb4c50824941821b156', + version: '1.5.0', + }, + chainSelector: '17198166215261833993', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0xE8FD6dE668fD120df5A00E03ce0de71eA5C6d408', + version: '1.5.0', + }, + router: { + address: '0x0A6436B56378D305729713ac332ccdCD367f3918', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x47d2D93EEDb694bf445E7F6458f17669459612c7', + version: '1.5.0', + }, + }, + zksync: { + armProxy: { + address: '0x2aBB46A2D32220b8801CE96CAbC32dd2dA7b7B20', + version: '1.0.0', + }, + chainSelector: '1562403441176082196', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0xab0731056C23b85eDd62F12E716fC75fc1fB1219', + version: '1.5.0', + }, + router: { + address: '0x748Fd769d81F5D94752bf8B0875E9301d0ba71bB', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x100a47C9DB342884E3314B91cec076BbAC8e619c', + version: '1.5.0', + }, + }, + ethereum: { + armProxy: { + address: '0x411dE17f12D1A34ecC7F45f49844626267c75e81', + version: '1.0.0', + }, + chainSelector: '5009297550715157269', + feeTokens: ['GHO', 'LINK', 'WETH'], + registryModule: { + address: '0x13022e3e6C77524308BD56AEd716E88311b2E533', + version: '1.5.0', + }, + router: { + address: '0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xb22764f98dD05c789929716D677382Df22C05Cb6', + version: '1.5.0', + }, + }, + polygon: { + armProxy: { + address: '0xf1ceAa46D8d13Cac9fC38aaEF3d3d14754C5A9c2', + version: '1.0.0', + }, + chainSelector: '4051577828743386545', + feeTokens: ['LINK', 'WMATIC'], + registryModule: { + address: '0x30CcdEa6a6B521B2B6Fa1Cdc2fd38FB2c1cC82b3', + version: '1.5.0', + }, + router: { + address: '0x849c5ED5a80F5B408Dd4969b78c2C8fdf0565Bfe', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x00F027eA6D0fb03256A15E9182B2B9227A4931d8', + version: '1.5.0', + }, + }, + astar: { + armProxy: { + address: '0x7317D216F3DCDa40144a54eC9bA09829a423cb35', + version: '1.0.0', + }, + chainSelector: '6422105447186081193', + feeTokens: ['LINK', 'WASTR'], + registryModule: { + address: '0x9c54A7E067E5bdB8e1A44eA7a657053780d35d58', + version: '1.5.0', + }, + router: { + address: '0x8D5c5CB8ec58285B424C93436189fB865e437feF', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xB98eEd70e3cE8E342B0f770589769E3A6bc20A09', + version: '1.5.0', + }, + }, + ronin: { + armProxy: { + address: '0xceA253a8c2BB995054524d071498281E89aACD59', + version: '1.5.0', + }, + chainSelector: '6916147374840168594', + feeTokens: ['LINK', 'WRON'], + registryModule: { + address: '0x5055DA89A16b71fEF91D1af323b139ceDe2d8320', + version: '1.5.0', + }, + router: { + address: '0x46527571D5D1B68eE7Eb60B18A32e6C60DcEAf99', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x90e83d532A4aD13940139c8ACE0B93b0DdbD323a', + version: '1.5.0', + }, + }, + sei: { + armProxy: { + address: '0x32C67585dA17839245c75D80d36c8CBD7d35E1a5', + version: '1.5.0', + }, + chainSelector: '9027416829622342829', + feeTokens: ['LINK', 'WSEI'], + registryModule: { + address: '0xd7327405609E3f9566830b1aCF79E25AC0a9DA4B', + version: '1.5.0', + }, + router: { + address: '0xAba60dA7E88F7E8f5868C2B6dE06CB759d693af0', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x910a46cA93E8086BF1d7D65190eE6AEe5256Bd61', + version: '1.5.0', + }, + }, + shibarium: { + armProxy: { + address: '0xD2bdb98dA1Ff575d091CA5b76412C23Cba88CA02', + version: '1.5.0', + }, + chainSelector: '3993510008929295315', + feeTokens: ['LINK', 'WBONE'], + registryModule: { + address: '0xB6e8B0158CDD1AaF280f53604b80686787BB9199', + version: '1.5.0', + }, + router: { + address: '0xc2CA5d5C17911e4B838194b51585DdF8fe5116C1', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x995d2Aa233aBeaCA2a64Edf898AE9F4e01bE15B9', + version: '1.5.0', + }, + }, + sonium: { + armProxy: { + address: '0x3117f515D763652A32d3D6D447171ea7c9d57218', + version: '1.5.0', + }, + chainSelector: '12505351618335765396', + feeTokens: ['LINK', 'WETH'], + registryModule: { + address: '0x1d0B6B3ef94dD6A68b7E16bd8B01fca9EA8e3d6E', + version: '1.5.0', + }, + router: { + address: '0x8C8B88d827Fe14Df2bc6392947d513C86afD6977', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x5ba21F6824400B91F232952CA6d7c8875C1755a4', + version: '1.5.0', + }, + }, + sonic: { + armProxy: { + address: '0x60536Ef486DB5E0e1771874E31485c12e3c2844f', + version: '1.5.0', + }, + chainSelector: '1673871237479749969', + feeTokens: ['LINK', 'WS'], + registryModule: { + address: '0xB9Ab30Fe6fa11780244815Bb87818D7Bd9beb529', + version: '1.5.0', + }, + router: { + address: '0xB4e1Ff7882474BB93042be9AD5E1fA387949B860', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x2961Cb47b5111F38d75f415c21ceB4120ddd1b69', + version: '1.5.0', + }, + }, + wemix: { + armProxy: { + address: '0x2375959c6571AC7a83c164C6FCcbd09E7782773d', + version: '1.0.0', + }, + chainSelector: '5142893604156789321', + feeTokens: ['LINK', 'WWEMIX'], + registryModule: { + address: '0xe89241cbE74349EA74a0c23823A516B3c74A289B', + version: '1.5.0', + }, + router: { + address: '0x7798b795Fde864f4Cd1b124a38Ba9619B7F8A442', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0xE993e046AC50659800a91Bab0bd2daBF59CbD171', + version: '1.5.0', + }, + }, + gnosis: { + armProxy: { + address: '0xf5e5e1676942520995c1e39aFaC58A75Fe1cd2bB', + version: '1.0.0', + }, + chainSelector: '465200170687744372', + feeTokens: ['LINK', 'WXDAI'], + registryModule: { + address: '0xdf529b48fCDfd095c81497E435585Ed465D600A2', + version: '1.5.0', + }, + router: { + address: '0x4aAD6071085df840abD9Baf1697d5D5992bDadce', + version: '1.2.0', + }, + tokenAdminRegistry: { + address: '0x73BC11423CBF14914998C23B0aFC9BE0cb5B2229', + version: '1.5.0', + }, + }, +}; diff --git a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts index 2b31d62346..e2a68ae7ee 100644 --- a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts +++ b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts @@ -118,6 +118,14 @@ function randomHookConfig( }; } + case HookType.CCIP: { + return { + type: hookType, + destinationChain: 'testChain', + ism: randomAddress(), + }; + } + case HookType.OP_STACK: return { owner: randomAddress(), diff --git a/typescript/sdk/src/hook/EvmHookReader.test.ts b/typescript/sdk/src/hook/EvmHookReader.test.ts index 3a8bb45763..c5d13db71c 100644 --- a/typescript/sdk/src/hook/EvmHookReader.test.ts +++ b/typescript/sdk/src/hook/EvmHookReader.test.ts @@ -4,6 +4,8 @@ import { randomBytes } from 'ethers/lib/utils.js'; import sinon from 'sinon'; import { + CCIPHook, + CCIPHook__factory, IPostDispatchHook, IPostDispatchHook__factory, MerkleTreeHook, @@ -23,6 +25,7 @@ import { randomAddress } from '../test/testUtils.js'; import { EvmHookReader } from './EvmHookReader.js'; import { + CCIPHookConfig, HookType, MerkleTreeHookConfig, OnchainHookType, @@ -184,6 +187,37 @@ describe('EvmHookReader', () => { expect(config).to.deep.equal(hookConfig); }); + it('should derive CCIPHook configuration correctly', async () => { + const ccipHookAddress = randomAddress(); + const destinationDomain = test1.domainId; + const ism = randomAddress(); + + // Mock the CCIPHook contract + const mockContract = { + hookType: sandbox.stub().resolves(OnchainHookType.ID_AUTH_ISM), + destinationChain: sandbox.stub().resolves(destinationDomain), + ism: sandbox.stub().resolves(ism), + }; + + sandbox + .stub(CCIPHook__factory, 'connect') + .returns(mockContract as unknown as CCIPHook); + sandbox + .stub(IPostDispatchHook__factory, 'connect') + .returns(mockContract as unknown as IPostDispatchHook); + + const config = await evmHookReader.deriveCcipConfig(ccipHookAddress); + + const expectedConfig: WithAddress = { + address: ccipHookAddress, + type: HookType.CCIP, + destinationChain: TestChainName.test2, + ism, + }; + + expect(config).to.deep.equal(expectedConfig); + }); + it('should throw if derivation fails', async () => { const mockAddress = randomAddress(); const mockOwner = randomAddress(); diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index 41119064b2..9149398b14 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -2,6 +2,7 @@ import { ethers } from 'ethers'; import { ArbL2ToL1Hook__factory, + CCIPHook__factory, DomainRoutingHook, DomainRoutingHook__factory, FallbackDomainRoutingHook, @@ -34,6 +35,7 @@ import { HyperlaneReader } from '../utils/HyperlaneReader.js'; import { AggregationHookConfig, ArbL2ToL1HookConfig, + CCIPHookConfig, DomainRoutingHookConfig, FallbackRoutingHookConfig, HookConfig, @@ -76,6 +78,8 @@ export interface HookReader { derivePausableConfig( address: Address, ): Promise>; + deriveIdAuthIsmConfig(address: Address): Promise; + deriveCcipConfig(address: Address): Promise>; assertHookType( hookType: OnchainHookType, expectedType: OnchainHookType, @@ -151,10 +155,8 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { case OnchainHookType.PROTOCOL_FEE: derivedHookConfig = await this.deriveProtocolFeeConfig(address); break; - // ID_AUTH_ISM could be OPStackHook, ERC5164Hook or LayerZeroV2Hook - // For now assume it's OP_STACK case OnchainHookType.ID_AUTH_ISM: - derivedHookConfig = await this.deriveOpStackConfig(address); + derivedHookConfig = await this.deriveIdAuthIsmConfig(address); break; case OnchainHookType.ARB_L2_TO_L1: derivedHookConfig = await this.deriveArbL2ToL1Config(address); @@ -185,6 +187,51 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { return derivedHookConfig; } + async deriveIdAuthIsmConfig(address: Address): Promise { + // First check if it's a CCIP hook + try { + const ccipHook = CCIPHook__factory.connect(address, this.provider); + // This method only exists on CCIPHook + await ccipHook.ccipDestination(); + return this.deriveCcipConfig(address); + } catch { + // Not a CCIP hook, try OPStack + try { + const opStackHook = OPStackHook__factory.connect( + address, + this.provider, + ); + // This method only exists on OPStackHook + await opStackHook.l1Messenger(); + return this.deriveOpStackConfig(address); + } catch { + throw new Error( + `Could not determine hook type - neither CCIP nor OPStack methods found`, + ); + } + } + } + + async deriveCcipConfig( + address: Address, + ): Promise> { + const ccipHook = CCIPHook__factory.connect(address, this.provider); + const destinationDomain = await ccipHook.destinationDomain(); + const destinationChain = this.multiProvider.getChainName(destinationDomain); + const ism = await ccipHook.ism(); + + const config: WithAddress = { + address, + type: HookType.CCIP, + destinationChain, + ism, + }; + + this._cache.set(address, config); + + return config; + } + async deriveMerkleTreeConfig( address: Address, ): Promise> { diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index 5bbc30fe70..a5b1783e15 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; import { + CCIPHook, DomainRoutingHook, FallbackDomainRoutingHook, IL1CrossDomainMessenger__factory, @@ -16,6 +17,7 @@ import { rootLogger, } from '@hyperlane-xyz/utils'; +import { CCIP_NETWORKS } from '../consts/ccip.js'; import { HyperlaneContracts } from '../contracts/types.js'; import { CoreAddresses } from '../core/contracts.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; @@ -30,6 +32,7 @@ import { ChainMap, ChainName } from '../types.js'; import { DeployedHook, HookFactories, hookFactories } from './contracts.js'; import { AggregationHookConfig, + CCIPHookConfig, DomainRoutingHookConfig, FallbackRoutingHookConfig, HookConfig, @@ -107,6 +110,8 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< await this.transferOwnershipOfContracts(chain, config, { [HookType.PAUSABLE]: hook, }); + } else if (config.type === HookType.CCIP) { + hook = await this.deployCcip(chain, config); } else { throw new Error(`Unsupported hook config: ${config}`); } @@ -116,6 +121,34 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< return deployedContracts; } + async deployCcip( + chain: ChainName, + config: CCIPHookConfig, + coreAddresses = this.core[chain], + ): Promise { + this.logger.debug('Deploying CCIPHook for %s', chain); + + const mailbox = coreAddresses.mailbox; + if (!mailbox) { + throw new Error(`Mailbox address is required for ${config.type}`); + } + + const destinationDomain = this.multiProvider.getDomainId( + config.destinationChain, + ); + + const originCCIPNetwork = CCIP_NETWORKS[chain]; + const destinationCCIPNetwork = CCIP_NETWORKS[config.destinationChain]; + + return this.deployContract(chain, HookType.CCIP, [ + originCCIPNetwork.router.address, + destinationCCIPNetwork.chainSelector, + mailbox, + destinationDomain, + config.ism, + ]); + } + async deployProtocolFee( chain: ChainName, config: ProtocolFeeHookConfig, diff --git a/typescript/sdk/src/hook/contracts.ts b/typescript/sdk/src/hook/contracts.ts index 4d9f51b862..c34fdcaab1 100644 --- a/typescript/sdk/src/hook/contracts.ts +++ b/typescript/sdk/src/hook/contracts.ts @@ -1,5 +1,6 @@ import { ArbL2ToL1Hook__factory, + CCIPHook__factory, DomainRoutingHook__factory, FallbackDomainRoutingHook__factory, InterchainGasPaymaster__factory, @@ -23,6 +24,7 @@ export const hookFactories = { [HookType.FALLBACK_ROUTING]: new FallbackDomainRoutingHook__factory(), [HookType.PAUSABLE]: new PausableHook__factory(), [HookType.ARB_L2_TO_L1]: new ArbL2ToL1Hook__factory(), + [HookType.CCIP]: new CCIPHook__factory(), }; export type HookFactories = typeof hookFactories; diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index 76e749fd95..4c5f998b06 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -36,6 +36,7 @@ export enum HookType { FALLBACK_ROUTING = 'fallbackRoutingHook', PAUSABLE = 'pausableHook', ARB_L2_TO_L1 = 'arbL2ToL1Hook', + CCIP = 'ccipHook', } export type MerkleTreeHookConfig = z.infer; @@ -44,6 +45,7 @@ export type ProtocolFeeHookConfig = z.infer; export type PausableHookConfig = z.infer; export type OpStackHookConfig = z.infer; export type ArbL2ToL1HookConfig = z.infer; +export type CCIPHookConfig = z.infer; // explicitly typed to avoid zod circular dependency export type AggregationHookConfig = { @@ -110,6 +112,12 @@ export const ArbL2ToL1HookSchema = z.object({ childHook: z.lazy((): z.ZodSchema => HookConfigSchema), }); +export const CCIPHookSchema = z.object({ + type: z.literal(HookType.CCIP), + destinationChain: z.string(), + ism: z.string(), +}); + export const IgpSchema = OwnableSchema.extend({ type: z.literal(HookType.INTERCHAIN_GAS_PAYMASTER), beneficiary: z.string(), @@ -154,4 +162,5 @@ export const HookConfigSchema = z.union([ FallbackRoutingHookConfigSchema, AggregationHookConfigSchema, ArbL2ToL1HookSchema, + CCIPHookSchema, ]); From 824da547582ae79351f7ab3005cfdbb9f6aeb18e Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Sat, 8 Feb 2025 01:09:07 +0000 Subject: [PATCH 2/5] ism --- .../sdk/src/hook/HyperlaneHookDeployer.ts | 22 +++++++----- typescript/sdk/src/ism/EvmIsmReader.test.ts | 5 +++ typescript/sdk/src/ism/EvmIsmReader.ts | 22 ++++++++++++ .../ism/HyperlaneIsmFactory.hardhat-test.ts | 21 +++++++++++ typescript/sdk/src/ism/HyperlaneIsmFactory.ts | 28 +++++++++++++++ typescript/sdk/src/ism/types.ts | 14 +++++++- typescript/sdk/src/ism/utils.ts | 15 ++++++++ typescript/sdk/src/utils/ccip.ts | 35 +++++++++++++++++++ 8 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 typescript/sdk/src/utils/ccip.ts diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index a5b1783e15..ea01f3a70c 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -13,11 +13,11 @@ import { import { Address, addressToBytes32, + assert, deepEquals, rootLogger, } from '@hyperlane-xyz/utils'; -import { CCIP_NETWORKS } from '../consts/ccip.js'; import { HyperlaneContracts } from '../contracts/types.js'; import { CoreAddresses } from '../core/contracts.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; @@ -28,6 +28,7 @@ import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { IsmType, OpStackIsmConfig } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainMap, ChainName } from '../types.js'; +import { getCCIPChainSelector, getCCIPRouterAddress } from '../utils/ccip.js'; import { DeployedHook, HookFactories, hookFactories } from './contracts.js'; import { @@ -129,20 +130,23 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< this.logger.debug('Deploying CCIPHook for %s', chain); const mailbox = coreAddresses.mailbox; - if (!mailbox) { - throw new Error(`Mailbox address is required for ${config.type}`); - } + assert(mailbox, `Mailbox address is required for ${config.type}`); + + const ccipRouterAddress = getCCIPRouterAddress(chain); + const ccipChainSelector = getCCIPChainSelector(config.destinationChain); + assert(ccipRouterAddress, `CCIP router address not found for ${chain}`); + assert( + ccipChainSelector, + `CCIP chain selector not found for ${config.destinationChain}`, + ); const destinationDomain = this.multiProvider.getDomainId( config.destinationChain, ); - const originCCIPNetwork = CCIP_NETWORKS[chain]; - const destinationCCIPNetwork = CCIP_NETWORKS[config.destinationChain]; - return this.deployContract(chain, HookType.CCIP, [ - originCCIPNetwork.router.address, - destinationCCIPNetwork.chainSelector, + ccipRouterAddress, + ccipChainSelector, mailbox, destinationDomain, config.ism, diff --git a/typescript/sdk/src/ism/EvmIsmReader.test.ts b/typescript/sdk/src/ism/EvmIsmReader.test.ts index 4da13a80a9..40e47aa9e9 100644 --- a/typescript/sdk/src/ism/EvmIsmReader.test.ts +++ b/typescript/sdk/src/ism/EvmIsmReader.test.ts @@ -2,6 +2,8 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { + CCIPIsm, + CCIPIsm__factory, IInterchainSecurityModule, IInterchainSecurityModule__factory, IMultisigIsm, @@ -136,6 +138,9 @@ describe('EvmIsmReader', () => { sandbox .stub(TrustedRelayerIsm__factory, 'connect') .returns(mockContract as unknown as TrustedRelayerIsm); + sandbox + .stub(CCIPIsm__factory, 'connect') + .returns(mockContract as unknown as CCIPIsm); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockContract as unknown as IInterchainSecurityModule); diff --git a/typescript/sdk/src/ism/EvmIsmReader.ts b/typescript/sdk/src/ism/EvmIsmReader.ts index 2848fd18ec..ce8e103645 100644 --- a/typescript/sdk/src/ism/EvmIsmReader.ts +++ b/typescript/sdk/src/ism/EvmIsmReader.ts @@ -3,6 +3,7 @@ import { BigNumber, ethers } from 'ethers'; import { AbstractRoutingIsm__factory, ArbL2ToL1Ism__factory, + CCIPIsm__factory, DefaultFallbackRoutingIsm__factory, IInterchainSecurityModule__factory, IMultisigIsm__factory, @@ -27,6 +28,7 @@ import { ChainTechnicalStack } from '../metadata/chainMetadataTypes.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainNameOrId } from '../types.js'; import { HyperlaneReader } from '../utils/HyperlaneReader.js'; +import { getChainNameFromCCIPSelector } from '../utils/ccip.js'; import { AggregationIsmConfig, @@ -312,6 +314,26 @@ export class EvmIsmReader extends HyperlaneReader implements IsmReader { ); } + // if it has ccipOrigin property --> CCIP + const ccipIsm = CCIPIsm__factory.connect(address, this.provider); + try { + const ccipOrigin = await ccipIsm.ccipOrigin(); + const originChain = getChainNameFromCCIPSelector(ccipOrigin.toString()); + if (!originChain) { + throw new Error('Unknown CCIP origin chain'); + } + return { + address, + type: IsmType.CCIP, + originChain, + }; + } catch { + this.logger.debug( + 'Error accessing "ccipOrigin" property, implying this is not a CCIP ISM.', + address, + ); + } + // if it has VERIFIED_MASK_INDEX, it's AbstractMessageIdAuthorizedIsm which means OPStackIsm const opStackIsm = OPStackIsm__factory.connect(address, this.provider); try { diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index 737266e73f..c2123c4d97 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -16,6 +16,7 @@ import { randomAddress } from '../test/testUtils.js'; import { HyperlaneIsmFactory } from './HyperlaneIsmFactory.js'; import { AggregationIsmConfig, + CCIPIsmConfig, DomainRoutingIsmConfig, IsmConfig, IsmType, @@ -215,6 +216,26 @@ describe('HyperlaneIsmFactory', async () => { expect(matches).to.be.true; }); + it('deploys a ccip ism', async () => { + const config: CCIPIsmConfig = { + type: IsmType.CCIP, + originChain: 'ethereum', + }; + const ism = await ismFactory.deploy({ + destination: chain, + config, + mailbox: mailboxAddress, + }); + const matches = await moduleMatchesConfig( + chain, + ism.address, + config, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + ); + expect(matches).to.be.true; + }); + for (let i = 0; i < 16; i++) { it('deploys a random ism config', async () => { const config = randomIsmConfig(); diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index 515a2da0a9..05e13b09ad 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -3,6 +3,8 @@ import { Logger } from 'pino'; import { ArbL2ToL1Ism__factory, + CCIPIsm, + CCIPIsm__factory, DefaultFallbackRoutingIsm, DefaultFallbackRoutingIsm__factory, DomainRoutingIsm, @@ -49,9 +51,11 @@ import { } from '../deploy/contracts.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainMap, ChainName } from '../types.js'; +import { getCCIPChainSelector, getCCIPRouterAddress } from '../utils/ccip.js'; import { AggregationIsmConfig, + CCIPIsmConfig, DeployedIsm, DeployedIsmType, DomainRoutingIsmConfig, @@ -70,6 +74,7 @@ const ismFactories = { [IsmType.TEST_ISM]: new TestIsm__factory(), [IsmType.OP_STACK]: new OPStackIsm__factory(), [IsmType.ARB_L2_TO_L1]: new ArbL2ToL1Ism__factory(), + [IsmType.CCIP]: new CCIPIsm__factory(), }; class IsmDeployer extends HyperlaneDeployer<{}, typeof ismFactories> { @@ -208,6 +213,9 @@ export class HyperlaneIsmFactory extends HyperlaneApp { [config.bridge], ); break; + case IsmType.CCIP: + contract = await this.deployCCIPIsm(destination, config); + break; default: throw new Error(`Unsupported ISM type ${ismType}`); } @@ -230,6 +238,26 @@ export class HyperlaneIsmFactory extends HyperlaneApp { return contract; } + protected async deployCCIPIsm( + destination: ChainName, + config: CCIPIsmConfig, + ): Promise { + const ccipChainSelector = getCCIPChainSelector(config.originChain); + const ccipRouterAddress = getCCIPRouterAddress(config.originChain); + assert( + ccipChainSelector, + `CCIP chain selector not found for ${config.originChain}`, + ); + assert( + ccipRouterAddress, + `CCIP router address not found for ${config.originChain}`, + ); + return this.deployer.deployContract(destination, IsmType.CCIP, [ + ccipRouterAddress, + ccipChainSelector, + ]); + } + protected async deployMultisigIsm( destination: ChainName, config: MultisigIsmConfig, diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index 87adb2cff2..f1a5eb66a1 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -2,6 +2,7 @@ import { z } from 'zod'; import { ArbL2ToL1Ism, + CCIPIsm, IAggregationIsm, IInterchainSecurityModule, IMultisigIsm, @@ -58,6 +59,7 @@ export enum IsmType { ARB_L2_TO_L1 = 'arbL2ToL1Ism', WEIGHTED_MERKLE_ROOT_MULTISIG = 'weightedMerkleRootMultisigIsm', WEIGHTED_MESSAGE_ID_MULTISIG = 'weightedMessageIdMultisigIsm', + CCIP = 'ccipIsm', } // ISM types that can be updated in-place @@ -88,6 +90,7 @@ export function ismTypeToModuleType(ismType: IsmType): ModuleType { case IsmType.PAUSABLE: case IsmType.CUSTOM: case IsmType.TRUSTED_RELAYER: + case IsmType.CCIP: return ModuleType.NULL; case IsmType.ARB_L2_TO_L1: return ModuleType.ARB_L2_TO_L1; @@ -119,12 +122,14 @@ export type TrustedRelayerIsmConfig = z.infer< typeof TrustedRelayerIsmConfigSchema >; export type ArbL2ToL1IsmConfig = z.infer; +export type CCIPIsmConfig = z.infer; export type NullIsmConfig = | TestIsmConfig | PausableIsmConfig | OpStackIsmConfig - | TrustedRelayerIsmConfig; + | TrustedRelayerIsmConfig + | CCIPIsmConfig; type BaseRoutingIsmConfig< T extends IsmType.ROUTING | IsmType.FALLBACK_ROUTING | IsmType.ICA_ROUTING, @@ -167,6 +172,7 @@ export type DeployedIsmType = { [IsmType.ARB_L2_TO_L1]: ArbL2ToL1Ism; [IsmType.WEIGHTED_MERKLE_ROOT_MULTISIG]: IStaticWeightedMultisigIsm; [IsmType.WEIGHTED_MESSAGE_ID_MULTISIG]: IStaticWeightedMultisigIsm; + [IsmType.CCIP]: CCIPIsm; }; export type DeployedIsm = ValueOf; @@ -220,6 +226,11 @@ export const PausableIsmConfigSchema = PausableSchema.and( }), ); +export const CCIPIsmConfigSchema = z.object({ + type: z.literal(IsmType.CCIP), + originChain: z.string(), +}); + export const MultisigIsmConfigSchema = MultisigConfigSchema.and( z.object({ type: z.union([ @@ -280,4 +291,5 @@ export const IsmConfigSchema = z.union([ RoutingIsmConfigSchema, AggregationIsmConfigSchema, ArbL2ToL1IsmConfigSchema, + CCIPIsmConfigSchema, ]); diff --git a/typescript/sdk/src/ism/utils.ts b/typescript/sdk/src/ism/utils.ts index 5989854ab7..121391c53a 100644 --- a/typescript/sdk/src/ism/utils.ts +++ b/typescript/sdk/src/ism/utils.ts @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; import { + CCIPIsm__factory, DomainRoutingIsm__factory, IAggregationIsm__factory, IInterchainSecurityModule__factory, @@ -26,6 +27,7 @@ import { HyperlaneContracts } from '../contracts/types.js'; import { ProxyFactoryFactories } from '../deploy/contracts.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainName } from '../types.js'; +import { getChainNameFromCCIPSelector } from '../utils/ccip.js'; import { DomainRoutingIsmConfig, @@ -350,6 +352,19 @@ export async function moduleMatchesConfig( matches = true; break; } + case IsmType.CCIP: { + const ccipIsm = CCIPIsm__factory.connect(moduleAddress, provider); + const type = await ccipIsm.moduleType(); + matches &&= type === ModuleType.NULL; + + // Check that the origin chain selector matches the config + const originCcipChainSelector = await ccipIsm.ccipOrigin(); + const chainName = getChainNameFromCCIPSelector( + originCcipChainSelector.toString(), + ); + matches &&= chainName === config.originChain; + break; + } case IsmType.TRUSTED_RELAYER: { const trustedRelayerIsm = TrustedRelayerIsm__factory.connect( moduleAddress, diff --git a/typescript/sdk/src/utils/ccip.ts b/typescript/sdk/src/utils/ccip.ts new file mode 100644 index 0000000000..cf9f00c125 --- /dev/null +++ b/typescript/sdk/src/utils/ccip.ts @@ -0,0 +1,35 @@ +import { CCIP_NETWORKS } from '../consts/ccip.js'; + +/** + * Gets the chain name from a CCIP chain selector value + * @param chainSelector The CCIP chain selector value + * @returns The chain name if found, undefined otherwise + */ +export function getChainNameFromCCIPSelector( + chainSelector: string, +): string | undefined { + for (const [chainName, networkInfo] of Object.entries(CCIP_NETWORKS)) { + if (networkInfo.chainSelector === chainSelector) { + return chainName; + } + } + return undefined; +} + +/** + * Gets the CCIP chain selector value for a given chain name + * @param chainName The name of the chain + * @returns The CCIP chain selector if found, undefined otherwise + */ +export function getCCIPChainSelector(chainName: string): string | undefined { + return CCIP_NETWORKS[chainName]?.chainSelector; +} + +/** + * Gets the CCIP router address for a given chain name + * @param chainName The name of the chain + * @returns The CCIP router address if found, undefined otherwise + */ +export function getCCIPRouterAddress(chainName: string): string | undefined { + return CCIP_NETWORKS[chainName]?.router?.address; +} From 468354076ffb60a0c447c05ef6d5fb29ee413952 Mon Sep 17 00:00:00 2001 From: xeno097 Date: Fri, 7 Feb 2025 23:33:44 -0400 Subject: [PATCH 3/5] feat(sdk): added ccip hook deployment to the EvmHookModule --- .../src/hook/EvmHookModule.hardhat-test.ts | 2 + typescript/sdk/src/hook/EvmHookModule.ts | 112 +++++++++++++++++- typescript/sdk/src/hook/EvmHookReader.test.ts | 4 +- 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts index e2a68ae7ee..01eeb41be5 100644 --- a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts +++ b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts @@ -38,6 +38,7 @@ function randomHookType(): HookType { (type) => type !== HookType.OP_STACK && type !== HookType.ARB_L2_TO_L1 && + type !== HookType.CCIP && type !== HookType.CUSTOM, ); return filteredHookTypes[ @@ -304,6 +305,7 @@ describe('EvmHookModule', async () => { (hookType) => hookType !== HookType.OP_STACK && hookType !== HookType.ARB_L2_TO_L1 && + hookType !== HookType.CCIP && hookType !== HookType.CUSTOM, ) // generate a random config for each hook type diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index 6a7cfbbd3e..efc13e24e9 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -4,6 +4,8 @@ import { BigNumber, ethers } from 'ethers'; import { ArbL2ToL1Hook, ArbL2ToL1Ism__factory, + CCIPHook, + CCIPIsm__factory, DomainRoutingHook, DomainRoutingHook__factory, FallbackDomainRoutingHook, @@ -30,6 +32,7 @@ import { EvmChainId, ProtocolType, addressToBytes32, + assert, deepEquals, eqAddress, rootLogger, @@ -48,10 +51,16 @@ import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { IgpConfig } from '../gas/types.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; -import { ArbL2ToL1IsmConfig, IsmType, OpStackIsmConfig } from '../ism/types.js'; +import { + ArbL2ToL1IsmConfig, + CCIPIsmConfig, + IsmType, + OpStackIsmConfig, +} from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ChainName, ChainNameOrId } from '../types.js'; +import { getCCIPChainSelector, getCCIPRouterAddress } from '../utils/ccip.js'; import { normalizeConfig } from '../utils/ism.js'; import { EvmHookReader } from './EvmHookReader.js'; @@ -59,6 +68,7 @@ import { DeployedHook, HookFactories, hookFactories } from './contracts.js'; import { AggregationHookConfig, ArbL2ToL1HookConfig, + CCIPHookConfig, DomainRoutingHookConfig, FallbackRoutingHookConfig, HookConfig, @@ -654,6 +664,9 @@ export class EvmHookModule extends HyperlaneModule< case HookType.PAUSABLE: { return this.deployPausableHook({ config }); } + case HookType.CCIP: { + return this.deployCCIPHook({ config }); + } default: throw new Error(`Unsupported hook config: ${config}`); } @@ -916,6 +929,103 @@ export class EvmHookModule extends HyperlaneModule< return hook; } + // NOTE: this deploys the ism too on the destination chain if it doesn't exist + protected async deployCCIPHook({ + config, + }: { + config: CCIPHookConfig; + }): Promise { + const chain = this.chain; + const mailbox = this.args.addresses.mailbox; + this.logger.debug( + 'Deploying CCIPHook for %s to %s...', + chain, + config.destinationChain, + ); + + // deploy ccip ism + const ismConfig: CCIPIsmConfig = { + type: IsmType.CCIP, + originChain: chain, + }; + + // deploy opstack ism + const ccipIsmAddress = ( + await EvmIsmModule.create({ + chain: config.destinationChain, + config: ismConfig, + proxyFactoryFactories: this.args.addresses, + mailbox: mailbox, + multiProvider: this.multiProvider, + contractVerifier: this.contractVerifier, + }) + ).serialize().deployedIsm; + + // connect to ISM + const ccipIsm = CCIPIsm__factory.connect( + ccipIsmAddress, + this.multiProvider.getSignerOrProvider(config.destinationChain), + ); + + // deploy CCIP hook + const destinationDomain = this.multiProvider.getDomainId( + config.destinationChain, + ); + + const ccipRouterAddress = getCCIPRouterAddress(chain); + const ccipChainSelector = getCCIPChainSelector(config.destinationChain); + assert(ccipRouterAddress, `CCIP router address not found for ${chain}`); + assert( + ccipChainSelector, + `CCIP chain selector not found for ${config.destinationChain}`, + ); + + const hook = await this.deployer.deployContract(chain, HookType.CCIP, [ + ccipRouterAddress, + ccipChainSelector, + mailbox, + destinationDomain, + ccipIsm.address, + ]); + + // set authorized hook on ccip ism + const authorizedHook = await ccipIsm.authorizedHook(); + if (authorizedHook === addressToBytes32(hook.address)) { + this.logger.debug( + 'Authorized hook already set on ism %s', + ccipIsm.address, + ); + return hook; + } else if ( + authorizedHook !== addressToBytes32(ethers.constants.AddressZero) + ) { + this.logger.debug( + 'Authorized hook mismatch on ism %s, expected %s, got %s', + ccipIsm.address, + addressToBytes32(hook.address), + authorizedHook, + ); + throw new Error('Authorized hook mismatch'); + } + + // check if mismatch and redeploy hook + this.logger.debug( + 'Setting authorized hook %s on ism % on destination %s', + hook.address, + ccipIsm.address, + config.destinationChain, + ); + await this.multiProvider.handleTx( + config.destinationChain, + ccipIsm.setAuthorizedHook( + addressToBytes32(hook.address), + this.multiProvider.getTransactionOverrides(config.destinationChain), + ), + ); + + return hook; + } + protected async deployRoutingHook({ config, }: { diff --git a/typescript/sdk/src/hook/EvmHookReader.test.ts b/typescript/sdk/src/hook/EvmHookReader.test.ts index c5d13db71c..3bd3c57b6d 100644 --- a/typescript/sdk/src/hook/EvmHookReader.test.ts +++ b/typescript/sdk/src/hook/EvmHookReader.test.ts @@ -195,7 +195,7 @@ describe('EvmHookReader', () => { // Mock the CCIPHook contract const mockContract = { hookType: sandbox.stub().resolves(OnchainHookType.ID_AUTH_ISM), - destinationChain: sandbox.stub().resolves(destinationDomain), + destinationDomain: sandbox.stub().resolves(destinationDomain), ism: sandbox.stub().resolves(ism), }; @@ -211,7 +211,7 @@ describe('EvmHookReader', () => { const expectedConfig: WithAddress = { address: ccipHookAddress, type: HookType.CCIP, - destinationChain: TestChainName.test2, + destinationChain: TestChainName.test1, ism, }; From bbdea86ee158b0dd9f8291c87e6b22d1fc067333 Mon Sep 17 00:00:00 2001 From: xeno097 Date: Sat, 8 Feb 2025 00:52:46 -0400 Subject: [PATCH 4/5] fix(dk+cli): fixed ccip and opstack hook deployment --- typescript/sdk/src/consts/ccip.ts | 2 +- .../src/hook/EvmHookModule.hardhat-test.ts | 1 - typescript/sdk/src/hook/EvmHookModule.ts | 12 ++-- typescript/sdk/src/hook/EvmHookReader.test.ts | 1 - typescript/sdk/src/hook/EvmHookReader.ts | 2 - .../sdk/src/hook/HyperlaneHookDeployer.ts | 60 ++++++++++++++++--- typescript/sdk/src/hook/types.ts | 1 - typescript/utils/src/addresses.ts | 3 + typescript/utils/src/index.ts | 1 + 9 files changed, 62 insertions(+), 21 deletions(-) diff --git a/typescript/sdk/src/consts/ccip.ts b/typescript/sdk/src/consts/ccip.ts index 5a7821bf6e..602fcc13f8 100644 --- a/typescript/sdk/src/consts/ccip.ts +++ b/typescript/sdk/src/consts/ccip.ts @@ -624,7 +624,7 @@ export const CCIP_NETWORKS: ChainMap = { version: '1.5.0', }, }, - sonium: { + soneium: { armProxy: { address: '0x3117f515D763652A32d3D6D447171ea7c9d57218', version: '1.5.0', diff --git a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts index 01eeb41be5..e386f0a61d 100644 --- a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts +++ b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts @@ -123,7 +123,6 @@ function randomHookConfig( return { type: hookType, destinationChain: 'testChain', - ism: randomAddress(), }; } diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index efc13e24e9..b313c83029 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -31,6 +31,7 @@ import { Domain, EvmChainId, ProtocolType, + ZERO_ADDRESS_HEX_32, addressToBytes32, assert, deepEquals, @@ -806,9 +807,7 @@ export class EvmHookModule extends HyperlaneModule< opstackIsm.address, ); return hook; - } else if ( - authorizedHook !== addressToBytes32(ethers.constants.AddressZero) - ) { + } else if (authorizedHook !== ZERO_ADDRESS_HEX_32) { this.logger.debug( 'Authorized hook mismatch on ism %s, expected %s, got %s', opstackIsm.address, @@ -985,20 +984,19 @@ export class EvmHookModule extends HyperlaneModule< ccipChainSelector, mailbox, destinationDomain, - ccipIsm.address, + addressToBytes32(ccipIsm.address), ]); // set authorized hook on ccip ism const authorizedHook = await ccipIsm.authorizedHook(); + if (authorizedHook === addressToBytes32(hook.address)) { this.logger.debug( 'Authorized hook already set on ism %s', ccipIsm.address, ); return hook; - } else if ( - authorizedHook !== addressToBytes32(ethers.constants.AddressZero) - ) { + } else if (authorizedHook !== ZERO_ADDRESS_HEX_32) { this.logger.debug( 'Authorized hook mismatch on ism %s, expected %s, got %s', ccipIsm.address, diff --git a/typescript/sdk/src/hook/EvmHookReader.test.ts b/typescript/sdk/src/hook/EvmHookReader.test.ts index 3bd3c57b6d..f31c3ecfae 100644 --- a/typescript/sdk/src/hook/EvmHookReader.test.ts +++ b/typescript/sdk/src/hook/EvmHookReader.test.ts @@ -212,7 +212,6 @@ describe('EvmHookReader', () => { address: ccipHookAddress, type: HookType.CCIP, destinationChain: TestChainName.test1, - ism, }; expect(config).to.deep.equal(expectedConfig); diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index 9149398b14..9f20b2f4fb 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -218,13 +218,11 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { const ccipHook = CCIPHook__factory.connect(address, this.provider); const destinationDomain = await ccipHook.destinationDomain(); const destinationChain = this.multiProvider.getChainName(destinationDomain); - const ism = await ccipHook.ism(); const config: WithAddress = { address, type: HookType.CCIP, destinationChain, - ism, }; this._cache.set(address, config); diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index ea01f3a70c..a1c1dc13f1 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -1,7 +1,6 @@ -import { ethers } from 'ethers'; - import { CCIPHook, + CCIPIsm, DomainRoutingHook, FallbackDomainRoutingHook, IL1CrossDomainMessenger__factory, @@ -12,6 +11,7 @@ import { } from '@hyperlane-xyz/core'; import { Address, + ZERO_ADDRESS_HEX_32, addressToBytes32, assert, deepEquals, @@ -25,7 +25,7 @@ import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { HyperlaneIgpDeployer } from '../gas/HyperlaneIgpDeployer.js'; import { IgpFactories } from '../gas/contracts.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; -import { IsmType, OpStackIsmConfig } from '../ism/types.js'; +import { CCIPIsmConfig, IsmType, OpStackIsmConfig } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainMap, ChainName } from '../types.js'; import { getCCIPChainSelector, getCCIPRouterAddress } from '../utils/ccip.js'; @@ -144,13 +144,59 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< config.destinationChain, ); - return this.deployContract(chain, HookType.CCIP, [ + const ismConfig: CCIPIsmConfig = { + type: IsmType.CCIP, + originChain: chain, + }; + const ccipIsm = (await this.ismFactory.deploy({ + destination: config.destinationChain, + config: ismConfig, + origin: chain, + })) as CCIPIsm; + + const hook = await this.deployContract(chain, HookType.CCIP, [ ccipRouterAddress, ccipChainSelector, mailbox, destinationDomain, - config.ism, + addressToBytes32(ccipIsm.address), ]); + + // set authorized hook on ccip ism + const authorizedHook = await ccipIsm.authorizedHook(); + + if (authorizedHook === addressToBytes32(hook.address)) { + this.logger.debug( + 'Authorized hook already set on ism %s', + ccipIsm.address, + ); + return hook; + } else if (authorizedHook !== ZERO_ADDRESS_HEX_32) { + this.logger.debug( + 'Authorized hook mismatch on ism %s, expected %s, got %s', + ccipIsm.address, + addressToBytes32(hook.address), + authorizedHook, + ); + throw new Error('Authorized hook mismatch'); + } + + // check if mismatch and redeploy hook + this.logger.debug( + 'Setting authorized hook %s on ism % on destination %s', + hook.address, + ccipIsm.address, + config.destinationChain, + ); + await this.multiProvider.handleTx( + config.destinationChain, + ccipIsm.setAuthorizedHook( + addressToBytes32(hook.address), + this.multiProvider.getTransactionOverrides(config.destinationChain), + ), + ); + + return hook; } async deployProtocolFee( @@ -279,9 +325,7 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< opstackIsm.address, ); return hook; - } else if ( - authorizedHook !== addressToBytes32(ethers.constants.AddressZero) - ) { + } else if (authorizedHook !== ZERO_ADDRESS_HEX_32) { this.logger.debug( 'Authorized hook mismatch on ism %s, expected %s, got %s', opstackIsm.address, diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index 4c5f998b06..41e0ddd857 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -115,7 +115,6 @@ export const ArbL2ToL1HookSchema = z.object({ export const CCIPHookSchema = z.object({ type: z.literal(HookType.CCIP), destinationChain: z.string(), - ism: z.string(), }); export const IgpSchema = OwnableSchema.extend({ diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index a244c810ba..1b209129ae 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -293,6 +293,9 @@ export function addressToByteHexString( ); } +export const ZERO_ADDRESS_HEX_32 = + '0x0000000000000000000000000000000000000000000000000000000000000000'; + export function addressToBytes32( address: Address, protocol?: ProtocolType, diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index 85b6d406e0..70a0890ffe 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -39,6 +39,7 @@ export { padBytesToLength, shortenAddress, strip0x, + ZERO_ADDRESS_HEX_32, } from './addresses.js'; export { addBufferToGasLimit, From ec736487896135816b02833d238b3c6ccf5f3c72 Mon Sep 17 00:00:00 2001 From: xeno097 Date: Sat, 8 Feb 2025 18:52:08 -0400 Subject: [PATCH 5/5] fix(sdk): fixed ism configuration when deplying ccip ism --- solidity/remappings.txt | 1 + typescript/sdk/src/ism/HyperlaneIsmFactory.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/solidity/remappings.txt b/solidity/remappings.txt index 49ae22c339..61bde863a7 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -2,6 +2,7 @@ @eth-optimism=../node_modules/@eth-optimism @layerzerolabs=../node_modules/@layerzerolabs @openzeppelin=../node_modules/@openzeppelin +@chainlink=../node_modules/@chainlink ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ fx-portal/=lib/fx-portal/ diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index 05e13b09ad..c161d9b858 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -243,14 +243,16 @@ export class HyperlaneIsmFactory extends HyperlaneApp { config: CCIPIsmConfig, ): Promise { const ccipChainSelector = getCCIPChainSelector(config.originChain); - const ccipRouterAddress = getCCIPRouterAddress(config.originChain); + + // Get the CCIP router address to set it on the ISM so that it can call ccipReceive + const ccipRouterAddress = getCCIPRouterAddress(destination); assert( ccipChainSelector, `CCIP chain selector not found for ${config.originChain}`, ); assert( ccipRouterAddress, - `CCIP router address not found for ${config.originChain}`, + `CCIP router address not found for ${destination}`, ); return this.deployer.deployContract(destination, IsmType.CCIP, [ ccipRouterAddress,