@humanode-hyperlane/rebalancer 27.2.2 → 1029.1.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 (295) hide show
  1. package/dist/bridges/LiFiBridge.d.ts +3 -3
  2. package/dist/bridges/LiFiBridge.d.ts.map +1 -1
  3. package/dist/bridges/LiFiBridge.js +66 -55
  4. package/dist/bridges/LiFiBridge.js.map +1 -1
  5. package/dist/bridges/LiFiBridge.test.js +213 -0
  6. package/dist/bridges/LiFiBridge.test.js.map +1 -1
  7. package/dist/config/RebalancerConfig.d.ts +4 -3
  8. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  9. package/dist/config/RebalancerConfig.js +7 -5
  10. package/dist/config/RebalancerConfig.js.map +1 -1
  11. package/dist/config/RebalancerConfig.test.js +278 -2
  12. package/dist/config/RebalancerConfig.test.js.map +1 -1
  13. package/dist/config/types.d.ts +707 -2664
  14. package/dist/config/types.d.ts.map +1 -1
  15. package/dist/config/types.js +62 -7
  16. package/dist/config/types.js.map +1 -1
  17. package/dist/core/InventoryRebalancer.d.ts +13 -19
  18. package/dist/core/InventoryRebalancer.d.ts.map +1 -1
  19. package/dist/core/InventoryRebalancer.js +404 -276
  20. package/dist/core/InventoryRebalancer.js.map +1 -1
  21. package/dist/core/InventoryRebalancer.test.js +814 -25
  22. package/dist/core/InventoryRebalancer.test.js.map +1 -1
  23. package/dist/core/Rebalancer.d.ts.map +1 -1
  24. package/dist/core/Rebalancer.js +12 -6
  25. package/dist/core/Rebalancer.js.map +1 -1
  26. package/dist/core/Rebalancer.test.js +51 -0
  27. package/dist/core/Rebalancer.test.js.map +1 -1
  28. package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
  29. package/dist/core/RebalancerOrchestrator.test.js +0 -1
  30. package/dist/core/RebalancerOrchestrator.test.js.map +1 -1
  31. package/dist/core/RebalancerService.d.ts.map +1 -1
  32. package/dist/core/RebalancerService.js +3 -2
  33. package/dist/core/RebalancerService.js.map +1 -1
  34. package/dist/core/RebalancerService.test.js +24 -0
  35. package/dist/core/RebalancerService.test.js.map +1 -1
  36. package/dist/e2e/fixtures/routes.d.ts +3 -3
  37. package/dist/e2e/fixtures/routes.d.ts.map +1 -1
  38. package/dist/e2e/harness/BaseLocalDeploymentManager.d.ts.map +1 -1
  39. package/dist/e2e/harness/BaseLocalDeploymentManager.js +3 -2
  40. package/dist/e2e/harness/BaseLocalDeploymentManager.js.map +1 -1
  41. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.d.ts.map +1 -1
  42. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.js.map +1 -1
  43. package/dist/e2e/harness/Erc20LocalDeploymentManager.d.ts.map +1 -1
  44. package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
  45. package/dist/e2e/harness/ForkIndexer.js.map +1 -1
  46. package/dist/e2e/harness/LocalProvider.d.ts +3 -0
  47. package/dist/e2e/harness/LocalProvider.d.ts.map +1 -0
  48. package/dist/e2e/harness/LocalProvider.js +9 -0
  49. package/dist/e2e/harness/LocalProvider.js.map +1 -0
  50. package/dist/e2e/harness/LocalProvider.test.d.ts +2 -0
  51. package/dist/e2e/harness/LocalProvider.test.d.ts.map +1 -0
  52. package/dist/e2e/harness/LocalProvider.test.js +14 -0
  53. package/dist/e2e/harness/LocalProvider.test.js.map +1 -0
  54. package/dist/e2e/harness/MixedLocalDeploymentManager.d.ts.map +1 -1
  55. package/dist/e2e/harness/MixedLocalDeploymentManager.js.map +1 -1
  56. package/dist/e2e/harness/MockExplorerClient.d.ts.map +1 -1
  57. package/dist/e2e/harness/MockExplorerClient.js.map +1 -1
  58. package/dist/e2e/harness/MockExternalBridge.d.ts.map +1 -1
  59. package/dist/e2e/harness/MockExternalBridge.js.map +1 -1
  60. package/dist/e2e/harness/NativeLocalDeploymentManager.d.ts.map +1 -1
  61. package/dist/e2e/harness/NativeLocalDeploymentManager.js +2 -1
  62. package/dist/e2e/harness/NativeLocalDeploymentManager.js.map +1 -1
  63. package/dist/e2e/harness/SnapshotHelper.d.ts.map +1 -1
  64. package/dist/e2e/harness/SnapshotHelper.js +3 -5
  65. package/dist/e2e/harness/SnapshotHelper.js.map +1 -1
  66. package/dist/e2e/harness/TestHelpers.js +1 -2
  67. package/dist/e2e/harness/TestHelpers.js.map +1 -1
  68. package/dist/e2e/harness/TestRebalancer.d.ts +5 -1
  69. package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
  70. package/dist/e2e/harness/TestRebalancer.js +14 -4
  71. package/dist/e2e/harness/TestRebalancer.js.map +1 -1
  72. package/dist/e2e/relayer-denylist.e2e-test.d.ts +2 -0
  73. package/dist/e2e/relayer-denylist.e2e-test.d.ts.map +1 -0
  74. package/dist/e2e/relayer-denylist.e2e-test.js +388 -0
  75. package/dist/e2e/relayer-denylist.e2e-test.js.map +1 -0
  76. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  77. package/dist/factories/RebalancerContextFactory.js +61 -31
  78. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  79. package/dist/factories/RebalancerContextFactory.test.js +336 -5
  80. package/dist/factories/RebalancerContextFactory.test.js.map +1 -1
  81. package/dist/index.d.ts +2 -2
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/interfaces/IRebalancer.d.ts +4 -2
  86. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  87. package/dist/metrics/Metrics.d.ts.map +1 -1
  88. package/dist/metrics/Metrics.js +1 -1
  89. package/dist/metrics/Metrics.js.map +1 -1
  90. package/dist/metrics/PriceGetter.d.ts +1 -0
  91. package/dist/metrics/PriceGetter.d.ts.map +1 -1
  92. package/dist/metrics/PriceGetter.js +14 -4
  93. package/dist/metrics/PriceGetter.js.map +1 -1
  94. package/dist/metrics/PriceGetter.test.d.ts +2 -0
  95. package/dist/metrics/PriceGetter.test.d.ts.map +1 -0
  96. package/dist/metrics/PriceGetter.test.js +84 -0
  97. package/dist/metrics/PriceGetter.test.js.map +1 -0
  98. package/dist/metrics/scripts/metrics.d.ts +4 -4
  99. package/dist/monitor/Monitor.d.ts +1 -0
  100. package/dist/monitor/Monitor.d.ts.map +1 -1
  101. package/dist/monitor/Monitor.js +55 -46
  102. package/dist/monitor/Monitor.js.map +1 -1
  103. package/dist/monitor/Monitor.test.d.ts +2 -0
  104. package/dist/monitor/Monitor.test.d.ts.map +1 -0
  105. package/dist/monitor/Monitor.test.js +346 -0
  106. package/dist/monitor/Monitor.test.js.map +1 -0
  107. package/dist/service.d.ts +1 -1
  108. package/dist/service.d.ts.map +1 -1
  109. package/dist/service.js +9 -5
  110. package/dist/service.js.map +1 -1
  111. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  112. package/dist/strategy/BaseStrategy.js +13 -11
  113. package/dist/strategy/BaseStrategy.js.map +1 -1
  114. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
  115. package/dist/strategy/CollateralDeficitStrategy.js +2 -2
  116. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
  117. package/dist/strategy/CompositeStrategy.d.ts.map +1 -1
  118. package/dist/strategy/CompositeStrategy.js.map +1 -1
  119. package/dist/strategy/CompositeStrategy.test.js.map +1 -1
  120. package/dist/strategy/MinAmountStrategy.d.ts +1 -0
  121. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
  122. package/dist/strategy/MinAmountStrategy.js +12 -8
  123. package/dist/strategy/MinAmountStrategy.js.map +1 -1
  124. package/dist/strategy/MinAmountStrategy.test.js +189 -2
  125. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  126. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  127. package/dist/strategy/StrategyFactory.js.map +1 -1
  128. package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
  129. package/dist/test/helpers.d.ts +11 -3
  130. package/dist/test/helpers.d.ts.map +1 -1
  131. package/dist/test/helpers.js +9 -11
  132. package/dist/test/helpers.js.map +1 -1
  133. package/dist/test/lifiMocks.d.ts +6 -6
  134. package/dist/test/lifiMocks.d.ts.map +1 -1
  135. package/dist/test/lifiMocks.js +5 -2
  136. package/dist/test/lifiMocks.js.map +1 -1
  137. package/dist/test/pendingTransferMiddlewareMocks.d.ts +17 -0
  138. package/dist/test/pendingTransferMiddlewareMocks.d.ts.map +1 -0
  139. package/dist/test/pendingTransferMiddlewareMocks.js +43 -0
  140. package/dist/test/pendingTransferMiddlewareMocks.js.map +1 -0
  141. package/dist/tracking/ActionTracker.d.ts +4 -0
  142. package/dist/tracking/ActionTracker.d.ts.map +1 -1
  143. package/dist/tracking/ActionTracker.js +76 -9
  144. package/dist/tracking/ActionTracker.js.map +1 -1
  145. package/dist/tracking/ActionTracker.test.js +418 -1
  146. package/dist/tracking/ActionTracker.test.js.map +1 -1
  147. package/dist/tracking/IActionTracker.d.ts +4 -2
  148. package/dist/tracking/IActionTracker.d.ts.map +1 -1
  149. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
  150. package/dist/tracking/InflightContextAdapter.js.map +1 -1
  151. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts +64 -0
  152. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts.map +1 -0
  153. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js +474 -0
  154. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js.map +1 -0
  155. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts +2 -0
  156. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts.map +1 -0
  157. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js +1168 -0
  158. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js.map +1 -0
  159. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts +61 -0
  160. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts.map +1 -0
  161. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js +160 -0
  162. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js.map +1 -0
  163. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts +2 -0
  164. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts.map +1 -0
  165. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js +369 -0
  166. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js.map +1 -0
  167. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts +60 -0
  168. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts.map +1 -0
  169. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js +288 -0
  170. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js.map +1 -0
  171. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts +2 -0
  172. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts.map +1 -0
  173. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js +606 -0
  174. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js.map +1 -0
  175. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts +25 -0
  176. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts.map +1 -0
  177. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js +41 -0
  178. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js.map +1 -0
  179. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts +2 -0
  180. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts.map +1 -0
  181. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js +141 -0
  182. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js.map +1 -0
  183. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts +12 -0
  184. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts.map +1 -0
  185. package/dist/tracking/pendingTransferMiddlewares/matchBody.js +29 -0
  186. package/dist/tracking/pendingTransferMiddlewares/matchBody.js.map +1 -0
  187. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts +59 -0
  188. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts.map +1 -0
  189. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js +661 -0
  190. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js.map +1 -0
  191. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts +2 -0
  192. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts.map +1 -0
  193. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js +1093 -0
  194. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js.map +1 -0
  195. package/dist/tracking/pendingTransferMiddlewares/types.d.ts +2 -0
  196. package/dist/tracking/pendingTransferMiddlewares/types.d.ts.map +1 -0
  197. package/dist/tracking/pendingTransferMiddlewares/types.js +4 -0
  198. package/dist/tracking/pendingTransferMiddlewares/types.js.map +1 -0
  199. package/dist/tracking/store/InMemoryStore.d.ts.map +1 -1
  200. package/dist/utils/ExplorerClient.d.ts +1 -0
  201. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  202. package/dist/utils/ExplorerClient.js +8 -3
  203. package/dist/utils/ExplorerClient.js.map +1 -1
  204. package/dist/utils/ExplorerClient.test.js +25 -0
  205. package/dist/utils/ExplorerClient.test.js.map +1 -1
  206. package/dist/utils/balanceUtils.d.ts +7 -1
  207. package/dist/utils/balanceUtils.d.ts.map +1 -1
  208. package/dist/utils/balanceUtils.js +39 -1
  209. package/dist/utils/balanceUtils.js.map +1 -1
  210. package/dist/utils/balanceUtils.test.js +55 -1
  211. package/dist/utils/balanceUtils.test.js.map +1 -1
  212. package/dist/utils/blockTag.d.ts +5 -4
  213. package/dist/utils/blockTag.d.ts.map +1 -1
  214. package/dist/utils/blockTag.js +26 -10
  215. package/dist/utils/blockTag.js.map +1 -1
  216. package/dist/utils/blockTag.test.d.ts +2 -0
  217. package/dist/utils/blockTag.test.d.ts.map +1 -0
  218. package/dist/utils/blockTag.test.js +132 -0
  219. package/dist/utils/blockTag.test.js.map +1 -0
  220. package/dist/utils/bridgeUtils.js.map +1 -1
  221. package/dist/utils/errors.d.ts.map +1 -1
  222. package/dist/utils/gasEstimation.d.ts +2 -1
  223. package/dist/utils/gasEstimation.d.ts.map +1 -1
  224. package/dist/utils/gasEstimation.js +16 -13
  225. package/dist/utils/gasEstimation.js.map +1 -1
  226. package/dist/utils/gasEstimation.test.d.ts +2 -0
  227. package/dist/utils/gasEstimation.test.d.ts.map +1 -0
  228. package/dist/utils/gasEstimation.test.js +104 -0
  229. package/dist/utils/gasEstimation.test.js.map +1 -0
  230. package/dist/utils/tokenUtils.d.ts.map +1 -1
  231. package/dist/utils/tokenUtils.js +5 -2
  232. package/dist/utils/tokenUtils.js.map +1 -1
  233. package/package.json +23 -22
  234. package/src/bridges/LiFiBridge.test.ts +270 -0
  235. package/src/bridges/LiFiBridge.ts +90 -71
  236. package/src/config/RebalancerConfig.test.ts +315 -1
  237. package/src/config/RebalancerConfig.ts +6 -3
  238. package/src/config/types.ts +98 -8
  239. package/src/core/InventoryRebalancer.test.ts +1092 -38
  240. package/src/core/InventoryRebalancer.ts +584 -351
  241. package/src/core/Rebalancer.test.ts +84 -0
  242. package/src/core/Rebalancer.ts +22 -6
  243. package/src/core/RebalancerOrchestrator.test.ts +0 -1
  244. package/src/core/RebalancerService.test.ts +35 -0
  245. package/src/core/RebalancerService.ts +3 -2
  246. package/src/e2e/harness/BaseLocalDeploymentManager.ts +3 -1
  247. package/src/e2e/harness/LocalProvider.test.ts +16 -0
  248. package/src/e2e/harness/LocalProvider.ts +10 -0
  249. package/src/e2e/harness/NativeLocalDeploymentManager.ts +2 -1
  250. package/src/e2e/harness/SnapshotHelper.ts +2 -5
  251. package/src/e2e/harness/TestHelpers.ts +3 -3
  252. package/src/e2e/harness/TestRebalancer.ts +21 -1
  253. package/src/e2e/relayer-denylist.e2e-test.ts +558 -0
  254. package/src/factories/RebalancerContextFactory.test.ts +489 -8
  255. package/src/factories/RebalancerContextFactory.ts +86 -36
  256. package/src/index.ts +2 -0
  257. package/src/interfaces/IRebalancer.ts +4 -1
  258. package/src/metrics/Metrics.ts +1 -0
  259. package/src/metrics/PriceGetter.test.ts +113 -0
  260. package/src/metrics/PriceGetter.ts +18 -3
  261. package/src/monitor/Monitor.test.ts +408 -0
  262. package/src/monitor/Monitor.ts +123 -97
  263. package/src/service.ts +14 -8
  264. package/src/strategy/BaseStrategy.ts +18 -15
  265. package/src/strategy/CollateralDeficitStrategy.ts +4 -3
  266. package/src/strategy/MinAmountStrategy.test.ts +238 -2
  267. package/src/strategy/MinAmountStrategy.ts +29 -17
  268. package/src/test/helpers.ts +13 -12
  269. package/src/test/lifiMocks.ts +5 -2
  270. package/src/test/pendingTransferMiddlewareMocks.ts +65 -0
  271. package/src/tracking/ActionTracker.test.ts +635 -3
  272. package/src/tracking/ActionTracker.ts +92 -12
  273. package/src/tracking/IActionTracker.ts +4 -2
  274. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.ts +1693 -0
  275. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.ts +784 -0
  276. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.ts +554 -0
  277. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.ts +233 -0
  278. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.ts +894 -0
  279. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.ts +404 -0
  280. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.ts +205 -0
  281. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.ts +89 -0
  282. package/src/tracking/pendingTransferMiddlewares/fixtures/mainnet-denylist.json +3556 -0
  283. package/src/tracking/pendingTransferMiddlewares/matchBody.ts +43 -0
  284. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.ts +1220 -0
  285. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.ts +934 -0
  286. package/src/tracking/pendingTransferMiddlewares/types.ts +3 -0
  287. package/src/utils/ExplorerClient.test.ts +42 -0
  288. package/src/utils/ExplorerClient.ts +14 -3
  289. package/src/utils/balanceUtils.test.ts +87 -1
  290. package/src/utils/balanceUtils.ts +73 -2
  291. package/src/utils/blockTag.test.ts +173 -0
  292. package/src/utils/blockTag.ts +35 -15
  293. package/src/utils/gasEstimation.test.ts +192 -0
  294. package/src/utils/gasEstimation.ts +17 -11
  295. package/src/utils/tokenUtils.ts +5 -2
@@ -0,0 +1,1168 @@
1
+ import chai, { expect } from 'chai';
2
+ import chaiAsPromised from 'chai-as-promised';
3
+ import { ethers } from 'ethers';
4
+ import { pino } from 'pino';
5
+ import Sinon from 'sinon';
6
+ import { BlacklistIsm__factory, DomainRoutingIsm__factory, IInterchainSecurityModule__factory, MailboxClient__factory, Mailbox__factory, StaticAggregationIsm__factory, } from '@humanode-hyperlane/core';
7
+ import { EvmIsmReader, IsmType, MultiProvider, } from '@humanode-hyperlane/sdk';
8
+ import { ProtocolType } from '@humanode-hyperlane/utils';
9
+ import { BLACKLIST_ISM_NO_BLOCKING_ROUTES_MESSAGE, BLACKLIST_ISM_REFRESH_FAILED_MESSAGE, BlacklistIsmMiddleware, } from './BlacklistIsmMiddleware.js';
10
+ import { PENDING_TRANSFER_MIDDLEWARE_FAILED_MESSAGE } from './PendingTransferMiddleware.js';
11
+ chai.use(chaiAsPromised);
12
+ const ETHEREUM = 1;
13
+ const ARBITRUM = 42161;
14
+ const RADIX = 1633970780;
15
+ const CHAIN_NAMES = {
16
+ [ETHEREUM]: 'ethereum',
17
+ [ARBITRUM]: 'arbitrum',
18
+ [RADIX]: 'radix',
19
+ };
20
+ const REFRESH_INTERVAL_MS = 600_000;
21
+ const MAILBOX = '0xc005dc82818d67af737725bd4bf75435d065d239';
22
+ const ROUTER = '0x67899664446ccfadf75e0d3f5668be262acd1069';
23
+ const ARBITRUM_ROUTER = '0x867a19a5b257bcb99e1840de2ef852268efd5e46';
24
+ const RADIX_ROUTER = '0x5d6b4e1a3c2f7e8d9b0a1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f';
25
+ const OWNER = '0x995a921d333029512aa166885856c8af760e9381';
26
+ const AGGREGATION_ISM = '0xa076bf16992ef5e54a52b4002e3a1b5353a5f14f';
27
+ const INNER_AGGREGATION_ISM = '0x7777777777777777777777777777777777777777';
28
+ const BLACKLIST_ISM = '0x435e9f0e54af1e0c37a6075233059f7625f2cfc9';
29
+ const SECOND_BLACKLIST_ISM = '0x3333333333333333333333333333333333333333';
30
+ const ARBITRUM_BLACKLIST_ISM = '0x6666666666666666666666666666666666666666';
31
+ const ROUTING_ISM = '0x1111111111111111111111111111111111111111';
32
+ const MULTISIG_ISM = '0x2222222222222222222222222222222222222222';
33
+ const PAUSABLE_ISM = '0x52d10c47e9e4a20e4561271a8440e32c44c00f45';
34
+ const DEFAULT_ISM = '0x4444444444444444444444444444444444444444';
35
+ const MAILBOX_DEFAULT_ISM = '0x5555555555555555555555555555555555555555';
36
+ const BLACKLISTED_ID = '0x8960c5e41eb0d91973140ebc196892f5f07f512dd86047e8536e71424462c5d3';
37
+ const CLEAN_ID = '0xdae65a8dd678bf8ea03faed7d53b99e52f5bdbae7c95834eff009cc37a18103c';
38
+ const mailboxInterface = Mailbox__factory.createInterface();
39
+ const blacklistInterface = BlacklistIsm__factory.createInterface();
40
+ const ismInterface = IInterchainSecurityModule__factory.createInterface();
41
+ const aggregationInterface = StaticAggregationIsm__factory.createInterface();
42
+ const routingInterface = DomainRoutingIsm__factory.createInterface();
43
+ const errorStringInterface = new ethers.utils.Interface([
44
+ 'function Error(string)',
45
+ ]);
46
+ const MODULE_TYPE = { ROUTING: 1, AGGREGATION: 2, MULTISIG: 5, NULL: 6 };
47
+ const MODULE_TYPE_SELECTOR = ismInterface.getSighash('moduleType');
48
+ const MODULE_SELECTOR = routingInterface.getSighash('module');
49
+ const mailboxClientInterface = MailboxClient__factory.createInterface();
50
+ const MAILBOX_SELECTOR = mailboxClientInterface.getSighash('mailbox');
51
+ const VALUES_SELECTOR = blacklistInterface.getSighash('values');
52
+ const PARAMS = {
53
+ routersByDomain: { [ETHEREUM]: ROUTER, [ARBITRUM]: ARBITRUM_ROUTER },
54
+ excludeTxSenders: [],
55
+ };
56
+ const EXPECTED_REASON = `Blacklisted by a BlacklistIsm (${BLACKLIST_ISM}) in the ISM of router ${ROUTER} on domain ${ETHEREUM}`;
57
+ const THIRD_ID = '0x1c3b5f0e2d4a6c8e0f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70';
58
+ function blacklist(address, ids = []) {
59
+ return {
60
+ type: IsmType.BLACKLIST,
61
+ address,
62
+ owner: OWNER,
63
+ blacklistedIds: ids,
64
+ };
65
+ }
66
+ function multisig(address) {
67
+ return {
68
+ type: IsmType.MESSAGE_ID_MULTISIG,
69
+ address,
70
+ validators: [OWNER],
71
+ threshold: 1,
72
+ };
73
+ }
74
+ function aggregation(address, modules, threshold) {
75
+ return { type: IsmType.AGGREGATION, address, modules, threshold };
76
+ }
77
+ function domainRouting(type, address, domains) {
78
+ return { type, address, owner: OWNER, domains };
79
+ }
80
+ function mailboxDefaultWrapper(address) {
81
+ return { type: IsmType.MAILBOX_DEFAULT, address };
82
+ }
83
+ /** The incident ISM: mailbox default wrapper, BlacklistIsm and PausableIsm. */
84
+ function incidentIsm(threshold, blacklistedIds = [BLACKLISTED_ID]) {
85
+ return aggregation(AGGREGATION_ISM, [
86
+ mailboxDefaultWrapper(DEFAULT_ISM),
87
+ blacklist(BLACKLIST_ISM, blacklistedIds),
88
+ {
89
+ type: IsmType.PAUSABLE,
90
+ address: PAUSABLE_ISM,
91
+ owner: OWNER,
92
+ paused: false,
93
+ },
94
+ ], threshold);
95
+ }
96
+ /** Answers eth_call by contract address and selector. */
97
+ class FakeChain {
98
+ provider = new ethers.providers.StaticJsonRpcProvider('http://127.0.0.1:1', { chainId: ETHEREUM, name: 'fake' });
99
+ /** `${to}:${selector}` of every call, in order. */
100
+ calls = [];
101
+ /** Origins passed to `module(origin)`, in order. */
102
+ routedOrigins = [];
103
+ /** Block tags of every `values()` call, in order. */
104
+ valuesBlockTags = [];
105
+ /** Thrown by every `values()` call at the finalized block when set. */
106
+ finalizedError;
107
+ handlers = new Map();
108
+ recipientIsms = new Map();
109
+ constructor() {
110
+ Sinon.stub(this.provider, 'call').callsFake(async (tx, blockTag) => {
111
+ const { to, data } = await ethers.utils.resolveProperties(tx);
112
+ if (typeof to !== 'string' || typeof data !== 'string') {
113
+ throw new Error('Unexpected call');
114
+ }
115
+ const key = `${to.toLowerCase()}:${data.slice(0, 10)}`;
116
+ this.calls.push(key);
117
+ const handler = this.handlers.get(key);
118
+ if (!handler)
119
+ throw new Error(`Unexpected call ${key}`);
120
+ return handler(data, await blockTag);
121
+ });
122
+ }
123
+ on(address, selector, handler) {
124
+ this.handlers.set(`${address.toLowerCase()}:${selector}`, handler);
125
+ }
126
+ recipientIsm(router, ism) {
127
+ this.recipientIsms.set(router, ism);
128
+ this.on(MAILBOX, mailboxInterface.getSighash('recipientIsm'), (data) => {
129
+ const [recipient] = mailboxInterface.decodeFunctionData('recipientIsm', data);
130
+ const routerIsm = this.recipientIsms.get(String(recipient).toLowerCase());
131
+ if (!routerIsm)
132
+ throw new Error(`Unexpected recipient ${recipient}`);
133
+ return mailboxInterface.encodeFunctionResult('recipientIsm', [routerIsm]);
134
+ });
135
+ }
136
+ defaultIsm(ism, mailbox = MAILBOX) {
137
+ this.on(mailbox, mailboxInterface.getSighash('defaultIsm'), () => mailboxInterface.encodeFunctionResult('defaultIsm', [ism]));
138
+ }
139
+ /** The mailbox a wrapper or mailbox client reports. */
140
+ mailboxOf(ism, mailbox = MAILBOX) {
141
+ this.on(ism, MAILBOX_SELECTOR, () => mailboxClientInterface.encodeFunctionResult('mailbox', [mailbox]));
142
+ }
143
+ moduleType(address, moduleType) {
144
+ this.on(address, MODULE_TYPE_SELECTOR, () => ismInterface.encodeFunctionResult('moduleType', [moduleType]));
145
+ }
146
+ aggregationIsm(address, modules, threshold) {
147
+ this.moduleType(address, MODULE_TYPE.AGGREGATION);
148
+ this.on(address, aggregationInterface.getSighash('modulesAndThreshold'), () => aggregationInterface.encodeFunctionResult('modulesAndThreshold', [
149
+ modules,
150
+ threshold,
151
+ ]));
152
+ }
153
+ /** A domain routing ISM; `module(origin)` reverts for an unset origin. */
154
+ domainRoutingIsm(address, modules) {
155
+ this.moduleType(address, MODULE_TYPE.ROUTING);
156
+ this.on(address, MODULE_SELECTOR, (data) => {
157
+ const [origin] = routingInterface.decodeFunctionData('module', data);
158
+ this.routedOrigins.push(Number(origin));
159
+ const module = modules[Number(origin)];
160
+ return module
161
+ ? routingInterface.encodeFunctionResult('module', [module])
162
+ : errorStringInterface.encodeFunctionData('Error', [
163
+ `No ISM found for origin: ${origin}`,
164
+ ]);
165
+ });
166
+ }
167
+ /** A contract without the selector: the call returns no data. */
168
+ missingSelector(address, selector) {
169
+ this.on(address, selector, () => '0x');
170
+ }
171
+ /**
172
+ * Serves the BlacklistIsm's `values()`: `ids` at the finalized block and
173
+ * `latestIds` at the latest one.
174
+ */
175
+ blacklist(address, ids, latestIds = ids) {
176
+ this.on(address, VALUES_SELECTOR, (_data, blockTag) => {
177
+ this.valuesBlockTags.push(blockTag);
178
+ if (blockTag === 'finalized') {
179
+ if (this.finalizedError)
180
+ throw this.finalizedError;
181
+ return blacklistInterface.encodeFunctionResult('values', [ids]);
182
+ }
183
+ if (blockTag === undefined || blockTag === 'latest') {
184
+ return blacklistInterface.encodeFunctionResult('values', [latestIds]);
185
+ }
186
+ throw new Error(`Unexpected block tag ${blockTag}`);
187
+ });
188
+ }
189
+ /** Serves `values()` for every BlacklistIsm in `config` with its ids. */
190
+ blacklistsIn(config) {
191
+ if (typeof config === 'string')
192
+ return;
193
+ switch (config.type) {
194
+ case IsmType.BLACKLIST:
195
+ if ('address' in config && typeof config.address === 'string') {
196
+ this.blacklist(config.address, config.blacklistedIds);
197
+ }
198
+ return;
199
+ case IsmType.AGGREGATION:
200
+ case IsmType.STORAGE_AGGREGATION:
201
+ config.modules.forEach((module) => this.blacklistsIn(module));
202
+ return;
203
+ case IsmType.ROUTING:
204
+ case IsmType.INCREMENTAL_ROUTING:
205
+ case IsmType.FALLBACK_ROUTING:
206
+ Object.values(config.domains).forEach((module) => this.blacklistsIn(module));
207
+ return;
208
+ default:
209
+ return;
210
+ }
211
+ }
212
+ }
213
+ function buildMessage(msgId, origin = ARBITRUM, destination = ETHEREUM) {
214
+ return {
215
+ msg_id: msgId,
216
+ origin_domain_id: origin,
217
+ destination_domain_id: destination,
218
+ sender: ARBITRUM_ROUTER,
219
+ recipient: ROUTER,
220
+ origin_tx_hash: '0x5e3c1f6a3c1a7f86e0f2d5e9c9c1b8d1e1e7e2b6c4d8a9f0e1d2c3b4a5968778',
221
+ origin_tx_sender: OWNER,
222
+ origin_tx_recipient: ARBITRUM_ROUTER,
223
+ is_delivered: false,
224
+ message_body: `0x${'00'.repeat(64)}`,
225
+ send_occurred_at: null,
226
+ };
227
+ }
228
+ describe('BlacklistIsmMiddleware', () => {
229
+ let chain;
230
+ let clock;
231
+ let logger;
232
+ let warn;
233
+ let info;
234
+ let derivedConfigs;
235
+ let deriveIsmConfig;
236
+ let explorerMessages;
237
+ const inner = {
238
+ getInflightUserTransfers: async () => explorerMessages,
239
+ getInflightRebalanceActions: async () => [],
240
+ };
241
+ beforeEach(() => {
242
+ chain = new FakeChain();
243
+ chain.recipientIsm(ARBITRUM_ROUTER, MULTISIG_ISM);
244
+ clock = Sinon.useFakeTimers({ now: 1_000_000, toFake: ['Date'] });
245
+ logger = pino({ level: 'silent' });
246
+ warn = Sinon.spy(logger, 'warn');
247
+ info = Sinon.spy(logger, 'info');
248
+ explorerMessages = [];
249
+ derivedConfigs = new Map([[MULTISIG_ISM, multisig(MULTISIG_ISM)]]);
250
+ chain.defaultIsm(MAILBOX_DEFAULT_ISM);
251
+ chain.moduleType(MAILBOX_DEFAULT_ISM, MODULE_TYPE.MULTISIG);
252
+ chain.moduleType(MULTISIG_ISM, MODULE_TYPE.MULTISIG);
253
+ chain.mailboxOf(DEFAULT_ISM);
254
+ chain.mailboxOf(ROUTING_ISM);
255
+ deriveIsmConfig = Sinon.stub(EvmIsmReader.prototype, 'deriveIsmConfigFromAddress').callsFake(async (address) => {
256
+ const config = derivedConfigs.get(address.toLowerCase());
257
+ if (!config)
258
+ throw new Error(`No derived config for ${address}`);
259
+ return config;
260
+ });
261
+ });
262
+ afterEach(() => {
263
+ clock.restore();
264
+ deriveIsmConfig.restore();
265
+ });
266
+ /** The chain and ISM address of every startup derivation, in order. */
267
+ function derivations() {
268
+ return deriveIsmConfig
269
+ .getCalls()
270
+ .map((call) => [call.thisValue['chain'], call.args[0]]);
271
+ }
272
+ /**
273
+ * Registers a derived config, with its BlacklistIsms reporting the same ids
274
+ * at the finalized block.
275
+ */
276
+ function derivedIsm(config) {
277
+ derivedConfigs.set(config.address.toLowerCase(), config);
278
+ chain.blacklistsIn(config);
279
+ }
280
+ /** Registers a derived config and points the router at it. */
281
+ function routerIsm(router, config) {
282
+ derivedIsm(config);
283
+ chain.recipientIsm(router, config.address);
284
+ }
285
+ /** Makes the mailbox default ISM a 1-of-1 aggregation of a BlacklistIsm. */
286
+ function defaultIsmWithBlacklist() {
287
+ chain.aggregationIsm(MAILBOX_DEFAULT_ISM, [BLACKLIST_ISM], 1);
288
+ chain.moduleType(BLACKLIST_ISM, MODULE_TYPE.NULL);
289
+ derivedIsm(blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]));
290
+ }
291
+ /** Gives the route a second pair whose ISM can block, so startup succeeds. */
292
+ function blockingArbitrumIsm() {
293
+ routerIsm(ARBITRUM_ROUTER, blacklist(ARBITRUM_BLACKLIST_ISM));
294
+ chain.blacklist(ARBITRUM_BLACKLIST_ISM, []);
295
+ }
296
+ function createMiddleware() {
297
+ const multiProvider = Sinon.createStubInstance(MultiProvider);
298
+ multiProvider.getProtocol.callsFake((domain) => domain === RADIX ? ProtocolType.Radix : ProtocolType.Ethereum);
299
+ multiProvider.getChainName.callsFake((domain) => CHAIN_NAMES[Number(domain)]);
300
+ multiProvider.getProvider.returns(chain.provider);
301
+ // EvmIsmReader's constructor reads chain metadata and explorer settings.
302
+ multiProvider.getChainMetadata.callsFake((chainName) => ({
303
+ name: String(chainName),
304
+ chainId: ETHEREUM,
305
+ domainId: ETHEREUM,
306
+ protocol: ProtocolType.Ethereum,
307
+ rpcUrls: [{ http: 'http://127.0.0.1:1' }],
308
+ }));
309
+ multiProvider.tryGetEvmExplorerMetadataList.returns([]);
310
+ return BlacklistIsmMiddleware.create(inner, {
311
+ refreshIntervalMs: REFRESH_INTERVAL_MS,
312
+ routersByDomain: {
313
+ [ETHEREUM]: ROUTER,
314
+ [ARBITRUM]: ARBITRUM_ROUTER,
315
+ [RADIX]: RADIX_ROUTER,
316
+ },
317
+ mailboxByDomain: { [ETHEREUM]: MAILBOX, [ARBITRUM]: MAILBOX },
318
+ multiProvider,
319
+ }, logger);
320
+ }
321
+ async function createMiddlewareAndCountCalls() {
322
+ const middleware = await createMiddleware();
323
+ return { middleware, callsAfterInit: chain.calls.length };
324
+ }
325
+ /** The `values()` calls made since `start`, in order. */
326
+ function valuesCallsSince(start) {
327
+ return chain.calls
328
+ .slice(start)
329
+ .filter((call) => call.endsWith(`:${VALUES_SELECTOR}`));
330
+ }
331
+ /** Ids of `messages` the middleware drops when the Explorer returns them. */
332
+ async function queryIgnoredIds(middleware, messages) {
333
+ explorerMessages = messages;
334
+ const kept = await middleware.getInflightUserTransfers(PARAMS, logger);
335
+ const keptIds = new Set(kept.map((msg) => msg.msg_id));
336
+ expect(warn.calledWith(Sinon.match.any, PENDING_TRANSFER_MIDDLEWARE_FAILED_MESSAGE)).to.be.false;
337
+ return messages.map((msg) => msg.msg_id).filter((id) => !keptIds.has(id));
338
+ }
339
+ function expectIgnoredWithReason(messageId, reason) {
340
+ expect(info.calledWith(Sinon.match({ msgId: messageId, reason }), 'Ignoring pending transfer')).to.be.true;
341
+ }
342
+ it('ignores a message blacklisted in a 3-of-3 aggregation with no runtime RPC', async () => {
343
+ routerIsm(ROUTER, incidentIsm(3));
344
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
345
+ const ignored = await queryIgnoredIds(middleware, [
346
+ buildMessage(BLACKLISTED_ID),
347
+ buildMessage(CLEAN_ID, RADIX),
348
+ ]);
349
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
350
+ expect(middleware.isIgnored(BLACKLISTED_ID)).to.be.true;
351
+ expectIgnoredWithReason(BLACKLISTED_ID, EXPECTED_REASON);
352
+ expect(chain.calls).to.have.lengthOf(callsAfterInit);
353
+ expect(warn.called).to.be.false;
354
+ });
355
+ it('drops a 2-of-3 aggregation the BlacklistIsm cannot block on its own', async () => {
356
+ routerIsm(ROUTER, incidentIsm(2));
357
+ blockingArbitrumIsm();
358
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
359
+ clock.tick(REFRESH_INTERVAL_MS);
360
+ const ignored = await queryIgnoredIds(middleware, [
361
+ buildMessage(BLACKLISTED_ID),
362
+ ]);
363
+ expect(ignored).to.deep.equal([]);
364
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
365
+ `${ARBITRUM_BLACKLIST_ISM}:${VALUES_SELECTOR}`,
366
+ ]);
367
+ });
368
+ const nestedCases = [
369
+ {
370
+ name: 'drops a nested 1-of-2 subtree whose BlacklistIsm cannot block',
371
+ innerThreshold: 1,
372
+ ignored: false,
373
+ },
374
+ {
375
+ name: 'keeps a nested 2-of-2 subtree whose BlacklistIsm can block',
376
+ innerThreshold: 2,
377
+ ignored: true,
378
+ },
379
+ ];
380
+ for (const c of nestedCases) {
381
+ it(c.name, async () => {
382
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
383
+ multisig(MULTISIG_ISM),
384
+ aggregation(INNER_AGGREGATION_ISM, [
385
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
386
+ multisig(MULTISIG_ISM),
387
+ ], c.innerThreshold),
388
+ ], 2));
389
+ blockingArbitrumIsm();
390
+ const middleware = await createMiddleware();
391
+ const ignored = await queryIgnoredIds(middleware, [
392
+ buildMessage(BLACKLISTED_ID),
393
+ ]);
394
+ expect(ignored.includes(BLACKLISTED_ID)).to.equal(c.ignored);
395
+ });
396
+ }
397
+ it('ignores a message one mandatory BlacklistIsm among several rejects', async () => {
398
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
399
+ multisig(MULTISIG_ISM),
400
+ blacklist(BLACKLIST_ISM),
401
+ blacklist(SECOND_BLACKLIST_ISM, [BLACKLISTED_ID]),
402
+ ], 3));
403
+ const middleware = await createMiddleware();
404
+ const ignored = await queryIgnoredIds(middleware, [
405
+ buildMessage(BLACKLISTED_ID),
406
+ ]);
407
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
408
+ expectIgnoredWithReason(BLACKLISTED_ID, `Blacklisted by a BlacklistIsm (${BLACKLIST_ISM}, ${SECOND_BLACKLIST_ISM}) in the ISM of router ${ROUTER} on domain ${ETHEREUM}`);
409
+ });
410
+ it('ignores a message in one BlacklistIsm of a 2-of-2 aggregation of BlacklistIsms', async () => {
411
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
412
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
413
+ blacklist(SECOND_BLACKLIST_ISM),
414
+ ], 2));
415
+ const middleware = await createMiddleware();
416
+ const ignored = await queryIgnoredIds(middleware, [
417
+ buildMessage(BLACKLISTED_ID),
418
+ ]);
419
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
420
+ });
421
+ it('counts copies of one BlacklistIsm together in an aggregation', async () => {
422
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
423
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
424
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
425
+ multisig(MULTISIG_ISM),
426
+ ], 2));
427
+ const middleware = await createMiddleware();
428
+ const ignored = await queryIgnoredIds(middleware, [
429
+ buildMessage(BLACKLISTED_ID),
430
+ ]);
431
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
432
+ });
433
+ it('shares one BlacklistIsm that blocks through only one of its branches', async () => {
434
+ const secondInnerAggregation = '0x8888888888888888888888888888888888888888';
435
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
436
+ aggregation(INNER_AGGREGATION_ISM, [
437
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
438
+ multisig(MULTISIG_ISM),
439
+ ], 1),
440
+ aggregation(secondInnerAggregation, [
441
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
442
+ multisig(MULTISIG_ISM),
443
+ ], 2),
444
+ ], 2));
445
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]);
446
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
447
+ clock.tick(REFRESH_INTERVAL_MS);
448
+ const ignored = await queryIgnoredIds(middleware, [
449
+ buildMessage(BLACKLISTED_ID),
450
+ ]);
451
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
452
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
453
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
454
+ ]);
455
+ });
456
+ it('treats BlacklistIsms that only block together as never blocking', async () => {
457
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
458
+ multisig(MULTISIG_ISM),
459
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
460
+ blacklist(SECOND_BLACKLIST_ISM, [BLACKLISTED_ID]),
461
+ ], 2));
462
+ blockingArbitrumIsm();
463
+ const middleware = await createMiddleware();
464
+ const ignored = await queryIgnoredIds(middleware, [
465
+ buildMessage(BLACKLISTED_ID),
466
+ ]);
467
+ expect(ignored).to.deep.equal([]);
468
+ });
469
+ it('fails to initialize when no route pair has a BlacklistIsm that can block', async () => {
470
+ routerIsm(ROUTER, incidentIsm(2));
471
+ const result = createMiddleware();
472
+ await expect(result).to.be.rejectedWith('No route pair has a BlacklistIsm that can block a message; remove the blacklistIsm pending transfer middleware');
473
+ });
474
+ const routingCases = [
475
+ { type: IsmType.ROUTING },
476
+ { type: IsmType.INCREMENTAL_ROUTING },
477
+ ];
478
+ for (const c of routingCases) {
479
+ it(`skips and warns about a pair a ${c.type} has no module for`, async () => {
480
+ routerIsm(ROUTER, domainRouting(c.type, ROUTING_ISM, {
481
+ arbitrum: blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
482
+ }));
483
+ const middleware = await createMiddleware();
484
+ expect(warn.callCount).to.equal(1);
485
+ expect(warn.firstCall.args).to.deep.equal([
486
+ {
487
+ router: ROUTER,
488
+ destination: ETHEREUM,
489
+ origin: RADIX,
490
+ routingIsm: ROUTING_ISM,
491
+ },
492
+ 'Routing ISM has no module for origin, skipping BlacklistIsm check for this route pair',
493
+ ]);
494
+ const fromRadix = await queryIgnoredIds(middleware, [
495
+ buildMessage(BLACKLISTED_ID, RADIX),
496
+ ]);
497
+ expect(fromRadix).to.deep.equal([]);
498
+ const fromArbitrum = await queryIgnoredIds(middleware, [
499
+ buildMessage(BLACKLISTED_ID, ARBITRUM),
500
+ ]);
501
+ expect(fromArbitrum).to.deep.equal([BLACKLISTED_ID]);
502
+ });
503
+ }
504
+ it('uses the mailbox default ISM for an origin a fallback routing ISM does not list', async () => {
505
+ routerIsm(ROUTER, domainRouting(IsmType.FALLBACK_ROUTING, ROUTING_ISM, {
506
+ arbitrum: multisig(MULTISIG_ISM),
507
+ }));
508
+ defaultIsmWithBlacklist();
509
+ const middleware = await createMiddleware();
510
+ const ignored = await queryIgnoredIds(middleware, [
511
+ buildMessage(BLACKLISTED_ID, RADIX),
512
+ buildMessage(CLEAN_ID, ARBITRUM),
513
+ ]);
514
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
515
+ expect(warn.called).to.be.false;
516
+ expect(derivations()).to.not.deep.include([
517
+ 'ethereum',
518
+ ethers.utils.getAddress(MAILBOX_DEFAULT_ISM),
519
+ ]);
520
+ });
521
+ it('uses the listed module of a fallback routing ISM', async () => {
522
+ routerIsm(ROUTER, domainRouting(IsmType.FALLBACK_ROUTING, ROUTING_ISM, {
523
+ arbitrum: blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
524
+ radix: multisig(MULTISIG_ISM),
525
+ }));
526
+ const middleware = await createMiddleware();
527
+ const fromArbitrum = await queryIgnoredIds(middleware, [
528
+ buildMessage(BLACKLISTED_ID, ARBITRUM),
529
+ ]);
530
+ const fromRadix = await queryIgnoredIds(middleware, [
531
+ buildMessage(BLACKLISTED_ID, RADIX),
532
+ ]);
533
+ expect(fromArbitrum).to.deep.equal([BLACKLISTED_ID]);
534
+ expect(fromRadix).to.deep.equal([]);
535
+ expect(chain.calls).to.not.include(`${MAILBOX_DEFAULT_ISM}:${MODULE_TYPE_SELECTOR}`);
536
+ });
537
+ const nestedFallbackCases = [
538
+ {
539
+ name: 'inside an aggregation',
540
+ routerConfig: () => aggregation(AGGREGATION_ISM, [
541
+ multisig(MULTISIG_ISM),
542
+ domainRouting(IsmType.FALLBACK_ROUTING, ROUTING_ISM, {
543
+ arbitrum: multisig(MULTISIG_ISM),
544
+ }),
545
+ ], 2),
546
+ },
547
+ {
548
+ name: 'routed to by a domain routing ISM',
549
+ routerConfig: () => domainRouting(IsmType.ROUTING, INNER_AGGREGATION_ISM, {
550
+ arbitrum: multisig(MULTISIG_ISM),
551
+ radix: domainRouting(IsmType.FALLBACK_ROUTING, ROUTING_ISM, {
552
+ arbitrum: multisig(MULTISIG_ISM),
553
+ }),
554
+ }),
555
+ },
556
+ ];
557
+ for (const c of nestedFallbackCases) {
558
+ it(`uses the mailbox default ISM for a fallback routing ISM ${c.name}`, async () => {
559
+ routerIsm(ROUTER, c.routerConfig());
560
+ defaultIsmWithBlacklist();
561
+ const middleware = await createMiddleware();
562
+ const ignored = await queryIgnoredIds(middleware, [
563
+ buildMessage(BLACKLISTED_ID, RADIX),
564
+ buildMessage(CLEAN_ID, ARBITRUM),
565
+ ]);
566
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
567
+ expect(chain.calls).to.include(`${MAILBOX_DEFAULT_ISM}:${MODULE_TYPE_SELECTOR}`);
568
+ });
569
+ }
570
+ it('fails to initialize when the mailbox default ISM falls back to itself', async () => {
571
+ routerIsm(ROUTER, domainRouting(IsmType.FALLBACK_ROUTING, ROUTING_ISM, {
572
+ arbitrum: blacklist(BLACKLIST_ISM),
573
+ }));
574
+ // A default fallback routing ISM returns the mailbox default for an
575
+ // unset origin, which is itself.
576
+ chain.domainRoutingIsm(MAILBOX_DEFAULT_ISM, {
577
+ [ARBITRUM]: MULTISIG_ISM,
578
+ [RADIX]: MAILBOX_DEFAULT_ISM,
579
+ });
580
+ const result = createMiddleware();
581
+ await expect(result).to.be.rejectedWith(`Failed to resolve the ISM of router ${ROUTER} on domain ${ETHEREUM}: Mailbox default ISM ${MAILBOX_DEFAULT_ISM} loops back to itself for radix`);
582
+ });
583
+ it("reads only the route's branches of a domain routing mailbox default ISM", async () => {
584
+ const modules = {
585
+ [ARBITRUM]: AGGREGATION_ISM,
586
+ [RADIX]: MULTISIG_ISM,
587
+ };
588
+ for (let domain = 1_000; domain < 1_150; domain++) {
589
+ modules[domain] = MULTISIG_ISM;
590
+ }
591
+ chain.recipientIsm(ROUTER, MAILBOX_DEFAULT_ISM);
592
+ chain.domainRoutingIsm(MAILBOX_DEFAULT_ISM, modules);
593
+ chain.aggregationIsm(AGGREGATION_ISM, [MULTISIG_ISM, BLACKLIST_ISM], 2);
594
+ chain.moduleType(BLACKLIST_ISM, MODULE_TYPE.NULL);
595
+ derivedIsm(blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]));
596
+ const middleware = await createMiddleware();
597
+ const ignored = await queryIgnoredIds(middleware, [
598
+ buildMessage(BLACKLISTED_ID, ARBITRUM),
599
+ buildMessage(CLEAN_ID, RADIX),
600
+ ]);
601
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
602
+ expect(chain.routedOrigins).to.deep.equal([ARBITRUM, RADIX]);
603
+ expect(derivations()).to.deep.equal([
604
+ ['ethereum', ethers.utils.getAddress(BLACKLIST_ISM)],
605
+ ['arbitrum', ethers.utils.getAddress(MULTISIG_ISM)],
606
+ ]);
607
+ });
608
+ it('skips a pair the domain routing mailbox default ISM has no module for', async () => {
609
+ chain.recipientIsm(ROUTER, MAILBOX_DEFAULT_ISM);
610
+ chain.domainRoutingIsm(MAILBOX_DEFAULT_ISM, { [ARBITRUM]: BLACKLIST_ISM });
611
+ chain.moduleType(BLACKLIST_ISM, MODULE_TYPE.NULL);
612
+ derivedIsm(blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]));
613
+ const middleware = await createMiddleware();
614
+ expect(warn.callCount).to.equal(1);
615
+ expect(warn.firstCall.args).to.deep.equal([
616
+ {
617
+ router: ROUTER,
618
+ destination: ETHEREUM,
619
+ origin: RADIX,
620
+ routingIsm: MAILBOX_DEFAULT_ISM,
621
+ },
622
+ 'Routing ISM has no module for origin, skipping BlacklistIsm check for this route pair',
623
+ ]);
624
+ const fromRadix = await queryIgnoredIds(middleware, [
625
+ buildMessage(BLACKLISTED_ID, RADIX),
626
+ ]);
627
+ const fromArbitrum = await queryIgnoredIds(middleware, [
628
+ buildMessage(BLACKLISTED_ID, ARBITRUM),
629
+ ]);
630
+ expect(fromRadix).to.deep.equal([]);
631
+ expect(fromArbitrum).to.deep.equal([BLACKLISTED_ID]);
632
+ });
633
+ it('fails to initialize when the mailbox default ISM cannot be read', async () => {
634
+ chain.recipientIsm(ROUTER, MAILBOX_DEFAULT_ISM);
635
+ chain.moduleType(MAILBOX_DEFAULT_ISM, MODULE_TYPE.ROUTING);
636
+ chain.on(MAILBOX_DEFAULT_ISM, MODULE_SELECTOR, () => {
637
+ throw new Error('rpc unavailable');
638
+ });
639
+ const result = createMiddleware();
640
+ await expect(result).to.be.rejectedWith(`Failed to resolve the ISM of router ${ROUTER} on domain ${ETHEREUM}: rpc unavailable`);
641
+ });
642
+ it('uses the mailbox default ISM behind a mailbox default ISM wrapper', async () => {
643
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [mailboxDefaultWrapper(DEFAULT_ISM), multisig(MULTISIG_ISM)], 2));
644
+ chain.aggregationIsm(MAILBOX_DEFAULT_ISM, [MULTISIG_ISM, BLACKLIST_ISM], 2);
645
+ chain.moduleType(BLACKLIST_ISM, MODULE_TYPE.NULL);
646
+ derivedIsm(blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]));
647
+ const middleware = await createMiddleware();
648
+ const ignored = await queryIgnoredIds(middleware, [
649
+ buildMessage(BLACKLISTED_ID),
650
+ buildMessage(CLEAN_ID),
651
+ ]);
652
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
653
+ expect(derivations().filter(([chainName]) => chainName === 'ethereum')).to.deep.equal([
654
+ ['ethereum', ethers.utils.getAddress(AGGREGATION_ISM)],
655
+ ['ethereum', ethers.utils.getAddress(BLACKLIST_ISM)],
656
+ ]);
657
+ });
658
+ it('treats a routing ISM without module() in the mailbox default ISM as never blocking, with no further reads', async () => {
659
+ const amountRoutingIsm = '0x9999999999999999999999999999999999999999';
660
+ chain.recipientIsm(ROUTER, MAILBOX_DEFAULT_ISM);
661
+ chain.aggregationIsm(MAILBOX_DEFAULT_ISM, [amountRoutingIsm, BLACKLIST_ISM], 2);
662
+ chain.moduleType(amountRoutingIsm, MODULE_TYPE.ROUTING);
663
+ chain.missingSelector(amountRoutingIsm, MODULE_SELECTOR);
664
+ chain.moduleType(BLACKLIST_ISM, MODULE_TYPE.NULL);
665
+ derivedIsm(blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]));
666
+ const middleware = await createMiddleware();
667
+ const ignored = await queryIgnoredIds(middleware, [
668
+ buildMessage(BLACKLISTED_ID),
669
+ buildMessage(CLEAN_ID),
670
+ ]);
671
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
672
+ expect(chain.calls.filter((call) => call.startsWith(amountRoutingIsm))).to.have.members([
673
+ `${amountRoutingIsm}:${MODULE_TYPE_SELECTOR}`,
674
+ `${amountRoutingIsm}:${MODULE_SELECTOR}`,
675
+ `${amountRoutingIsm}:${MODULE_SELECTOR}`,
676
+ ]);
677
+ expect(derivations().map(([, address]) => address)).to.not.include(ethers.utils.getAddress(amountRoutingIsm));
678
+ });
679
+ const wrapperMailboxCases = [
680
+ {
681
+ name: 'a mailbox default ISM wrapper',
682
+ routerConfig: () => aggregation(AGGREGATION_ISM, [mailboxDefaultWrapper(DEFAULT_ISM), multisig(MULTISIG_ISM)], 2),
683
+ wrapper: DEFAULT_ISM,
684
+ },
685
+ {
686
+ name: 'a fallback routing ISM without the origin',
687
+ routerConfig: () => domainRouting(IsmType.FALLBACK_ROUTING, ROUTING_ISM, {}),
688
+ wrapper: ROUTING_ISM,
689
+ },
690
+ ];
691
+ for (const c of wrapperMailboxCases) {
692
+ it(`follows the mailbox of ${c.name} bound to another mailbox`, async () => {
693
+ const otherMailbox = '0xd4c1905bb1d26bc93dac913e13cacc278cdcc80d';
694
+ const otherDefaultIsm = '0x7777777777777777777777777777777777777770';
695
+ routerIsm(ROUTER, c.routerConfig());
696
+ chain.mailboxOf(c.wrapper, otherMailbox);
697
+ chain.defaultIsm(otherDefaultIsm, otherMailbox);
698
+ chain.aggregationIsm(otherDefaultIsm, [BLACKLIST_ISM], 1);
699
+ chain.moduleType(BLACKLIST_ISM, MODULE_TYPE.NULL);
700
+ derivedIsm(blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]));
701
+ const middleware = await createMiddleware();
702
+ const ignored = await queryIgnoredIds(middleware, [
703
+ buildMessage(BLACKLISTED_ID),
704
+ buildMessage(CLEAN_ID),
705
+ ]);
706
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
707
+ expect(chain.calls).to.not.include(`${MAILBOX_DEFAULT_ISM}:${MODULE_TYPE_SELECTOR}`);
708
+ });
709
+ }
710
+ /** A routing ISM that has a module for arbitrum only, so radix is unrouted. */
711
+ function arbitrumOnlyRouting() {
712
+ return domainRouting(IsmType.ROUTING, ROUTING_ISM, {
713
+ arbitrum: multisig(MULTISIG_ISM),
714
+ });
715
+ }
716
+ const unroutedBlockingCases = [
717
+ {
718
+ name: 'an unrouted branch that an inner aggregation can do without',
719
+ routerConfig: () => aggregation(AGGREGATION_ISM, [
720
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
721
+ aggregation(INNER_AGGREGATION_ISM, [multisig(MULTISIG_ISM), arbitrumOnlyRouting()], 1),
722
+ ], 2),
723
+ },
724
+ {
725
+ name: 'an unrouted module in a 2-of-3 aggregation',
726
+ routerConfig: () => aggregation(AGGREGATION_ISM, [
727
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
728
+ multisig(MULTISIG_ISM),
729
+ arbitrumOnlyRouting(),
730
+ ], 2),
731
+ },
732
+ ];
733
+ for (const c of unroutedBlockingCases) {
734
+ it(`still checks the BlacklistIsm next to ${c.name}`, async () => {
735
+ routerIsm(ROUTER, c.routerConfig());
736
+ const middleware = await createMiddleware();
737
+ const ignored = await queryIgnoredIds(middleware, [
738
+ buildMessage(BLACKLISTED_ID, RADIX),
739
+ buildMessage(CLEAN_ID, RADIX),
740
+ ]);
741
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
742
+ expect(warn.called).to.be.false;
743
+ });
744
+ }
745
+ it('skips a pair whose ISM always rejects because of an unrouted module', async () => {
746
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]), arbitrumOnlyRouting()], 2));
747
+ const middleware = await createMiddleware();
748
+ expect(warn.callCount).to.equal(1);
749
+ expect(warn.firstCall.args).to.deep.equal([
750
+ {
751
+ router: ROUTER,
752
+ destination: ETHEREUM,
753
+ origin: RADIX,
754
+ routingIsm: ROUTING_ISM,
755
+ },
756
+ 'Routing ISM has no module for origin, skipping BlacklistIsm check for this route pair',
757
+ ]);
758
+ const fromRadix = await queryIgnoredIds(middleware, [
759
+ buildMessage(BLACKLISTED_ID, RADIX),
760
+ ]);
761
+ const fromArbitrum = await queryIgnoredIds(middleware, [
762
+ buildMessage(BLACKLISTED_ID, ARBITRUM),
763
+ ]);
764
+ expect(fromRadix).to.deep.equal([]);
765
+ expect(fromArbitrum).to.deep.equal([BLACKLISTED_ID]);
766
+ });
767
+ const opaqueCases = [
768
+ {
769
+ name: 'an amount routing ISM',
770
+ module: {
771
+ type: IsmType.AMOUNT_ROUTING,
772
+ lowerIsm: blacklist(SECOND_BLACKLIST_ISM, [BLACKLISTED_ID]),
773
+ upperIsm: blacklist(SECOND_BLACKLIST_ISM, [BLACKLISTED_ID]),
774
+ threshold: 100,
775
+ },
776
+ },
777
+ {
778
+ name: 'an interchain account routing ISM',
779
+ module: {
780
+ type: IsmType.INTERCHAIN_ACCOUNT_ROUTING,
781
+ owner: OWNER,
782
+ isms: { arbitrum: SECOND_BLACKLIST_ISM },
783
+ },
784
+ },
785
+ { name: 'a plain address entry', module: SECOND_BLACKLIST_ISM },
786
+ ];
787
+ for (const c of opaqueCases) {
788
+ it(`treats ${c.name} as never blocking`, async () => {
789
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [c.module, blacklist(BLACKLIST_ISM)], 2));
790
+ const middleware = await createMiddleware();
791
+ const ignored = await queryIgnoredIds(middleware, [
792
+ buildMessage(BLACKLISTED_ID),
793
+ ]);
794
+ expect(ignored).to.deep.equal([]);
795
+ });
796
+ }
797
+ it('fails to initialize with context when an ISM cannot be derived', async () => {
798
+ chain.recipientIsm(ROUTER, AGGREGATION_ISM);
799
+ const result = createMiddleware();
800
+ await expect(result).to.be.rejectedWith(`Failed to resolve the ISM of router ${ROUTER} on domain ${ETHEREUM}: No derived config for ${ethers.utils.getAddress(AGGREGATION_ISM)}`);
801
+ });
802
+ it('fails to initialize when the mailbox cannot be read', async () => {
803
+ routerIsm(ROUTER, incidentIsm(3));
804
+ chain.on(MAILBOX, mailboxInterface.getSighash('recipientIsm'), () => {
805
+ throw new Error('rpc unavailable');
806
+ });
807
+ const result = createMiddleware();
808
+ await expect(result).to.be.rejectedWith(`Failed to resolve the ISM of router ${ROUTER} on domain ${ETHEREUM}: rpc unavailable`);
809
+ });
810
+ it('derives each destination ISM once, not once per origin', async () => {
811
+ routerIsm(ROUTER, incidentIsm(3));
812
+ await createMiddleware();
813
+ expect(derivations()).to.deep.equal([
814
+ ['ethereum', ethers.utils.getAddress(AGGREGATION_ISM)],
815
+ ['arbitrum', ethers.utils.getAddress(MULTISIG_ISM)],
816
+ ]);
817
+ });
818
+ it('never resolves a non-EVM destination', async () => {
819
+ routerIsm(ROUTER, incidentIsm(3));
820
+ await createMiddleware();
821
+ expect(derivations().map(([chainName]) => chainName)).to.not.include('radix');
822
+ expect(chain.calls.filter((call) => call === `${MAILBOX}:${mailboxInterface.getSighash('recipientIsm')}`)).to.have.lengthOf(2);
823
+ });
824
+ it('keeps a message that blacklisted ids do not cover', async () => {
825
+ routerIsm(ROUTER, incidentIsm(3));
826
+ const middleware = await createMiddleware();
827
+ const ignored = await queryIgnoredIds(middleware, [buildMessage(CLEAN_ID)]);
828
+ expect(ignored).to.deep.equal([]);
829
+ });
830
+ it('makes no RPC calls for pairs without a BlacklistIsm or non-EVM destinations', async () => {
831
+ routerIsm(ROUTER, multisig(MULTISIG_ISM));
832
+ blockingArbitrumIsm();
833
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
834
+ const ignored = await queryIgnoredIds(middleware, [
835
+ buildMessage(BLACKLISTED_ID, ARBITRUM, ETHEREUM),
836
+ buildMessage(BLACKLISTED_ID, RADIX, ETHEREUM),
837
+ buildMessage(BLACKLISTED_ID, ETHEREUM, RADIX),
838
+ ]);
839
+ expect(ignored).to.deep.equal([]);
840
+ expect(chain.calls).to.have.lengthOf(callsAfterInit);
841
+ });
842
+ it('re-derives the ISM graph only once the refresh interval passes', async () => {
843
+ routerIsm(ROUTER, incidentIsm(3));
844
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]);
845
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
846
+ const messages = [buildMessage(BLACKLISTED_ID)];
847
+ await queryIgnoredIds(middleware, messages);
848
+ expect(chain.calls).to.have.lengthOf(callsAfterInit);
849
+ expect(deriveIsmConfig.callCount).to.equal(2);
850
+ clock.tick(REFRESH_INTERVAL_MS);
851
+ await queryIgnoredIds(middleware, messages);
852
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
853
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
854
+ ]);
855
+ expect(deriveIsmConfig.callCount).to.equal(4);
856
+ });
857
+ const removalCases = [
858
+ {
859
+ name: 'is removed from an aggregation',
860
+ before: incidentIsm(3),
861
+ after: aggregation(AGGREGATION_ISM, [mailboxDefaultWrapper(DEFAULT_ISM), multisig(MULTISIG_ISM)], 2),
862
+ },
863
+ {
864
+ name: 'is replaced by DomainRoutingIsm.set',
865
+ before: domainRouting(IsmType.ROUTING, ROUTING_ISM, {
866
+ arbitrum: blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
867
+ radix: multisig(MULTISIG_ISM),
868
+ }),
869
+ after: domainRouting(IsmType.ROUTING, ROUTING_ISM, {
870
+ arbitrum: multisig(MULTISIG_ISM),
871
+ radix: multisig(MULTISIG_ISM),
872
+ }),
873
+ },
874
+ {
875
+ name: 'leaves the router ISM',
876
+ before: incidentIsm(3),
877
+ after: multisig(MULTISIG_ISM),
878
+ },
879
+ ];
880
+ for (const c of removalCases) {
881
+ it(`stops ignoring a message once its BlacklistIsm ${c.name}`, async () => {
882
+ routerIsm(ROUTER, c.before);
883
+ blockingArbitrumIsm();
884
+ const middleware = await createMiddleware();
885
+ const messages = [buildMessage(BLACKLISTED_ID)];
886
+ expect(await queryIgnoredIds(middleware, messages)).to.deep.equal([
887
+ BLACKLISTED_ID,
888
+ ]);
889
+ routerIsm(ROUTER, c.after);
890
+ clock.tick(REFRESH_INTERVAL_MS - 1);
891
+ expect(await queryIgnoredIds(middleware, messages)).to.deep.equal([
892
+ BLACKLISTED_ID,
893
+ ]);
894
+ clock.tick(1);
895
+ const ignored = await queryIgnoredIds(middleware, messages);
896
+ expect(ignored).to.deep.equal([]);
897
+ expect(middleware.isIgnored(BLACKLISTED_ID)).to.be.false;
898
+ expect(warn.called).to.be.false;
899
+ });
900
+ }
901
+ it('picks up a route pair gaining its first BlacklistIsm after startup', async () => {
902
+ routerIsm(ROUTER, incidentIsm(3));
903
+ const middleware = await createMiddleware();
904
+ const messages = [buildMessage(CLEAN_ID, ETHEREUM, ARBITRUM)];
905
+ expect(await queryIgnoredIds(middleware, messages)).to.deep.equal([]);
906
+ routerIsm(ARBITRUM_ROUTER, blacklist(ARBITRUM_BLACKLIST_ISM, [CLEAN_ID]));
907
+ clock.tick(REFRESH_INTERVAL_MS);
908
+ const ignored = await queryIgnoredIds(middleware, messages);
909
+ expect(ignored).to.deep.equal([CLEAN_ID]);
910
+ expectIgnoredWithReason(CLEAN_ID, `Blacklisted by a BlacklistIsm (${ARBITRUM_BLACKLIST_ISM}) in the ISM of router ${ARBITRUM_ROUTER} on domain ${ARBITRUM}`);
911
+ });
912
+ it('picks up an id blacklisted after startup once the refresh interval passes', async () => {
913
+ routerIsm(ROUTER, incidentIsm(3, []));
914
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
915
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]);
916
+ const messages = [buildMessage(BLACKLISTED_ID)];
917
+ clock.tick(REFRESH_INTERVAL_MS - 1);
918
+ const withinInterval = await queryIgnoredIds(middleware, messages);
919
+ expect(withinInterval).to.deep.equal([]);
920
+ expect(chain.calls).to.have.lengthOf(callsAfterInit);
921
+ clock.tick(1);
922
+ const ignored = await queryIgnoredIds(middleware, messages);
923
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
924
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
925
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
926
+ ]);
927
+ });
928
+ it('refreshes every pair at most once per interval, whatever the batch', async () => {
929
+ routerIsm(ROUTER, incidentIsm(3));
930
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]);
931
+ blockingArbitrumIsm();
932
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
933
+ const everyPairRead = [
934
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
935
+ `${ARBITRUM_BLACKLIST_ISM}:${VALUES_SELECTOR}`,
936
+ ];
937
+ clock.tick(REFRESH_INTERVAL_MS);
938
+ await queryIgnoredIds(middleware, []);
939
+ const callsAfterRefresh = chain.calls.length;
940
+ expect(valuesCallsSince(callsAfterInit)).to.have.members(everyPairRead);
941
+ expect(deriveIsmConfig.callCount).to.equal(4);
942
+ clock.tick(REFRESH_INTERVAL_MS - 1);
943
+ await queryIgnoredIds(middleware, [buildMessage(CLEAN_ID)]);
944
+ await queryIgnoredIds(middleware, [
945
+ buildMessage(CLEAN_ID, ETHEREUM, ARBITRUM),
946
+ ]);
947
+ expect(chain.calls).to.have.lengthOf(callsAfterRefresh);
948
+ clock.tick(1);
949
+ await queryIgnoredIds(middleware, [
950
+ buildMessage(CLEAN_ID, ETHEREUM, ARBITRUM),
951
+ ]);
952
+ expect(valuesCallsSince(callsAfterRefresh)).to.have.members(everyPairRead);
953
+ expect(deriveIsmConfig.callCount).to.equal(6);
954
+ });
955
+ it('reads a BlacklistIsm shared by pairs once on refresh and updates every pair', async () => {
956
+ routerIsm(ROUTER, incidentIsm(3, []));
957
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
958
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID, THIRD_ID]);
959
+ clock.tick(REFRESH_INTERVAL_MS);
960
+ const ignored = await queryIgnoredIds(middleware, [
961
+ buildMessage(BLACKLISTED_ID, ARBITRUM),
962
+ buildMessage(THIRD_ID, RADIX),
963
+ ]);
964
+ expect(ignored).to.deep.equal([BLACKLISTED_ID, THIRD_ID]);
965
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
966
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
967
+ ]);
968
+ });
969
+ it('keeps every old id when one read of a refresh fails, until a later refresh succeeds', async () => {
970
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
971
+ multisig(MULTISIG_ISM),
972
+ blacklist(BLACKLIST_ISM),
973
+ blacklist(SECOND_BLACKLIST_ISM, [BLACKLISTED_ID]),
974
+ ], 3));
975
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
976
+ chain.blacklist(BLACKLIST_ISM, [CLEAN_ID]);
977
+ chain.on(SECOND_BLACKLIST_ISM, VALUES_SELECTOR, () => {
978
+ throw new Error('rpc unavailable');
979
+ });
980
+ const messages = [
981
+ buildMessage(BLACKLISTED_ID),
982
+ buildMessage(CLEAN_ID),
983
+ buildMessage(THIRD_ID),
984
+ ];
985
+ clock.tick(REFRESH_INTERVAL_MS);
986
+ const ignored = await queryIgnoredIds(middleware, messages);
987
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
988
+ expect(warn.callCount).to.equal(1);
989
+ expect(warn.firstCall.args).to.deep.equal([
990
+ {
991
+ destination: ETHEREUM,
992
+ error: `Failed to read the blacklisted ids of BlacklistIsm ${SECOND_BLACKLIST_ISM} on domain ${ETHEREUM}: rpc unavailable`,
993
+ },
994
+ BLACKLIST_ISM_REFRESH_FAILED_MESSAGE,
995
+ ]);
996
+ chain.blacklist(SECOND_BLACKLIST_ISM, [BLACKLISTED_ID, THIRD_ID]);
997
+ clock.tick(REFRESH_INTERVAL_MS);
998
+ const retried = await queryIgnoredIds(middleware, messages);
999
+ expect(retried).to.deep.equal([BLACKLISTED_ID, CLEAN_ID, THIRD_ID]);
1000
+ expect(warn.callCount).to.equal(1);
1001
+ expect(valuesCallsSince(callsAfterInit)).to.have.members([
1002
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1003
+ `${SECOND_BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1004
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1005
+ `${SECOND_BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1006
+ ]);
1007
+ });
1008
+ it('keeps the old routes of a destination whose refresh fails and applies the others', async () => {
1009
+ routerIsm(ROUTER, incidentIsm(3));
1010
+ routerIsm(ARBITRUM_ROUTER, blacklist(ARBITRUM_BLACKLIST_ISM, [CLEAN_ID]));
1011
+ const middleware = await createMiddleware();
1012
+ const messages = [
1013
+ buildMessage(BLACKLISTED_ID),
1014
+ buildMessage(THIRD_ID),
1015
+ buildMessage(CLEAN_ID, ETHEREUM, ARBITRUM),
1016
+ ];
1017
+ expect(await queryIgnoredIds(middleware, messages)).to.deep.equal([
1018
+ BLACKLISTED_ID,
1019
+ CLEAN_ID,
1020
+ ]);
1021
+ derivedConfigs.delete(AGGREGATION_ISM);
1022
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID, THIRD_ID]);
1023
+ routerIsm(ARBITRUM_ROUTER, multisig(MULTISIG_ISM));
1024
+ clock.tick(REFRESH_INTERVAL_MS);
1025
+ const ignored = await queryIgnoredIds(middleware, messages);
1026
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
1027
+ expect(warn.callCount).to.equal(1);
1028
+ expect(warn.firstCall.args).to.deep.equal([
1029
+ {
1030
+ destination: ETHEREUM,
1031
+ error: `Failed to resolve the ISM of router ${ROUTER} on domain ${ETHEREUM}: No derived config for ${ethers.utils.getAddress(AGGREGATION_ISM)}`,
1032
+ },
1033
+ BLACKLIST_ISM_REFRESH_FAILED_MESSAGE,
1034
+ ]);
1035
+ routerIsm(ROUTER, incidentIsm(3, [BLACKLISTED_ID, THIRD_ID]));
1036
+ clock.tick(REFRESH_INTERVAL_MS);
1037
+ const recovered = await queryIgnoredIds(middleware, messages);
1038
+ expect(recovered).to.deep.equal([BLACKLISTED_ID, THIRD_ID]);
1039
+ expect(warn.callCount).to.equal(1);
1040
+ });
1041
+ it('ignores nothing and warns when a refresh finds no BlacklistIsm that can block', async () => {
1042
+ routerIsm(ROUTER, incidentIsm(3));
1043
+ const middleware = await createMiddleware();
1044
+ const messages = [buildMessage(BLACKLISTED_ID)];
1045
+ routerIsm(ROUTER, multisig(MULTISIG_ISM));
1046
+ clock.tick(REFRESH_INTERVAL_MS);
1047
+ const ignored = await queryIgnoredIds(middleware, messages);
1048
+ expect(ignored).to.deep.equal([]);
1049
+ expect(middleware.isIgnored(BLACKLISTED_ID)).to.be.false;
1050
+ expect(warn.callCount).to.equal(1);
1051
+ expect(warn.firstCall.args).to.deep.equal([
1052
+ BLACKLIST_ISM_NO_BLOCKING_ROUTES_MESSAGE,
1053
+ ]);
1054
+ });
1055
+ it('keeps the last known ids and warns once when a refresh fails', async () => {
1056
+ routerIsm(ROUTER, incidentIsm(3));
1057
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
1058
+ chain.on(BLACKLIST_ISM, VALUES_SELECTOR, () => {
1059
+ throw new Error('rpc unavailable');
1060
+ });
1061
+ const messages = [buildMessage(BLACKLISTED_ID)];
1062
+ clock.tick(REFRESH_INTERVAL_MS * 2);
1063
+ const ignored = await queryIgnoredIds(middleware, messages);
1064
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
1065
+ expect(warn.callCount).to.equal(1);
1066
+ const [fields, message] = warn.firstCall.args;
1067
+ expect(message).to.equal(BLACKLIST_ISM_REFRESH_FAILED_MESSAGE);
1068
+ expect(fields).to.deep.equal({
1069
+ destination: ETHEREUM,
1070
+ error: `Failed to read the blacklisted ids of BlacklistIsm ${BLACKLIST_ISM} on domain ${ETHEREUM}: rpc unavailable`,
1071
+ });
1072
+ clock.tick(REFRESH_INTERVAL_MS - 1);
1073
+ const beforeRetry = await queryIgnoredIds(middleware, messages);
1074
+ expect(beforeRetry).to.deep.equal([BLACKLISTED_ID]);
1075
+ expect(warn.callCount).to.equal(1);
1076
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
1077
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1078
+ ]);
1079
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID, CLEAN_ID]);
1080
+ clock.tick(1);
1081
+ const afterRetry = await queryIgnoredIds(middleware, [
1082
+ buildMessage(BLACKLISTED_ID),
1083
+ buildMessage(CLEAN_ID),
1084
+ ]);
1085
+ expect(afterRetry).to.deep.equal([BLACKLISTED_ID, CLEAN_ID]);
1086
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
1087
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1088
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1089
+ ]);
1090
+ });
1091
+ it('does not seed an id blacklisted at the latest block but not at the finalized one', async () => {
1092
+ routerIsm(ROUTER, incidentIsm(3));
1093
+ chain.blacklist(BLACKLIST_ISM, [], [BLACKLISTED_ID]);
1094
+ const middleware = await createMiddleware();
1095
+ const ignored = await queryIgnoredIds(middleware, [
1096
+ buildMessage(BLACKLISTED_ID),
1097
+ ]);
1098
+ expect(ignored).to.deep.equal([]);
1099
+ });
1100
+ it('ignores an id only once it is blacklisted at the finalized block', async () => {
1101
+ routerIsm(ROUTER, incidentIsm(3, []));
1102
+ const middleware = await createMiddleware();
1103
+ const messages = [buildMessage(BLACKLISTED_ID)];
1104
+ chain.blacklist(BLACKLIST_ISM, [], [BLACKLISTED_ID]);
1105
+ clock.tick(REFRESH_INTERVAL_MS);
1106
+ const unfinalized = await queryIgnoredIds(middleware, messages);
1107
+ expect(unfinalized).to.deep.equal([]);
1108
+ // Reorged out before finality: latest drops it, finalized never had it.
1109
+ chain.blacklist(BLACKLIST_ISM, [], []);
1110
+ clock.tick(REFRESH_INTERVAL_MS);
1111
+ const reorged = await queryIgnoredIds(middleware, messages);
1112
+ expect(reorged).to.deep.equal([]);
1113
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]);
1114
+ clock.tick(REFRESH_INTERVAL_MS);
1115
+ const finalized = await queryIgnoredIds(middleware, messages);
1116
+ expect(finalized).to.deep.equal([BLACKLISTED_ID]);
1117
+ expect(warn.called).to.be.false;
1118
+ });
1119
+ it('reads every BlacklistIsm at the finalized block, at startup and on refresh', async () => {
1120
+ routerIsm(ROUTER, aggregation(AGGREGATION_ISM, [
1121
+ blacklist(BLACKLIST_ISM, [BLACKLISTED_ID]),
1122
+ blacklist(SECOND_BLACKLIST_ISM, [THIRD_ID]),
1123
+ ], 2));
1124
+ const middleware = await createMiddleware();
1125
+ expect(chain.valuesBlockTags).to.deep.equal(['finalized', 'finalized']);
1126
+ clock.tick(REFRESH_INTERVAL_MS);
1127
+ const ignored = await queryIgnoredIds(middleware, [
1128
+ buildMessage(BLACKLISTED_ID),
1129
+ buildMessage(THIRD_ID),
1130
+ ]);
1131
+ expect(ignored).to.deep.equal([BLACKLISTED_ID, THIRD_ID]);
1132
+ expect(chain.valuesBlockTags).to.deep.equal([
1133
+ 'finalized',
1134
+ 'finalized',
1135
+ 'finalized',
1136
+ 'finalized',
1137
+ ]);
1138
+ });
1139
+ it('fails to initialize when a BlacklistIsm cannot be read at the finalized block', async () => {
1140
+ routerIsm(ROUTER, incidentIsm(3));
1141
+ chain.finalizedError = new Error('finalized block tag not supported');
1142
+ const result = createMiddleware();
1143
+ await expect(result).to.be.rejectedWith(`Failed to read the blacklisted ids of BlacklistIsm ${BLACKLIST_ISM} on domain ${ETHEREUM}: finalized block tag not supported`);
1144
+ });
1145
+ it('keeps the last known ids and warns when a finalized read fails on refresh', async () => {
1146
+ routerIsm(ROUTER, incidentIsm(3));
1147
+ const { middleware, callsAfterInit } = await createMiddlewareAndCountCalls();
1148
+ chain.blacklist(BLACKLIST_ISM, [BLACKLISTED_ID, CLEAN_ID]);
1149
+ chain.finalizedError = new Error('finalized block tag not supported');
1150
+ clock.tick(REFRESH_INTERVAL_MS);
1151
+ const ignored = await queryIgnoredIds(middleware, [
1152
+ buildMessage(BLACKLISTED_ID),
1153
+ buildMessage(CLEAN_ID),
1154
+ ]);
1155
+ expect(ignored).to.deep.equal([BLACKLISTED_ID]);
1156
+ expect(warn.callCount).to.equal(1);
1157
+ expect(warn.firstCall.args[1]).to.equal(BLACKLIST_ISM_REFRESH_FAILED_MESSAGE);
1158
+ expect(warn.firstCall.args[0]).to.deep.equal({
1159
+ destination: ETHEREUM,
1160
+ error: `Failed to read the blacklisted ids of BlacklistIsm ${BLACKLIST_ISM} on domain ${ETHEREUM}: finalized block tag not supported`,
1161
+ });
1162
+ expect(valuesCallsSince(callsAfterInit)).to.deep.equal([
1163
+ `${BLACKLIST_ISM}:${VALUES_SELECTOR}`,
1164
+ ]);
1165
+ expect(chain.valuesBlockTags.slice(-1)).to.deep.equal(['finalized']);
1166
+ });
1167
+ });
1168
+ //# sourceMappingURL=BlacklistIsmMiddleware.test.js.map