@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
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
import chai, { expect } from 'chai';
|
|
2
|
+
import chaiAsPromised from 'chai-as-promised';
|
|
3
|
+
import { pino } from 'pino';
|
|
4
|
+
import Sinon from 'sinon';
|
|
5
|
+
|
|
6
|
+
import type { RebalancerConfig } from '../config/RebalancerConfig.js';
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_INTENT_TTL_MS,
|
|
9
|
+
ExecutionType,
|
|
10
|
+
RebalancerStrategyOptions,
|
|
11
|
+
} from '../config/types.js';
|
|
12
|
+
import type { IExternalBridge } from '../interfaces/IExternalBridge.js';
|
|
13
|
+
import { MonitorEventType } from '../interfaces/IMonitor.js';
|
|
14
|
+
import type { IRebalancer } from '../interfaces/IRebalancer.js';
|
|
15
|
+
import type { IStrategy } from '../interfaces/IStrategy.js';
|
|
16
|
+
import { Metrics } from '../metrics/Metrics.js';
|
|
17
|
+
import { TEST_ADDRESSES, getTestAddress } from '../test/helpers.js';
|
|
18
|
+
import type { IActionTracker } from '../tracking/IActionTracker.js';
|
|
19
|
+
import { InflightContextAdapter } from '../tracking/InflightContextAdapter.js';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
RebalancerOrchestrator,
|
|
23
|
+
type RebalancerOrchestratorDeps,
|
|
24
|
+
} from './RebalancerOrchestrator.js';
|
|
25
|
+
|
|
26
|
+
chai.use(chaiAsPromised);
|
|
27
|
+
|
|
28
|
+
const testLogger = pino({ level: 'silent' });
|
|
29
|
+
|
|
30
|
+
function createMockRebalancerConfig(): RebalancerConfig {
|
|
31
|
+
return {
|
|
32
|
+
warpRouteId: 'TEST/route',
|
|
33
|
+
strategyConfig: [
|
|
34
|
+
{
|
|
35
|
+
rebalanceStrategy: RebalancerStrategyOptions.Weighted,
|
|
36
|
+
chains: {
|
|
37
|
+
ethereum: {
|
|
38
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
39
|
+
bridgeMinAcceptedAmount: 0,
|
|
40
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
41
|
+
},
|
|
42
|
+
arbitrum: {
|
|
43
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
44
|
+
bridgeMinAcceptedAmount: 0,
|
|
45
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
intentTTL: DEFAULT_INTENT_TTL_MS,
|
|
51
|
+
} as RebalancerConfig;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createMockRebalancer(): IRebalancer & { rebalance: Sinon.SinonStub } {
|
|
55
|
+
return {
|
|
56
|
+
rebalancerType: 'movableCollateral' as const,
|
|
57
|
+
rebalance: Sinon.stub().resolves([]),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createMockStrategy(): IStrategy & {
|
|
62
|
+
getRebalancingRoutes: Sinon.SinonStub;
|
|
63
|
+
} {
|
|
64
|
+
return {
|
|
65
|
+
name: 'mock-strategy',
|
|
66
|
+
getRebalancingRoutes: Sinon.stub().returns([]),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createMockActionTracker(): IActionTracker {
|
|
71
|
+
return {
|
|
72
|
+
initialize: Sinon.stub().resolves(),
|
|
73
|
+
createRebalanceIntent: Sinon.stub().callsFake(async () => ({
|
|
74
|
+
id: `intent-${Date.now()}`,
|
|
75
|
+
status: 'not_started',
|
|
76
|
+
})),
|
|
77
|
+
createRebalanceAction: Sinon.stub().resolves(),
|
|
78
|
+
completeRebalanceAction: Sinon.stub().resolves(),
|
|
79
|
+
failRebalanceAction: Sinon.stub().resolves(),
|
|
80
|
+
completeRebalanceIntent: Sinon.stub().resolves(),
|
|
81
|
+
cancelRebalanceIntent: Sinon.stub().resolves(),
|
|
82
|
+
failRebalanceIntent: Sinon.stub().resolves(),
|
|
83
|
+
syncTransfers: Sinon.stub().resolves(),
|
|
84
|
+
syncRebalanceIntents: Sinon.stub().resolves(),
|
|
85
|
+
syncRebalanceActions: Sinon.stub().resolves(),
|
|
86
|
+
syncInventoryMovementActions: Sinon.stub().resolves({
|
|
87
|
+
completed: 0,
|
|
88
|
+
failed: 0,
|
|
89
|
+
}),
|
|
90
|
+
logStoreContents: Sinon.stub().resolves(),
|
|
91
|
+
getInProgressTransfers: Sinon.stub().resolves([]),
|
|
92
|
+
getActiveRebalanceIntents: Sinon.stub().resolves([]),
|
|
93
|
+
getTransfersByDestination: Sinon.stub().resolves([]),
|
|
94
|
+
getRebalanceIntentsByDestination: Sinon.stub().resolves([]),
|
|
95
|
+
getTransfer: Sinon.stub().resolves(undefined),
|
|
96
|
+
getRebalanceIntent: Sinon.stub().resolves(undefined),
|
|
97
|
+
getRebalanceAction: Sinon.stub().resolves(undefined),
|
|
98
|
+
getInProgressActions: Sinon.stub().resolves([]),
|
|
99
|
+
getPartiallyFulfilledInventoryIntents: Sinon.stub().resolves([]),
|
|
100
|
+
getActionsByType: Sinon.stub().resolves([]),
|
|
101
|
+
getActionsForIntent: Sinon.stub().resolves([]),
|
|
102
|
+
getInflightInventoryMovements: Sinon.stub().resolves(0n),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createMockInflightContextAdapter(): InflightContextAdapter & {
|
|
107
|
+
getInflightContext: Sinon.SinonStub;
|
|
108
|
+
} {
|
|
109
|
+
return {
|
|
110
|
+
getInflightContext: Sinon.stub().resolves({
|
|
111
|
+
pendingRebalances: [],
|
|
112
|
+
pendingTransfers: [],
|
|
113
|
+
}),
|
|
114
|
+
} as unknown as InflightContextAdapter & {
|
|
115
|
+
getInflightContext: Sinon.SinonStub;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createMockInventoryRebalancer(): IRebalancer & {
|
|
120
|
+
rebalance: Sinon.SinonStub;
|
|
121
|
+
setInventoryBalances: Sinon.SinonStub;
|
|
122
|
+
} {
|
|
123
|
+
return {
|
|
124
|
+
rebalancerType: 'inventory' as const,
|
|
125
|
+
rebalance: Sinon.stub().resolves([]),
|
|
126
|
+
setInventoryBalances: Sinon.stub(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createMockBridge(): IExternalBridge {
|
|
131
|
+
return {
|
|
132
|
+
bridgeId: 'lifi',
|
|
133
|
+
quote: Sinon.stub().resolves({}),
|
|
134
|
+
execute: Sinon.stub().resolves({}),
|
|
135
|
+
getStatus: Sinon.stub().resolves({}),
|
|
136
|
+
} as unknown as IExternalBridge;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function createMockMetrics(): Metrics {
|
|
140
|
+
return {
|
|
141
|
+
recordRebalancerSuccess: Sinon.stub(),
|
|
142
|
+
recordRebalancerFailure: Sinon.stub(),
|
|
143
|
+
recordIntentCreated: Sinon.stub(),
|
|
144
|
+
processToken: Sinon.stub().resolves(),
|
|
145
|
+
} as unknown as Metrics;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function createMonitorEvent(overrides?: any) {
|
|
149
|
+
return {
|
|
150
|
+
type: MonitorEventType.TokenInfo,
|
|
151
|
+
tokensInfo: [
|
|
152
|
+
{
|
|
153
|
+
token: {
|
|
154
|
+
chainName: 'ethereum',
|
|
155
|
+
name: 'EthereumToken',
|
|
156
|
+
decimals: 18,
|
|
157
|
+
addressOrDenom: getTestAddress('ethereum'),
|
|
158
|
+
standard: 'EvmHypCollateral',
|
|
159
|
+
isCollateralized: () => true,
|
|
160
|
+
},
|
|
161
|
+
balance: 5000n,
|
|
162
|
+
bridgedSupply: 5000n,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
token: {
|
|
166
|
+
chainName: 'arbitrum',
|
|
167
|
+
name: 'ArbitrumToken',
|
|
168
|
+
decimals: 18,
|
|
169
|
+
addressOrDenom: getTestAddress('arbitrum'),
|
|
170
|
+
standard: 'EvmHypCollateral',
|
|
171
|
+
isCollateralized: () => true,
|
|
172
|
+
},
|
|
173
|
+
balance: 5000n,
|
|
174
|
+
bridgedSupply: 5000n,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
confirmedBlockTags: {
|
|
178
|
+
ethereum: 100,
|
|
179
|
+
arbitrum: 200,
|
|
180
|
+
},
|
|
181
|
+
...overrides,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
describe('RebalancerOrchestrator', () => {
|
|
186
|
+
let sandbox: Sinon.SinonSandbox;
|
|
187
|
+
|
|
188
|
+
beforeEach(() => {
|
|
189
|
+
sandbox = Sinon.createSandbox();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
afterEach(() => {
|
|
193
|
+
sandbox.restore();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('executeCycle() - No Routes', () => {
|
|
197
|
+
it('should complete cycle when no routes proposed', async () => {
|
|
198
|
+
const strategy = createMockStrategy();
|
|
199
|
+
strategy.getRebalancingRoutes.returns([]);
|
|
200
|
+
|
|
201
|
+
const actionTracker = createMockActionTracker();
|
|
202
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
203
|
+
|
|
204
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
205
|
+
strategy,
|
|
206
|
+
actionTracker,
|
|
207
|
+
inflightContextAdapter: inflightAdapter,
|
|
208
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
209
|
+
logger: testLogger,
|
|
210
|
+
rebalancers: [],
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
214
|
+
const event = createMonitorEvent();
|
|
215
|
+
|
|
216
|
+
const result = await orchestrator.executeCycle(event);
|
|
217
|
+
|
|
218
|
+
expect(result.proposedRoutes).to.have.lengthOf(0);
|
|
219
|
+
expect(result.executedCount).to.equal(0);
|
|
220
|
+
expect(result.failedCount).to.equal(0);
|
|
221
|
+
expect(strategy.getRebalancingRoutes.calledOnce).to.be.true;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should sync action tracker even when no routes', async () => {
|
|
225
|
+
const strategy = createMockStrategy();
|
|
226
|
+
strategy.getRebalancingRoutes.returns([]);
|
|
227
|
+
|
|
228
|
+
const actionTracker = createMockActionTracker();
|
|
229
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
230
|
+
|
|
231
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
232
|
+
strategy,
|
|
233
|
+
actionTracker,
|
|
234
|
+
inflightContextAdapter: inflightAdapter,
|
|
235
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
236
|
+
logger: testLogger,
|
|
237
|
+
rebalancers: [],
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
241
|
+
const event = createMonitorEvent();
|
|
242
|
+
|
|
243
|
+
await orchestrator.executeCycle(event);
|
|
244
|
+
|
|
245
|
+
expect((actionTracker.syncTransfers as Sinon.SinonStub).calledOnce).to.be
|
|
246
|
+
.true;
|
|
247
|
+
expect((actionTracker.syncRebalanceIntents as Sinon.SinonStub).calledOnce)
|
|
248
|
+
.to.be.true;
|
|
249
|
+
expect((actionTracker.syncRebalanceActions as Sinon.SinonStub).calledOnce)
|
|
250
|
+
.to.be.true;
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('executeCycle() - Movable Collateral Routes Only', () => {
|
|
255
|
+
it('should execute movable collateral routes successfully', async () => {
|
|
256
|
+
const strategy = createMockStrategy();
|
|
257
|
+
strategy.getRebalancingRoutes.returns([
|
|
258
|
+
{
|
|
259
|
+
origin: 'ethereum',
|
|
260
|
+
destination: 'arbitrum',
|
|
261
|
+
amount: 1000n,
|
|
262
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
263
|
+
executionType: 'movableCollateral',
|
|
264
|
+
},
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
const rebalancer = createMockRebalancer();
|
|
268
|
+
rebalancer.rebalance.resolves([
|
|
269
|
+
{
|
|
270
|
+
route: {
|
|
271
|
+
origin: 'ethereum',
|
|
272
|
+
destination: 'arbitrum',
|
|
273
|
+
amount: 1000n,
|
|
274
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
275
|
+
},
|
|
276
|
+
success: true,
|
|
277
|
+
messageId:
|
|
278
|
+
'0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
279
|
+
txHash:
|
|
280
|
+
'0x2222222222222222222222222222222222222222222222222222222222222222',
|
|
281
|
+
},
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
const actionTracker = createMockActionTracker();
|
|
285
|
+
|
|
286
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
287
|
+
|
|
288
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
289
|
+
strategy,
|
|
290
|
+
rebalancers: [rebalancer],
|
|
291
|
+
actionTracker,
|
|
292
|
+
inflightContextAdapter: inflightAdapter,
|
|
293
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
294
|
+
logger: testLogger,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
298
|
+
const event = createMonitorEvent();
|
|
299
|
+
|
|
300
|
+
const result = await orchestrator.executeCycle(event);
|
|
301
|
+
|
|
302
|
+
expect(result.proposedRoutes).to.have.lengthOf(1);
|
|
303
|
+
expect(result.executedCount).to.equal(1);
|
|
304
|
+
expect(result.failedCount).to.equal(0);
|
|
305
|
+
expect(rebalancer.rebalance.calledOnce).to.be.true;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should handle failed movable collateral routes', async () => {
|
|
309
|
+
const strategy = createMockStrategy();
|
|
310
|
+
strategy.getRebalancingRoutes.returns([
|
|
311
|
+
{
|
|
312
|
+
origin: 'ethereum',
|
|
313
|
+
destination: 'arbitrum',
|
|
314
|
+
amount: 1000n,
|
|
315
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
316
|
+
executionType: 'movableCollateral',
|
|
317
|
+
},
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
const rebalancer = createMockRebalancer();
|
|
321
|
+
rebalancer.rebalance.resolves([
|
|
322
|
+
{
|
|
323
|
+
route: {
|
|
324
|
+
origin: 'ethereum',
|
|
325
|
+
destination: 'arbitrum',
|
|
326
|
+
amount: 1000n,
|
|
327
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
328
|
+
},
|
|
329
|
+
success: false,
|
|
330
|
+
error: 'Gas estimation failed',
|
|
331
|
+
},
|
|
332
|
+
]);
|
|
333
|
+
|
|
334
|
+
const actionTracker = createMockActionTracker();
|
|
335
|
+
|
|
336
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
337
|
+
|
|
338
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
339
|
+
strategy,
|
|
340
|
+
rebalancers: [rebalancer],
|
|
341
|
+
actionTracker,
|
|
342
|
+
inflightContextAdapter: inflightAdapter,
|
|
343
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
344
|
+
logger: testLogger,
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
348
|
+
const event = createMonitorEvent();
|
|
349
|
+
|
|
350
|
+
const result = await orchestrator.executeCycle(event);
|
|
351
|
+
|
|
352
|
+
expect(result.proposedRoutes).to.have.lengthOf(1);
|
|
353
|
+
expect(result.executedCount).to.equal(0);
|
|
354
|
+
expect(result.failedCount).to.equal(1);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe('executeCycle() - Inventory Routes Only', () => {
|
|
359
|
+
it('should execute inventory routes successfully', async () => {
|
|
360
|
+
const config: RebalancerConfig = {
|
|
361
|
+
warpRouteId: 'TEST/route',
|
|
362
|
+
strategyConfig: [
|
|
363
|
+
{
|
|
364
|
+
rebalanceStrategy: RebalancerStrategyOptions.Weighted,
|
|
365
|
+
chains: {
|
|
366
|
+
ethereum: {
|
|
367
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
368
|
+
bridgeMinAcceptedAmount: 0,
|
|
369
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
370
|
+
executionType: ExecutionType.Inventory,
|
|
371
|
+
},
|
|
372
|
+
arbitrum: {
|
|
373
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
374
|
+
bridgeMinAcceptedAmount: 0,
|
|
375
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
intentTTL: DEFAULT_INTENT_TTL_MS,
|
|
381
|
+
} as RebalancerConfig;
|
|
382
|
+
|
|
383
|
+
const strategy = createMockStrategy();
|
|
384
|
+
strategy.getRebalancingRoutes.returns([
|
|
385
|
+
{
|
|
386
|
+
origin: 'ethereum',
|
|
387
|
+
destination: 'arbitrum',
|
|
388
|
+
amount: 1000n,
|
|
389
|
+
externalBridge: 'lifi',
|
|
390
|
+
executionType: 'inventory',
|
|
391
|
+
},
|
|
392
|
+
]);
|
|
393
|
+
|
|
394
|
+
const inventoryRebalancer = createMockInventoryRebalancer();
|
|
395
|
+
inventoryRebalancer.rebalance.resolves([
|
|
396
|
+
{
|
|
397
|
+
success: true,
|
|
398
|
+
route: {
|
|
399
|
+
origin: 'ethereum',
|
|
400
|
+
destination: 'arbitrum',
|
|
401
|
+
amount: 1000n,
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
const actionTracker = createMockActionTracker();
|
|
407
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
408
|
+
|
|
409
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
410
|
+
strategy,
|
|
411
|
+
rebalancers: [inventoryRebalancer],
|
|
412
|
+
actionTracker,
|
|
413
|
+
inflightContextAdapter: inflightAdapter,
|
|
414
|
+
rebalancerConfig: config,
|
|
415
|
+
logger: testLogger,
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
419
|
+
const event = createMonitorEvent();
|
|
420
|
+
|
|
421
|
+
const result = await orchestrator.executeCycle(event);
|
|
422
|
+
|
|
423
|
+
expect(result.proposedRoutes).to.have.lengthOf(1);
|
|
424
|
+
expect(inventoryRebalancer.rebalance.calledOnce).to.be.true;
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe('executeCycle() - Mixed Routes', () => {
|
|
429
|
+
it('should execute both movable collateral and inventory routes', async () => {
|
|
430
|
+
const config: RebalancerConfig = {
|
|
431
|
+
warpRouteId: 'TEST/route',
|
|
432
|
+
strategyConfig: [
|
|
433
|
+
{
|
|
434
|
+
rebalanceStrategy: RebalancerStrategyOptions.Weighted,
|
|
435
|
+
chains: {
|
|
436
|
+
ethereum: {
|
|
437
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
438
|
+
bridgeMinAcceptedAmount: 0,
|
|
439
|
+
weighted: { weight: 33n, tolerance: 10n },
|
|
440
|
+
},
|
|
441
|
+
arbitrum: {
|
|
442
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
443
|
+
bridgeMinAcceptedAmount: 0,
|
|
444
|
+
weighted: { weight: 33n, tolerance: 10n },
|
|
445
|
+
executionType: ExecutionType.Inventory,
|
|
446
|
+
},
|
|
447
|
+
optimism: {
|
|
448
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
449
|
+
bridgeMinAcceptedAmount: 0,
|
|
450
|
+
weighted: { weight: 34n, tolerance: 10n },
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
intentTTL: DEFAULT_INTENT_TTL_MS,
|
|
456
|
+
} as RebalancerConfig;
|
|
457
|
+
|
|
458
|
+
const strategy = createMockStrategy();
|
|
459
|
+
strategy.getRebalancingRoutes.returns([
|
|
460
|
+
{
|
|
461
|
+
origin: 'ethereum',
|
|
462
|
+
destination: 'optimism',
|
|
463
|
+
amount: 1000n,
|
|
464
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
465
|
+
executionType: 'movableCollateral',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
origin: 'arbitrum',
|
|
469
|
+
destination: 'ethereum',
|
|
470
|
+
amount: 500n,
|
|
471
|
+
externalBridge: 'lifi',
|
|
472
|
+
executionType: 'inventory',
|
|
473
|
+
},
|
|
474
|
+
]);
|
|
475
|
+
|
|
476
|
+
const rebalancer = createMockRebalancer();
|
|
477
|
+
rebalancer.rebalance.resolves([
|
|
478
|
+
{
|
|
479
|
+
route: {
|
|
480
|
+
origin: 'ethereum',
|
|
481
|
+
destination: 'optimism',
|
|
482
|
+
amount: 1000n,
|
|
483
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
484
|
+
},
|
|
485
|
+
success: true,
|
|
486
|
+
messageId: '0x1111',
|
|
487
|
+
txHash: '0x2222',
|
|
488
|
+
},
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
const inventoryRebalancer = createMockInventoryRebalancer();
|
|
492
|
+
inventoryRebalancer.rebalance.resolves([
|
|
493
|
+
{
|
|
494
|
+
success: true,
|
|
495
|
+
route: {
|
|
496
|
+
origin: 'arbitrum',
|
|
497
|
+
destination: 'ethereum',
|
|
498
|
+
amount: 500n,
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
]);
|
|
502
|
+
|
|
503
|
+
const actionTracker = createMockActionTracker();
|
|
504
|
+
|
|
505
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
506
|
+
|
|
507
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
508
|
+
strategy,
|
|
509
|
+
rebalancers: [rebalancer, inventoryRebalancer],
|
|
510
|
+
actionTracker,
|
|
511
|
+
inflightContextAdapter: inflightAdapter,
|
|
512
|
+
rebalancerConfig: config,
|
|
513
|
+
logger: testLogger,
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
517
|
+
const event = createMonitorEvent();
|
|
518
|
+
|
|
519
|
+
const result = await orchestrator.executeCycle(event);
|
|
520
|
+
|
|
521
|
+
expect(result.proposedRoutes).to.have.lengthOf(2);
|
|
522
|
+
expect(result.executedCount).to.equal(1);
|
|
523
|
+
expect(result.failedCount).to.equal(0);
|
|
524
|
+
expect(rebalancer.rebalance.calledOnce).to.be.true;
|
|
525
|
+
expect(inventoryRebalancer.rebalance.calledOnce).to.be.true;
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe('executeCycle() - Continue Inventory Intents', () => {
|
|
530
|
+
it('should call inventoryRebalancer.rebalance([]) when no routes proposed', async () => {
|
|
531
|
+
const config: RebalancerConfig = {
|
|
532
|
+
warpRouteId: 'TEST/route',
|
|
533
|
+
strategyConfig: [
|
|
534
|
+
{
|
|
535
|
+
rebalanceStrategy: RebalancerStrategyOptions.Weighted,
|
|
536
|
+
chains: {
|
|
537
|
+
ethereum: {
|
|
538
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
539
|
+
bridgeMinAcceptedAmount: 0,
|
|
540
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
541
|
+
executionType: ExecutionType.Inventory,
|
|
542
|
+
},
|
|
543
|
+
arbitrum: {
|
|
544
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
545
|
+
bridgeMinAcceptedAmount: 0,
|
|
546
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
intentTTL: DEFAULT_INTENT_TTL_MS,
|
|
552
|
+
} as RebalancerConfig;
|
|
553
|
+
|
|
554
|
+
const strategy = createMockStrategy();
|
|
555
|
+
strategy.getRebalancingRoutes.returns([]);
|
|
556
|
+
|
|
557
|
+
const inventoryRebalancer = createMockInventoryRebalancer();
|
|
558
|
+
inventoryRebalancer.rebalance.resolves([]);
|
|
559
|
+
|
|
560
|
+
const actionTracker = createMockActionTracker();
|
|
561
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
562
|
+
|
|
563
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
564
|
+
strategy,
|
|
565
|
+
rebalancers: [inventoryRebalancer],
|
|
566
|
+
actionTracker,
|
|
567
|
+
inflightContextAdapter: inflightAdapter,
|
|
568
|
+
rebalancerConfig: config,
|
|
569
|
+
logger: testLogger,
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
573
|
+
const event = createMonitorEvent();
|
|
574
|
+
|
|
575
|
+
await orchestrator.executeCycle(event);
|
|
576
|
+
|
|
577
|
+
expect(inventoryRebalancer.rebalance.calledOnce).to.be.true;
|
|
578
|
+
expect(inventoryRebalancer.rebalance.calledWith([])).to.be.true;
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it('should NOT call inventoryRebalancer.rebalance([]) when routes are proposed', async () => {
|
|
582
|
+
const config: RebalancerConfig = {
|
|
583
|
+
warpRouteId: 'TEST/route',
|
|
584
|
+
strategyConfig: [
|
|
585
|
+
{
|
|
586
|
+
rebalanceStrategy: RebalancerStrategyOptions.Weighted,
|
|
587
|
+
chains: {
|
|
588
|
+
ethereum: {
|
|
589
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
590
|
+
bridgeMinAcceptedAmount: 0,
|
|
591
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
592
|
+
executionType: ExecutionType.Inventory,
|
|
593
|
+
},
|
|
594
|
+
arbitrum: {
|
|
595
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
596
|
+
bridgeMinAcceptedAmount: 0,
|
|
597
|
+
weighted: { weight: 50n, tolerance: 10n },
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
intentTTL: DEFAULT_INTENT_TTL_MS,
|
|
603
|
+
} as RebalancerConfig;
|
|
604
|
+
|
|
605
|
+
const strategy = createMockStrategy();
|
|
606
|
+
strategy.getRebalancingRoutes.returns([
|
|
607
|
+
{
|
|
608
|
+
origin: 'ethereum',
|
|
609
|
+
destination: 'arbitrum',
|
|
610
|
+
amount: 1000n,
|
|
611
|
+
externalBridge: 'lifi',
|
|
612
|
+
executionType: 'inventory',
|
|
613
|
+
},
|
|
614
|
+
]);
|
|
615
|
+
|
|
616
|
+
const inventoryRebalancer = createMockInventoryRebalancer();
|
|
617
|
+
inventoryRebalancer.rebalance.resolves([
|
|
618
|
+
{
|
|
619
|
+
success: true,
|
|
620
|
+
route: {
|
|
621
|
+
origin: 'ethereum',
|
|
622
|
+
destination: 'arbitrum',
|
|
623
|
+
amount: 1000n,
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
]);
|
|
627
|
+
|
|
628
|
+
const actionTracker = createMockActionTracker();
|
|
629
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
630
|
+
|
|
631
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
632
|
+
strategy,
|
|
633
|
+
rebalancers: [inventoryRebalancer],
|
|
634
|
+
actionTracker,
|
|
635
|
+
inflightContextAdapter: inflightAdapter,
|
|
636
|
+
rebalancerConfig: config,
|
|
637
|
+
logger: testLogger,
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
641
|
+
const event = createMonitorEvent();
|
|
642
|
+
|
|
643
|
+
await orchestrator.executeCycle(event);
|
|
644
|
+
|
|
645
|
+
expect(inventoryRebalancer.rebalance.calledOnce).to.be.true;
|
|
646
|
+
expect(inventoryRebalancer.rebalance.calledWith([])).to.be.false;
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('should NOT call inventoryRebalancer.rebalance([]) when inventoryRebalancer is not in rebalancers', async () => {
|
|
650
|
+
const strategy = createMockStrategy();
|
|
651
|
+
strategy.getRebalancingRoutes.returns([]);
|
|
652
|
+
|
|
653
|
+
const actionTracker = createMockActionTracker();
|
|
654
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
655
|
+
|
|
656
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
657
|
+
strategy,
|
|
658
|
+
actionTracker,
|
|
659
|
+
inflightContextAdapter: inflightAdapter,
|
|
660
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
661
|
+
logger: testLogger,
|
|
662
|
+
rebalancers: [],
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
666
|
+
const event = createMonitorEvent();
|
|
667
|
+
|
|
668
|
+
await orchestrator.executeCycle(event);
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
describe('syncActionTracker() Error Handling', () => {
|
|
673
|
+
it('should warn but continue when syncTransfers fails', async () => {
|
|
674
|
+
const strategy = createMockStrategy();
|
|
675
|
+
strategy.getRebalancingRoutes.returns([]);
|
|
676
|
+
|
|
677
|
+
const actionTracker = createMockActionTracker();
|
|
678
|
+
(actionTracker.syncTransfers as Sinon.SinonStub).rejects(
|
|
679
|
+
new Error('Sync failed'),
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
683
|
+
|
|
684
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
685
|
+
strategy,
|
|
686
|
+
actionTracker,
|
|
687
|
+
inflightContextAdapter: inflightAdapter,
|
|
688
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
689
|
+
logger: testLogger,
|
|
690
|
+
rebalancers: [],
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
694
|
+
const event = createMonitorEvent();
|
|
695
|
+
|
|
696
|
+
const result = await orchestrator.executeCycle(event);
|
|
697
|
+
|
|
698
|
+
expect(result.proposedRoutes).to.have.lengthOf(0);
|
|
699
|
+
expect(strategy.getRebalancingRoutes.calledOnce).to.be.true;
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should sync inventory movement actions when bridge is provided', async () => {
|
|
703
|
+
const strategy = createMockStrategy();
|
|
704
|
+
strategy.getRebalancingRoutes.returns([]);
|
|
705
|
+
|
|
706
|
+
const actionTracker = createMockActionTracker();
|
|
707
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
708
|
+
const bridge = createMockBridge();
|
|
709
|
+
|
|
710
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
711
|
+
strategy,
|
|
712
|
+
actionTracker,
|
|
713
|
+
inflightContextAdapter: inflightAdapter,
|
|
714
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
715
|
+
logger: testLogger,
|
|
716
|
+
rebalancers: [],
|
|
717
|
+
externalBridgeRegistry: { lifi: bridge },
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
721
|
+
const event = createMonitorEvent();
|
|
722
|
+
|
|
723
|
+
await orchestrator.executeCycle(event);
|
|
724
|
+
|
|
725
|
+
expect(
|
|
726
|
+
(actionTracker.syncInventoryMovementActions as Sinon.SinonStub)
|
|
727
|
+
.calledOnce,
|
|
728
|
+
).to.be.true;
|
|
729
|
+
expect(
|
|
730
|
+
(
|
|
731
|
+
actionTracker.syncInventoryMovementActions as Sinon.SinonStub
|
|
732
|
+
).calledWith({ lifi: bridge }),
|
|
733
|
+
).to.be.true;
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
describe('Metrics Recording', () => {
|
|
738
|
+
it('should record success metric when all routes succeed', async () => {
|
|
739
|
+
const strategy = createMockStrategy();
|
|
740
|
+
strategy.getRebalancingRoutes.returns([
|
|
741
|
+
{
|
|
742
|
+
origin: 'ethereum',
|
|
743
|
+
destination: 'arbitrum',
|
|
744
|
+
amount: 1000n,
|
|
745
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
746
|
+
executionType: 'movableCollateral',
|
|
747
|
+
},
|
|
748
|
+
]);
|
|
749
|
+
|
|
750
|
+
const rebalancer = createMockRebalancer();
|
|
751
|
+
rebalancer.rebalance.resolves([
|
|
752
|
+
{
|
|
753
|
+
route: {
|
|
754
|
+
origin: 'ethereum',
|
|
755
|
+
destination: 'arbitrum',
|
|
756
|
+
amount: 1000n,
|
|
757
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
758
|
+
},
|
|
759
|
+
success: true,
|
|
760
|
+
messageId: '0x1111',
|
|
761
|
+
txHash: '0x2222',
|
|
762
|
+
},
|
|
763
|
+
]);
|
|
764
|
+
|
|
765
|
+
const actionTracker = createMockActionTracker();
|
|
766
|
+
|
|
767
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
768
|
+
const metrics = createMockMetrics();
|
|
769
|
+
|
|
770
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
771
|
+
strategy,
|
|
772
|
+
rebalancers: [rebalancer],
|
|
773
|
+
actionTracker,
|
|
774
|
+
inflightContextAdapter: inflightAdapter,
|
|
775
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
776
|
+
logger: testLogger,
|
|
777
|
+
metrics,
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
781
|
+
const event = createMonitorEvent();
|
|
782
|
+
|
|
783
|
+
await orchestrator.executeCycle(event);
|
|
784
|
+
|
|
785
|
+
expect((metrics.recordRebalancerSuccess as Sinon.SinonStub).calledOnce).to
|
|
786
|
+
.be.true;
|
|
787
|
+
expect((metrics.recordRebalancerFailure as Sinon.SinonStub).called).to.be
|
|
788
|
+
.false;
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it('should record failure metric when any route fails', async () => {
|
|
792
|
+
const strategy = createMockStrategy();
|
|
793
|
+
strategy.getRebalancingRoutes.returns([
|
|
794
|
+
{
|
|
795
|
+
origin: 'ethereum',
|
|
796
|
+
destination: 'arbitrum',
|
|
797
|
+
amount: 1000n,
|
|
798
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
799
|
+
executionType: 'movableCollateral',
|
|
800
|
+
},
|
|
801
|
+
]);
|
|
802
|
+
|
|
803
|
+
const rebalancer = createMockRebalancer();
|
|
804
|
+
rebalancer.rebalance.resolves([
|
|
805
|
+
{
|
|
806
|
+
route: {
|
|
807
|
+
origin: 'ethereum',
|
|
808
|
+
destination: 'arbitrum',
|
|
809
|
+
amount: 1000n,
|
|
810
|
+
bridge: TEST_ADDRESSES.bridge,
|
|
811
|
+
},
|
|
812
|
+
success: false,
|
|
813
|
+
error: 'Gas estimation failed',
|
|
814
|
+
},
|
|
815
|
+
]);
|
|
816
|
+
|
|
817
|
+
const actionTracker = createMockActionTracker();
|
|
818
|
+
|
|
819
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
820
|
+
const metrics = createMockMetrics();
|
|
821
|
+
|
|
822
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
823
|
+
strategy,
|
|
824
|
+
rebalancers: [rebalancer],
|
|
825
|
+
actionTracker,
|
|
826
|
+
inflightContextAdapter: inflightAdapter,
|
|
827
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
828
|
+
logger: testLogger,
|
|
829
|
+
metrics,
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
833
|
+
const event = createMonitorEvent();
|
|
834
|
+
|
|
835
|
+
await orchestrator.executeCycle(event);
|
|
836
|
+
|
|
837
|
+
expect((metrics.recordRebalancerFailure as Sinon.SinonStub).calledOnce).to
|
|
838
|
+
.be.true;
|
|
839
|
+
expect((metrics.recordRebalancerSuccess as Sinon.SinonStub).called).to.be
|
|
840
|
+
.false;
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it('should process token metrics when metrics is provided', async () => {
|
|
844
|
+
const strategy = createMockStrategy();
|
|
845
|
+
strategy.getRebalancingRoutes.returns([]);
|
|
846
|
+
|
|
847
|
+
const actionTracker = createMockActionTracker();
|
|
848
|
+
const inflightAdapter = createMockInflightContextAdapter();
|
|
849
|
+
const metrics = createMockMetrics();
|
|
850
|
+
|
|
851
|
+
const deps: RebalancerOrchestratorDeps = {
|
|
852
|
+
strategy,
|
|
853
|
+
actionTracker,
|
|
854
|
+
inflightContextAdapter: inflightAdapter,
|
|
855
|
+
rebalancerConfig: createMockRebalancerConfig(),
|
|
856
|
+
logger: testLogger,
|
|
857
|
+
rebalancers: [],
|
|
858
|
+
metrics,
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
const orchestrator = new RebalancerOrchestrator(deps);
|
|
862
|
+
const event = createMonitorEvent();
|
|
863
|
+
|
|
864
|
+
await orchestrator.executeCycle(event);
|
|
865
|
+
|
|
866
|
+
expect((metrics.processToken as Sinon.SinonStub).calledTwice).to.be.true;
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
});
|