@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,625 @@
1
+ import { AddressZero } from '@ethersproject/constants';
2
+ import { expect } from 'chai';
3
+ import { pino } from 'pino';
4
+
5
+ import {
6
+ type ChainMap,
7
+ type ChainName,
8
+ RebalancerMinAmountType,
9
+ Token,
10
+ TokenStandard,
11
+ } from '@hyperlane-xyz/sdk';
12
+
13
+ import type { RawBalances } from '../interfaces/IStrategy.js';
14
+
15
+ import { MinAmountStrategy } from './MinAmountStrategy.js';
16
+
17
+ const testLogger = pino({ level: 'silent' });
18
+
19
+ describe('MinAmountStrategy', () => {
20
+ let chain1: ChainName;
21
+ let chain2: ChainName;
22
+ let chain3: ChainName;
23
+ const tokensByChainName: ChainMap<Token> = {};
24
+ const tokenArgs = {
25
+ name: 'token',
26
+ decimals: 18,
27
+ symbol: 'TOKEN',
28
+ standard: TokenStandard.ERC20,
29
+ addressOrDenom: '',
30
+ };
31
+ const totalCollateral = BigInt(300e18);
32
+
33
+ beforeEach(() => {
34
+ chain1 = 'chain1';
35
+ chain2 = 'chain2';
36
+ chain3 = 'chain3';
37
+ tokensByChainName[chain1] = new Token({ ...tokenArgs, chainName: chain1 });
38
+ tokensByChainName[chain2] = new Token({ ...tokenArgs, chainName: chain2 });
39
+ tokensByChainName[chain3] = new Token({ ...tokenArgs, chainName: chain3 });
40
+ });
41
+
42
+ describe('constructor', () => {
43
+ it('should throw an error when less than two chains are configured', () => {
44
+ expect(
45
+ () =>
46
+ new MinAmountStrategy(
47
+ {
48
+ [chain1]: {
49
+ minAmount: {
50
+ min: '100',
51
+ target: '120',
52
+ type: RebalancerMinAmountType.Absolute,
53
+ },
54
+ bridge: AddressZero,
55
+ bridgeLockTime: 1,
56
+ },
57
+ },
58
+ tokensByChainName,
59
+ totalCollateral,
60
+ testLogger,
61
+ ),
62
+ ).to.throw('At least two chains must be configured');
63
+ });
64
+
65
+ it('should create a strategy with minAmount and target using absolute values', () => {
66
+ new MinAmountStrategy(
67
+ {
68
+ [chain1]: {
69
+ minAmount: {
70
+ min: '100',
71
+ target: '120',
72
+ type: RebalancerMinAmountType.Absolute,
73
+ },
74
+ bridge: AddressZero,
75
+ bridgeLockTime: 1,
76
+ },
77
+ [chain2]: {
78
+ minAmount: {
79
+ min: '100',
80
+ target: '120',
81
+ type: RebalancerMinAmountType.Absolute,
82
+ },
83
+ bridge: AddressZero,
84
+ bridgeLockTime: 1,
85
+ },
86
+ },
87
+ tokensByChainName,
88
+ totalCollateral,
89
+ testLogger,
90
+ );
91
+ });
92
+
93
+ it('should create a strategy with minAmount and target using relative values', () => {
94
+ new MinAmountStrategy(
95
+ {
96
+ [chain1]: {
97
+ minAmount: {
98
+ min: 0.3,
99
+ target: 0.4,
100
+ type: RebalancerMinAmountType.Relative,
101
+ },
102
+ bridge: AddressZero,
103
+ bridgeLockTime: 1,
104
+ },
105
+ [chain2]: {
106
+ minAmount: {
107
+ min: 0.4,
108
+ target: 0.5,
109
+ type: RebalancerMinAmountType.Relative,
110
+ },
111
+ bridge: AddressZero,
112
+ bridgeLockTime: 1,
113
+ },
114
+ },
115
+ tokensByChainName,
116
+ totalCollateral,
117
+ testLogger,
118
+ );
119
+ });
120
+
121
+ it('should throw an error when minAmount is negative', () => {
122
+ expect(
123
+ () =>
124
+ new MinAmountStrategy(
125
+ {
126
+ [chain1]: {
127
+ minAmount: {
128
+ min: 100,
129
+ target: '120',
130
+ type: RebalancerMinAmountType.Absolute,
131
+ },
132
+ bridge: AddressZero,
133
+ bridgeLockTime: 1,
134
+ },
135
+ [chain2]: {
136
+ minAmount: {
137
+ min: '-10',
138
+ target: '120',
139
+ type: RebalancerMinAmountType.Absolute,
140
+ },
141
+ bridge: AddressZero,
142
+ bridgeLockTime: 1,
143
+ },
144
+ },
145
+ tokensByChainName,
146
+ totalCollateral,
147
+ testLogger,
148
+ ),
149
+ ).to.throw('Minimum amount (-10) cannot be negative for chain chain2');
150
+ });
151
+
152
+ it('should throw an error when target is less than min', () => {
153
+ expect(
154
+ () =>
155
+ new MinAmountStrategy(
156
+ {
157
+ [chain1]: {
158
+ minAmount: {
159
+ min: '100',
160
+ target: '80',
161
+ type: RebalancerMinAmountType.Absolute,
162
+ },
163
+ bridge: AddressZero,
164
+ bridgeLockTime: 1,
165
+ },
166
+ [chain2]: {
167
+ minAmount: {
168
+ min: '100',
169
+ target: '120',
170
+ type: RebalancerMinAmountType.Absolute,
171
+ },
172
+ bridge: AddressZero,
173
+ bridgeLockTime: 1,
174
+ },
175
+ },
176
+ tokensByChainName,
177
+ totalCollateral,
178
+ testLogger,
179
+ ),
180
+ ).to.throw(
181
+ 'Target (80) must be greater than or equal to min (100) for chain chain1',
182
+ );
183
+ });
184
+
185
+ it('should throw an error when relative target is less than relative min', () => {
186
+ expect(
187
+ () =>
188
+ new MinAmountStrategy(
189
+ {
190
+ [chain1]: {
191
+ minAmount: {
192
+ min: 0.5,
193
+ target: 0.4,
194
+ type: RebalancerMinAmountType.Relative,
195
+ },
196
+ bridge: AddressZero,
197
+ bridgeLockTime: 1,
198
+ },
199
+ [chain2]: {
200
+ minAmount: {
201
+ min: 0.3,
202
+ target: 0.5,
203
+ type: RebalancerMinAmountType.Relative,
204
+ },
205
+ bridge: AddressZero,
206
+ bridgeLockTime: 1,
207
+ },
208
+ },
209
+ tokensByChainName,
210
+ totalCollateral,
211
+ testLogger,
212
+ ),
213
+ ).to.throw(
214
+ 'Target (0.4) must be greater than or equal to min (0.5) for chain chain1',
215
+ );
216
+ });
217
+
218
+ it('should throw an error when raw balances chains length does not match configured chains length', () => {
219
+ expect(() =>
220
+ new MinAmountStrategy(
221
+ {
222
+ [chain1]: {
223
+ minAmount: {
224
+ min: '100',
225
+ target: '120',
226
+ type: RebalancerMinAmountType.Absolute,
227
+ },
228
+ bridge: AddressZero,
229
+ bridgeLockTime: 1,
230
+ },
231
+ [chain2]: {
232
+ minAmount: {
233
+ min: '100',
234
+ target: '120',
235
+ type: RebalancerMinAmountType.Absolute,
236
+ },
237
+ bridge: AddressZero,
238
+ bridgeLockTime: 1,
239
+ },
240
+ },
241
+ tokensByChainName,
242
+ totalCollateral,
243
+ testLogger,
244
+ ).getRebalancingRoutes({
245
+ [chain1]: 100n,
246
+ [chain2]: 200n,
247
+ [chain3]: 300n,
248
+ }),
249
+ ).to.throw('Config chains do not match raw balances chains length');
250
+ });
251
+
252
+ it('should throw an error when a raw balance is missing', () => {
253
+ expect(() =>
254
+ new MinAmountStrategy(
255
+ {
256
+ [chain1]: {
257
+ minAmount: {
258
+ min: '100',
259
+ target: '120',
260
+ type: RebalancerMinAmountType.Absolute,
261
+ },
262
+ bridge: AddressZero,
263
+ bridgeLockTime: 1,
264
+ },
265
+ [chain2]: {
266
+ minAmount: {
267
+ min: '100',
268
+ target: '120',
269
+ type: RebalancerMinAmountType.Absolute,
270
+ },
271
+ bridge: AddressZero,
272
+ bridgeLockTime: 1,
273
+ },
274
+ },
275
+ tokensByChainName,
276
+ totalCollateral,
277
+ testLogger,
278
+ ).getRebalancingRoutes({
279
+ [chain1]: 100n,
280
+ [chain3]: 300n,
281
+ } as RawBalances),
282
+ ).to.throw('Raw balance for chain chain2 not found');
283
+ });
284
+
285
+ it('should throw an error when a raw balance is negative', () => {
286
+ expect(() =>
287
+ new MinAmountStrategy(
288
+ {
289
+ [chain1]: {
290
+ minAmount: {
291
+ min: '100',
292
+ target: '120',
293
+ type: RebalancerMinAmountType.Absolute,
294
+ },
295
+ bridge: AddressZero,
296
+ bridgeLockTime: 1,
297
+ },
298
+ [chain2]: {
299
+ minAmount: {
300
+ min: '100',
301
+ target: '120',
302
+ type: RebalancerMinAmountType.Absolute,
303
+ },
304
+ bridge: AddressZero,
305
+ bridgeLockTime: 1,
306
+ },
307
+ },
308
+ tokensByChainName,
309
+ totalCollateral,
310
+ testLogger,
311
+ ).getRebalancingRoutes({
312
+ [chain1]: 100n,
313
+ [chain2]: -2n,
314
+ }),
315
+ ).to.throw('Raw balance for chain chain2 is negative');
316
+ });
317
+ });
318
+
319
+ describe('getRebalancingRoutes', () => {
320
+ it('should return an empty array when all chains have at least the minimum amount', () => {
321
+ const strategy = new MinAmountStrategy(
322
+ {
323
+ [chain1]: {
324
+ minAmount: {
325
+ min: '100',
326
+ target: '120',
327
+ type: RebalancerMinAmountType.Absolute,
328
+ },
329
+ bridge: AddressZero,
330
+ bridgeLockTime: 1,
331
+ },
332
+ [chain2]: {
333
+ minAmount: {
334
+ min: '100',
335
+ target: '120',
336
+ type: RebalancerMinAmountType.Absolute,
337
+ },
338
+ bridge: AddressZero,
339
+ bridgeLockTime: 1,
340
+ },
341
+ },
342
+ tokensByChainName,
343
+ totalCollateral,
344
+ testLogger,
345
+ );
346
+
347
+ const rawBalances: RawBalances = {
348
+ [chain1]: BigInt(120e18),
349
+ [chain2]: BigInt(120e18),
350
+ };
351
+
352
+ const routes = strategy.getRebalancingRoutes(rawBalances);
353
+
354
+ expect(routes).to.be.empty;
355
+ });
356
+
357
+ it('should return a single route when a chain is below minimum amount', () => {
358
+ const strategy = new MinAmountStrategy(
359
+ {
360
+ [chain1]: {
361
+ minAmount: {
362
+ min: '100',
363
+ target: '120',
364
+ type: RebalancerMinAmountType.Absolute,
365
+ },
366
+ bridge: AddressZero,
367
+ bridgeLockTime: 1,
368
+ },
369
+ [chain2]: {
370
+ minAmount: {
371
+ min: '100',
372
+ target: '120',
373
+ type: RebalancerMinAmountType.Absolute,
374
+ },
375
+ bridge: AddressZero,
376
+ bridgeLockTime: 1,
377
+ },
378
+ },
379
+ tokensByChainName,
380
+ totalCollateral,
381
+ testLogger,
382
+ );
383
+
384
+ const rawBalances = {
385
+ [chain1]: BigInt(50e18),
386
+ [chain2]: BigInt(200e18),
387
+ };
388
+
389
+ const routes = strategy.getRebalancingRoutes(rawBalances);
390
+
391
+ expect(routes).to.deep.equal([
392
+ {
393
+ origin: chain2,
394
+ destination: chain1,
395
+ amount: BigInt(70e18),
396
+ },
397
+ ]);
398
+ });
399
+
400
+ it('should return multiple routes for multiple chains below minimum amount', () => {
401
+ const strategy = new MinAmountStrategy(
402
+ {
403
+ [chain1]: {
404
+ minAmount: {
405
+ min: '80',
406
+ target: '100',
407
+ type: RebalancerMinAmountType.Absolute,
408
+ },
409
+ bridge: AddressZero,
410
+ bridgeLockTime: 1,
411
+ },
412
+ [chain2]: {
413
+ minAmount: {
414
+ min: '80',
415
+ target: '100',
416
+ type: RebalancerMinAmountType.Absolute,
417
+ },
418
+ bridge: AddressZero,
419
+ bridgeLockTime: 1,
420
+ },
421
+ [chain3]: {
422
+ minAmount: {
423
+ min: '80',
424
+ target: '100',
425
+ type: RebalancerMinAmountType.Absolute,
426
+ },
427
+ bridge: AddressZero,
428
+ bridgeLockTime: 1,
429
+ },
430
+ },
431
+ tokensByChainName,
432
+ totalCollateral,
433
+ testLogger,
434
+ );
435
+
436
+ const rawBalances = {
437
+ [chain1]: BigInt(50e18),
438
+ [chain2]: BigInt(75e18),
439
+ [chain3]: BigInt(300e18),
440
+ };
441
+
442
+ const routes = strategy.getRebalancingRoutes(rawBalances);
443
+
444
+ expect(routes).to.deep.equal([
445
+ {
446
+ origin: chain3,
447
+ destination: chain1,
448
+ amount: BigInt(50e18),
449
+ },
450
+ {
451
+ origin: chain3,
452
+ destination: chain2,
453
+ amount: BigInt(25e18),
454
+ },
455
+ ]);
456
+ });
457
+
458
+ it('should throw an error when the sum of `target` is greater than the sum of collaterals', () => {
459
+ expect(
460
+ () =>
461
+ new MinAmountStrategy(
462
+ {
463
+ [chain1]: {
464
+ minAmount: {
465
+ min: '100',
466
+ target: '220',
467
+ type: RebalancerMinAmountType.Absolute,
468
+ },
469
+ bridge: AddressZero,
470
+ bridgeLockTime: 1,
471
+ },
472
+ [chain2]: {
473
+ minAmount: {
474
+ min: '100',
475
+ target: '120',
476
+ type: RebalancerMinAmountType.Absolute,
477
+ },
478
+ bridge: AddressZero,
479
+ bridgeLockTime: 1,
480
+ },
481
+ },
482
+ tokensByChainName,
483
+ totalCollateral,
484
+ testLogger,
485
+ ),
486
+ ).to.throw(
487
+ `Consider reducing the targets as the sum (340) is greater than sum of collaterals (300)`,
488
+ );
489
+ });
490
+
491
+ it('should handle case where there is not enough surplus to meet all minimum requirements by scaling down deficits', () => {
492
+ const strategy = new MinAmountStrategy(
493
+ {
494
+ [chain1]: {
495
+ minAmount: {
496
+ min: '100',
497
+ target: '100',
498
+ type: RebalancerMinAmountType.Absolute,
499
+ },
500
+ bridge: AddressZero,
501
+ bridgeLockTime: 1,
502
+ },
503
+ [chain2]: {
504
+ minAmount: {
505
+ min: '100',
506
+ target: '100',
507
+ type: RebalancerMinAmountType.Absolute,
508
+ },
509
+ bridge: AddressZero,
510
+ bridgeLockTime: 1,
511
+ },
512
+ [chain3]: {
513
+ minAmount: {
514
+ min: '100',
515
+ target: '100',
516
+ type: RebalancerMinAmountType.Absolute,
517
+ },
518
+ bridge: AddressZero,
519
+ bridgeLockTime: 1,
520
+ },
521
+ },
522
+ tokensByChainName,
523
+ totalCollateral,
524
+ testLogger,
525
+ );
526
+
527
+ const rawBalances = {
528
+ [chain1]: BigInt(50e18),
529
+ [chain2]: BigInt(50e18),
530
+ [chain3]: BigInt(150e18), // Only 50n of surplus, not enough to bring both chains up to minimum
531
+ };
532
+
533
+ const routes = strategy.getRebalancingRoutes(rawBalances);
534
+
535
+ // It scales down the deficits to prevent sending all surplus to a single chain
536
+ expect(routes.length).to.equal(2);
537
+ expect(routes[0].origin).to.equal(chain3);
538
+ expect(routes[0].destination).to.equal(chain1);
539
+ expect(routes[0].amount).to.equal(BigInt(25e18));
540
+ expect(routes[1].origin).to.equal(chain3);
541
+ expect(routes[1].destination).to.equal(chain2);
542
+ expect(routes[1].amount).to.equal(BigInt(25e18));
543
+ });
544
+
545
+ it('should have no surplus or deficit when all at min', () => {
546
+ const strategy = new MinAmountStrategy(
547
+ {
548
+ [chain1]: {
549
+ minAmount: {
550
+ min: '100',
551
+ target: '100',
552
+ type: RebalancerMinAmountType.Absolute,
553
+ },
554
+ bridge: AddressZero,
555
+ bridgeLockTime: 1,
556
+ },
557
+ [chain2]: {
558
+ minAmount: {
559
+ min: '100',
560
+ target: '100',
561
+ type: RebalancerMinAmountType.Absolute,
562
+ },
563
+ bridge: AddressZero,
564
+ bridgeLockTime: 1,
565
+ },
566
+ },
567
+ tokensByChainName,
568
+ totalCollateral,
569
+ testLogger,
570
+ );
571
+
572
+ const rawBalances = {
573
+ [chain1]: BigInt(100e18),
574
+ [chain2]: BigInt(100e18),
575
+ };
576
+
577
+ const routes = strategy.getRebalancingRoutes(rawBalances);
578
+
579
+ expect(routes).to.be.empty;
580
+ });
581
+
582
+ it('should consider the target amount with relative configuration', () => {
583
+ const strategy = new MinAmountStrategy(
584
+ {
585
+ [chain1]: {
586
+ minAmount: {
587
+ min: 0.25,
588
+ target: 0.3,
589
+ type: RebalancerMinAmountType.Relative,
590
+ },
591
+ bridge: AddressZero,
592
+ bridgeLockTime: 1,
593
+ },
594
+ [chain2]: {
595
+ minAmount: {
596
+ min: 0.25,
597
+ target: 0.3,
598
+ type: RebalancerMinAmountType.Relative,
599
+ },
600
+ bridge: AddressZero,
601
+ bridgeLockTime: 1,
602
+ },
603
+ },
604
+ tokensByChainName,
605
+ totalCollateral,
606
+ testLogger,
607
+ );
608
+
609
+ const rawBalances: RawBalances = {
610
+ [chain1]: 200n,
611
+ [chain2]: 800n,
612
+ };
613
+
614
+ const routes = strategy.getRebalancingRoutes(rawBalances);
615
+
616
+ expect(routes).to.deep.equal([
617
+ {
618
+ origin: chain2,
619
+ destination: chain1,
620
+ amount: 100n,
621
+ },
622
+ ]);
623
+ });
624
+ });
625
+ });