@hyperlane-xyz/sdk 41.3.1 → 42.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/ccip/consts.d.ts.map +1 -1
  2. package/dist/ccip/consts.js +20 -0
  3. package/dist/ccip/consts.js.map +1 -1
  4. package/dist/consts/multisigIsm.d.ts.map +1 -1
  5. package/dist/consts/multisigIsm.js +20 -0
  6. package/dist/consts/multisigIsm.js.map +1 -1
  7. package/dist/contracts/contracts.d.ts.map +1 -1
  8. package/dist/contracts/contracts.js +15 -0
  9. package/dist/contracts/contracts.js.map +1 -1
  10. package/dist/contracts/contracts.test.d.ts +2 -0
  11. package/dist/contracts/contracts.test.d.ts.map +1 -0
  12. package/dist/contracts/contracts.test.js +88 -0
  13. package/dist/contracts/contracts.test.js.map +1 -0
  14. package/dist/core/types.d.ts +80 -2602
  15. package/dist/core/types.d.ts.map +1 -1
  16. package/dist/core/types.js +19 -1
  17. package/dist/core/types.js.map +1 -1
  18. package/dist/core/types.test.js +58 -0
  19. package/dist/core/types.test.js.map +1 -1
  20. package/dist/deploy/warp.d.ts +119 -3
  21. package/dist/deploy/warp.d.ts.map +1 -1
  22. package/dist/deploy/warp.js +696 -33
  23. package/dist/deploy/warp.js.map +1 -1
  24. package/dist/deploy/warp.test.d.ts +2 -0
  25. package/dist/deploy/warp.test.d.ts.map +1 -0
  26. package/dist/deploy/warp.test.js +358 -0
  27. package/dist/deploy/warp.test.js.map +1 -0
  28. package/dist/deploy/warpHybridPlan.d.ts +104 -0
  29. package/dist/deploy/warpHybridPlan.d.ts.map +1 -0
  30. package/dist/deploy/warpHybridPlan.js +178 -0
  31. package/dist/deploy/warpHybridPlan.js.map +1 -0
  32. package/dist/deploy/warpHybridPlan.test.d.ts +2 -0
  33. package/dist/deploy/warpHybridPlan.test.d.ts.map +1 -0
  34. package/dist/deploy/warpHybridPlan.test.js +141 -0
  35. package/dist/deploy/warpHybridPlan.test.js.map +1 -0
  36. package/dist/hook/EvmHookModule.d.ts +1 -1
  37. package/dist/hook/EvmHookModule.d.ts.map +1 -1
  38. package/dist/hook/EvmHookModule.js +16 -3
  39. package/dist/hook/EvmHookModule.js.map +1 -1
  40. package/dist/hook/EvmHookReader.d.ts +10 -0
  41. package/dist/hook/EvmHookReader.d.ts.map +1 -1
  42. package/dist/hook/EvmHookReader.js +92 -11
  43. package/dist/hook/EvmHookReader.js.map +1 -1
  44. package/dist/hook/EvmHookReader.test.js +155 -1
  45. package/dist/hook/EvmHookReader.test.js.map +1 -1
  46. package/dist/hook/types.d.ts +107 -1983
  47. package/dist/hook/types.d.ts.map +1 -1
  48. package/dist/hook/types.js +49 -0
  49. package/dist/hook/types.js.map +1 -1
  50. package/dist/hook/types.test.js +21 -1
  51. package/dist/hook/types.test.js.map +1 -1
  52. package/dist/hook/updates.d.ts +1 -0
  53. package/dist/hook/updates.d.ts.map +1 -1
  54. package/dist/hook/updates.js +2 -2
  55. package/dist/hook/updates.js.map +1 -1
  56. package/dist/hook/utils.d.ts +28 -1
  57. package/dist/hook/utils.d.ts.map +1 -1
  58. package/dist/hook/utils.js +89 -0
  59. package/dist/hook/utils.js.map +1 -1
  60. package/dist/ica/types.d.ts +4 -674
  61. package/dist/ica/types.d.ts.map +1 -1
  62. package/dist/index.d.ts +6 -5
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +6 -5
  65. package/dist/index.js.map +1 -1
  66. package/dist/ism/EvmIsmModule.d.ts +28 -2
  67. package/dist/ism/EvmIsmModule.d.ts.map +1 -1
  68. package/dist/ism/EvmIsmModule.hardhat-test.js +478 -3
  69. package/dist/ism/EvmIsmModule.hardhat-test.js.map +1 -1
  70. package/dist/ism/EvmIsmModule.js +224 -10
  71. package/dist/ism/EvmIsmModule.js.map +1 -1
  72. package/dist/ism/EvmIsmReader.d.ts.map +1 -1
  73. package/dist/ism/EvmIsmReader.js +77 -1
  74. package/dist/ism/EvmIsmReader.js.map +1 -1
  75. package/dist/ism/EvmIsmReader.test.js +245 -4
  76. package/dist/ism/EvmIsmReader.test.js.map +1 -1
  77. package/dist/ism/HyperlaneIsmFactory.d.ts +14 -4
  78. package/dist/ism/HyperlaneIsmFactory.d.ts.map +1 -1
  79. package/dist/ism/HyperlaneIsmFactory.hardhat-test.js +372 -3
  80. package/dist/ism/HyperlaneIsmFactory.hardhat-test.js.map +1 -1
  81. package/dist/ism/HyperlaneIsmFactory.js +80 -6
  82. package/dist/ism/HyperlaneIsmFactory.js.map +1 -1
  83. package/dist/ism/delayedFlow.d.ts +31 -0
  84. package/dist/ism/delayedFlow.d.ts.map +1 -0
  85. package/dist/ism/delayedFlow.js +33 -0
  86. package/dist/ism/delayedFlow.js.map +1 -0
  87. package/dist/ism/types.d.ts +135 -4
  88. package/dist/ism/types.d.ts.map +1 -1
  89. package/dist/ism/types.js +263 -11
  90. package/dist/ism/types.js.map +1 -1
  91. package/dist/ism/types.test.js +433 -1
  92. package/dist/ism/types.test.js.map +1 -1
  93. package/dist/ism/utils.d.ts.map +1 -1
  94. package/dist/ism/utils.js +113 -3
  95. package/dist/ism/utils.js.map +1 -1
  96. package/dist/ism/utils.test.js +44 -0
  97. package/dist/ism/utils.test.js.map +1 -1
  98. package/dist/providers/MultiProviderAdapter.d.ts +3 -3
  99. package/dist/providers/MultiProviderAdapter.d.ts.map +1 -1
  100. package/dist/providers/MultiProviderAdapter.js +2 -1
  101. package/dist/providers/MultiProviderAdapter.js.map +1 -1
  102. package/dist/providers/SmartProvider/HyperlaneJsonRpcProvider.d.ts.map +1 -1
  103. package/dist/providers/SmartProvider/HyperlaneJsonRpcProvider.js +4 -0
  104. package/dist/providers/SmartProvider/HyperlaneJsonRpcProvider.js.map +1 -1
  105. package/dist/providers/transactionFeeEstimators.d.ts +16 -9
  106. package/dist/providers/transactionFeeEstimators.d.ts.map +1 -1
  107. package/dist/providers/transactionFeeEstimators.js +66 -17
  108. package/dist/providers/transactionFeeEstimators.js.map +1 -1
  109. package/dist/providers/transactionFeeEstimators.test.js +153 -1
  110. package/dist/providers/transactionFeeEstimators.test.js.map +1 -1
  111. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.d.ts +14 -1
  112. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.d.ts.map +1 -1
  113. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js +46 -5
  114. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js.map +1 -1
  115. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.d.ts +2 -0
  116. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.d.ts.map +1 -0
  117. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.js +125 -0
  118. package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.js.map +1 -0
  119. package/dist/router/HyperlaneRouterDeployer.d.ts +3 -1
  120. package/dist/router/HyperlaneRouterDeployer.d.ts.map +1 -1
  121. package/dist/router/HyperlaneRouterDeployer.js +4 -2
  122. package/dist/router/HyperlaneRouterDeployer.js.map +1 -1
  123. package/dist/router/types.d.ts +6 -1011
  124. package/dist/router/types.d.ts.map +1 -1
  125. package/dist/test/testUtils.d.ts +1 -1
  126. package/dist/test/testUtils.d.ts.map +1 -1
  127. package/dist/test/testUtils.js +17 -0
  128. package/dist/test/testUtils.js.map +1 -1
  129. package/dist/test/testUtils.test.d.ts +2 -0
  130. package/dist/test/testUtils.test.d.ts.map +1 -0
  131. package/dist/test/testUtils.test.js +28 -0
  132. package/dist/test/testUtils.test.js.map +1 -0
  133. package/dist/token/EvmWarpModule.d.ts +44 -8
  134. package/dist/token/EvmWarpModule.d.ts.map +1 -1
  135. package/dist/token/EvmWarpModule.hardhat-test.js +574 -4
  136. package/dist/token/EvmWarpModule.hardhat-test.js.map +1 -1
  137. package/dist/token/EvmWarpModule.js +237 -25
  138. package/dist/token/EvmWarpModule.js.map +1 -1
  139. package/dist/token/configUtils.d.ts +17 -7
  140. package/dist/token/configUtils.d.ts.map +1 -1
  141. package/dist/token/configUtils.js +108 -0
  142. package/dist/token/configUtils.js.map +1 -1
  143. package/dist/token/configUtils.test.js +155 -1
  144. package/dist/token/configUtils.test.js.map +1 -1
  145. package/dist/token/deploy.d.ts +3 -1
  146. package/dist/token/deploy.d.ts.map +1 -1
  147. package/dist/token/deploy.hardhat-test.js +454 -3
  148. package/dist/token/deploy.hardhat-test.js.map +1 -1
  149. package/dist/token/deploy.js +25 -6
  150. package/dist/token/deploy.js.map +1 -1
  151. package/dist/token/types.d.ts +169 -2931
  152. package/dist/token/types.d.ts.map +1 -1
  153. package/dist/token/types.test.js +48 -0
  154. package/dist/token/types.test.js.map +1 -1
  155. package/dist/token/warpCheck.d.ts.map +1 -1
  156. package/dist/token/warpCheck.js +36 -1
  157. package/dist/token/warpCheck.js.map +1 -1
  158. package/dist/utils/ism.d.ts +167 -1
  159. package/dist/utils/ism.d.ts.map +1 -1
  160. package/dist/utils/ism.js +412 -2
  161. package/dist/utils/ism.js.map +1 -1
  162. package/dist/utils/ism.test.d.ts +2 -0
  163. package/dist/utils/ism.test.d.ts.map +1 -0
  164. package/dist/utils/ism.test.js +372 -0
  165. package/dist/utils/ism.test.js.map +1 -0
  166. package/package.json +10 -10
@@ -1,16 +1,20 @@
1
1
  import { expect } from 'chai';
2
2
  import { ethers } from 'ethers';
3
3
  import hre from 'hardhat';
4
- import { AtomicLocalRebalancingBridge__factory, CrossCollateralRouter__factory, ERC20Test__factory, GasRouter__factory, HypXERC20Lockbox__factory, LinearFee__factory, MailboxClient__factory, ProxyAdmin__factory, RateLimitedIsm__factory, RoutingFee__factory, StaticAggregationIsm__factory, TokenRouter__factory, TransparentUpgradeableProxy__factory, XERC20LockboxTest__factory, XERC20Test__factory, } from '@hyperlane-xyz/core';
5
- import { ProtocolType, assert, addressToBytes32, deepCopy, eqAddress, isZeroishAddress, objMap, } from '@hyperlane-xyz/utils';
4
+ import sinon from 'sinon';
5
+ import { AtomicLocalRebalancingBridge__factory, CrossCollateralRouter__factory, DelayedFlowRouterHookIsm__factory, ERC20Test__factory, GasRouter__factory, HypXERC20Lockbox__factory, LinearFee__factory, MailboxClient__factory, NetFlowRateLimitedHookIsm__factory, ProxyAdmin__factory, RateLimitedIsm__factory, RoutingFee__factory, StaticAggregationIsm__factory, TokenRouter__factory, TransparentUpgradeableProxy__factory, XERC20LockboxTest__factory, XERC20Test__factory, } from '@hyperlane-xyz/core';
6
+ import { ProtocolType, addressToBytes32, assert, deepCopy, eqAddress, isZeroishAddress, objMap, } from '@hyperlane-xyz/utils';
6
7
  import { TestChainName } from '../consts/testChains.js';
7
8
  import { TestCoreDeployer } from '../core/TestCoreDeployer.js';
8
9
  import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js';
9
- import { executeWarpDeploy } from '../deploy/warp.js';
10
+ import { enrollCrossChainRouters, executeWarpDeploy } from '../deploy/warp.js';
10
11
  import { TokenFeeType } from '../fee/types.js';
12
+ import { HookType } from '../hook/types.js';
11
13
  import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js';
12
14
  import { IsmType, } from '../ism/types.js';
13
15
  import { MultiProvider } from '../providers/MultiProvider.js';
16
+ import { randomAddress } from '../test/testUtils.js';
17
+ import { collectHybridIsmNodes } from '../utils/ism.js';
14
18
  import { EvmWarpRouteReader } from './EvmWarpRouteReader.js';
15
19
  import { TokenStandard } from './TokenStandard.js';
16
20
  import { TokenType } from './config.js';
@@ -29,6 +33,12 @@ function addOverridesToConfig(config, ownerOverrides) {
29
33
  ];
30
34
  }));
31
35
  }
36
+ function assertEvmTransaction(transaction, chain) {
37
+ assert('to' in transaction &&
38
+ typeof transaction.to === 'string' &&
39
+ 'data' in transaction &&
40
+ typeof transaction.data === 'string', `Expected an EVM transaction for ${chain}`);
41
+ }
32
42
  describe('TokenDeployer', async () => {
33
43
  let signer;
34
44
  let deployer;
@@ -683,6 +693,447 @@ describe('TokenDeployer', async () => {
683
693
  expect((await router.owner()).toLowerCase()).to.equal(warpOwner.toLowerCase());
684
694
  });
685
695
  });
696
+ describe('Hybrid hook/ISM deferred deploy', () => {
697
+ let registryAddresses;
698
+ const remoteChain = TestChainName.test2;
699
+ before(async () => {
700
+ const pfd = new HyperlaneProxyFactoryDeployer(multiProvider);
701
+ const factories = await pfd.deploy(multiProvider.mapKnownChains(() => ({})));
702
+ registryAddresses = {};
703
+ for (const chainName of [chain, remoteChain]) {
704
+ registryAddresses[chainName] = {
705
+ ...objMap(factories[chainName], (_, contract) => contract.address),
706
+ mailbox: coreApp.getContracts(chainName).mailbox.address,
707
+ };
708
+ }
709
+ // DELAYED_FLOW_ROUTER refuses nonce-0 mailboxes. Seed both test
710
+ // mailboxes before any staged deploy spends gas.
711
+ for (const [origin, destination] of [
712
+ [chain, remoteChain],
713
+ [remoteChain, chain],
714
+ ]) {
715
+ const mailbox = coreApp.getContracts(origin).mailbox;
716
+ if ((await mailbox.nonce()) !== 0)
717
+ continue;
718
+ const recipient = addressToBytes32(signer.address);
719
+ const quote = await mailbox['quoteDispatch(uint32,bytes32,bytes)'](multiProvider.getDomainId(destination), recipient, '0x');
720
+ await multiProvider.handleTx(origin, mailbox['dispatch(uint32,bytes32,bytes)'](multiProvider.getDomainId(destination), recipient, '0x', { value: quote }));
721
+ }
722
+ });
723
+ function delayedFlowTree(owner) {
724
+ return {
725
+ type: IsmType.AGGREGATION,
726
+ threshold: 2,
727
+ modules: [
728
+ {
729
+ type: IsmType.TRUSTED_RELAYER,
730
+ relayer: signer.address,
731
+ },
732
+ {
733
+ type: IsmType.DELAYED_FLOW_ROUTER,
734
+ thresholdBps: 10000,
735
+ maxDelay: 3600,
736
+ duration: 86400n,
737
+ owner,
738
+ },
739
+ ],
740
+ };
741
+ }
742
+ const hookView = (ism) => {
743
+ const nodes = collectHybridIsmNodes(ism);
744
+ assert(nodes.length === 1, `Expected one hybrid node, found ${nodes.length}`);
745
+ const node = nodes[0];
746
+ return node.type === IsmType.DELAYED_FLOW_ROUTER
747
+ ? { ...node, type: HookType.DELAYED_FLOW_ROUTER }
748
+ : { ...node, type: HookType.NET_FLOW_RATE_LIMITED };
749
+ };
750
+ async function deployHybridRoute(warpConfig, isms) {
751
+ const planned = objMap(warpConfig, (chainName, chainConfig) => {
752
+ const ism = isms[chainName];
753
+ assert(ism, `Missing hybrid ISM tree for ${chainName}`);
754
+ return {
755
+ ...chainConfig,
756
+ interchainSecurityModule: ism,
757
+ hook: hookView(ism),
758
+ };
759
+ });
760
+ const routers = await executeWarpDeploy(planned, multiProvider, {}, registryAddresses, {});
761
+ return objMap(routers, (_, address) => ({
762
+ synthetic: { address },
763
+ }));
764
+ }
765
+ async function expectDeployRejection(promise, message) {
766
+ const error = await promise.then(() => undefined, (thrown) => thrown instanceof Error ? thrown : new Error(String(thrown)));
767
+ assert(error, 'Expected staged deploy to reject');
768
+ expect(error.message).to.include(message);
769
+ }
770
+ it('wires a nested delayedFlowRouterHookIsm as ISM member AND hook, keeping deployer ownership for enrollment', async () => {
771
+ const warpOwner = ethers.Wallet.createRandom().address;
772
+ const warpConfig = {
773
+ [chain]: {
774
+ ...config[chain],
775
+ type: TokenType.synthetic,
776
+ owner: warpOwner,
777
+ },
778
+ };
779
+ const contracts = await deployHybridRoute(warpConfig, {
780
+ [chain]: delayedFlowTree(warpOwner),
781
+ });
782
+ const routerAddress = contracts[chain].synthetic.address;
783
+ const provider = multiProvider.getProvider(chain);
784
+ const tokenClient = MailboxClient__factory.connect(routerAddress, provider);
785
+ const ismAddress = await tokenClient.interchainSecurityModule();
786
+ expect(isZeroishAddress(ismAddress)).to.be.false;
787
+ // hook must be wired to the hybrid instance
788
+ const hookAddress = await tokenClient.hook();
789
+ expect(isZeroishAddress(hookAddress)).to.be.false;
790
+ // the hook is the DelayedFlowRouterHookIsm paired with this router
791
+ const delayedIsm = DelayedFlowRouterHookIsm__factory.connect(hookAddress, provider);
792
+ expect(eqAddress(await delayedIsm.warpRouter(), routerAddress)).to.be
793
+ .true;
794
+ expect(await delayedIsm.maxDelay()).to.equal(3600);
795
+ // the same instance must be a member of the aggregation ISM
796
+ const aggregationIsm = StaticAggregationIsm__factory.connect(ismAddress, provider);
797
+ const [modules] = await aggregationIsm.modulesAndThreshold(ethers.constants.AddressZero);
798
+ expect(modules.map((m) => m.toLowerCase())).to.include(hookAddress.toLowerCase());
799
+ // Both stay with the deployer until the final router-enrollment pass.
800
+ expect(eqAddress(await delayedIsm.owner(), signer.address)).to.be.true;
801
+ const router = GasRouter__factory.connect(routerAddress, provider);
802
+ expect((await router.owner()).toLowerCase()).to.equal(signer.address.toLowerCase());
803
+ });
804
+ it('does not proceed to the ISM phase when installing the hook fails', async () => {
805
+ const setHookSighash = MailboxClient__factory.createInterface().getSighash('setHook');
806
+ const setIsmSighash = MailboxClient__factory.createInterface().getSighash('setInterchainSecurityModule');
807
+ let sawIsmInstall = false;
808
+ const sendTransaction = multiProvider.sendTransaction.bind(multiProvider);
809
+ const sandbox = sinon.createSandbox();
810
+ sandbox
811
+ .stub(multiProvider, 'sendTransaction')
812
+ .callsFake(async (txChain, txProm, options) => {
813
+ const tx = await txProm;
814
+ if (typeof tx.data === 'string' &&
815
+ tx.data.startsWith(setIsmSighash)) {
816
+ sawIsmInstall = true;
817
+ }
818
+ if (typeof tx.data === 'string' &&
819
+ tx.data.startsWith(setHookSighash)) {
820
+ throw new Error('execution reverted: setHook');
821
+ }
822
+ return sendTransaction(txChain, tx, options);
823
+ });
824
+ try {
825
+ const warpConfig = {
826
+ [chain]: {
827
+ ...config[chain],
828
+ type: TokenType.synthetic,
829
+ owner: signer.address,
830
+ },
831
+ };
832
+ const error = await deployHybridRoute(warpConfig, {
833
+ [chain]: delayedFlowTree(signer.address),
834
+ }).then(() => undefined, (thrown) => thrown instanceof Error ? thrown : new Error(String(thrown)));
835
+ assert(error, 'expected the deploy to fail');
836
+ expect(error.message).to.include('execution reverted: setHook');
837
+ expect(sawIsmInstall).to.be.false;
838
+ }
839
+ finally {
840
+ sandbox.restore();
841
+ }
842
+ });
843
+ it('rejects a hybrid on an unmeterable router type before staged deploy', async () => {
844
+ const warpConfig = {
845
+ [chain]: {
846
+ ...config[chain],
847
+ type: TokenType.collateralVault,
848
+ token: erc20.address,
849
+ owner: signer.address,
850
+ },
851
+ };
852
+ await expectDeployRejection(deployHybridRoute(warpConfig, {
853
+ [chain]: delayedFlowTree(signer.address),
854
+ }), 'cannot meter');
855
+ });
856
+ it('rejects a hybrid combined with a predicateWrapper before staged deploy', async () => {
857
+ const warpConfig = {
858
+ [chain]: {
859
+ ...config[chain],
860
+ type: TokenType.synthetic,
861
+ owner: signer.address,
862
+ predicateWrapper: {
863
+ predicateRegistry: randomAddress(),
864
+ policyId: 'test-policy',
865
+ owner: signer.address,
866
+ },
867
+ },
868
+ };
869
+ await expectDeployRejection(deployHybridRoute(warpConfig, {
870
+ [chain]: delayedFlowTree(signer.address),
871
+ }), 'both must own');
872
+ });
873
+ it('rejects a top-level netFlowRateLimitedHookIsm (no authenticating ISM)', async () => {
874
+ const warpOwner = ethers.Wallet.createRandom().address;
875
+ const warpConfig = {
876
+ [chain]: {
877
+ ...config[chain],
878
+ type: TokenType.synthetic,
879
+ owner: warpOwner,
880
+ },
881
+ };
882
+ // moduleType() is NULL: alone it authenticates nothing, so a forged
883
+ // message would mint on this synthetic leg subject only to bucket
884
+ // capacity. The composition rule must reject it.
885
+ const netFlowConfig = {
886
+ type: IsmType.NET_FLOW_RATE_LIMITED,
887
+ thresholdBps: 500,
888
+ duration: 86400n,
889
+ owner: warpOwner,
890
+ };
891
+ await expectDeployRejection(deployHybridRoute(warpConfig, { [chain]: netFlowConfig }), 'verifies flow, not message authenticity');
892
+ });
893
+ it('wires a netFlowRateLimitedHookIsm composed under an authenticating aggregation as both hook and ISM', async () => {
894
+ const warpOwner = ethers.Wallet.createRandom().address;
895
+ const warpConfig = {
896
+ [chain]: {
897
+ ...config[chain],
898
+ type: TokenType.synthetic,
899
+ owner: warpOwner,
900
+ },
901
+ };
902
+ const netFlowConfig = {
903
+ type: IsmType.AGGREGATION,
904
+ threshold: 2,
905
+ modules: [
906
+ { type: IsmType.TRUSTED_RELAYER, relayer: signer.address },
907
+ {
908
+ type: IsmType.NET_FLOW_RATE_LIMITED,
909
+ thresholdBps: 500,
910
+ duration: 86400n,
911
+ owner: warpOwner,
912
+ },
913
+ ],
914
+ };
915
+ const contracts = await deployHybridRoute(warpConfig, {
916
+ [chain]: netFlowConfig,
917
+ });
918
+ const routerAddress = contracts[chain].synthetic.address;
919
+ const provider = multiProvider.getProvider(chain);
920
+ // Mirror the CLI's post-deploy pass. It enrolls route peers and hands
921
+ // the router, ProxyAdmin, and shared hybrid to their final owner.
922
+ const enrollmentTxs = await enrollCrossChainRouters({
923
+ multiProvider,
924
+ altVmSigners: {},
925
+ registryAddresses,
926
+ warpDeployConfig: warpConfig,
927
+ }, { [chain]: routerAddress });
928
+ for (const rawTx of enrollmentTxs[chain] ?? []) {
929
+ assertEvmTransaction(rawTx, chain);
930
+ await multiProvider.sendTransaction(chain, {
931
+ to: rawTx.to,
932
+ data: rawTx.data,
933
+ value: rawTx.value,
934
+ });
935
+ }
936
+ const tokenClient = MailboxClient__factory.connect(routerAddress, provider);
937
+ const ismAddress = await tokenClient.interchainSecurityModule();
938
+ const hookAddress = await tokenClient.hook();
939
+ expect(isZeroishAddress(hookAddress)).to.be.false;
940
+ // The hook is the hybrid leaf inside the installed aggregation, not the
941
+ // aggregation itself.
942
+ const [modules] = await StaticAggregationIsm__factory.connect(ismAddress, provider).modulesAndThreshold(ethers.constants.AddressZero);
943
+ expect(modules.map((m) => m.toLowerCase())).to.include(hookAddress.toLowerCase());
944
+ const netFlowIsm = NetFlowRateLimitedHookIsm__factory.connect(hookAddress, provider);
945
+ expect(eqAddress(await netFlowIsm.warpRouter(), routerAddress)).to.be
946
+ .true;
947
+ // Ownership transfers only after all route wiring is complete.
948
+ expect((await netFlowIsm.owner()).toLowerCase()).to.equal(warpOwner.toLowerCase());
949
+ expect((await TokenRouter__factory.connect(routerAddress, provider).owner()).toLowerCase()).to.equal(warpOwner.toLowerCase());
950
+ const secondEnrollmentTxs = await enrollCrossChainRouters({
951
+ multiProvider,
952
+ altVmSigners: {},
953
+ registryAddresses,
954
+ warpDeployConfig: warpConfig,
955
+ }, { [chain]: routerAddress });
956
+ expect(Object.keys(secondEnrollmentTxs)).to.have.length(0);
957
+ });
958
+ it('defaults an omitted netFlowRateLimitedHookIsm owner to the chain config owner', async () => {
959
+ const warpOwner = ethers.Wallet.createRandom().address;
960
+ const warpConfig = {
961
+ [chain]: {
962
+ ...config[chain],
963
+ type: TokenType.synthetic,
964
+ owner: warpOwner,
965
+ },
966
+ };
967
+ const ownerlessNetFlow = {
968
+ type: IsmType.AGGREGATION,
969
+ threshold: 2,
970
+ modules: [
971
+ { type: IsmType.TRUSTED_RELAYER, relayer: signer.address },
972
+ {
973
+ type: IsmType.NET_FLOW_RATE_LIMITED,
974
+ thresholdBps: 500,
975
+ duration: 86400n,
976
+ },
977
+ ],
978
+ };
979
+ const contracts = await deployHybridRoute(warpConfig, {
980
+ [chain]: ownerlessNetFlow,
981
+ });
982
+ const routerAddress = contracts[chain].synthetic.address;
983
+ const provider = multiProvider.getProvider(chain);
984
+ const enrollmentTxs = await enrollCrossChainRouters({
985
+ multiProvider,
986
+ altVmSigners: {},
987
+ registryAddresses,
988
+ warpDeployConfig: warpConfig,
989
+ }, { [chain]: routerAddress });
990
+ for (const rawTx of enrollmentTxs[chain] ?? []) {
991
+ assertEvmTransaction(rawTx, chain);
992
+ await multiProvider.sendTransaction(chain, {
993
+ to: rawTx.to,
994
+ data: rawTx.data,
995
+ value: rawTx.value,
996
+ });
997
+ }
998
+ // the hybrid leaf is the router's hook; the ISM is the aggregation
999
+ const hookAddress = await MailboxClient__factory.connect(routerAddress, provider).hook();
1000
+ const netFlowIsm = NetFlowRateLimitedHookIsm__factory.connect(hookAddress, provider);
1001
+ // The final pass defaults an omitted hybrid owner to the chain owner.
1002
+ expect((await netFlowIsm.owner()).toLowerCase()).to.equal(warpOwner.toLowerCase());
1003
+ });
1004
+ it('enrolls delayedFlowRouterHookIsm counterparts cross-chain and transfers ownership last', async () => {
1005
+ const warpOwner = ethers.Wallet.createRandom().address;
1006
+ const warpDeployConfig = {
1007
+ [chain]: {
1008
+ ...config[chain],
1009
+ type: TokenType.synthetic,
1010
+ owner: warpOwner,
1011
+ interchainSecurityModule: delayedFlowTree(warpOwner),
1012
+ },
1013
+ [remoteChain]: {
1014
+ ...config[remoteChain],
1015
+ type: TokenType.synthetic,
1016
+ owner: warpOwner,
1017
+ interchainSecurityModule: delayedFlowTree(warpOwner),
1018
+ },
1019
+ };
1020
+ const contracts = await deployHybridRoute(warpDeployConfig, {
1021
+ [chain]: delayedFlowTree(warpOwner),
1022
+ [remoteChain]: delayedFlowTree(warpOwner),
1023
+ });
1024
+ const deployedContracts = objMap(contracts, (_, c) => c.synthetic.address);
1025
+ const enrollTxs = await enrollCrossChainRouters({
1026
+ multiProvider,
1027
+ altVmSigners: {},
1028
+ registryAddresses,
1029
+ warpDeployConfig,
1030
+ }, deployedContracts);
1031
+ for (const [txChain, txs] of Object.entries(enrollTxs)) {
1032
+ for (const rawTx of txs) {
1033
+ assertEvmTransaction(rawTx, txChain);
1034
+ // drop the annotated chainId: every hardhat test chain shares one
1035
+ // node (chainId 31337) while test-chain metadata carries fake ids
1036
+ await multiProvider.sendTransaction(txChain, {
1037
+ to: rawTx.to,
1038
+ data: rawTx.data,
1039
+ value: rawTx.value,
1040
+ });
1041
+ }
1042
+ }
1043
+ const localProvider = multiProvider.getProvider(chain);
1044
+ const remoteProvider = multiProvider.getProvider(remoteChain);
1045
+ const localDfr = DelayedFlowRouterHookIsm__factory.connect(await MailboxClient__factory.connect(deployedContracts[chain], localProvider).hook(), localProvider);
1046
+ const remoteDfr = DelayedFlowRouterHookIsm__factory.connect(await MailboxClient__factory.connect(deployedContracts[remoteChain], remoteProvider).hook(), remoteProvider);
1047
+ // mutual DFR enrollment
1048
+ const localDomain = multiProvider.getDomainId(chain);
1049
+ const remoteDomain = multiProvider.getDomainId(remoteChain);
1050
+ expect(await localDfr.routers(remoteDomain)).to.equal(addressToBytes32(remoteDfr.address).toLowerCase());
1051
+ expect(await remoteDfr.routers(localDomain)).to.equal(addressToBytes32(localDfr.address).toLowerCase());
1052
+ // ownership handed to the configured owner after enrollment
1053
+ expect((await localDfr.owner()).toLowerCase()).to.equal(warpOwner.toLowerCase());
1054
+ expect((await remoteDfr.owner()).toLowerCase()).to.equal(warpOwner.toLowerCase());
1055
+ // a second pass converges to zero transactions
1056
+ const secondEnrollTxs = await enrollCrossChainRouters({
1057
+ multiProvider,
1058
+ altVmSigners: {},
1059
+ registryAddresses,
1060
+ warpDeployConfig,
1061
+ }, deployedContracts);
1062
+ expect(Object.keys(secondEnrollTxs)).to.have.length(0);
1063
+ });
1064
+ it('reconciles delayedFlowRouterHookIsm enrollment drift', async () => {
1065
+ // Owner is the deployer so the drift can be introduced directly and the
1066
+ // corrective transactions can be submitted from this test.
1067
+ const owner = signer.address;
1068
+ const warpDeployConfig = {
1069
+ [chain]: {
1070
+ ...config[chain],
1071
+ type: TokenType.synthetic,
1072
+ owner,
1073
+ interchainSecurityModule: delayedFlowTree(owner),
1074
+ },
1075
+ [remoteChain]: {
1076
+ ...config[remoteChain],
1077
+ type: TokenType.synthetic,
1078
+ owner,
1079
+ interchainSecurityModule: delayedFlowTree(owner),
1080
+ },
1081
+ };
1082
+ const contracts = await deployHybridRoute(objMap(warpDeployConfig, (_, c) => ({
1083
+ ...c,
1084
+ interchainSecurityModule: undefined,
1085
+ })), {
1086
+ [chain]: delayedFlowTree(owner),
1087
+ [remoteChain]: delayedFlowTree(owner),
1088
+ });
1089
+ const deployedContracts = objMap(contracts, (_, c) => c.synthetic.address);
1090
+ const submit = async (txs) => {
1091
+ for (const [txChain, chainTxs] of Object.entries(txs)) {
1092
+ for (const rawTx of chainTxs) {
1093
+ assertEvmTransaction(rawTx, txChain);
1094
+ await multiProvider.sendTransaction(txChain, {
1095
+ to: rawTx.to,
1096
+ data: rawTx.data,
1097
+ value: rawTx.value,
1098
+ });
1099
+ }
1100
+ }
1101
+ };
1102
+ await submit(await enrollCrossChainRouters({
1103
+ multiProvider,
1104
+ altVmSigners: {},
1105
+ registryAddresses,
1106
+ warpDeployConfig,
1107
+ }, deployedContracts));
1108
+ const localProvider = multiProvider.getProvider(chain);
1109
+ const localDfrAddress = await MailboxClient__factory.connect(deployedContracts[chain], localProvider).hook();
1110
+ const localDfr = DelayedFlowRouterHookIsm__factory.connect(localDfrAddress, multiProvider.getSigner(chain));
1111
+ const remoteDomain = multiProvider.getDomainId(remoteChain);
1112
+ // Introduce drift: point the local instance at a bogus counterpart.
1113
+ const bogusRouter = addressToBytes32(randomAddress()).toLowerCase();
1114
+ await multiProvider.handleTx(chain, localDfr.enrollRemoteRouter(remoteDomain, bogusRouter));
1115
+ expect(await localDfr.routers(remoteDomain)).to.equal(bogusRouter);
1116
+ // Re-running enrollment must emit corrective transactions...
1117
+ const driftTxs = await enrollCrossChainRouters({
1118
+ multiProvider,
1119
+ altVmSigners: {},
1120
+ registryAddresses,
1121
+ warpDeployConfig,
1122
+ }, deployedContracts);
1123
+ expect(Object.keys(driftTxs)).to.include(chain);
1124
+ await submit(driftTxs);
1125
+ // ...restoring the real counterpart, and then converging again.
1126
+ const remoteDfrAddress = await MailboxClient__factory.connect(deployedContracts[remoteChain], multiProvider.getProvider(remoteChain)).hook();
1127
+ expect(await localDfr.routers(remoteDomain)).to.equal(addressToBytes32(remoteDfrAddress).toLowerCase());
1128
+ const convergedTxs = await enrollCrossChainRouters({
1129
+ multiProvider,
1130
+ altVmSigners: {},
1131
+ registryAddresses,
1132
+ warpDeployConfig,
1133
+ }, deployedContracts);
1134
+ expect(Object.keys(convergedTxs)).to.have.length(0);
1135
+ });
1136
+ });
686
1137
  describe('TokenFee with optional token for synthetic', () => {
687
1138
  it('should deploy LinearFee without token and resolve to router address', async () => {
688
1139
  const syntheticConfig = {