@hyperlane-xyz/rebalancer 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +7 -2
- package/dist/config/RebalancerConfig.d.ts.map +1 -1
- package/dist/config/RebalancerConfig.js +7 -4
- package/dist/config/RebalancerConfig.js.map +1 -1
- package/dist/config/RebalancerConfig.test.js +134 -1
- package/dist/config/RebalancerConfig.test.js.map +1 -1
- package/dist/config/types.d.ts +1016 -304
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +105 -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 +885 -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 +1351 -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 +714 -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 +71 -109
- 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/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 +6 -7
- 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 +170 -17
- package/dist/factories/RebalancerContextFactory.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- 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 +33 -1
- package/dist/tracking/ActionTracker.d.ts.map +1 -1
- package/dist/tracking/ActionTracker.js +193 -22
- package/dist/tracking/ActionTracker.js.map +1 -1
- package/dist/tracking/ActionTracker.test.js +107 -19
- package/dist/tracking/ActionTracker.test.js.map +1 -1
- package/dist/tracking/IActionTracker.d.ts +47 -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 +31 -2
- package/dist/tracking/types.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.d.ts +2 -1
- package/dist/utils/ExplorerClient.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.js +13 -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 +160 -0
- package/src/config/RebalancerConfig.ts +14 -3
- package/src/config/types.ts +136 -10
- package/src/core/InventoryRebalancer.test.ts +1684 -0
- package/src/core/InventoryRebalancer.ts +1255 -0
- package/src/core/Rebalancer.test.ts +84 -30
- package/src/core/Rebalancer.ts +144 -23
- package/src/core/RebalancerOrchestrator.test.ts +860 -0
- package/src/core/RebalancerOrchestrator.ts +146 -95
- package/src/core/RebalancerService.test.ts +80 -123
- 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/TestHelpers.ts +1 -4
- package/src/e2e/harness/TestRebalancer.ts +7 -7
- package/src/e2e/minAmount.e2e-test.ts +1 -2
- package/src/e2e/weighted.e2e-test.ts +1 -2
- package/src/factories/RebalancerContextFactory.ts +293 -24
- package/src/index.ts +20 -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 +122 -19
- package/src/tracking/ActionTracker.ts +284 -24
- package/src/tracking/IActionTracker.ts +58 -3
- package/src/tracking/InflightContextAdapter.test.ts +7 -4
- package/src/tracking/InflightContextAdapter.ts +42 -9
- package/src/tracking/types.ts +43 -2
- package/src/utils/ExplorerClient.ts +23 -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
package/src/test/helpers.ts
CHANGED
|
@@ -16,18 +16,24 @@ import {
|
|
|
16
16
|
import type { RebalancerConfig } from '../config/RebalancerConfig.js';
|
|
17
17
|
import { RebalancerStrategyOptions } from '../config/types.js';
|
|
18
18
|
import type {
|
|
19
|
+
ExecutionResult,
|
|
19
20
|
IRebalancer,
|
|
21
|
+
MovableCollateralExecutionResult,
|
|
20
22
|
PreparedTransaction,
|
|
21
|
-
|
|
22
|
-
RebalanceRoute,
|
|
23
|
+
RebalancerType,
|
|
23
24
|
} from '../interfaces/IRebalancer.js';
|
|
24
|
-
import type {
|
|
25
|
-
|
|
25
|
+
import type {
|
|
26
|
+
MovableCollateralRoute,
|
|
27
|
+
StrategyRoute,
|
|
28
|
+
} from '../interfaces/IStrategy.js';
|
|
29
|
+
import type { BridgeConfigWithOverride } from '../utils/bridgeUtils.js';
|
|
26
30
|
|
|
27
31
|
// === Mock Classes ===
|
|
28
32
|
|
|
29
33
|
export class MockRebalancer implements IRebalancer {
|
|
30
|
-
|
|
34
|
+
readonly rebalancerType: RebalancerType = 'movableCollateral';
|
|
35
|
+
|
|
36
|
+
rebalance(_routes: MovableCollateralRoute[]): Promise<ExecutionResult[]> {
|
|
31
37
|
return Promise.resolve([]);
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -36,33 +42,45 @@ export class MockRebalancer implements IRebalancer {
|
|
|
36
42
|
|
|
37
43
|
export function buildTestRoute(
|
|
38
44
|
overrides: Partial<StrategyRoute> = {},
|
|
45
|
+
executionType: 'movableCollateral' | 'inventory' = 'movableCollateral',
|
|
39
46
|
): StrategyRoute {
|
|
47
|
+
if (executionType === 'inventory') {
|
|
48
|
+
return {
|
|
49
|
+
origin: 'ethereum',
|
|
50
|
+
destination: 'arbitrum',
|
|
51
|
+
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
52
|
+
executionType: 'inventory',
|
|
53
|
+
externalBridge: 'lifi',
|
|
54
|
+
...overrides,
|
|
55
|
+
} as StrategyRoute;
|
|
56
|
+
}
|
|
40
57
|
return {
|
|
41
58
|
origin: 'ethereum',
|
|
42
59
|
destination: 'arbitrum',
|
|
43
60
|
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
61
|
+
executionType: 'movableCollateral',
|
|
44
62
|
bridge: TEST_ADDRESSES.bridge,
|
|
45
63
|
...overrides,
|
|
46
|
-
};
|
|
64
|
+
} as StrategyRoute;
|
|
47
65
|
}
|
|
48
66
|
|
|
49
|
-
export function
|
|
50
|
-
overrides: Partial<
|
|
51
|
-
):
|
|
67
|
+
export function buildTestMovableCollateralRoute(
|
|
68
|
+
overrides: Partial<MovableCollateralRoute> = {},
|
|
69
|
+
): MovableCollateralRoute {
|
|
52
70
|
return {
|
|
53
|
-
intentId: overrides.intentId ?? `test-route-${Date.now()}`,
|
|
54
71
|
origin: 'ethereum',
|
|
55
72
|
destination: 'arbitrum',
|
|
56
73
|
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
74
|
+
executionType: 'movableCollateral',
|
|
57
75
|
bridge: TEST_ADDRESSES.bridge,
|
|
58
76
|
...overrides,
|
|
59
77
|
};
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
export function buildTestResult(
|
|
63
|
-
overrides: Partial<
|
|
64
|
-
):
|
|
65
|
-
const route = overrides.route ??
|
|
81
|
+
overrides: Partial<MovableCollateralExecutionResult> = {},
|
|
82
|
+
): MovableCollateralExecutionResult {
|
|
83
|
+
const route = overrides.route ?? buildTestMovableCollateralRoute();
|
|
66
84
|
return {
|
|
67
85
|
route,
|
|
68
86
|
success: true,
|
|
@@ -77,7 +95,12 @@ export function buildTestResult(
|
|
|
77
95
|
export function buildTestPreparedTransaction(
|
|
78
96
|
overrides: Partial<PreparedTransaction> = {},
|
|
79
97
|
): PreparedTransaction {
|
|
80
|
-
const route =
|
|
98
|
+
const route =
|
|
99
|
+
overrides.route ??
|
|
100
|
+
({
|
|
101
|
+
...buildTestMovableCollateralRoute(),
|
|
102
|
+
intentId: 'test-intent',
|
|
103
|
+
} as MovableCollateralRoute & { intentId: string });
|
|
81
104
|
return {
|
|
82
105
|
populatedTx: {
|
|
83
106
|
to: TEST_ADDRESSES.token,
|
|
@@ -272,6 +295,7 @@ export function buildTestBridges(
|
|
|
272
295
|
): ChainMap<BridgeConfigWithOverride> {
|
|
273
296
|
return chains.reduce((acc, chain) => {
|
|
274
297
|
acc[chain] = {
|
|
298
|
+
executionType: 'movableCollateral',
|
|
275
299
|
bridge: TEST_ADDRESSES.bridge,
|
|
276
300
|
bridgeMinAcceptedAmount: 0,
|
|
277
301
|
};
|
|
@@ -291,6 +315,7 @@ export function extractBridgeConfigs(
|
|
|
291
315
|
): ChainMap<BridgeConfigWithOverride> {
|
|
292
316
|
return Object.entries(chainConfig).reduce((acc, [chain, config]) => {
|
|
293
317
|
acc[chain] = {
|
|
318
|
+
executionType: 'movableCollateral',
|
|
294
319
|
bridge: config.bridge,
|
|
295
320
|
bridgeMinAcceptedAmount: config.bridgeMinAcceptedAmount ?? 0,
|
|
296
321
|
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import Sinon, { type SinonStub } from 'sinon';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
BridgeQuote,
|
|
5
|
+
BridgeTransferStatus,
|
|
6
|
+
} from '../interfaces/IExternalBridge.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create mock functions for LiFi SDK.
|
|
10
|
+
*/
|
|
11
|
+
export function createLiFiSdkMocks() {
|
|
12
|
+
return {
|
|
13
|
+
createConfig: Sinon.stub(),
|
|
14
|
+
getQuote: Sinon.stub(),
|
|
15
|
+
executeRoute: Sinon.stub(),
|
|
16
|
+
getStatus: Sinon.stub(),
|
|
17
|
+
convertQuoteToRoute: Sinon.stub().callsFake((quote: unknown) => {
|
|
18
|
+
const q = quote as Record<string, unknown> & {
|
|
19
|
+
action?: { fromChainId?: number; toChainId?: number };
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
...q,
|
|
23
|
+
fromChainId: q.action?.fromChainId ?? 42161,
|
|
24
|
+
toChainId: q.action?.toChainId ?? 1399811149,
|
|
25
|
+
steps: [],
|
|
26
|
+
};
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configure a mock getQuote to return a successful quote.
|
|
33
|
+
*/
|
|
34
|
+
export function mockSuccessfulQuote(
|
|
35
|
+
stub: SinonStub,
|
|
36
|
+
overrides?: Partial<{
|
|
37
|
+
id: string;
|
|
38
|
+
tool: string;
|
|
39
|
+
fromAmount: string;
|
|
40
|
+
toAmount: string;
|
|
41
|
+
toAmountMin: string;
|
|
42
|
+
executionDuration: number;
|
|
43
|
+
fromChainId: number;
|
|
44
|
+
toChainId: number;
|
|
45
|
+
}>,
|
|
46
|
+
) {
|
|
47
|
+
const fromChainId = overrides?.fromChainId ?? 42161;
|
|
48
|
+
const toChainId = overrides?.toChainId ?? 1399811149;
|
|
49
|
+
const fromAmount = overrides?.fromAmount ?? '10000000000';
|
|
50
|
+
const toAmount = overrides?.toAmount ?? '9950000000';
|
|
51
|
+
const toAmountMin = overrides?.toAmountMin ?? '9900000000';
|
|
52
|
+
|
|
53
|
+
stub.resolves({
|
|
54
|
+
id: overrides?.id ?? 'quote-123',
|
|
55
|
+
tool: overrides?.tool ?? 'across',
|
|
56
|
+
action: {
|
|
57
|
+
fromAmount,
|
|
58
|
+
fromChainId,
|
|
59
|
+
toChainId,
|
|
60
|
+
},
|
|
61
|
+
estimate: {
|
|
62
|
+
toAmount,
|
|
63
|
+
toAmountMin,
|
|
64
|
+
executionDuration: overrides?.executionDuration ?? 300,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Configure a mock executeRoute to return a successful execution.
|
|
71
|
+
*/
|
|
72
|
+
export function mockSuccessfulExecution(stub: SinonStub, txHash: string) {
|
|
73
|
+
stub.resolves({
|
|
74
|
+
steps: [
|
|
75
|
+
{
|
|
76
|
+
execution: {
|
|
77
|
+
process: [{ txHash }],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Configure a mock getStatus to return a specific status.
|
|
86
|
+
*/
|
|
87
|
+
export function mockLiFiStatus(
|
|
88
|
+
stub: SinonStub,
|
|
89
|
+
status: 'DONE' | 'PENDING' | 'FAILED' | 'NOT_FOUND',
|
|
90
|
+
overrides?: Partial<{
|
|
91
|
+
receivingTxHash: string;
|
|
92
|
+
amount: string;
|
|
93
|
+
substatus: string;
|
|
94
|
+
}>,
|
|
95
|
+
) {
|
|
96
|
+
const responses: Record<string, unknown> = {
|
|
97
|
+
DONE: {
|
|
98
|
+
status: 'DONE',
|
|
99
|
+
receiving: {
|
|
100
|
+
txHash: overrides?.receivingTxHash ?? '0xReceivingTxHash',
|
|
101
|
+
amount: overrides?.amount ?? '9950000000',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
PENDING: {
|
|
105
|
+
status: 'PENDING',
|
|
106
|
+
substatus: overrides?.substatus ?? 'WAIT_SOURCE_CONFIRMATIONS',
|
|
107
|
+
},
|
|
108
|
+
FAILED: {
|
|
109
|
+
status: 'FAILED',
|
|
110
|
+
substatus: overrides?.substatus ?? 'BRIDGE_CALL_FAILED',
|
|
111
|
+
},
|
|
112
|
+
NOT_FOUND: {
|
|
113
|
+
status: 'NOT_FOUND',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
stub.resolves(responses[status]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a mock BridgeQuote for testing.
|
|
122
|
+
*/
|
|
123
|
+
export function createMockBridgeQuote(
|
|
124
|
+
overrides?: Partial<BridgeQuote>,
|
|
125
|
+
): BridgeQuote {
|
|
126
|
+
return {
|
|
127
|
+
id: 'quote-123',
|
|
128
|
+
tool: 'across',
|
|
129
|
+
fromAmount: 10000000000n,
|
|
130
|
+
toAmount: 9950000000n,
|
|
131
|
+
toAmountMin: 9900000000n,
|
|
132
|
+
executionDuration: 300,
|
|
133
|
+
gasCosts: 50000000n, // Default mock gas costs
|
|
134
|
+
feeCosts: 0n, // Default mock fee costs
|
|
135
|
+
route: {
|
|
136
|
+
action: { fromChainId: 42161, toChainId: 1399811149 },
|
|
137
|
+
},
|
|
138
|
+
...overrides,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create a mock BridgeTransferStatus for testing.
|
|
144
|
+
*/
|
|
145
|
+
export function createMockBridgeStatus(
|
|
146
|
+
status: 'pending' | 'complete' | 'failed' | 'not_found',
|
|
147
|
+
overrides?: Partial<{
|
|
148
|
+
substatus: string;
|
|
149
|
+
receivingTxHash: string;
|
|
150
|
+
receivedAmount: bigint;
|
|
151
|
+
error: string;
|
|
152
|
+
}>,
|
|
153
|
+
): BridgeTransferStatus {
|
|
154
|
+
switch (status) {
|
|
155
|
+
case 'pending':
|
|
156
|
+
return {
|
|
157
|
+
status: 'pending',
|
|
158
|
+
substatus: overrides?.substatus,
|
|
159
|
+
};
|
|
160
|
+
case 'complete':
|
|
161
|
+
return {
|
|
162
|
+
status: 'complete',
|
|
163
|
+
receivingTxHash: overrides?.receivingTxHash ?? '0xReceivingTxHash',
|
|
164
|
+
receivedAmount: overrides?.receivedAmount ?? 9950000000n,
|
|
165
|
+
};
|
|
166
|
+
case 'failed':
|
|
167
|
+
return {
|
|
168
|
+
status: 'failed',
|
|
169
|
+
error: overrides?.error,
|
|
170
|
+
};
|
|
171
|
+
case 'not_found':
|
|
172
|
+
return { status: 'not_found' };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -148,6 +148,7 @@ describe('ActionTracker', () => {
|
|
|
148
148
|
// Pre-create action
|
|
149
149
|
await rebalanceActionStore.save({
|
|
150
150
|
id: '0xmsg1',
|
|
151
|
+
type: 'rebalance_message',
|
|
151
152
|
status: 'in_progress',
|
|
152
153
|
intentId: 'existing-intent',
|
|
153
154
|
messageId: '0xmsg1',
|
|
@@ -269,18 +270,32 @@ describe('ActionTracker', () => {
|
|
|
269
270
|
|
|
270
271
|
describe('syncRebalanceIntents', () => {
|
|
271
272
|
it('should mark intents as complete when fully fulfilled', async () => {
|
|
273
|
+
// Intent derives completion from action states, so we need a complete action
|
|
272
274
|
const intent: RebalanceIntent = {
|
|
273
275
|
id: 'intent-1',
|
|
274
276
|
status: 'in_progress',
|
|
275
277
|
origin: 1,
|
|
276
278
|
destination: 2,
|
|
277
279
|
amount: 100n,
|
|
278
|
-
|
|
280
|
+
createdAt: Date.now(),
|
|
281
|
+
updatedAt: Date.now(),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const action: RebalanceAction = {
|
|
285
|
+
id: 'action-1',
|
|
286
|
+
type: 'rebalance_message',
|
|
287
|
+
status: 'complete',
|
|
288
|
+
intentId: 'intent-1',
|
|
289
|
+
messageId: '0xmsg1',
|
|
290
|
+
origin: 1,
|
|
291
|
+
destination: 2,
|
|
292
|
+
amount: 100n,
|
|
279
293
|
createdAt: Date.now(),
|
|
280
294
|
updatedAt: Date.now(),
|
|
281
295
|
};
|
|
282
296
|
|
|
283
297
|
await rebalanceIntentStore.save(intent);
|
|
298
|
+
await rebalanceActionStore.save(action);
|
|
284
299
|
|
|
285
300
|
await tracker.syncRebalanceIntents();
|
|
286
301
|
|
|
@@ -289,18 +304,32 @@ describe('ActionTracker', () => {
|
|
|
289
304
|
});
|
|
290
305
|
|
|
291
306
|
it('should not mark intents as complete if not fully fulfilled', async () => {
|
|
307
|
+
// Intent with only partial completion via actions
|
|
292
308
|
const intent: RebalanceIntent = {
|
|
293
309
|
id: 'intent-1',
|
|
294
310
|
status: 'in_progress',
|
|
295
311
|
origin: 1,
|
|
296
312
|
destination: 2,
|
|
297
313
|
amount: 100n,
|
|
298
|
-
|
|
314
|
+
createdAt: Date.now(),
|
|
315
|
+
updatedAt: Date.now(),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const action: RebalanceAction = {
|
|
319
|
+
id: 'action-1',
|
|
320
|
+
type: 'rebalance_message',
|
|
321
|
+
status: 'complete',
|
|
322
|
+
intentId: 'intent-1',
|
|
323
|
+
messageId: '0xmsg1',
|
|
324
|
+
origin: 1,
|
|
325
|
+
destination: 2,
|
|
326
|
+
amount: 50n, // Only partial
|
|
299
327
|
createdAt: Date.now(),
|
|
300
328
|
updatedAt: Date.now(),
|
|
301
329
|
};
|
|
302
330
|
|
|
303
331
|
await rebalanceIntentStore.save(intent);
|
|
332
|
+
await rebalanceActionStore.save(action);
|
|
304
333
|
|
|
305
334
|
await tracker.syncRebalanceIntents();
|
|
306
335
|
|
|
@@ -317,13 +346,13 @@ describe('ActionTracker', () => {
|
|
|
317
346
|
origin: 1,
|
|
318
347
|
destination: 2,
|
|
319
348
|
amount: 100n,
|
|
320
|
-
fulfilledAmount: 0n,
|
|
321
349
|
createdAt: Date.now(),
|
|
322
350
|
updatedAt: Date.now(),
|
|
323
351
|
};
|
|
324
352
|
|
|
325
353
|
const action: RebalanceAction = {
|
|
326
354
|
id: 'action-1',
|
|
355
|
+
type: 'rebalance_message',
|
|
327
356
|
status: 'in_progress',
|
|
328
357
|
intentId: 'intent-1',
|
|
329
358
|
messageId: '0xmsg1',
|
|
@@ -345,15 +374,15 @@ describe('ActionTracker', () => {
|
|
|
345
374
|
const updatedAction = await rebalanceActionStore.get('action-1');
|
|
346
375
|
expect(updatedAction?.status).to.equal('complete');
|
|
347
376
|
|
|
348
|
-
// Intent should be
|
|
377
|
+
// Intent should be complete (derived from completed action amounts)
|
|
349
378
|
const updatedIntent = await rebalanceIntentStore.get('intent-1');
|
|
350
|
-
expect(updatedIntent?.fulfilledAmount).to.equal(100n);
|
|
351
379
|
expect(updatedIntent?.status).to.equal('complete');
|
|
352
380
|
});
|
|
353
381
|
|
|
354
382
|
it('should not mark actions as complete if not delivered', async () => {
|
|
355
383
|
const action: RebalanceAction = {
|
|
356
384
|
id: 'action-1',
|
|
385
|
+
type: 'rebalance_message',
|
|
357
386
|
status: 'in_progress',
|
|
358
387
|
intentId: 'intent-1',
|
|
359
388
|
messageId: '0xmsg1',
|
|
@@ -417,7 +446,6 @@ describe('ActionTracker', () => {
|
|
|
417
446
|
origin: 1,
|
|
418
447
|
destination: 2,
|
|
419
448
|
amount: 100n,
|
|
420
|
-
fulfilledAmount: 0n,
|
|
421
449
|
createdAt: Date.now(),
|
|
422
450
|
updatedAt: Date.now(),
|
|
423
451
|
});
|
|
@@ -428,7 +456,6 @@ describe('ActionTracker', () => {
|
|
|
428
456
|
origin: 2,
|
|
429
457
|
destination: 3,
|
|
430
458
|
amount: 200n,
|
|
431
|
-
fulfilledAmount: 50n,
|
|
432
459
|
createdAt: Date.now(),
|
|
433
460
|
updatedAt: Date.now(),
|
|
434
461
|
});
|
|
@@ -439,7 +466,6 @@ describe('ActionTracker', () => {
|
|
|
439
466
|
origin: 3,
|
|
440
467
|
destination: 1,
|
|
441
468
|
amount: 300n,
|
|
442
|
-
fulfilledAmount: 300n,
|
|
443
469
|
createdAt: Date.now(),
|
|
444
470
|
updatedAt: Date.now(),
|
|
445
471
|
});
|
|
@@ -452,6 +478,85 @@ describe('ActionTracker', () => {
|
|
|
452
478
|
});
|
|
453
479
|
});
|
|
454
480
|
|
|
481
|
+
describe('getPartiallyFulfilledInventoryIntents', () => {
|
|
482
|
+
it('returns not_started inventory intents', async () => {
|
|
483
|
+
// Create a not_started inventory intent (simulates failed execution before any action created)
|
|
484
|
+
await rebalanceIntentStore.save({
|
|
485
|
+
id: 'stuck-intent',
|
|
486
|
+
status: 'not_started',
|
|
487
|
+
origin: 1,
|
|
488
|
+
destination: 2,
|
|
489
|
+
amount: 1000000000000000000n, // 1 ETH
|
|
490
|
+
executionMethod: 'inventory',
|
|
491
|
+
createdAt: Date.now(),
|
|
492
|
+
updatedAt: Date.now(),
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Should be returned even though status is 'not_started'
|
|
496
|
+
const partialIntents =
|
|
497
|
+
await tracker.getPartiallyFulfilledInventoryIntents();
|
|
498
|
+
|
|
499
|
+
expect(partialIntents).to.have.lengthOf(1);
|
|
500
|
+
expect(partialIntents[0].intent.id).to.equal('stuck-intent');
|
|
501
|
+
expect(partialIntents[0].completedAmount).to.equal(0n);
|
|
502
|
+
expect(partialIntents[0].remaining).to.equal(1000000000000000000n);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('returns in_progress inventory intents with partial completion', async () => {
|
|
506
|
+
// Create an in_progress inventory intent with a completed action
|
|
507
|
+
await rebalanceIntentStore.save({
|
|
508
|
+
id: 'partial-intent',
|
|
509
|
+
status: 'in_progress',
|
|
510
|
+
origin: 1,
|
|
511
|
+
destination: 2,
|
|
512
|
+
amount: 1000000000000000000n, // 1 ETH
|
|
513
|
+
executionMethod: 'inventory',
|
|
514
|
+
createdAt: Date.now(),
|
|
515
|
+
updatedAt: Date.now(),
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Create a completed inventory_deposit action for partial amount
|
|
519
|
+
await rebalanceActionStore.save({
|
|
520
|
+
id: 'action-1',
|
|
521
|
+
type: 'inventory_deposit',
|
|
522
|
+
status: 'complete',
|
|
523
|
+
intentId: 'partial-intent',
|
|
524
|
+
origin: 1,
|
|
525
|
+
destination: 2,
|
|
526
|
+
amount: 400000000000000000n, // 0.4 ETH completed
|
|
527
|
+
createdAt: Date.now(),
|
|
528
|
+
updatedAt: Date.now(),
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const partialIntents =
|
|
532
|
+
await tracker.getPartiallyFulfilledInventoryIntents();
|
|
533
|
+
|
|
534
|
+
expect(partialIntents).to.have.lengthOf(1);
|
|
535
|
+
expect(partialIntents[0].intent.id).to.equal('partial-intent');
|
|
536
|
+
expect(partialIntents[0].completedAmount).to.equal(400000000000000000n);
|
|
537
|
+
expect(partialIntents[0].remaining).to.equal(600000000000000000n); // 0.6 ETH remaining
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('does not return non-inventory intents', async () => {
|
|
541
|
+
// Create a not_started intent without executionMethod: 'inventory'
|
|
542
|
+
await rebalanceIntentStore.save({
|
|
543
|
+
id: 'non-inventory-intent',
|
|
544
|
+
status: 'not_started',
|
|
545
|
+
origin: 1,
|
|
546
|
+
destination: 2,
|
|
547
|
+
amount: 1000000000000000000n,
|
|
548
|
+
// executionMethod is undefined - not an inventory intent
|
|
549
|
+
createdAt: Date.now(),
|
|
550
|
+
updatedAt: Date.now(),
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
const partialIntents =
|
|
554
|
+
await tracker.getPartiallyFulfilledInventoryIntents();
|
|
555
|
+
|
|
556
|
+
expect(partialIntents).to.have.lengthOf(0);
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
455
560
|
describe('createRebalanceIntent', () => {
|
|
456
561
|
it('should create a new intent with status not_started', async () => {
|
|
457
562
|
const result = await tracker.createRebalanceIntent({
|
|
@@ -466,7 +571,6 @@ describe('ActionTracker', () => {
|
|
|
466
571
|
expect(result.origin).to.equal(1);
|
|
467
572
|
expect(result.destination).to.equal(2);
|
|
468
573
|
expect(result.amount).to.equal(100n);
|
|
469
|
-
expect(result.fulfilledAmount).to.equal(0n);
|
|
470
574
|
expect(result.priority).to.equal(1);
|
|
471
575
|
expect(result.strategyType).to.equal('MinAmountStrategy');
|
|
472
576
|
|
|
@@ -483,7 +587,6 @@ describe('ActionTracker', () => {
|
|
|
483
587
|
origin: 1,
|
|
484
588
|
destination: 2,
|
|
485
589
|
amount: 100n,
|
|
486
|
-
fulfilledAmount: 0n,
|
|
487
590
|
createdAt: Date.now(),
|
|
488
591
|
updatedAt: Date.now(),
|
|
489
592
|
};
|
|
@@ -491,6 +594,7 @@ describe('ActionTracker', () => {
|
|
|
491
594
|
await rebalanceIntentStore.save(intent);
|
|
492
595
|
|
|
493
596
|
const result = await tracker.createRebalanceAction({
|
|
597
|
+
type: 'rebalance_message',
|
|
494
598
|
intentId: 'intent-1',
|
|
495
599
|
origin: 1,
|
|
496
600
|
destination: 2,
|
|
@@ -514,7 +618,6 @@ describe('ActionTracker', () => {
|
|
|
514
618
|
origin: 1,
|
|
515
619
|
destination: 2,
|
|
516
620
|
amount: 100n,
|
|
517
|
-
fulfilledAmount: 50n,
|
|
518
621
|
createdAt: Date.now(),
|
|
519
622
|
updatedAt: Date.now(),
|
|
520
623
|
};
|
|
@@ -522,6 +625,7 @@ describe('ActionTracker', () => {
|
|
|
522
625
|
await rebalanceIntentStore.save(intent);
|
|
523
626
|
|
|
524
627
|
await tracker.createRebalanceAction({
|
|
628
|
+
type: 'rebalance_message',
|
|
525
629
|
intentId: 'intent-1',
|
|
526
630
|
origin: 1,
|
|
527
631
|
destination: 2,
|
|
@@ -532,25 +636,24 @@ describe('ActionTracker', () => {
|
|
|
532
636
|
|
|
533
637
|
const updatedIntent = await rebalanceIntentStore.get('intent-1');
|
|
534
638
|
expect(updatedIntent?.status).to.equal('in_progress');
|
|
535
|
-
expect(updatedIntent?.fulfilledAmount).to.equal(50n); // Should not change
|
|
536
639
|
});
|
|
537
640
|
});
|
|
538
641
|
|
|
539
642
|
describe('completeRebalanceAction', () => {
|
|
540
|
-
it('should mark action as complete and
|
|
643
|
+
it('should mark action as complete and mark parent intent complete if fully fulfilled', async () => {
|
|
541
644
|
const intent: RebalanceIntent = {
|
|
542
645
|
id: 'intent-1',
|
|
543
646
|
status: 'in_progress',
|
|
544
647
|
origin: 1,
|
|
545
648
|
destination: 2,
|
|
546
649
|
amount: 100n,
|
|
547
|
-
fulfilledAmount: 0n,
|
|
548
650
|
createdAt: Date.now(),
|
|
549
651
|
updatedAt: Date.now(),
|
|
550
652
|
};
|
|
551
653
|
|
|
552
654
|
const action: RebalanceAction = {
|
|
553
655
|
id: 'action-1',
|
|
656
|
+
type: 'rebalance_message',
|
|
554
657
|
status: 'in_progress',
|
|
555
658
|
intentId: 'intent-1',
|
|
556
659
|
messageId: '0xmsg1',
|
|
@@ -569,8 +672,8 @@ describe('ActionTracker', () => {
|
|
|
569
672
|
const updatedAction = await rebalanceActionStore.get('action-1');
|
|
570
673
|
expect(updatedAction?.status).to.equal('complete');
|
|
571
674
|
|
|
675
|
+
// Intent should be complete (derived from completed action amounts)
|
|
572
676
|
const updatedIntent = await rebalanceIntentStore.get('intent-1');
|
|
573
|
-
expect(updatedIntent?.fulfilledAmount).to.equal(100n);
|
|
574
677
|
expect(updatedIntent?.status).to.equal('complete');
|
|
575
678
|
});
|
|
576
679
|
|
|
@@ -589,7 +692,6 @@ describe('ActionTracker', () => {
|
|
|
589
692
|
origin: 1,
|
|
590
693
|
destination: 2,
|
|
591
694
|
amount: 100n,
|
|
592
|
-
fulfilledAmount: 0n,
|
|
593
695
|
createdAt: Date.now(),
|
|
594
696
|
updatedAt: Date.now(),
|
|
595
697
|
};
|
|
@@ -607,6 +709,7 @@ describe('ActionTracker', () => {
|
|
|
607
709
|
it('should mark action as failed', async () => {
|
|
608
710
|
const action: RebalanceAction = {
|
|
609
711
|
id: 'action-1',
|
|
712
|
+
type: 'rebalance_message',
|
|
610
713
|
status: 'in_progress',
|
|
611
714
|
intentId: 'intent-1',
|
|
612
715
|
messageId: '0xmsg1',
|
|
@@ -653,7 +756,7 @@ describe('ActionTracker', () => {
|
|
|
653
756
|
|
|
654
757
|
const params = call.args[0];
|
|
655
758
|
expect(params.routersByDomain).to.deep.equal(config.routersByDomain);
|
|
656
|
-
expect(params.
|
|
759
|
+
expect(params.excludeTxSenders).to.deep.equal([config.rebalancerAddress]);
|
|
657
760
|
});
|
|
658
761
|
});
|
|
659
762
|
|
|
@@ -692,13 +795,13 @@ describe('ActionTracker', () => {
|
|
|
692
795
|
origin: 1,
|
|
693
796
|
destination: 2,
|
|
694
797
|
amount: 100n,
|
|
695
|
-
fulfilledAmount: 0n,
|
|
696
798
|
createdAt: Date.now(),
|
|
697
799
|
updatedAt: Date.now(),
|
|
698
800
|
};
|
|
699
801
|
|
|
700
802
|
const action: RebalanceAction = {
|
|
701
803
|
id: 'action-1',
|
|
804
|
+
type: 'rebalance_message',
|
|
702
805
|
status: 'in_progress',
|
|
703
806
|
intentId: 'intent-1',
|
|
704
807
|
messageId: '0xmsg1',
|
|
@@ -806,7 +909,6 @@ describe('ActionTracker', () => {
|
|
|
806
909
|
origin: 1,
|
|
807
910
|
destination: 2,
|
|
808
911
|
amount: 100n,
|
|
809
|
-
fulfilledAmount: 0n,
|
|
810
912
|
createdAt: Date.now(),
|
|
811
913
|
updatedAt: Date.now(),
|
|
812
914
|
};
|
|
@@ -814,6 +916,7 @@ describe('ActionTracker', () => {
|
|
|
814
916
|
const action: RebalanceAction = {
|
|
815
917
|
id: 'action-1',
|
|
816
918
|
status: 'in_progress',
|
|
919
|
+
type: 'rebalance_message',
|
|
817
920
|
intentId: 'intent-1',
|
|
818
921
|
messageId: '0xmsg1',
|
|
819
922
|
origin: 1,
|