@hyperlane-xyz/rebalancer 0.1.2 → 1.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 (223) hide show
  1. package/README.md +134 -14
  2. package/dist/config/RebalancerConfig.d.ts +2 -2
  3. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  4. package/dist/config/RebalancerConfig.js +4 -3
  5. package/dist/config/RebalancerConfig.js.map +1 -1
  6. package/dist/config/RebalancerConfig.test.js +434 -163
  7. package/dist/config/RebalancerConfig.test.js.map +1 -1
  8. package/dist/config/types.d.ts +1650 -290
  9. package/dist/config/types.d.ts.map +1 -1
  10. package/dist/config/types.js +124 -46
  11. package/dist/config/types.js.map +1 -1
  12. package/dist/core/Rebalancer.d.ts +14 -7
  13. package/dist/core/Rebalancer.d.ts.map +1 -1
  14. package/dist/core/Rebalancer.js +168 -99
  15. package/dist/core/Rebalancer.js.map +1 -1
  16. package/dist/core/Rebalancer.test.d.ts +2 -0
  17. package/dist/core/Rebalancer.test.d.ts.map +1 -0
  18. package/dist/core/Rebalancer.test.js +391 -0
  19. package/dist/core/Rebalancer.test.js.map +1 -0
  20. package/dist/core/RebalancerService.d.ts +16 -2
  21. package/dist/core/RebalancerService.d.ts.map +1 -1
  22. package/dist/core/RebalancerService.js +164 -21
  23. package/dist/core/RebalancerService.js.map +1 -1
  24. package/dist/core/RebalancerService.test.d.ts +2 -0
  25. package/dist/core/RebalancerService.test.d.ts.map +1 -0
  26. package/dist/core/RebalancerService.test.js +809 -0
  27. package/dist/core/RebalancerService.test.js.map +1 -0
  28. package/dist/factories/RebalancerContextFactory.d.ts +11 -0
  29. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  30. package/dist/factories/RebalancerContextFactory.js +60 -13
  31. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  32. package/dist/index.d.ts +6 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/interfaces/IMonitor.d.ts +6 -8
  37. package/dist/interfaces/IMonitor.d.ts.map +1 -1
  38. package/dist/interfaces/IMonitor.js.map +1 -1
  39. package/dist/interfaces/IRebalancer.d.ts +20 -4
  40. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  41. package/dist/interfaces/IStrategy.d.ts +18 -2
  42. package/dist/interfaces/IStrategy.d.ts.map +1 -1
  43. package/dist/metrics/Metrics.d.ts +4 -2
  44. package/dist/metrics/Metrics.d.ts.map +1 -1
  45. package/dist/metrics/Metrics.js +21 -1
  46. package/dist/metrics/Metrics.js.map +1 -1
  47. package/dist/metrics/scripts/metrics.d.ts +2 -0
  48. package/dist/metrics/scripts/metrics.d.ts.map +1 -1
  49. package/dist/metrics/scripts/metrics.js +12 -0
  50. package/dist/metrics/scripts/metrics.js.map +1 -1
  51. package/dist/monitor/Monitor.d.ts +8 -3
  52. package/dist/monitor/Monitor.d.ts.map +1 -1
  53. package/dist/monitor/Monitor.js +75 -15
  54. package/dist/monitor/Monitor.js.map +1 -1
  55. package/dist/strategy/BaseStrategy.d.ts +51 -5
  56. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  57. package/dist/strategy/BaseStrategy.js +199 -19
  58. package/dist/strategy/BaseStrategy.js.map +1 -1
  59. package/dist/strategy/CollateralDeficitStrategy.d.ts +65 -0
  60. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -0
  61. package/dist/strategy/CollateralDeficitStrategy.js +245 -0
  62. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -0
  63. package/dist/strategy/CollateralDeficitStrategy.test.d.ts +2 -0
  64. package/dist/strategy/CollateralDeficitStrategy.test.d.ts.map +1 -0
  65. package/dist/strategy/CollateralDeficitStrategy.test.js +364 -0
  66. package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -0
  67. package/dist/strategy/CompositeStrategy.d.ts +18 -0
  68. package/dist/strategy/CompositeStrategy.d.ts.map +1 -0
  69. package/dist/strategy/CompositeStrategy.js +63 -0
  70. package/dist/strategy/CompositeStrategy.js.map +1 -0
  71. package/dist/strategy/CompositeStrategy.test.d.ts +2 -0
  72. package/dist/strategy/CompositeStrategy.test.d.ts.map +1 -0
  73. package/dist/strategy/CompositeStrategy.test.js +265 -0
  74. package/dist/strategy/CompositeStrategy.test.js.map +1 -0
  75. package/dist/strategy/MinAmountStrategy.d.ts +12 -5
  76. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
  77. package/dist/strategy/MinAmountStrategy.js +23 -14
  78. package/dist/strategy/MinAmountStrategy.js.map +1 -1
  79. package/dist/strategy/MinAmountStrategy.test.js +88 -20
  80. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  81. package/dist/strategy/StrategyFactory.d.ts +15 -6
  82. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  83. package/dist/strategy/StrategyFactory.js +48 -10
  84. package/dist/strategy/StrategyFactory.js.map +1 -1
  85. package/dist/strategy/StrategyFactory.test.js +2 -2
  86. package/dist/strategy/StrategyFactory.test.js.map +1 -1
  87. package/dist/strategy/WeightedStrategy.d.ts +13 -4
  88. package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
  89. package/dist/strategy/WeightedStrategy.js +18 -6
  90. package/dist/strategy/WeightedStrategy.js.map +1 -1
  91. package/dist/strategy/WeightedStrategy.test.js +108 -18
  92. package/dist/strategy/WeightedStrategy.test.js.map +1 -1
  93. package/dist/strategy/index.d.ts +2 -0
  94. package/dist/strategy/index.d.ts.map +1 -1
  95. package/dist/strategy/index.js +2 -0
  96. package/dist/strategy/index.js.map +1 -1
  97. package/dist/test/helpers.d.ts +93 -3
  98. package/dist/test/helpers.d.ts.map +1 -1
  99. package/dist/test/helpers.js +267 -10
  100. package/dist/test/helpers.js.map +1 -1
  101. package/dist/tracking/ActionTracker.d.ts +49 -0
  102. package/dist/tracking/ActionTracker.d.ts.map +1 -0
  103. package/dist/tracking/ActionTracker.js +422 -0
  104. package/dist/tracking/ActionTracker.js.map +1 -0
  105. package/dist/tracking/ActionTracker.test.d.ts +2 -0
  106. package/dist/tracking/ActionTracker.test.d.ts.map +1 -0
  107. package/dist/tracking/ActionTracker.test.js +637 -0
  108. package/dist/tracking/ActionTracker.test.js.map +1 -0
  109. package/dist/tracking/IActionTracker.d.ts +101 -0
  110. package/dist/tracking/IActionTracker.d.ts.map +1 -0
  111. package/dist/tracking/IActionTracker.js +2 -0
  112. package/dist/tracking/IActionTracker.js.map +1 -0
  113. package/dist/tracking/InflightContextAdapter.d.ts +18 -0
  114. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -0
  115. package/dist/tracking/InflightContextAdapter.js +35 -0
  116. package/dist/tracking/InflightContextAdapter.js.map +1 -0
  117. package/dist/tracking/InflightContextAdapter.test.d.ts +2 -0
  118. package/dist/tracking/InflightContextAdapter.test.d.ts.map +1 -0
  119. package/dist/tracking/InflightContextAdapter.test.js +172 -0
  120. package/dist/tracking/InflightContextAdapter.test.js.map +1 -0
  121. package/dist/tracking/index.d.ts +7 -0
  122. package/dist/tracking/index.d.ts.map +1 -0
  123. package/dist/tracking/index.js +6 -0
  124. package/dist/tracking/index.js.map +1 -0
  125. package/dist/tracking/store/IStore.d.ts +41 -0
  126. package/dist/tracking/store/IStore.d.ts.map +1 -0
  127. package/dist/tracking/store/IStore.js +2 -0
  128. package/dist/tracking/store/IStore.js.map +1 -0
  129. package/dist/tracking/store/InMemoryStore.d.ts +21 -0
  130. package/dist/tracking/store/InMemoryStore.d.ts.map +1 -0
  131. package/dist/tracking/store/InMemoryStore.js +40 -0
  132. package/dist/tracking/store/InMemoryStore.js.map +1 -0
  133. package/dist/tracking/store/InMemoryStore.test.d.ts +2 -0
  134. package/dist/tracking/store/InMemoryStore.test.d.ts.map +1 -0
  135. package/dist/tracking/store/InMemoryStore.test.js +290 -0
  136. package/dist/tracking/store/InMemoryStore.test.js.map +1 -0
  137. package/dist/tracking/store/index.d.ts +3 -0
  138. package/dist/tracking/store/index.d.ts.map +1 -0
  139. package/dist/tracking/store/index.js +2 -0
  140. package/dist/tracking/store/index.js.map +1 -0
  141. package/dist/tracking/types.d.ts +43 -0
  142. package/dist/tracking/types.d.ts.map +1 -0
  143. package/dist/tracking/types.js +2 -0
  144. package/dist/tracking/types.js.map +1 -0
  145. package/dist/utils/ExplorerClient.d.ts +39 -1
  146. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  147. package/dist/utils/ExplorerClient.js +205 -2
  148. package/dist/utils/ExplorerClient.js.map +1 -1
  149. package/dist/utils/balanceUtils.js +2 -2
  150. package/dist/utils/balanceUtils.js.map +1 -1
  151. package/dist/utils/balanceUtils.test.js +1 -0
  152. package/dist/utils/balanceUtils.test.js.map +1 -1
  153. package/dist/utils/bridgeUtils.d.ts +1 -3
  154. package/dist/utils/bridgeUtils.d.ts.map +1 -1
  155. package/dist/utils/bridgeUtils.js +1 -5
  156. package/dist/utils/bridgeUtils.js.map +1 -1
  157. package/dist/utils/bridgeUtils.test.js +3 -14
  158. package/dist/utils/bridgeUtils.test.js.map +1 -1
  159. package/package.json +11 -9
  160. package/src/config/RebalancerConfig.test.ts +459 -163
  161. package/src/config/RebalancerConfig.ts +5 -3
  162. package/src/config/types.ts +159 -52
  163. package/src/core/Rebalancer.test.ts +632 -0
  164. package/src/core/Rebalancer.ts +247 -157
  165. package/src/core/RebalancerService.test.ts +1144 -0
  166. package/src/core/RebalancerService.ts +245 -23
  167. package/src/factories/RebalancerContextFactory.ts +115 -14
  168. package/src/index.ts +16 -4
  169. package/src/interfaces/IMonitor.ts +15 -8
  170. package/src/interfaces/IRebalancer.ts +22 -4
  171. package/src/interfaces/IStrategy.ts +23 -2
  172. package/src/metrics/Metrics.ts +26 -5
  173. package/src/metrics/scripts/metrics.ts +14 -0
  174. package/src/monitor/Monitor.ts +109 -22
  175. package/src/strategy/BaseStrategy.ts +316 -26
  176. package/src/strategy/CollateralDeficitStrategy.test.ts +551 -0
  177. package/src/strategy/CollateralDeficitStrategy.ts +390 -0
  178. package/src/strategy/CompositeStrategy.test.ts +405 -0
  179. package/src/strategy/CompositeStrategy.ts +102 -0
  180. package/src/strategy/MinAmountStrategy.test.ts +189 -88
  181. package/src/strategy/MinAmountStrategy.ts +44 -13
  182. package/src/strategy/StrategyFactory.test.ts +2 -2
  183. package/src/strategy/StrategyFactory.ts +91 -8
  184. package/src/strategy/WeightedStrategy.test.ts +187 -72
  185. package/src/strategy/WeightedStrategy.ts +41 -7
  186. package/src/strategy/index.ts +2 -0
  187. package/src/test/helpers.ts +418 -14
  188. package/src/tracking/ActionTracker.test.ts +783 -0
  189. package/src/tracking/ActionTracker.ts +647 -0
  190. package/src/tracking/IActionTracker.ts +140 -0
  191. package/src/tracking/InflightContextAdapter.test.ts +203 -0
  192. package/src/tracking/InflightContextAdapter.ts +42 -0
  193. package/src/tracking/index.ts +36 -0
  194. package/src/tracking/store/IStore.ts +48 -0
  195. package/src/tracking/store/InMemoryStore.test.ts +338 -0
  196. package/src/tracking/store/InMemoryStore.ts +58 -0
  197. package/src/tracking/store/index.ts +2 -0
  198. package/src/tracking/types.ts +74 -0
  199. package/src/utils/ExplorerClient.ts +266 -3
  200. package/src/utils/balanceUtils.test.ts +1 -0
  201. package/src/utils/balanceUtils.ts +2 -2
  202. package/src/utils/bridgeUtils.test.ts +3 -15
  203. package/src/utils/bridgeUtils.ts +0 -10
  204. package/dist/core/WithInflightGuard.d.ts +0 -20
  205. package/dist/core/WithInflightGuard.d.ts.map +0 -1
  206. package/dist/core/WithInflightGuard.js +0 -47
  207. package/dist/core/WithInflightGuard.js.map +0 -1
  208. package/dist/core/WithInflightGuard.test.d.ts +0 -2
  209. package/dist/core/WithInflightGuard.test.d.ts.map +0 -1
  210. package/dist/core/WithInflightGuard.test.js +0 -64
  211. package/dist/core/WithInflightGuard.test.js.map +0 -1
  212. package/dist/core/WithSemaphore.d.ts +0 -22
  213. package/dist/core/WithSemaphore.d.ts.map +0 -1
  214. package/dist/core/WithSemaphore.js +0 -67
  215. package/dist/core/WithSemaphore.js.map +0 -1
  216. package/dist/core/WithSemaphore.test.d.ts +0 -2
  217. package/dist/core/WithSemaphore.test.d.ts.map +0 -1
  218. package/dist/core/WithSemaphore.test.js +0 -83
  219. package/dist/core/WithSemaphore.test.js.map +0 -1
  220. package/src/core/WithInflightGuard.test.ts +0 -131
  221. package/src/core/WithInflightGuard.ts +0 -67
  222. package/src/core/WithSemaphore.test.ts +0 -111
  223. package/src/core/WithSemaphore.ts +0 -92
@@ -5,34 +5,41 @@ import { tmpdir } from 'os';
5
5
  import { join } from 'path';
6
6
  import { writeYamlOrJson } from '@hyperlane-xyz/utils/fs';
7
7
  import { RebalancerConfig } from './RebalancerConfig.js';
8
- import { RebalancerMinAmountType, RebalancerStrategyOptions, } from './types.js';
8
+ import { RebalancerMinAmountType, RebalancerStrategyOptions, getAllBridges, } from './types.js';
9
9
  const TEST_CONFIG_PATH = join(tmpdir(), 'rebalancer-config-test.yaml');
10
+ // Helper to get strategy as array (for test type safety)
11
+ // Schema accepts both single object and array, but tests use array format
12
+ function getStrategyArray(data) {
13
+ return Array.isArray(data.strategy) ? data.strategy : [data.strategy];
14
+ }
10
15
  describe('RebalancerConfig', () => {
11
16
  let data;
12
17
  beforeEach(() => {
13
18
  data = {
14
19
  warpRouteId: 'warpRouteId',
15
- strategy: {
16
- rebalanceStrategy: RebalancerStrategyOptions.Weighted,
17
- chains: {
18
- chain1: {
19
- weighted: {
20
- weight: 100,
21
- tolerance: 0,
20
+ strategy: [
21
+ {
22
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
23
+ chains: {
24
+ chain1: {
25
+ weighted: {
26
+ weight: 100,
27
+ tolerance: 0,
28
+ },
29
+ bridge: ethers.constants.AddressZero,
30
+ bridgeLockTime: 1,
22
31
  },
23
- bridge: ethers.constants.AddressZero,
24
- bridgeLockTime: 1,
25
- },
26
- chain2: {
27
- weighted: {
28
- weight: 100,
29
- tolerance: 0,
32
+ chain2: {
33
+ weighted: {
34
+ weight: 100,
35
+ tolerance: 0,
36
+ },
37
+ bridge: ethers.constants.AddressZero,
38
+ bridgeLockTime: 1,
30
39
  },
31
- bridge: ethers.constants.AddressZero,
32
- bridgeLockTime: 1,
33
40
  },
34
41
  },
35
- },
42
+ ],
36
43
  };
37
44
  writeYamlOrJson(TEST_CONFIG_PATH, data);
38
45
  });
@@ -46,31 +53,33 @@ describe('RebalancerConfig', () => {
46
53
  it('should load config from file', () => {
47
54
  expect(RebalancerConfig.load(TEST_CONFIG_PATH)).to.deep.equal({
48
55
  warpRouteId: 'warpRouteId',
49
- strategyConfig: {
50
- rebalanceStrategy: RebalancerStrategyOptions.Weighted,
51
- chains: {
52
- chain1: {
53
- weighted: {
54
- weight: 100n,
55
- tolerance: 0n,
56
+ strategyConfig: [
57
+ {
58
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
59
+ chains: {
60
+ chain1: {
61
+ weighted: {
62
+ weight: 100n,
63
+ tolerance: 0n,
64
+ },
65
+ bridge: ethers.constants.AddressZero,
66
+ bridgeLockTime: 1_000,
56
67
  },
57
- bridge: ethers.constants.AddressZero,
58
- bridgeLockTime: 1_000,
59
- },
60
- chain2: {
61
- weighted: {
62
- weight: 100n,
63
- tolerance: 0n,
68
+ chain2: {
69
+ weighted: {
70
+ weight: 100n,
71
+ tolerance: 0n,
72
+ },
73
+ bridge: ethers.constants.AddressZero,
74
+ bridgeLockTime: 1_000,
64
75
  },
65
- bridge: ethers.constants.AddressZero,
66
- bridgeLockTime: 1_000,
67
76
  },
68
77
  },
69
- },
78
+ ],
70
79
  });
71
80
  });
72
81
  it('should throw if chains are not configured', () => {
73
- data.strategy.chains = {};
82
+ getStrategyArray(data)[0].chains = {};
74
83
  writeYamlOrJson(TEST_CONFIG_PATH, data);
75
84
  expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw('No chains configured');
76
85
  });
@@ -83,115 +92,122 @@ describe('RebalancerConfig', () => {
83
92
  it('should load relative params without modifications', () => {
84
93
  data = {
85
94
  warpRouteId: 'warpRouteId',
86
- strategy: {
87
- rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
88
- chains: {
89
- chain1: {
90
- minAmount: {
91
- min: '0.2',
92
- target: 0.3,
93
- type: RebalancerMinAmountType.Relative,
95
+ strategy: [
96
+ {
97
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
98
+ chains: {
99
+ chain1: {
100
+ minAmount: {
101
+ min: '0.2',
102
+ target: 0.3,
103
+ type: RebalancerMinAmountType.Relative,
104
+ },
105
+ bridge: ethers.constants.AddressZero,
106
+ bridgeLockTime: 1,
94
107
  },
95
- bridge: ethers.constants.AddressZero,
96
- bridgeLockTime: 1,
97
- },
98
- chain2: {
99
- minAmount: {
100
- min: '0.2',
101
- target: 0.3,
102
- type: RebalancerMinAmountType.Relative,
108
+ chain2: {
109
+ minAmount: {
110
+ min: '0.2',
111
+ target: 0.3,
112
+ type: RebalancerMinAmountType.Relative,
113
+ },
114
+ bridge: ethers.constants.AddressZero,
115
+ bridgeLockTime: 1,
103
116
  },
104
- bridge: ethers.constants.AddressZero,
105
- bridgeLockTime: 1,
106
117
  },
107
118
  },
108
- },
119
+ ],
109
120
  };
110
121
  writeYamlOrJson(TEST_CONFIG_PATH, data);
111
- expect(RebalancerConfig.load(TEST_CONFIG_PATH).strategyConfig.chains.chain1).to.deep.equal({
112
- ...data.strategy.chains.chain1,
122
+ expect(RebalancerConfig.load(TEST_CONFIG_PATH).strategyConfig[0].chains.chain1).to.deep.equal({
123
+ ...getStrategyArray(data)[0].chains.chain1,
113
124
  bridgeLockTime: 1_000,
114
125
  });
115
126
  });
116
127
  it('should load absolute params without modifications', () => {
117
128
  data = {
118
129
  warpRouteId: 'warpRouteId',
119
- strategy: {
120
- rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
121
- chains: {
122
- chain1: {
123
- minAmount: {
124
- min: '100000',
125
- target: 140000,
126
- type: RebalancerMinAmountType.Absolute,
127
- },
128
- bridge: ethers.constants.AddressZero,
129
- bridgeLockTime: 1,
130
- },
131
- chain2: {
132
- minAmount: {
133
- min: '100000',
134
- target: 140000,
135
- type: RebalancerMinAmountType.Absolute,
136
- },
137
- bridge: ethers.constants.AddressZero,
138
- bridgeLockTime: 1,
139
- },
140
- },
141
- },
142
- };
143
- writeYamlOrJson(TEST_CONFIG_PATH, data);
144
- expect(RebalancerConfig.load(TEST_CONFIG_PATH).strategyConfig.chains.chain1).to.deep.equal({
145
- ...data.strategy.chains.chain1,
146
- bridgeLockTime: 1_000,
147
- });
148
- });
149
- describe('override functionality', () => {
150
- it('should parse a config with overrides', () => {
151
- data = {
152
- warpRouteId: 'warpRouteId',
153
- strategy: {
130
+ strategy: [
131
+ {
154
132
  rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
155
133
  chains: {
156
134
  chain1: {
157
135
  minAmount: {
158
- min: 1000,
159
- target: 1100,
136
+ min: '100000',
137
+ target: 140000,
160
138
  type: RebalancerMinAmountType.Absolute,
161
139
  },
162
140
  bridge: ethers.constants.AddressZero,
163
141
  bridgeLockTime: 1,
164
- override: {
165
- chain2: {
166
- bridge: '0x1234567890123456789012345678901234567890',
167
- },
168
- },
169
142
  },
170
143
  chain2: {
171
144
  minAmount: {
172
- min: 2000,
173
- target: 2200,
145
+ min: '100000',
146
+ target: 140000,
174
147
  type: RebalancerMinAmountType.Absolute,
175
148
  },
176
149
  bridge: ethers.constants.AddressZero,
177
150
  bridgeLockTime: 1,
178
151
  },
179
- chain3: {
180
- minAmount: {
181
- min: 3000,
182
- target: 3300,
183
- type: RebalancerMinAmountType.Absolute,
152
+ },
153
+ },
154
+ ],
155
+ };
156
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
157
+ expect(RebalancerConfig.load(TEST_CONFIG_PATH).strategyConfig[0].chains.chain1).to.deep.equal({
158
+ ...getStrategyArray(data)[0].chains.chain1,
159
+ bridgeLockTime: 1_000,
160
+ });
161
+ });
162
+ describe('override functionality', () => {
163
+ it('should parse a config with overrides', () => {
164
+ data = {
165
+ warpRouteId: 'warpRouteId',
166
+ strategy: [
167
+ {
168
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
169
+ chains: {
170
+ chain1: {
171
+ minAmount: {
172
+ min: 1000,
173
+ target: 1100,
174
+ type: RebalancerMinAmountType.Absolute,
175
+ },
176
+ bridge: ethers.constants.AddressZero,
177
+ bridgeLockTime: 1,
178
+ override: {
179
+ chain2: {
180
+ bridge: '0x1234567890123456789012345678901234567890',
181
+ },
182
+ },
183
+ },
184
+ chain2: {
185
+ minAmount: {
186
+ min: 2000,
187
+ target: 2200,
188
+ type: RebalancerMinAmountType.Absolute,
189
+ },
190
+ bridge: ethers.constants.AddressZero,
191
+ bridgeLockTime: 1,
192
+ },
193
+ chain3: {
194
+ minAmount: {
195
+ min: 3000,
196
+ target: 3300,
197
+ type: RebalancerMinAmountType.Absolute,
198
+ },
199
+ bridge: ethers.constants.AddressZero,
200
+ bridgeLockTime: 1,
184
201
  },
185
- bridge: ethers.constants.AddressZero,
186
- bridgeLockTime: 1,
187
202
  },
188
203
  },
189
- },
204
+ ],
190
205
  };
191
206
  writeYamlOrJson(TEST_CONFIG_PATH, data);
192
207
  const config = RebalancerConfig.load(TEST_CONFIG_PATH);
193
- expect(config.strategyConfig.chains.chain1).to.have.property('override');
194
- const override = config.strategyConfig.chains.chain1.override;
208
+ const chainConfig = config.strategyConfig[0].chains.chain1;
209
+ expect(chainConfig).to.have.property('override');
210
+ const override = chainConfig.override;
195
211
  expect(override).to.not.be.undefined;
196
212
  expect(override).to.have.property('chain2');
197
213
  const toChain2Override = override.chain2;
@@ -201,37 +217,39 @@ describe('RebalancerConfig', () => {
201
217
  it('should throw when an override references a non-existent chain', () => {
202
218
  data = {
203
219
  warpRouteId: 'warpRouteId',
204
- strategy: {
205
- rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
206
- chains: {
207
- chain1: {
208
- minAmount: {
209
- min: 1000,
210
- target: 1100,
211
- type: RebalancerMinAmountType.Absolute,
212
- },
213
- bridge: ethers.constants.AddressZero,
214
- bridgeLockTime: 1,
215
- override: {
216
- chain2: {
217
- bridge: '0x1234567890123456789012345678901234567890',
220
+ strategy: [
221
+ {
222
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
223
+ chains: {
224
+ chain1: {
225
+ minAmount: {
226
+ min: 1000,
227
+ target: 1100,
228
+ type: RebalancerMinAmountType.Absolute,
218
229
  },
219
- chain3: {
220
- bridgeMinAcceptedAmount: 1000,
230
+ bridge: ethers.constants.AddressZero,
231
+ bridgeLockTime: 1,
232
+ override: {
233
+ chain2: {
234
+ bridge: '0x1234567890123456789012345678901234567890',
235
+ },
236
+ chain3: {
237
+ bridgeMinAcceptedAmount: 1000,
238
+ },
221
239
  },
222
240
  },
223
- },
224
- chain2: {
225
- minAmount: {
226
- min: 2000,
227
- target: 2200,
228
- type: RebalancerMinAmountType.Absolute,
241
+ chain2: {
242
+ minAmount: {
243
+ min: 2000,
244
+ target: 2200,
245
+ type: RebalancerMinAmountType.Absolute,
246
+ },
247
+ bridge: ethers.constants.AddressZero,
248
+ bridgeLockTime: 1,
229
249
  },
230
- bridge: ethers.constants.AddressZero,
231
- bridgeLockTime: 1,
232
250
  },
233
251
  },
234
- },
252
+ ],
235
253
  };
236
254
  writeYamlOrJson(TEST_CONFIG_PATH, data);
237
255
  expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw("Chain 'chain1' has an override for 'chain3', but 'chain3' is not defined in the config");
@@ -239,40 +257,42 @@ describe('RebalancerConfig', () => {
239
257
  it('should throw when an override references itself', () => {
240
258
  data = {
241
259
  warpRouteId: 'warpRouteId',
242
- strategy: {
243
- rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
244
- chains: {
245
- chain1: {
246
- minAmount: {
247
- min: 1000,
248
- target: 1100,
249
- type: RebalancerMinAmountType.Absolute,
250
- },
251
- bridge: ethers.constants.AddressZero,
252
- bridgeLockTime: 1,
253
- override: {
254
- chain1: {
255
- bridgeMinAcceptedAmount: 1000,
260
+ strategy: [
261
+ {
262
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
263
+ chains: {
264
+ chain1: {
265
+ minAmount: {
266
+ min: 1000,
267
+ target: 1100,
268
+ type: RebalancerMinAmountType.Absolute,
269
+ },
270
+ bridge: ethers.constants.AddressZero,
271
+ bridgeLockTime: 1,
272
+ override: {
273
+ chain1: {
274
+ bridgeMinAcceptedAmount: 1000,
275
+ },
256
276
  },
257
277
  },
258
- },
259
- chain2: {
260
- minAmount: {
261
- min: 2000,
262
- target: 2200,
263
- type: RebalancerMinAmountType.Absolute,
278
+ chain2: {
279
+ minAmount: {
280
+ min: 2000,
281
+ target: 2200,
282
+ type: RebalancerMinAmountType.Absolute,
283
+ },
284
+ bridge: ethers.constants.AddressZero,
285
+ bridgeLockTime: 1,
264
286
  },
265
- bridge: ethers.constants.AddressZero,
266
- bridgeLockTime: 1,
267
287
  },
268
288
  },
269
- },
289
+ ],
270
290
  };
271
291
  writeYamlOrJson(TEST_CONFIG_PATH, data);
272
292
  expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw("Chain 'chain1' has an override for 'chain1', but 'chain1' is self-referencing");
273
293
  });
274
294
  it('should allow multiple chain overrides', () => {
275
- data.strategy.chains.chain1 = {
295
+ getStrategyArray(data)[0].chains.chain1 = {
276
296
  bridge: ethers.constants.AddressZero,
277
297
  bridgeMinAcceptedAmount: 3000,
278
298
  bridgeLockTime: 1,
@@ -289,7 +309,7 @@ describe('RebalancerConfig', () => {
289
309
  },
290
310
  },
291
311
  };
292
- data.strategy.chains.chain2 = {
312
+ getStrategyArray(data)[0].chains.chain2 = {
293
313
  bridge: ethers.constants.AddressZero,
294
314
  bridgeMinAcceptedAmount: 5000,
295
315
  bridgeLockTime: 1,
@@ -298,7 +318,7 @@ describe('RebalancerConfig', () => {
298
318
  tolerance: 0,
299
319
  },
300
320
  };
301
- data.strategy.chains.chain3 = {
321
+ getStrategyArray(data)[0].chains.chain3 = {
302
322
  bridge: ethers.constants.AddressZero,
303
323
  bridgeMinAcceptedAmount: 6000,
304
324
  bridgeLockTime: 1,
@@ -309,7 +329,8 @@ describe('RebalancerConfig', () => {
309
329
  };
310
330
  writeYamlOrJson(TEST_CONFIG_PATH, data);
311
331
  const config = RebalancerConfig.load(TEST_CONFIG_PATH);
312
- const chain1Overrides = config.strategyConfig.chains.chain1.override;
332
+ const chainConfig = config.strategyConfig[0].chains.chain1;
333
+ const chain1Overrides = chainConfig.override;
313
334
  expect(chain1Overrides).to.not.be.undefined;
314
335
  expect(chain1Overrides).to.have.property('chain2');
315
336
  expect(chain1Overrides).to.have.property('chain3');
@@ -321,5 +342,255 @@ describe('RebalancerConfig', () => {
321
342
  expect(chain3Overrides.bridge).to.equal('0x1234567890123456789012345678901234567890');
322
343
  });
323
344
  });
345
+ describe('composite strategy validation', () => {
346
+ it('should throw if CollateralDeficitStrategy is not first in composite', () => {
347
+ data = {
348
+ warpRouteId: 'warpRouteId',
349
+ strategy: [
350
+ {
351
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
352
+ chains: {
353
+ chain1: {
354
+ weighted: { weight: 100, tolerance: 0 },
355
+ bridge: ethers.constants.AddressZero,
356
+ bridgeLockTime: 1,
357
+ },
358
+ chain2: {
359
+ weighted: { weight: 100, tolerance: 0 },
360
+ bridge: ethers.constants.AddressZero,
361
+ bridgeLockTime: 1,
362
+ },
363
+ },
364
+ },
365
+ {
366
+ rebalanceStrategy: RebalancerStrategyOptions.CollateralDeficit,
367
+ chains: {
368
+ chain1: {
369
+ buffer: 1000,
370
+ bridge: ethers.constants.AddressZero,
371
+ bridgeLockTime: 1,
372
+ },
373
+ chain2: {
374
+ buffer: 1000,
375
+ bridge: ethers.constants.AddressZero,
376
+ bridgeLockTime: 1,
377
+ },
378
+ },
379
+ },
380
+ ],
381
+ };
382
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
383
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw('CollateralDeficitStrategy must be first when used in composite strategy');
384
+ });
385
+ it('should allow CollateralDeficitStrategy first in composite', () => {
386
+ data = {
387
+ warpRouteId: 'warpRouteId',
388
+ strategy: [
389
+ {
390
+ rebalanceStrategy: RebalancerStrategyOptions.CollateralDeficit,
391
+ chains: {
392
+ chain1: {
393
+ buffer: 1000,
394
+ bridge: ethers.constants.AddressZero,
395
+ bridgeLockTime: 1,
396
+ },
397
+ chain2: {
398
+ buffer: 1000,
399
+ bridge: ethers.constants.AddressZero,
400
+ bridgeLockTime: 1,
401
+ },
402
+ },
403
+ },
404
+ {
405
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
406
+ chains: {
407
+ chain1: {
408
+ weighted: { weight: 100, tolerance: 0 },
409
+ bridge: ethers.constants.AddressZero,
410
+ bridgeLockTime: 1,
411
+ },
412
+ chain2: {
413
+ weighted: { weight: 100, tolerance: 0 },
414
+ bridge: ethers.constants.AddressZero,
415
+ bridgeLockTime: 1,
416
+ },
417
+ },
418
+ },
419
+ ],
420
+ };
421
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
422
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.not.throw();
423
+ });
424
+ });
425
+ });
426
+ describe('getAllBridges', () => {
427
+ const BRIDGE_A = '0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
428
+ const BRIDGE_B = '0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
429
+ const BRIDGE_C = '0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC';
430
+ it('should return empty array for empty strategies', () => {
431
+ const result = getAllBridges([]);
432
+ expect(result).to.deep.equal([]);
433
+ });
434
+ it('should return bridge from single strategy', () => {
435
+ const strategies = [
436
+ {
437
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
438
+ chains: {
439
+ chain1: {
440
+ weighted: { weight: 100n, tolerance: 0n },
441
+ bridge: BRIDGE_A,
442
+ bridgeLockTime: 1000,
443
+ },
444
+ chain2: {
445
+ weighted: { weight: 100n, tolerance: 0n },
446
+ bridge: BRIDGE_A,
447
+ bridgeLockTime: 1000,
448
+ },
449
+ },
450
+ },
451
+ ];
452
+ const result = getAllBridges(strategies);
453
+ expect(result).to.deep.equal([BRIDGE_A]);
454
+ });
455
+ it('should return all bridges from multiple strategies', () => {
456
+ const strategies = [
457
+ {
458
+ rebalanceStrategy: RebalancerStrategyOptions.CollateralDeficit,
459
+ chains: {
460
+ chain1: {
461
+ buffer: 1000,
462
+ bridge: BRIDGE_A,
463
+ bridgeLockTime: 1000,
464
+ },
465
+ chain2: {
466
+ buffer: 1000,
467
+ bridge: BRIDGE_A,
468
+ bridgeLockTime: 1000,
469
+ },
470
+ },
471
+ },
472
+ {
473
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
474
+ chains: {
475
+ chain1: {
476
+ weighted: { weight: 100n, tolerance: 0n },
477
+ bridge: BRIDGE_B,
478
+ bridgeLockTime: 1000,
479
+ },
480
+ chain2: {
481
+ weighted: { weight: 100n, tolerance: 0n },
482
+ bridge: BRIDGE_B,
483
+ bridgeLockTime: 1000,
484
+ },
485
+ },
486
+ },
487
+ ];
488
+ const result = getAllBridges(strategies);
489
+ expect(result).to.have.members([BRIDGE_A, BRIDGE_B]);
490
+ expect(result).to.have.lengthOf(2);
491
+ });
492
+ it('should include bridges from per-destination overrides', () => {
493
+ const strategies = [
494
+ {
495
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
496
+ chains: {
497
+ chain1: {
498
+ weighted: { weight: 100n, tolerance: 0n },
499
+ bridge: BRIDGE_A,
500
+ bridgeLockTime: 1000,
501
+ override: {
502
+ chain2: {
503
+ bridge: BRIDGE_B,
504
+ },
505
+ chain3: {
506
+ bridge: BRIDGE_C,
507
+ },
508
+ },
509
+ },
510
+ chain2: {
511
+ weighted: { weight: 100n, tolerance: 0n },
512
+ bridge: BRIDGE_A,
513
+ bridgeLockTime: 1000,
514
+ },
515
+ chain3: {
516
+ weighted: { weight: 100n, tolerance: 0n },
517
+ bridge: BRIDGE_A,
518
+ bridgeLockTime: 1000,
519
+ },
520
+ },
521
+ },
522
+ ];
523
+ const result = getAllBridges(strategies);
524
+ expect(result).to.have.members([BRIDGE_A, BRIDGE_B, BRIDGE_C]);
525
+ expect(result).to.have.lengthOf(3);
526
+ });
527
+ it('should deduplicate bridges across strategies and overrides', () => {
528
+ const strategies = [
529
+ {
530
+ rebalanceStrategy: RebalancerStrategyOptions.CollateralDeficit,
531
+ chains: {
532
+ chain1: {
533
+ buffer: 1000,
534
+ bridge: BRIDGE_A,
535
+ bridgeLockTime: 1000,
536
+ },
537
+ chain2: {
538
+ buffer: 1000,
539
+ bridge: BRIDGE_B,
540
+ bridgeLockTime: 1000,
541
+ },
542
+ },
543
+ },
544
+ {
545
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
546
+ chains: {
547
+ chain1: {
548
+ weighted: { weight: 100n, tolerance: 0n },
549
+ bridge: BRIDGE_A, // Same as first strategy
550
+ bridgeLockTime: 1000,
551
+ override: {
552
+ chain2: {
553
+ bridge: BRIDGE_B, // Same as chain2 default
554
+ },
555
+ },
556
+ },
557
+ chain2: {
558
+ weighted: { weight: 100n, tolerance: 0n },
559
+ bridge: BRIDGE_B,
560
+ bridgeLockTime: 1000,
561
+ },
562
+ },
563
+ },
564
+ ];
565
+ const result = getAllBridges(strategies);
566
+ expect(result).to.have.members([BRIDGE_A, BRIDGE_B]);
567
+ expect(result).to.have.lengthOf(2);
568
+ });
569
+ it('should handle overrides without bridge property', () => {
570
+ const strategies = [
571
+ {
572
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
573
+ chains: {
574
+ chain1: {
575
+ weighted: { weight: 100n, tolerance: 0n },
576
+ bridge: BRIDGE_A,
577
+ bridgeLockTime: 1000,
578
+ override: {
579
+ chain2: {
580
+ bridgeMinAcceptedAmount: 5000, // Override without bridge
581
+ },
582
+ },
583
+ },
584
+ chain2: {
585
+ weighted: { weight: 100n, tolerance: 0n },
586
+ bridge: BRIDGE_A,
587
+ bridgeLockTime: 1000,
588
+ },
589
+ },
590
+ },
591
+ ];
592
+ const result = getAllBridges(strategies);
593
+ expect(result).to.deep.equal([BRIDGE_A]);
594
+ });
324
595
  });
325
596
  //# sourceMappingURL=RebalancerConfig.test.js.map