@hyperlane-xyz/rebalancer 0.1.0-beta.5a8bd28ab

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 (202) hide show
  1. package/README.md +178 -0
  2. package/dist/config/RebalancerConfig.d.ts +12 -0
  3. package/dist/config/RebalancerConfig.d.ts.map +1 -0
  4. package/dist/config/RebalancerConfig.js +29 -0
  5. package/dist/config/RebalancerConfig.js.map +1 -0
  6. package/dist/config/RebalancerConfig.test.d.ts +2 -0
  7. package/dist/config/RebalancerConfig.test.d.ts.map +1 -0
  8. package/dist/config/RebalancerConfig.test.js +325 -0
  9. package/dist/config/RebalancerConfig.test.js.map +1 -0
  10. package/dist/core/Rebalancer.d.ts +23 -0
  11. package/dist/core/Rebalancer.d.ts.map +1 -0
  12. package/dist/core/Rebalancer.js +290 -0
  13. package/dist/core/Rebalancer.js.map +1 -0
  14. package/dist/core/RebalancerService.d.ts +115 -0
  15. package/dist/core/RebalancerService.d.ts.map +1 -0
  16. package/dist/core/RebalancerService.js +227 -0
  17. package/dist/core/RebalancerService.js.map +1 -0
  18. package/dist/core/WithInflightGuard.d.ts +20 -0
  19. package/dist/core/WithInflightGuard.d.ts.map +1 -0
  20. package/dist/core/WithInflightGuard.js +47 -0
  21. package/dist/core/WithInflightGuard.js.map +1 -0
  22. package/dist/core/WithInflightGuard.test.d.ts +2 -0
  23. package/dist/core/WithInflightGuard.test.d.ts.map +1 -0
  24. package/dist/core/WithInflightGuard.test.js +64 -0
  25. package/dist/core/WithInflightGuard.test.js.map +1 -0
  26. package/dist/core/WithSemaphore.d.ts +22 -0
  27. package/dist/core/WithSemaphore.d.ts.map +1 -0
  28. package/dist/core/WithSemaphore.js +67 -0
  29. package/dist/core/WithSemaphore.js.map +1 -0
  30. package/dist/core/WithSemaphore.test.d.ts +2 -0
  31. package/dist/core/WithSemaphore.test.d.ts.map +1 -0
  32. package/dist/core/WithSemaphore.test.js +83 -0
  33. package/dist/core/WithSemaphore.test.js.map +1 -0
  34. package/dist/factories/RebalancerContextFactory.d.ts +41 -0
  35. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -0
  36. package/dist/factories/RebalancerContextFactory.js +115 -0
  37. package/dist/factories/RebalancerContextFactory.js.map +1 -0
  38. package/dist/index.d.ts +33 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +35 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/interfaces/IMetrics.d.ts +5 -0
  43. package/dist/interfaces/IMetrics.d.ts.map +1 -0
  44. package/dist/interfaces/IMetrics.js +2 -0
  45. package/dist/interfaces/IMetrics.js.map +1 -0
  46. package/dist/interfaces/IMonitor.d.ts +51 -0
  47. package/dist/interfaces/IMonitor.d.ts.map +1 -0
  48. package/dist/interfaces/IMonitor.js +14 -0
  49. package/dist/interfaces/IMonitor.js.map +1 -0
  50. package/dist/interfaces/IRebalancer.d.ts +15 -0
  51. package/dist/interfaces/IRebalancer.d.ts.map +1 -0
  52. package/dist/interfaces/IRebalancer.js +2 -0
  53. package/dist/interfaces/IRebalancer.js.map +1 -0
  54. package/dist/interfaces/IStrategy.d.ts +11 -0
  55. package/dist/interfaces/IStrategy.d.ts.map +1 -0
  56. package/dist/interfaces/IStrategy.js +2 -0
  57. package/dist/interfaces/IStrategy.js.map +1 -0
  58. package/dist/metrics/Metrics.d.ts +31 -0
  59. package/dist/metrics/Metrics.d.ts.map +1 -0
  60. package/dist/metrics/Metrics.js +302 -0
  61. package/dist/metrics/Metrics.js.map +1 -0
  62. package/dist/metrics/PriceGetter.d.ts +10 -0
  63. package/dist/metrics/PriceGetter.d.ts.map +1 -0
  64. package/dist/metrics/PriceGetter.js +41 -0
  65. package/dist/metrics/PriceGetter.js.map +1 -0
  66. package/dist/metrics/scripts/metrics.d.ts +14 -0
  67. package/dist/metrics/scripts/metrics.d.ts.map +1 -0
  68. package/dist/metrics/scripts/metrics.js +198 -0
  69. package/dist/metrics/scripts/metrics.js.map +1 -0
  70. package/dist/metrics/types.d.ts +24 -0
  71. package/dist/metrics/types.d.ts.map +1 -0
  72. package/dist/metrics/types.js +2 -0
  73. package/dist/metrics/types.js.map +1 -0
  74. package/dist/metrics/utils/metrics.d.ts +12 -0
  75. package/dist/metrics/utils/metrics.d.ts.map +1 -0
  76. package/dist/metrics/utils/metrics.js +28 -0
  77. package/dist/metrics/utils/metrics.js.map +1 -0
  78. package/dist/monitor/Monitor.d.ts +26 -0
  79. package/dist/monitor/Monitor.d.ts.map +1 -0
  80. package/dist/monitor/Monitor.js +116 -0
  81. package/dist/monitor/Monitor.js.map +1 -0
  82. package/dist/service.d.ts +3 -0
  83. package/dist/service.d.ts.map +1 -0
  84. package/dist/service.js +125 -0
  85. package/dist/service.js.map +1 -0
  86. package/dist/strategy/BaseStrategy.d.ts +34 -0
  87. package/dist/strategy/BaseStrategy.d.ts.map +1 -0
  88. package/dist/strategy/BaseStrategy.js +127 -0
  89. package/dist/strategy/BaseStrategy.js.map +1 -0
  90. package/dist/strategy/MinAmountStrategy.d.ts +27 -0
  91. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -0
  92. package/dist/strategy/MinAmountStrategy.js +103 -0
  93. package/dist/strategy/MinAmountStrategy.js.map +1 -0
  94. package/dist/strategy/MinAmountStrategy.test.d.ts +2 -0
  95. package/dist/strategy/MinAmountStrategy.test.d.ts.map +1 -0
  96. package/dist/strategy/MinAmountStrategy.test.js +472 -0
  97. package/dist/strategy/MinAmountStrategy.test.js.map +1 -0
  98. package/dist/strategy/StrategyFactory.d.ts +16 -0
  99. package/dist/strategy/StrategyFactory.d.ts.map +1 -0
  100. package/dist/strategy/StrategyFactory.js +25 -0
  101. package/dist/strategy/StrategyFactory.js.map +1 -0
  102. package/dist/strategy/StrategyFactory.test.d.ts +2 -0
  103. package/dist/strategy/StrategyFactory.test.d.ts.map +1 -0
  104. package/dist/strategy/StrategyFactory.test.js +80 -0
  105. package/dist/strategy/StrategyFactory.test.js.map +1 -0
  106. package/dist/strategy/WeightedStrategy.d.ts +23 -0
  107. package/dist/strategy/WeightedStrategy.d.ts.map +1 -0
  108. package/dist/strategy/WeightedStrategy.js +61 -0
  109. package/dist/strategy/WeightedStrategy.js.map +1 -0
  110. package/dist/strategy/WeightedStrategy.test.d.ts +2 -0
  111. package/dist/strategy/WeightedStrategy.test.d.ts.map +1 -0
  112. package/dist/strategy/WeightedStrategy.test.js +307 -0
  113. package/dist/strategy/WeightedStrategy.test.js.map +1 -0
  114. package/dist/strategy/index.d.ts +5 -0
  115. package/dist/strategy/index.d.ts.map +1 -0
  116. package/dist/strategy/index.js +5 -0
  117. package/dist/strategy/index.js.map +1 -0
  118. package/dist/test/helpers.d.ts +8 -0
  119. package/dist/test/helpers.d.ts.map +1 -0
  120. package/dist/test/helpers.js +33 -0
  121. package/dist/test/helpers.js.map +1 -0
  122. package/dist/utils/ExplorerClient.d.ts +14 -0
  123. package/dist/utils/ExplorerClient.d.ts.map +1 -0
  124. package/dist/utils/ExplorerClient.js +82 -0
  125. package/dist/utils/ExplorerClient.js.map +1 -0
  126. package/dist/utils/balanceUtils.d.ts +13 -0
  127. package/dist/utils/balanceUtils.d.ts.map +1 -0
  128. package/dist/utils/balanceUtils.js +43 -0
  129. package/dist/utils/balanceUtils.js.map +1 -0
  130. package/dist/utils/balanceUtils.test.d.ts +2 -0
  131. package/dist/utils/balanceUtils.test.d.ts.map +1 -0
  132. package/dist/utils/balanceUtils.test.js +54 -0
  133. package/dist/utils/balanceUtils.test.js.map +1 -0
  134. package/dist/utils/bridgeUtils.d.ts +19 -0
  135. package/dist/utils/bridgeUtils.d.ts.map +1 -0
  136. package/dist/utils/bridgeUtils.js +20 -0
  137. package/dist/utils/bridgeUtils.js.map +1 -0
  138. package/dist/utils/bridgeUtils.test.d.ts +2 -0
  139. package/dist/utils/bridgeUtils.test.d.ts.map +1 -0
  140. package/dist/utils/bridgeUtils.test.js +77 -0
  141. package/dist/utils/bridgeUtils.test.js.map +1 -0
  142. package/dist/utils/errors.d.ts +4 -0
  143. package/dist/utils/errors.d.ts.map +1 -0
  144. package/dist/utils/errors.js +6 -0
  145. package/dist/utils/errors.js.map +1 -0
  146. package/dist/utils/files.d.ts +35 -0
  147. package/dist/utils/files.d.ts.map +1 -0
  148. package/dist/utils/files.js +190 -0
  149. package/dist/utils/files.js.map +1 -0
  150. package/dist/utils/generalUtils.d.ts +3 -0
  151. package/dist/utils/generalUtils.d.ts.map +1 -0
  152. package/dist/utils/generalUtils.js +9 -0
  153. package/dist/utils/generalUtils.js.map +1 -0
  154. package/dist/utils/index.d.ts +5 -0
  155. package/dist/utils/index.d.ts.map +1 -0
  156. package/dist/utils/index.js +5 -0
  157. package/dist/utils/index.js.map +1 -0
  158. package/dist/utils/tokenUtils.d.ts +14 -0
  159. package/dist/utils/tokenUtils.d.ts.map +1 -0
  160. package/dist/utils/tokenUtils.js +21 -0
  161. package/dist/utils/tokenUtils.js.map +1 -0
  162. package/package.json +70 -0
  163. package/src/config/RebalancerConfig.test.ts +388 -0
  164. package/src/config/RebalancerConfig.ts +39 -0
  165. package/src/core/Rebalancer.ts +471 -0
  166. package/src/core/RebalancerService.ts +333 -0
  167. package/src/core/WithInflightGuard.test.ts +131 -0
  168. package/src/core/WithInflightGuard.ts +67 -0
  169. package/src/core/WithSemaphore.test.ts +112 -0
  170. package/src/core/WithSemaphore.ts +92 -0
  171. package/src/factories/RebalancerContextFactory.ts +210 -0
  172. package/src/index.ts +68 -0
  173. package/src/interfaces/IMetrics.ts +5 -0
  174. package/src/interfaces/IMonitor.ts +63 -0
  175. package/src/interfaces/IRebalancer.ts +20 -0
  176. package/src/interfaces/IStrategy.ts +13 -0
  177. package/src/metrics/Metrics.ts +558 -0
  178. package/src/metrics/PriceGetter.ts +74 -0
  179. package/src/metrics/scripts/metrics.ts +298 -0
  180. package/src/metrics/types.ts +27 -0
  181. package/src/metrics/utils/metrics.ts +33 -0
  182. package/src/monitor/Monitor.ts +174 -0
  183. package/src/service.ts +154 -0
  184. package/src/strategy/BaseStrategy.ts +210 -0
  185. package/src/strategy/MinAmountStrategy.test.ts +625 -0
  186. package/src/strategy/MinAmountStrategy.ts +170 -0
  187. package/src/strategy/StrategyFactory.test.ts +109 -0
  188. package/src/strategy/StrategyFactory.ts +48 -0
  189. package/src/strategy/WeightedStrategy.test.ts +408 -0
  190. package/src/strategy/WeightedStrategy.ts +93 -0
  191. package/src/strategy/index.ts +4 -0
  192. package/src/test/helpers.ts +46 -0
  193. package/src/utils/ExplorerClient.ts +99 -0
  194. package/src/utils/balanceUtils.test.ts +74 -0
  195. package/src/utils/balanceUtils.ts +69 -0
  196. package/src/utils/bridgeUtils.test.ts +92 -0
  197. package/src/utils/bridgeUtils.ts +42 -0
  198. package/src/utils/errors.ts +5 -0
  199. package/src/utils/files.ts +276 -0
  200. package/src/utils/generalUtils.ts +13 -0
  201. package/src/utils/index.ts +4 -0
  202. package/src/utils/tokenUtils.ts +26 -0
@@ -0,0 +1,388 @@
1
+ import { expect } from 'chai';
2
+ import { ethers } from 'ethers';
3
+ import { rmSync } from 'fs';
4
+ import { tmpdir } from 'os';
5
+ import { join } from 'path';
6
+
7
+ import {
8
+ RebalancerConfigFileInput,
9
+ RebalancerMinAmountType,
10
+ RebalancerStrategyOptions,
11
+ } from '@hyperlane-xyz/sdk';
12
+
13
+ import { writeYamlOrJson } from '../utils/files.js';
14
+
15
+ import { RebalancerConfig } from './RebalancerConfig.js';
16
+
17
+ const TEST_CONFIG_PATH = join(tmpdir(), 'rebalancer-config-test.yaml');
18
+
19
+ describe('RebalancerConfig', () => {
20
+ let data: RebalancerConfigFileInput;
21
+
22
+ beforeEach(() => {
23
+ data = {
24
+ warpRouteId: 'warpRouteId',
25
+ strategy: {
26
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
27
+ chains: {
28
+ chain1: {
29
+ weighted: {
30
+ weight: 100,
31
+ tolerance: 0,
32
+ },
33
+ bridge: ethers.constants.AddressZero,
34
+ bridgeLockTime: 1,
35
+ },
36
+ chain2: {
37
+ weighted: {
38
+ weight: 100,
39
+ tolerance: 0,
40
+ },
41
+ bridge: ethers.constants.AddressZero,
42
+ bridgeLockTime: 1,
43
+ },
44
+ },
45
+ },
46
+ };
47
+
48
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
49
+ });
50
+
51
+ afterEach(() => {
52
+ rmSync(TEST_CONFIG_PATH, { force: true });
53
+ });
54
+
55
+ it('should throw when the config file does not exist', () => {
56
+ rmSync(TEST_CONFIG_PATH, { force: true });
57
+
58
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw(
59
+ `File doesn't exist at ${TEST_CONFIG_PATH}`,
60
+ );
61
+ });
62
+
63
+ it('should load config from file', () => {
64
+ expect(RebalancerConfig.load(TEST_CONFIG_PATH)).to.deep.equal({
65
+ warpRouteId: 'warpRouteId',
66
+ strategyConfig: {
67
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
68
+ chains: {
69
+ chain1: {
70
+ weighted: {
71
+ weight: 100n,
72
+ tolerance: 0n,
73
+ },
74
+ bridge: ethers.constants.AddressZero,
75
+ bridgeLockTime: 1_000,
76
+ },
77
+ chain2: {
78
+ weighted: {
79
+ weight: 100n,
80
+ tolerance: 0n,
81
+ },
82
+ bridge: ethers.constants.AddressZero,
83
+ bridgeLockTime: 1_000,
84
+ },
85
+ },
86
+ },
87
+ });
88
+ });
89
+
90
+ it('should throw if chains are not configured', () => {
91
+ data.strategy.chains = {};
92
+
93
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
94
+
95
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw(
96
+ 'No chains configured',
97
+ );
98
+ });
99
+
100
+ it('should throw if no warp route id is configured', () => {
101
+ // @ts-ignore
102
+ delete data.warpRouteId;
103
+
104
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
105
+
106
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw(
107
+ 'Validation error: Required at "warpRouteId"',
108
+ );
109
+ });
110
+
111
+ it('should load relative params without modifications', () => {
112
+ data = {
113
+ warpRouteId: 'warpRouteId',
114
+ strategy: {
115
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
116
+ chains: {
117
+ chain1: {
118
+ minAmount: {
119
+ min: '0.2',
120
+ target: 0.3,
121
+ type: RebalancerMinAmountType.Relative,
122
+ },
123
+ bridge: ethers.constants.AddressZero,
124
+ bridgeLockTime: 1,
125
+ },
126
+ chain2: {
127
+ minAmount: {
128
+ min: '0.2',
129
+ target: 0.3,
130
+ type: RebalancerMinAmountType.Relative,
131
+ },
132
+ bridge: ethers.constants.AddressZero,
133
+ bridgeLockTime: 1,
134
+ },
135
+ },
136
+ },
137
+ };
138
+
139
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
140
+
141
+ expect(
142
+ RebalancerConfig.load(TEST_CONFIG_PATH).strategyConfig.chains.chain1,
143
+ ).to.deep.equal({
144
+ ...data.strategy.chains.chain1,
145
+ bridgeLockTime: 1_000,
146
+ });
147
+ });
148
+
149
+ it('should load absolute params without modifications', () => {
150
+ data = {
151
+ warpRouteId: 'warpRouteId',
152
+ strategy: {
153
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
154
+ chains: {
155
+ chain1: {
156
+ minAmount: {
157
+ min: '100000',
158
+ target: 140000,
159
+ type: RebalancerMinAmountType.Absolute,
160
+ },
161
+ bridge: ethers.constants.AddressZero,
162
+ bridgeLockTime: 1,
163
+ },
164
+ chain2: {
165
+ minAmount: {
166
+ min: '100000',
167
+ target: 140000,
168
+ type: RebalancerMinAmountType.Absolute,
169
+ },
170
+ bridge: ethers.constants.AddressZero,
171
+ bridgeLockTime: 1,
172
+ },
173
+ },
174
+ },
175
+ };
176
+
177
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
178
+
179
+ expect(
180
+ RebalancerConfig.load(TEST_CONFIG_PATH).strategyConfig.chains.chain1,
181
+ ).to.deep.equal({
182
+ ...data.strategy.chains.chain1,
183
+ bridgeLockTime: 1_000,
184
+ });
185
+ });
186
+
187
+ describe('override functionality', () => {
188
+ it('should parse a config with overrides', () => {
189
+ data = {
190
+ warpRouteId: 'warpRouteId',
191
+ strategy: {
192
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
193
+ chains: {
194
+ chain1: {
195
+ minAmount: {
196
+ min: 1000,
197
+ target: 1100,
198
+ type: RebalancerMinAmountType.Absolute,
199
+ },
200
+ bridge: ethers.constants.AddressZero,
201
+ bridgeLockTime: 1,
202
+ override: {
203
+ chain2: {
204
+ bridge: '0x1234567890123456789012345678901234567890',
205
+ },
206
+ },
207
+ },
208
+ chain2: {
209
+ minAmount: {
210
+ min: 2000,
211
+ target: 2200,
212
+ type: RebalancerMinAmountType.Absolute,
213
+ },
214
+ bridge: ethers.constants.AddressZero,
215
+ bridgeLockTime: 1,
216
+ },
217
+ chain3: {
218
+ minAmount: {
219
+ min: 3000,
220
+ target: 3300,
221
+ type: RebalancerMinAmountType.Absolute,
222
+ },
223
+ bridge: ethers.constants.AddressZero,
224
+ bridgeLockTime: 1,
225
+ },
226
+ },
227
+ },
228
+ };
229
+
230
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
231
+
232
+ const config = RebalancerConfig.load(TEST_CONFIG_PATH);
233
+ expect(config.strategyConfig.chains.chain1).to.have.property('override');
234
+
235
+ const override = config.strategyConfig.chains.chain1.override;
236
+ expect(override).to.not.be.undefined;
237
+ expect(override).to.have.property('chain2');
238
+
239
+ const toChain2Override = override!.chain2;
240
+ expect(toChain2Override).to.have.property('bridge');
241
+ expect(toChain2Override.bridge).to.equal(
242
+ '0x1234567890123456789012345678901234567890',
243
+ );
244
+ });
245
+
246
+ it('should throw when an override references a non-existent chain', () => {
247
+ data = {
248
+ warpRouteId: 'warpRouteId',
249
+ strategy: {
250
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
251
+ chains: {
252
+ chain1: {
253
+ minAmount: {
254
+ min: 1000,
255
+ target: 1100,
256
+ type: RebalancerMinAmountType.Absolute,
257
+ },
258
+ bridge: ethers.constants.AddressZero,
259
+ bridgeLockTime: 1,
260
+ override: {
261
+ chain2: {
262
+ bridge: '0x1234567890123456789012345678901234567890',
263
+ },
264
+ chain3: {
265
+ bridgeMinAcceptedAmount: 1000,
266
+ },
267
+ },
268
+ },
269
+ chain2: {
270
+ minAmount: {
271
+ min: 2000,
272
+ target: 2200,
273
+ type: RebalancerMinAmountType.Absolute,
274
+ },
275
+ bridge: ethers.constants.AddressZero,
276
+ bridgeLockTime: 1,
277
+ },
278
+ },
279
+ },
280
+ };
281
+
282
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
283
+
284
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw(
285
+ "Chain 'chain1' has an override for 'chain3', but 'chain3' is not defined in the config",
286
+ );
287
+ });
288
+
289
+ it('should throw when an override references itself', () => {
290
+ data = {
291
+ warpRouteId: 'warpRouteId',
292
+ strategy: {
293
+ rebalanceStrategy: RebalancerStrategyOptions.MinAmount,
294
+ chains: {
295
+ chain1: {
296
+ minAmount: {
297
+ min: 1000,
298
+ target: 1100,
299
+ type: RebalancerMinAmountType.Absolute,
300
+ },
301
+ bridge: ethers.constants.AddressZero,
302
+ bridgeLockTime: 1,
303
+ override: {
304
+ chain1: {
305
+ bridgeMinAcceptedAmount: 1000,
306
+ },
307
+ },
308
+ },
309
+ chain2: {
310
+ minAmount: {
311
+ min: 2000,
312
+ target: 2200,
313
+ type: RebalancerMinAmountType.Absolute,
314
+ },
315
+ bridge: ethers.constants.AddressZero,
316
+ bridgeLockTime: 1,
317
+ },
318
+ },
319
+ },
320
+ };
321
+
322
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
323
+
324
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw(
325
+ "Chain 'chain1' has an override for 'chain1', but 'chain1' is self-referencing",
326
+ );
327
+ });
328
+
329
+ it('should allow multiple chain overrides', () => {
330
+ data.strategy.chains.chain1 = {
331
+ bridge: ethers.constants.AddressZero,
332
+ bridgeMinAcceptedAmount: 3000,
333
+ bridgeLockTime: 1,
334
+ weighted: {
335
+ weight: 100,
336
+ tolerance: 0,
337
+ },
338
+ override: {
339
+ chain2: {
340
+ bridgeMinAcceptedAmount: 4000,
341
+ },
342
+ chain3: {
343
+ bridge: '0x1234567890123456789012345678901234567890',
344
+ },
345
+ },
346
+ };
347
+
348
+ data.strategy.chains.chain2 = {
349
+ bridge: ethers.constants.AddressZero,
350
+ bridgeMinAcceptedAmount: 5000,
351
+ bridgeLockTime: 1,
352
+ weighted: {
353
+ weight: 100,
354
+ tolerance: 0,
355
+ },
356
+ };
357
+
358
+ data.strategy.chains.chain3 = {
359
+ bridge: ethers.constants.AddressZero,
360
+ bridgeMinAcceptedAmount: 6000,
361
+ bridgeLockTime: 1,
362
+ weighted: {
363
+ weight: 100,
364
+ tolerance: 0,
365
+ },
366
+ };
367
+
368
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
369
+
370
+ const config = RebalancerConfig.load(TEST_CONFIG_PATH);
371
+
372
+ const chain1Overrides = config.strategyConfig.chains.chain1.override;
373
+ expect(chain1Overrides).to.not.be.undefined;
374
+ expect(chain1Overrides).to.have.property('chain2');
375
+ expect(chain1Overrides).to.have.property('chain3');
376
+
377
+ const chain2Overrides = chain1Overrides!.chain2;
378
+ expect(chain2Overrides).to.have.property('bridgeMinAcceptedAmount');
379
+ expect(chain2Overrides.bridgeMinAcceptedAmount).to.equal(4000);
380
+
381
+ const chain3Overrides = chain1Overrides!.chain3;
382
+ expect(chain3Overrides).to.have.property('bridge');
383
+ expect(chain3Overrides.bridge).to.equal(
384
+ '0x1234567890123456789012345678901234567890',
385
+ );
386
+ });
387
+ });
388
+ });
@@ -0,0 +1,39 @@
1
+ import { fromZodError } from 'zod-validation-error';
2
+
3
+ import {
4
+ type RebalancerConfigFileInput,
5
+ RebalancerConfigSchema,
6
+ type StrategyConfig,
7
+ } from '@hyperlane-xyz/sdk';
8
+ import { isObjEmpty } from '@hyperlane-xyz/utils';
9
+
10
+ import { readYamlOrJson } from '../utils/files.js';
11
+
12
+ export class RebalancerConfig {
13
+ constructor(
14
+ public readonly warpRouteId: string,
15
+ public readonly strategyConfig: StrategyConfig,
16
+ ) {}
17
+
18
+ /**
19
+ * Loads config from a file
20
+ * @param configFilePath Path to the config file
21
+ */
22
+ static load(configFilePath: string) {
23
+ const config: RebalancerConfigFileInput = readYamlOrJson(configFilePath);
24
+
25
+ const validationResult = RebalancerConfigSchema.safeParse(config);
26
+
27
+ if (!validationResult.success) {
28
+ throw new Error(fromZodError(validationResult.error).message);
29
+ }
30
+
31
+ const { warpRouteId, strategy } = validationResult.data;
32
+
33
+ if (isObjEmpty(strategy.chains)) {
34
+ throw new Error('No chains configured');
35
+ }
36
+
37
+ return new RebalancerConfig(warpRouteId, strategy);
38
+ }
39
+ }