@hyperlane-xyz/rebalancer 2.0.0 → 3.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.
- package/dist/bridges/LiFiBridge.d.ts +67 -0
- package/dist/bridges/LiFiBridge.d.ts.map +1 -0
- package/dist/bridges/LiFiBridge.js +386 -0
- package/dist/bridges/LiFiBridge.js.map +1 -0
- package/dist/config/RebalancerConfig.d.ts +8 -2
- package/dist/config/RebalancerConfig.d.ts.map +1 -1
- package/dist/config/RebalancerConfig.js +9 -4
- package/dist/config/RebalancerConfig.js.map +1 -1
- package/dist/config/RebalancerConfig.test.js +135 -1
- package/dist/config/RebalancerConfig.test.js.map +1 -1
- package/dist/config/types.d.ts +1023 -304
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +113 -10
- package/dist/config/types.js.map +1 -1
- package/dist/core/InventoryRebalancer.d.ts +190 -0
- package/dist/core/InventoryRebalancer.d.ts.map +1 -0
- package/dist/core/InventoryRebalancer.js +892 -0
- package/dist/core/InventoryRebalancer.js.map +1 -0
- package/dist/core/InventoryRebalancer.test.d.ts +2 -0
- package/dist/core/InventoryRebalancer.test.d.ts.map +1 -0
- package/dist/core/InventoryRebalancer.test.js +1382 -0
- package/dist/core/InventoryRebalancer.test.js.map +1 -0
- package/dist/core/Rebalancer.d.ts +11 -4
- package/dist/core/Rebalancer.d.ts.map +1 -1
- package/dist/core/Rebalancer.js +92 -9
- package/dist/core/Rebalancer.js.map +1 -1
- package/dist/core/Rebalancer.test.js +82 -49
- package/dist/core/Rebalancer.test.js.map +1 -1
- package/dist/core/RebalancerOrchestrator.d.ts +30 -9
- package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
- package/dist/core/RebalancerOrchestrator.js +79 -71
- package/dist/core/RebalancerOrchestrator.js.map +1 -1
- package/dist/core/RebalancerOrchestrator.test.d.ts +2 -0
- package/dist/core/RebalancerOrchestrator.test.d.ts.map +1 -0
- package/dist/core/RebalancerOrchestrator.test.js +719 -0
- package/dist/core/RebalancerOrchestrator.test.js.map +1 -0
- package/dist/core/RebalancerService.d.ts +7 -3
- package/dist/core/RebalancerService.d.ts.map +1 -1
- package/dist/core/RebalancerService.js +44 -24
- package/dist/core/RebalancerService.js.map +1 -1
- package/dist/core/RebalancerService.test.js +74 -110
- package/dist/core/RebalancerService.test.js.map +1 -1
- package/dist/e2e/collateral-deficit.e2e-test.js +1 -3
- package/dist/e2e/collateral-deficit.e2e-test.js.map +1 -1
- package/dist/e2e/composite.e2e-test.js.map +1 -1
- package/dist/e2e/harness/BridgeSetup.d.ts +6 -0
- package/dist/e2e/harness/BridgeSetup.d.ts.map +1 -1
- package/dist/e2e/harness/BridgeSetup.js +10 -1
- package/dist/e2e/harness/BridgeSetup.js.map +1 -1
- package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
- package/dist/e2e/harness/ForkIndexer.js +1 -0
- package/dist/e2e/harness/ForkIndexer.js.map +1 -1
- package/dist/e2e/harness/TestHelpers.d.ts.map +1 -1
- package/dist/e2e/harness/TestHelpers.js +1 -4
- package/dist/e2e/harness/TestHelpers.js.map +1 -1
- package/dist/e2e/harness/TestRebalancer.d.ts +1 -1
- package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
- package/dist/e2e/harness/TestRebalancer.js +9 -9
- package/dist/e2e/harness/TestRebalancer.js.map +1 -1
- package/dist/e2e/minAmount.e2e-test.js +0 -1
- package/dist/e2e/minAmount.e2e-test.js.map +1 -1
- package/dist/e2e/weighted.e2e-test.js +0 -1
- package/dist/e2e/weighted.e2e-test.js.map +1 -1
- package/dist/factories/RebalancerContextFactory.d.ts +48 -6
- package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
- package/dist/factories/RebalancerContextFactory.js +171 -17
- package/dist/factories/RebalancerContextFactory.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/interfaces/IExternalBridge.d.ts +101 -0
- package/dist/interfaces/IExternalBridge.d.ts.map +1 -0
- package/dist/interfaces/IExternalBridge.js +2 -0
- package/dist/interfaces/IExternalBridge.js.map +1 -0
- package/dist/interfaces/IMonitor.d.ts +1 -0
- package/dist/interfaces/IMonitor.d.ts.map +1 -1
- package/dist/interfaces/IRebalancer.d.ts +25 -25
- package/dist/interfaces/IRebalancer.d.ts.map +1 -1
- package/dist/interfaces/IStrategy.d.ts +36 -3
- package/dist/interfaces/IStrategy.d.ts.map +1 -1
- package/dist/interfaces/IStrategy.js +12 -1
- package/dist/interfaces/IStrategy.js.map +1 -1
- package/dist/metrics/PriceGetter.js +1 -1
- package/dist/metrics/PriceGetter.js.map +1 -1
- package/dist/metrics/scripts/metrics.d.ts +3 -3
- package/dist/monitor/Monitor.d.ts +12 -2
- package/dist/monitor/Monitor.d.ts.map +1 -1
- package/dist/monitor/Monitor.js +46 -1
- package/dist/monitor/Monitor.js.map +1 -1
- package/dist/service.js +40 -17
- package/dist/service.js.map +1 -1
- package/dist/strategy/BaseStrategy.d.ts +12 -6
- package/dist/strategy/BaseStrategy.d.ts.map +1 -1
- package/dist/strategy/BaseStrategy.js +56 -21
- package/dist/strategy/BaseStrategy.js.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.d.ts +1 -1
- package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.js +19 -11
- package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.test.js +135 -2
- package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -1
- package/dist/strategy/CompositeStrategy.test.js +13 -0
- package/dist/strategy/CompositeStrategy.test.js.map +1 -1
- package/dist/strategy/MinAmountStrategy.test.js +4 -0
- package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
- package/dist/strategy/StrategyFactory.d.ts +2 -1
- package/dist/strategy/StrategyFactory.d.ts.map +1 -1
- package/dist/strategy/StrategyFactory.js +24 -8
- package/dist/strategy/StrategyFactory.js.map +1 -1
- package/dist/strategy/WeightedStrategy.test.js +6 -0
- package/dist/strategy/WeightedStrategy.test.js.map +1 -1
- package/dist/test/helpers.d.ts +8 -7
- package/dist/test/helpers.d.ts.map +1 -1
- package/dist/test/helpers.js +23 -5
- package/dist/test/helpers.js.map +1 -1
- package/dist/test/lifiMocks.d.ts +51 -0
- package/dist/test/lifiMocks.d.ts.map +1 -0
- package/dist/test/lifiMocks.js +130 -0
- package/dist/test/lifiMocks.js.map +1 -0
- package/dist/tracking/ActionTracker.d.ts +34 -1
- package/dist/tracking/ActionTracker.d.ts.map +1 -1
- package/dist/tracking/ActionTracker.js +233 -26
- package/dist/tracking/ActionTracker.js.map +1 -1
- package/dist/tracking/ActionTracker.test.js +380 -19
- package/dist/tracking/ActionTracker.test.js.map +1 -1
- package/dist/tracking/IActionTracker.d.ts +48 -3
- package/dist/tracking/IActionTracker.d.ts.map +1 -1
- package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
- package/dist/tracking/InflightContextAdapter.js +24 -7
- package/dist/tracking/InflightContextAdapter.js.map +1 -1
- package/dist/tracking/InflightContextAdapter.test.js +7 -4
- package/dist/tracking/InflightContextAdapter.test.js.map +1 -1
- package/dist/tracking/types.d.ts +33 -2
- package/dist/tracking/types.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.d.ts +3 -1
- package/dist/utils/ExplorerClient.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.js +16 -8
- package/dist/utils/ExplorerClient.js.map +1 -1
- package/dist/utils/bridgeUtils.d.ts +27 -4
- package/dist/utils/bridgeUtils.d.ts.map +1 -1
- package/dist/utils/bridgeUtils.js +38 -0
- package/dist/utils/bridgeUtils.js.map +1 -1
- package/dist/utils/bridgeUtils.test.js +9 -0
- package/dist/utils/bridgeUtils.test.js.map +1 -1
- package/dist/utils/gasEstimation.d.ts +65 -0
- package/dist/utils/gasEstimation.d.ts.map +1 -0
- package/dist/utils/gasEstimation.js +176 -0
- package/dist/utils/gasEstimation.js.map +1 -0
- package/dist/utils/tokenUtils.d.ts +9 -1
- package/dist/utils/tokenUtils.d.ts.map +1 -1
- package/dist/utils/tokenUtils.js +11 -0
- package/dist/utils/tokenUtils.js.map +1 -1
- package/package.json +9 -7
- package/src/bridges/LiFiBridge.ts +538 -0
- package/src/config/RebalancerConfig.test.ts +162 -0
- package/src/config/RebalancerConfig.ts +21 -3
- package/src/config/types.ts +147 -10
- package/src/core/InventoryRebalancer.test.ts +1721 -0
- package/src/core/InventoryRebalancer.ts +1265 -0
- package/src/core/Rebalancer.test.ts +84 -30
- package/src/core/Rebalancer.ts +144 -23
- package/src/core/RebalancerOrchestrator.test.ts +869 -0
- package/src/core/RebalancerOrchestrator.ts +146 -95
- package/src/core/RebalancerService.test.ts +86 -124
- package/src/core/RebalancerService.ts +67 -33
- package/src/e2e/collateral-deficit.e2e-test.ts +2 -4
- package/src/e2e/composite.e2e-test.ts +5 -5
- package/src/e2e/harness/BridgeSetup.ts +28 -1
- package/src/e2e/harness/ForkIndexer.ts +1 -0
- package/src/e2e/harness/TestHelpers.ts +1 -4
- package/src/e2e/harness/TestRebalancer.ts +10 -7
- package/src/e2e/minAmount.e2e-test.ts +1 -2
- package/src/e2e/weighted.e2e-test.ts +1 -2
- package/src/factories/RebalancerContextFactory.ts +294 -24
- package/src/index.ts +22 -5
- package/src/interfaces/IExternalBridge.ts +115 -0
- package/src/interfaces/IMonitor.ts +1 -0
- package/src/interfaces/IRebalancer.ts +45 -29
- package/src/interfaces/IStrategy.ts +50 -3
- package/src/metrics/PriceGetter.ts +1 -1
- package/src/monitor/Monitor.ts +81 -2
- package/src/service.ts +59 -18
- package/src/strategy/BaseStrategy.ts +77 -24
- package/src/strategy/CollateralDeficitStrategy.test.ts +181 -4
- package/src/strategy/CollateralDeficitStrategy.ts +42 -15
- package/src/strategy/CompositeStrategy.test.ts +13 -0
- package/src/strategy/MinAmountStrategy.test.ts +4 -0
- package/src/strategy/StrategyFactory.ts +33 -6
- package/src/strategy/WeightedStrategy.test.ts +6 -0
- package/src/test/helpers.ts +39 -14
- package/src/test/lifiMocks.ts +174 -0
- package/src/tracking/ActionTracker.test.ts +443 -19
- package/src/tracking/ActionTracker.ts +339 -28
- package/src/tracking/IActionTracker.ts +59 -3
- package/src/tracking/InflightContextAdapter.test.ts +7 -4
- package/src/tracking/InflightContextAdapter.ts +42 -9
- package/src/tracking/types.ts +45 -2
- package/src/utils/ExplorerClient.ts +27 -10
- package/src/utils/bridgeUtils.test.ts +9 -0
- package/src/utils/bridgeUtils.ts +75 -6
- package/src/utils/gasEstimation.ts +272 -0
- package/src/utils/tokenUtils.ts +12 -0
- package/dist/tracking/index.d.ts +0 -7
- package/dist/tracking/index.d.ts.map +0 -1
- package/dist/tracking/index.js +0 -6
- package/dist/tracking/index.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -5
- package/dist/utils/index.js.map +0 -1
- package/src/tracking/index.ts +0 -36
- package/src/utils/index.ts +0 -4
|
@@ -7,12 +7,49 @@ import Sinon from 'sinon';
|
|
|
7
7
|
import { HyperlaneCore } from '@hyperlane-xyz/sdk';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
buildTestMovableCollateralRoute,
|
|
11
11
|
createRebalancerTestContext,
|
|
12
12
|
} from '../test/helpers.js';
|
|
13
|
+
import type { IActionTracker } from '../tracking/IActionTracker.js';
|
|
13
14
|
|
|
14
15
|
import { Rebalancer } from './Rebalancer.js';
|
|
15
16
|
|
|
17
|
+
function createMockActionTracker(): IActionTracker {
|
|
18
|
+
return {
|
|
19
|
+
initialize: Sinon.stub().resolves(),
|
|
20
|
+
createRebalanceIntent: Sinon.stub().callsFake(async () => ({
|
|
21
|
+
id: `intent-${Date.now()}`,
|
|
22
|
+
status: 'not_started',
|
|
23
|
+
})),
|
|
24
|
+
createRebalanceAction: Sinon.stub().resolves(),
|
|
25
|
+
completeRebalanceAction: Sinon.stub().resolves(),
|
|
26
|
+
failRebalanceAction: Sinon.stub().resolves(),
|
|
27
|
+
completeRebalanceIntent: Sinon.stub().resolves(),
|
|
28
|
+
cancelRebalanceIntent: Sinon.stub().resolves(),
|
|
29
|
+
failRebalanceIntent: Sinon.stub().resolves(),
|
|
30
|
+
syncTransfers: Sinon.stub().resolves(),
|
|
31
|
+
syncRebalanceIntents: Sinon.stub().resolves(),
|
|
32
|
+
syncRebalanceActions: Sinon.stub().resolves(),
|
|
33
|
+
syncInventoryMovementActions: Sinon.stub().resolves({
|
|
34
|
+
completed: 0,
|
|
35
|
+
failed: 0,
|
|
36
|
+
}),
|
|
37
|
+
logStoreContents: Sinon.stub().resolves(),
|
|
38
|
+
getInProgressTransfers: Sinon.stub().resolves([]),
|
|
39
|
+
getActiveRebalanceIntents: Sinon.stub().resolves([]),
|
|
40
|
+
getTransfersByDestination: Sinon.stub().resolves([]),
|
|
41
|
+
getRebalanceIntentsByDestination: Sinon.stub().resolves([]),
|
|
42
|
+
getTransfer: Sinon.stub().resolves(undefined),
|
|
43
|
+
getRebalanceIntent: Sinon.stub().resolves(undefined),
|
|
44
|
+
getRebalanceAction: Sinon.stub().resolves(undefined),
|
|
45
|
+
getInProgressActions: Sinon.stub().resolves([]),
|
|
46
|
+
getPartiallyFulfilledInventoryIntents: Sinon.stub().resolves([]),
|
|
47
|
+
getActionsByType: Sinon.stub().resolves([]),
|
|
48
|
+
getActionsForIntent: Sinon.stub().resolves([]),
|
|
49
|
+
getInflightInventoryMovements: Sinon.stub().resolves(0n),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
16
53
|
chai.use(chaiAsPromised);
|
|
17
54
|
|
|
18
55
|
const testLogger = pino({ level: 'silent' });
|
|
@@ -36,6 +73,7 @@ describe('Rebalancer', () => {
|
|
|
36
73
|
ctx.chainMetadata,
|
|
37
74
|
ctx.tokensByChainName,
|
|
38
75
|
ctx.multiProvider as any,
|
|
76
|
+
createMockActionTracker(),
|
|
39
77
|
testLogger,
|
|
40
78
|
);
|
|
41
79
|
|
|
@@ -58,19 +96,18 @@ describe('Rebalancer', () => {
|
|
|
58
96
|
ctx.chainMetadata,
|
|
59
97
|
ctx.tokensByChainName,
|
|
60
98
|
ctx.multiProvider as any,
|
|
99
|
+
createMockActionTracker(),
|
|
61
100
|
testLogger,
|
|
62
101
|
);
|
|
63
102
|
|
|
64
|
-
const route =
|
|
103
|
+
const route = buildTestMovableCollateralRoute({
|
|
65
104
|
origin: 'ethereum',
|
|
66
105
|
destination: 'arbitrum',
|
|
67
106
|
});
|
|
68
|
-
|
|
69
107
|
const results = await rebalancer.rebalance([route]);
|
|
70
108
|
|
|
71
109
|
expect(results).to.have.lengthOf(1);
|
|
72
110
|
expect(results[0].success).to.be.true;
|
|
73
|
-
expect(results[0].route).to.deep.equal(route);
|
|
74
111
|
});
|
|
75
112
|
|
|
76
113
|
it('should return failure results for routes that fail preparation', async () => {
|
|
@@ -83,15 +120,15 @@ describe('Rebalancer', () => {
|
|
|
83
120
|
ctx.chainMetadata,
|
|
84
121
|
ctx.tokensByChainName,
|
|
85
122
|
ctx.multiProvider as any,
|
|
123
|
+
createMockActionTracker(),
|
|
86
124
|
testLogger,
|
|
87
125
|
);
|
|
88
126
|
|
|
89
|
-
const route =
|
|
127
|
+
const route = buildTestMovableCollateralRoute();
|
|
90
128
|
const results = await rebalancer.rebalance([route]);
|
|
91
129
|
|
|
92
130
|
expect(results).to.have.lengthOf(1);
|
|
93
131
|
expect(results[0].success).to.be.false;
|
|
94
|
-
expect(results[0].route).to.deep.equal(route);
|
|
95
132
|
});
|
|
96
133
|
|
|
97
134
|
it('should handle mixed success and failure results', async () => {
|
|
@@ -121,15 +158,16 @@ describe('Rebalancer', () => {
|
|
|
121
158
|
},
|
|
122
159
|
ctx.tokensByChainName,
|
|
123
160
|
ctx.multiProvider as any,
|
|
161
|
+
createMockActionTracker(),
|
|
124
162
|
testLogger,
|
|
125
163
|
);
|
|
126
164
|
|
|
127
165
|
const routes = [
|
|
128
|
-
|
|
166
|
+
buildTestMovableCollateralRoute({
|
|
129
167
|
origin: 'ethereum',
|
|
130
168
|
destination: 'arbitrum',
|
|
131
169
|
}),
|
|
132
|
-
|
|
170
|
+
buildTestMovableCollateralRoute({
|
|
133
171
|
origin: 'optimism',
|
|
134
172
|
destination: 'arbitrum',
|
|
135
173
|
}),
|
|
@@ -154,10 +192,11 @@ describe('Rebalancer', () => {
|
|
|
154
192
|
ctx.chainMetadata,
|
|
155
193
|
ctx.tokensByChainName,
|
|
156
194
|
ctx.multiProvider as any,
|
|
195
|
+
createMockActionTracker(),
|
|
157
196
|
testLogger,
|
|
158
197
|
);
|
|
159
198
|
|
|
160
|
-
const route =
|
|
199
|
+
const route = buildTestMovableCollateralRoute({
|
|
161
200
|
origin: 'ethereum',
|
|
162
201
|
destination: 'arbitrum',
|
|
163
202
|
});
|
|
@@ -176,10 +215,11 @@ describe('Rebalancer', () => {
|
|
|
176
215
|
ctx.chainMetadata,
|
|
177
216
|
ctx.tokensByChainName,
|
|
178
217
|
ctx.multiProvider as any,
|
|
218
|
+
createMockActionTracker(),
|
|
179
219
|
testLogger,
|
|
180
220
|
);
|
|
181
221
|
|
|
182
|
-
const route =
|
|
222
|
+
const route = buildTestMovableCollateralRoute({
|
|
183
223
|
origin: 'ethereum',
|
|
184
224
|
destination: 'arbitrum',
|
|
185
225
|
});
|
|
@@ -199,10 +239,11 @@ describe('Rebalancer', () => {
|
|
|
199
239
|
ctx.chainMetadata,
|
|
200
240
|
ctx.tokensByChainName,
|
|
201
241
|
ctx.multiProvider as any,
|
|
242
|
+
createMockActionTracker(),
|
|
202
243
|
testLogger,
|
|
203
244
|
);
|
|
204
245
|
|
|
205
|
-
const route =
|
|
246
|
+
const route = buildTestMovableCollateralRoute();
|
|
206
247
|
const results = await rebalancer.rebalance([route]);
|
|
207
248
|
|
|
208
249
|
expect(results).to.have.lengthOf(1);
|
|
@@ -221,10 +262,11 @@ describe('Rebalancer', () => {
|
|
|
221
262
|
ctx.chainMetadata,
|
|
222
263
|
ctx.tokensByChainName,
|
|
223
264
|
ctx.multiProvider as any,
|
|
265
|
+
createMockActionTracker(),
|
|
224
266
|
testLogger,
|
|
225
267
|
);
|
|
226
268
|
|
|
227
|
-
const route =
|
|
269
|
+
const route = buildTestMovableCollateralRoute();
|
|
228
270
|
const results = await rebalancer.rebalance([route]);
|
|
229
271
|
|
|
230
272
|
expect(results).to.have.lengthOf(1);
|
|
@@ -241,10 +283,11 @@ describe('Rebalancer', () => {
|
|
|
241
283
|
ctx.chainMetadata,
|
|
242
284
|
ctx.tokensByChainName,
|
|
243
285
|
ctx.multiProvider as any,
|
|
286
|
+
createMockActionTracker(),
|
|
244
287
|
testLogger,
|
|
245
288
|
);
|
|
246
289
|
|
|
247
|
-
const route =
|
|
290
|
+
const route = buildTestMovableCollateralRoute();
|
|
248
291
|
const results = await rebalancer.rebalance([route]);
|
|
249
292
|
|
|
250
293
|
expect(results).to.have.lengthOf(1);
|
|
@@ -263,10 +306,11 @@ describe('Rebalancer', () => {
|
|
|
263
306
|
ctx.chainMetadata,
|
|
264
307
|
ctx.tokensByChainName,
|
|
265
308
|
ctx.multiProvider as any,
|
|
309
|
+
createMockActionTracker(),
|
|
266
310
|
testLogger,
|
|
267
311
|
);
|
|
268
312
|
|
|
269
|
-
const route =
|
|
313
|
+
const route = buildTestMovableCollateralRoute();
|
|
270
314
|
const results = await rebalancer.rebalance([route]);
|
|
271
315
|
|
|
272
316
|
expect(results).to.have.lengthOf(1);
|
|
@@ -283,10 +327,11 @@ describe('Rebalancer', () => {
|
|
|
283
327
|
ctx.chainMetadata,
|
|
284
328
|
ctx.tokensByChainName,
|
|
285
329
|
ctx.multiProvider as any,
|
|
330
|
+
createMockActionTracker(),
|
|
286
331
|
testLogger,
|
|
287
332
|
);
|
|
288
333
|
|
|
289
|
-
const route =
|
|
334
|
+
const route = buildTestMovableCollateralRoute();
|
|
290
335
|
const results = await rebalancer.rebalance([route]);
|
|
291
336
|
|
|
292
337
|
expect(results).to.have.lengthOf(1);
|
|
@@ -312,10 +357,11 @@ describe('Rebalancer', () => {
|
|
|
312
357
|
ctx.chainMetadata,
|
|
313
358
|
ctx.tokensByChainName,
|
|
314
359
|
ctx.multiProvider as any,
|
|
360
|
+
createMockActionTracker(),
|
|
315
361
|
testLogger,
|
|
316
362
|
);
|
|
317
363
|
|
|
318
|
-
const route =
|
|
364
|
+
const route = buildTestMovableCollateralRoute();
|
|
319
365
|
const results = await rebalancer.rebalance([route]);
|
|
320
366
|
|
|
321
367
|
expect(results).to.have.lengthOf(1);
|
|
@@ -357,15 +403,16 @@ describe('Rebalancer', () => {
|
|
|
357
403
|
},
|
|
358
404
|
{ ...ctx.tokensByChainName, optimism: ctx.tokensByChainName.ethereum },
|
|
359
405
|
ctx.multiProvider as any,
|
|
406
|
+
createMockActionTracker(),
|
|
360
407
|
testLogger,
|
|
361
408
|
);
|
|
362
409
|
|
|
363
410
|
const routes = [
|
|
364
|
-
|
|
411
|
+
buildTestMovableCollateralRoute({
|
|
365
412
|
origin: 'ethereum',
|
|
366
413
|
destination: 'arbitrum',
|
|
367
414
|
}),
|
|
368
|
-
|
|
415
|
+
buildTestMovableCollateralRoute({
|
|
369
416
|
origin: 'optimism',
|
|
370
417
|
destination: 'arbitrum',
|
|
371
418
|
}),
|
|
@@ -408,19 +455,20 @@ describe('Rebalancer', () => {
|
|
|
408
455
|
ctx.chainMetadata,
|
|
409
456
|
ctx.tokensByChainName,
|
|
410
457
|
ctx.multiProvider as any,
|
|
458
|
+
createMockActionTracker(),
|
|
411
459
|
testLogger,
|
|
412
460
|
);
|
|
413
461
|
|
|
414
462
|
const routes = [
|
|
415
|
-
|
|
463
|
+
buildTestMovableCollateralRoute({
|
|
416
464
|
origin: 'ethereum',
|
|
417
465
|
destination: 'arbitrum',
|
|
418
466
|
}),
|
|
419
|
-
|
|
467
|
+
buildTestMovableCollateralRoute({
|
|
420
468
|
origin: 'ethereum',
|
|
421
469
|
destination: 'optimism',
|
|
422
470
|
}),
|
|
423
|
-
|
|
471
|
+
buildTestMovableCollateralRoute({
|
|
424
472
|
origin: 'optimism',
|
|
425
473
|
destination: 'arbitrum',
|
|
426
474
|
}),
|
|
@@ -444,10 +492,11 @@ describe('Rebalancer', () => {
|
|
|
444
492
|
ctx.chainMetadata,
|
|
445
493
|
ctx.tokensByChainName,
|
|
446
494
|
ctx.multiProvider as any,
|
|
495
|
+
createMockActionTracker(),
|
|
447
496
|
testLogger,
|
|
448
497
|
);
|
|
449
498
|
|
|
450
|
-
const route =
|
|
499
|
+
const route = buildTestMovableCollateralRoute();
|
|
451
500
|
const results = await rebalancer.rebalance([route]);
|
|
452
501
|
|
|
453
502
|
expect(results).to.have.lengthOf(1);
|
|
@@ -483,14 +532,15 @@ describe('Rebalancer', () => {
|
|
|
483
532
|
ctx.chainMetadata,
|
|
484
533
|
ctx.tokensByChainName,
|
|
485
534
|
ctx.multiProvider as any,
|
|
535
|
+
createMockActionTracker(),
|
|
486
536
|
testLogger,
|
|
487
537
|
);
|
|
488
538
|
|
|
489
539
|
const routes = [
|
|
490
|
-
|
|
540
|
+
buildTestMovableCollateralRoute({
|
|
491
541
|
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
492
542
|
}),
|
|
493
|
-
|
|
543
|
+
buildTestMovableCollateralRoute({
|
|
494
544
|
amount: ethers.utils.parseEther('200').toBigInt(),
|
|
495
545
|
}),
|
|
496
546
|
];
|
|
@@ -533,16 +583,17 @@ describe('Rebalancer', () => {
|
|
|
533
583
|
ctx.chainMetadata,
|
|
534
584
|
ctx.tokensByChainName,
|
|
535
585
|
ctx.multiProvider as any,
|
|
586
|
+
createMockActionTracker(),
|
|
536
587
|
testLogger,
|
|
537
588
|
);
|
|
538
589
|
|
|
539
590
|
const routes = [
|
|
540
|
-
|
|
591
|
+
buildTestMovableCollateralRoute({
|
|
541
592
|
origin: 'ethereum',
|
|
542
593
|
destination: 'arbitrum',
|
|
543
594
|
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
544
595
|
}),
|
|
545
|
-
|
|
596
|
+
buildTestMovableCollateralRoute({
|
|
546
597
|
origin: 'ethereum',
|
|
547
598
|
destination: 'optimism',
|
|
548
599
|
amount: ethers.utils.parseEther('200').toBigInt(),
|
|
@@ -570,10 +621,11 @@ describe('Rebalancer', () => {
|
|
|
570
621
|
ctx.chainMetadata,
|
|
571
622
|
ctx.tokensByChainName,
|
|
572
623
|
ctx.multiProvider as any,
|
|
624
|
+
createMockActionTracker(),
|
|
573
625
|
testLogger,
|
|
574
626
|
);
|
|
575
627
|
|
|
576
|
-
const route =
|
|
628
|
+
const route = buildTestMovableCollateralRoute();
|
|
577
629
|
const results = await rebalancer.rebalance([route]);
|
|
578
630
|
|
|
579
631
|
expect(results).to.have.lengthOf(1);
|
|
@@ -591,16 +643,17 @@ describe('Rebalancer', () => {
|
|
|
591
643
|
ctx.chainMetadata,
|
|
592
644
|
ctx.tokensByChainName,
|
|
593
645
|
ctx.multiProvider as any,
|
|
646
|
+
createMockActionTracker(),
|
|
594
647
|
testLogger,
|
|
595
648
|
);
|
|
596
649
|
|
|
597
|
-
const route =
|
|
650
|
+
const route = buildTestMovableCollateralRoute();
|
|
598
651
|
const results = await rebalancer.rebalance([route]);
|
|
599
652
|
|
|
600
653
|
expect(results).to.have.lengthOf(1);
|
|
601
654
|
expect(results[0].success).to.be.false;
|
|
602
655
|
expect(results[0].error).to.include('no Dispatch event found');
|
|
603
|
-
expect(results[0].messageId).to.
|
|
656
|
+
expect(results[0].messageId).to.equal('');
|
|
604
657
|
});
|
|
605
658
|
|
|
606
659
|
it('should include txHash in result', async () => {
|
|
@@ -619,10 +672,11 @@ describe('Rebalancer', () => {
|
|
|
619
672
|
ctx.chainMetadata,
|
|
620
673
|
ctx.tokensByChainName,
|
|
621
674
|
ctx.multiProvider as any,
|
|
675
|
+
createMockActionTracker(),
|
|
622
676
|
testLogger,
|
|
623
677
|
);
|
|
624
678
|
|
|
625
|
-
const route =
|
|
679
|
+
const route = buildTestMovableCollateralRoute();
|
|
626
680
|
const results = await rebalancer.rebalance([route]);
|
|
627
681
|
|
|
628
682
|
expect(results).to.have.lengthOf(1);
|
package/src/core/Rebalancer.ts
CHANGED
|
@@ -16,20 +16,33 @@ import {
|
|
|
16
16
|
import { eqAddress, isNullish, mapAllSettled } from '@hyperlane-xyz/utils';
|
|
17
17
|
|
|
18
18
|
import type {
|
|
19
|
-
|
|
19
|
+
IMovableCollateralRebalancer,
|
|
20
|
+
MovableCollateralExecutionResult,
|
|
20
21
|
PreparedTransaction,
|
|
21
|
-
|
|
22
|
-
RebalanceRoute,
|
|
22
|
+
RebalancerType,
|
|
23
23
|
} from '../interfaces/IRebalancer.js';
|
|
24
|
+
import { MovableCollateralRoute } from '../interfaces/IStrategy.js';
|
|
24
25
|
import { type Metrics } from '../metrics/Metrics.js';
|
|
26
|
+
import type { IActionTracker } from '../tracking/IActionTracker.js';
|
|
27
|
+
import type { RebalanceIntent } from '../tracking/types.js';
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
// Internal types with intentId for tracking
|
|
30
|
+
type InternalExecutionResult = MovableCollateralExecutionResult & {
|
|
31
|
+
intentId: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type InternalRoute = MovableCollateralRoute & { intentId: string };
|
|
35
|
+
|
|
36
|
+
export class Rebalancer implements IMovableCollateralRebalancer {
|
|
37
|
+
public readonly rebalancerType: RebalancerType = 'movableCollateral';
|
|
27
38
|
private readonly logger: Logger;
|
|
39
|
+
|
|
28
40
|
constructor(
|
|
29
41
|
private readonly warpCore: WarpCore,
|
|
30
42
|
private readonly chainMetadata: ChainMap<ChainMetadata>,
|
|
31
43
|
private readonly tokensByChainName: ChainMap<Token>,
|
|
32
44
|
private readonly multiProvider: MultiProvider,
|
|
45
|
+
private readonly actionTracker: IActionTracker,
|
|
33
46
|
logger: Logger,
|
|
34
47
|
private readonly metrics?: Metrics,
|
|
35
48
|
) {
|
|
@@ -37,8 +50,8 @@ export class Rebalancer implements IRebalancer {
|
|
|
37
50
|
}
|
|
38
51
|
|
|
39
52
|
async rebalance(
|
|
40
|
-
routes:
|
|
41
|
-
): Promise<
|
|
53
|
+
routes: MovableCollateralRoute[],
|
|
54
|
+
): Promise<MovableCollateralExecutionResult[]> {
|
|
42
55
|
if (routes.length === 0) {
|
|
43
56
|
this.logger.info('No routes to execute, exiting');
|
|
44
57
|
return [];
|
|
@@ -46,20 +59,45 @@ export class Rebalancer implements IRebalancer {
|
|
|
46
59
|
|
|
47
60
|
this.logger.info({ numberOfRoutes: routes.length }, 'Rebalance initiated');
|
|
48
61
|
|
|
62
|
+
const invalidRoutes = routes.filter((r) => !r.bridge);
|
|
63
|
+
if (invalidRoutes.length > 0) {
|
|
64
|
+
this.logger.error(
|
|
65
|
+
{ count: invalidRoutes.length },
|
|
66
|
+
'Routes missing required bridge address',
|
|
67
|
+
);
|
|
68
|
+
return routes.map((r) => ({
|
|
69
|
+
route: r,
|
|
70
|
+
success: false,
|
|
71
|
+
error: r.bridge ? undefined : 'Missing required bridge address',
|
|
72
|
+
messageId: '', // Required by MovableCollateralExecutionResult, empty for validation failures
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const intents = await this.createIntents(routes);
|
|
77
|
+
|
|
78
|
+
const internalRoutes: InternalRoute[] = routes.map((route, idx) => ({
|
|
79
|
+
...route,
|
|
80
|
+
bridge: route.bridge!,
|
|
81
|
+
intentId: intents[idx].id,
|
|
82
|
+
}));
|
|
83
|
+
|
|
49
84
|
const { preparedTransactions, preparationFailureResults } =
|
|
50
|
-
await this.prepareTransactions(
|
|
85
|
+
await this.prepareTransactions(internalRoutes);
|
|
51
86
|
|
|
52
|
-
let executionResults:
|
|
87
|
+
let executionResults: InternalExecutionResult[] = [];
|
|
53
88
|
|
|
54
89
|
if (preparedTransactions.length > 0) {
|
|
55
90
|
executionResults = await this.executeTransactions(preparedTransactions);
|
|
56
91
|
}
|
|
57
92
|
|
|
58
|
-
|
|
59
|
-
|
|
93
|
+
const allInternalResults = [
|
|
94
|
+
...preparationFailureResults,
|
|
95
|
+
...executionResults,
|
|
96
|
+
];
|
|
60
97
|
|
|
61
|
-
|
|
62
|
-
|
|
98
|
+
await this.processResults(allInternalResults);
|
|
99
|
+
|
|
100
|
+
const successfulResults = allInternalResults.filter((r) => r.success);
|
|
63
101
|
if (this.metrics && successfulResults.length > 0) {
|
|
64
102
|
for (const result of successfulResults) {
|
|
65
103
|
const token = this.tokensByChainName[result.route.origin];
|
|
@@ -72,22 +110,94 @@ export class Rebalancer implements IRebalancer {
|
|
|
72
110
|
}
|
|
73
111
|
}
|
|
74
112
|
|
|
75
|
-
const failures =
|
|
113
|
+
const failures = allInternalResults.filter((r) => !r.success);
|
|
76
114
|
if (failures.length > 0) {
|
|
77
115
|
this.logger.error(
|
|
78
116
|
{ failureCount: failures.length, totalRoutes: routes.length },
|
|
79
117
|
'Some rebalance operations failed.',
|
|
80
118
|
);
|
|
81
119
|
} else {
|
|
82
|
-
this.logger.info('
|
|
120
|
+
this.logger.info('Rebalance successful');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return this.toPublicResults(allInternalResults);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async createIntents(
|
|
127
|
+
routes: MovableCollateralRoute[],
|
|
128
|
+
): Promise<RebalanceIntent[]> {
|
|
129
|
+
return Promise.all(
|
|
130
|
+
routes.map((route) =>
|
|
131
|
+
this.actionTracker.createRebalanceIntent({
|
|
132
|
+
origin: this.multiProvider.getDomainId(route.origin),
|
|
133
|
+
destination: this.multiProvider.getDomainId(route.destination),
|
|
134
|
+
amount: route.amount,
|
|
135
|
+
bridge: route.bridge,
|
|
136
|
+
executionMethod: 'movable_collateral',
|
|
137
|
+
}),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async processResults(
|
|
143
|
+
results: InternalExecutionResult[],
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
for (const result of results) {
|
|
146
|
+
const intentId = result.intentId;
|
|
147
|
+
|
|
148
|
+
if (result.success && result.messageId) {
|
|
149
|
+
await this.actionTracker.createRebalanceAction({
|
|
150
|
+
intentId,
|
|
151
|
+
origin: this.multiProvider.getDomainId(result.route.origin),
|
|
152
|
+
destination: this.multiProvider.getDomainId(result.route.destination),
|
|
153
|
+
amount: result.route.amount,
|
|
154
|
+
type: 'rebalance_message',
|
|
155
|
+
messageId: result.messageId,
|
|
156
|
+
txHash: result.txHash,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
this.logger.info(
|
|
160
|
+
{
|
|
161
|
+
intentId,
|
|
162
|
+
messageId: result.messageId,
|
|
163
|
+
txHash: result.txHash,
|
|
164
|
+
origin: result.route.origin,
|
|
165
|
+
destination: result.route.destination,
|
|
166
|
+
},
|
|
167
|
+
'Rebalance action created successfully',
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
await this.actionTracker.failRebalanceIntent(intentId);
|
|
171
|
+
|
|
172
|
+
this.logger.warn(
|
|
173
|
+
{
|
|
174
|
+
intentId,
|
|
175
|
+
success: result.success,
|
|
176
|
+
error: result.error,
|
|
177
|
+
origin: result.route.origin,
|
|
178
|
+
destination: result.route.destination,
|
|
179
|
+
},
|
|
180
|
+
'Rebalance intent marked as failed',
|
|
181
|
+
);
|
|
182
|
+
}
|
|
83
183
|
}
|
|
184
|
+
}
|
|
84
185
|
|
|
85
|
-
|
|
186
|
+
private toPublicResults(
|
|
187
|
+
internalResults: InternalExecutionResult[],
|
|
188
|
+
): MovableCollateralExecutionResult[] {
|
|
189
|
+
return internalResults.map((internal) => ({
|
|
190
|
+
route: internal.route,
|
|
191
|
+
success: internal.success,
|
|
192
|
+
error: internal.error,
|
|
193
|
+
messageId: internal.messageId || '', // Ensure messageId is always a string
|
|
194
|
+
txHash: internal.txHash,
|
|
195
|
+
}));
|
|
86
196
|
}
|
|
87
197
|
|
|
88
|
-
private async prepareTransactions(routes:
|
|
198
|
+
private async prepareTransactions(routes: InternalRoute[]): Promise<{
|
|
89
199
|
preparedTransactions: PreparedTransaction[];
|
|
90
|
-
preparationFailureResults:
|
|
200
|
+
preparationFailureResults: InternalExecutionResult[];
|
|
91
201
|
}> {
|
|
92
202
|
this.logger.info(
|
|
93
203
|
{ numRoutes: routes.length },
|
|
@@ -105,12 +215,14 @@ export class Rebalancer implements IRebalancer {
|
|
|
105
215
|
);
|
|
106
216
|
|
|
107
217
|
// Create failure results for tracking
|
|
108
|
-
const preparationFailureResults:
|
|
218
|
+
const preparationFailureResults: InternalExecutionResult[] = [];
|
|
109
219
|
for (const [i, error] of rejected) {
|
|
110
220
|
preparationFailureResults.push({
|
|
111
221
|
route: routes[i],
|
|
222
|
+
intentId: routes[i].intentId,
|
|
112
223
|
success: false,
|
|
113
224
|
error: String(error),
|
|
225
|
+
messageId: '', // Required by MovableCollateralExecutionResult, empty for failures
|
|
114
226
|
});
|
|
115
227
|
}
|
|
116
228
|
// Also track null results (validation failures)
|
|
@@ -118,8 +230,10 @@ export class Rebalancer implements IRebalancer {
|
|
|
118
230
|
if (isNullish(tx)) {
|
|
119
231
|
preparationFailureResults.push({
|
|
120
232
|
route: routes[i],
|
|
233
|
+
intentId: routes[i].intentId,
|
|
121
234
|
success: false,
|
|
122
235
|
error: 'Preparation returned null',
|
|
236
|
+
messageId: '', // Required by MovableCollateralExecutionResult, empty for failures
|
|
123
237
|
});
|
|
124
238
|
}
|
|
125
239
|
});
|
|
@@ -128,7 +242,7 @@ export class Rebalancer implements IRebalancer {
|
|
|
128
242
|
}
|
|
129
243
|
|
|
130
244
|
private async prepareTransaction(
|
|
131
|
-
route:
|
|
245
|
+
route: InternalRoute,
|
|
132
246
|
): Promise<PreparedTransaction | null> {
|
|
133
247
|
const { origin, destination, amount } = route;
|
|
134
248
|
|
|
@@ -209,7 +323,7 @@ export class Rebalancer implements IRebalancer {
|
|
|
209
323
|
return { populatedTx, route, originTokenAmount };
|
|
210
324
|
}
|
|
211
325
|
|
|
212
|
-
private async validateRoute(route:
|
|
326
|
+
private async validateRoute(route: InternalRoute): Promise<boolean> {
|
|
213
327
|
const { origin, destination, amount } = route;
|
|
214
328
|
const originToken = this.tokensByChainName[origin];
|
|
215
329
|
const destinationToken = this.tokensByChainName[destination];
|
|
@@ -322,13 +436,13 @@ export class Rebalancer implements IRebalancer {
|
|
|
322
436
|
|
|
323
437
|
private async executeTransactions(
|
|
324
438
|
transactions: PreparedTransaction[],
|
|
325
|
-
): Promise<
|
|
439
|
+
): Promise<InternalExecutionResult[]> {
|
|
326
440
|
this.logger.info(
|
|
327
441
|
{ numTransactions: transactions.length },
|
|
328
442
|
'Estimating gas for all prepared transactions.',
|
|
329
443
|
);
|
|
330
444
|
|
|
331
|
-
const results:
|
|
445
|
+
const results: InternalExecutionResult[] = [];
|
|
332
446
|
|
|
333
447
|
// 1. Estimate gas for rebalance transactions
|
|
334
448
|
const gasEstimateResults = await Promise.allSettled(
|
|
@@ -361,8 +475,10 @@ export class Rebalancer implements IRebalancer {
|
|
|
361
475
|
);
|
|
362
476
|
results.push({
|
|
363
477
|
route: failedTransaction.route,
|
|
478
|
+
intentId: failedTransaction.route.intentId,
|
|
364
479
|
success: false,
|
|
365
480
|
error: `Gas estimation failed: ${String(result.reason)}`,
|
|
481
|
+
messageId: '', // Required by MovableCollateralExecutionResult, empty for failures
|
|
366
482
|
});
|
|
367
483
|
}
|
|
368
484
|
});
|
|
@@ -411,8 +527,10 @@ export class Rebalancer implements IRebalancer {
|
|
|
411
527
|
} else {
|
|
412
528
|
results.push({
|
|
413
529
|
route: txResult.transaction.route,
|
|
530
|
+
intentId: txResult.transaction.route.intentId,
|
|
414
531
|
success: false,
|
|
415
532
|
error: `Transaction send failed: ${txResult.error}`,
|
|
533
|
+
messageId: '', // Required by MovableCollateralExecutionResult, empty for failures
|
|
416
534
|
});
|
|
417
535
|
this.metrics?.recordActionAttempt(
|
|
418
536
|
txResult.transaction.route,
|
|
@@ -533,7 +651,7 @@ export class Rebalancer implements IRebalancer {
|
|
|
533
651
|
private buildResult(
|
|
534
652
|
transaction: PreparedTransaction,
|
|
535
653
|
receipt: providers.TransactionReceipt,
|
|
536
|
-
):
|
|
654
|
+
): InternalExecutionResult {
|
|
537
655
|
const { origin, destination } = transaction.route;
|
|
538
656
|
const dispatchedMessages = HyperlaneCore.getDispatchedMessages(receipt);
|
|
539
657
|
|
|
@@ -544,14 +662,17 @@ export class Rebalancer implements IRebalancer {
|
|
|
544
662
|
);
|
|
545
663
|
return {
|
|
546
664
|
route: transaction.route,
|
|
665
|
+
intentId: transaction.route.intentId,
|
|
547
666
|
success: false,
|
|
548
667
|
error: `Transaction confirmed but no Dispatch event found`,
|
|
668
|
+
messageId: '', // Required by MovableCollateralExecutionResult, empty for failures
|
|
549
669
|
txHash: receipt.transactionHash,
|
|
550
670
|
};
|
|
551
671
|
}
|
|
552
672
|
|
|
553
673
|
return {
|
|
554
674
|
route: transaction.route,
|
|
675
|
+
intentId: transaction.route.intentId,
|
|
555
676
|
success: true,
|
|
556
677
|
messageId: dispatchedMessages[0].id,
|
|
557
678
|
txHash: receipt.transactionHash,
|