@hyperlane-xyz/rebalancer 0.1.2 → 1.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/README.md +134 -14
- package/dist/config/RebalancerConfig.d.ts +2 -2
- package/dist/config/RebalancerConfig.d.ts.map +1 -1
- package/dist/config/RebalancerConfig.js +4 -3
- package/dist/config/RebalancerConfig.js.map +1 -1
- package/dist/config/RebalancerConfig.test.js +434 -163
- package/dist/config/RebalancerConfig.test.js.map +1 -1
- package/dist/config/types.d.ts +1650 -290
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +124 -46
- package/dist/config/types.js.map +1 -1
- package/dist/core/Rebalancer.d.ts +14 -7
- package/dist/core/Rebalancer.d.ts.map +1 -1
- package/dist/core/Rebalancer.js +168 -99
- package/dist/core/Rebalancer.js.map +1 -1
- package/dist/core/Rebalancer.test.d.ts +2 -0
- package/dist/core/Rebalancer.test.d.ts.map +1 -0
- package/dist/core/Rebalancer.test.js +391 -0
- package/dist/core/Rebalancer.test.js.map +1 -0
- package/dist/core/RebalancerService.d.ts +16 -2
- package/dist/core/RebalancerService.d.ts.map +1 -1
- package/dist/core/RebalancerService.js +164 -21
- package/dist/core/RebalancerService.js.map +1 -1
- package/dist/core/RebalancerService.test.d.ts +2 -0
- package/dist/core/RebalancerService.test.d.ts.map +1 -0
- package/dist/core/RebalancerService.test.js +809 -0
- package/dist/core/RebalancerService.test.js.map +1 -0
- package/dist/factories/RebalancerContextFactory.d.ts +11 -0
- package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
- package/dist/factories/RebalancerContextFactory.js +60 -13
- 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 +4 -3
- package/dist/index.js.map +1 -1
- package/dist/interfaces/IMonitor.d.ts +6 -8
- package/dist/interfaces/IMonitor.d.ts.map +1 -1
- package/dist/interfaces/IMonitor.js.map +1 -1
- package/dist/interfaces/IRebalancer.d.ts +20 -4
- package/dist/interfaces/IRebalancer.d.ts.map +1 -1
- package/dist/interfaces/IStrategy.d.ts +18 -2
- package/dist/interfaces/IStrategy.d.ts.map +1 -1
- package/dist/metrics/Metrics.d.ts +4 -2
- package/dist/metrics/Metrics.d.ts.map +1 -1
- package/dist/metrics/Metrics.js +21 -1
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/metrics/scripts/metrics.d.ts +2 -0
- package/dist/metrics/scripts/metrics.d.ts.map +1 -1
- package/dist/metrics/scripts/metrics.js +12 -0
- package/dist/metrics/scripts/metrics.js.map +1 -1
- package/dist/monitor/Monitor.d.ts +8 -3
- package/dist/monitor/Monitor.d.ts.map +1 -1
- package/dist/monitor/Monitor.js +75 -15
- package/dist/monitor/Monitor.js.map +1 -1
- package/dist/strategy/BaseStrategy.d.ts +51 -5
- package/dist/strategy/BaseStrategy.d.ts.map +1 -1
- package/dist/strategy/BaseStrategy.js +199 -19
- package/dist/strategy/BaseStrategy.js.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.d.ts +65 -0
- package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.js +245 -0
- package/dist/strategy/CollateralDeficitStrategy.js.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.test.d.ts +2 -0
- package/dist/strategy/CollateralDeficitStrategy.test.d.ts.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.test.js +364 -0
- package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -0
- package/dist/strategy/CompositeStrategy.d.ts +18 -0
- package/dist/strategy/CompositeStrategy.d.ts.map +1 -0
- package/dist/strategy/CompositeStrategy.js +63 -0
- package/dist/strategy/CompositeStrategy.js.map +1 -0
- package/dist/strategy/CompositeStrategy.test.d.ts +2 -0
- package/dist/strategy/CompositeStrategy.test.d.ts.map +1 -0
- package/dist/strategy/CompositeStrategy.test.js +265 -0
- package/dist/strategy/CompositeStrategy.test.js.map +1 -0
- package/dist/strategy/MinAmountStrategy.d.ts +12 -5
- package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
- package/dist/strategy/MinAmountStrategy.js +23 -14
- package/dist/strategy/MinAmountStrategy.js.map +1 -1
- package/dist/strategy/MinAmountStrategy.test.js +88 -20
- package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
- package/dist/strategy/StrategyFactory.d.ts +15 -6
- package/dist/strategy/StrategyFactory.d.ts.map +1 -1
- package/dist/strategy/StrategyFactory.js +48 -10
- package/dist/strategy/StrategyFactory.js.map +1 -1
- package/dist/strategy/StrategyFactory.test.js +2 -2
- package/dist/strategy/StrategyFactory.test.js.map +1 -1
- package/dist/strategy/WeightedStrategy.d.ts +13 -4
- package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
- package/dist/strategy/WeightedStrategy.js +18 -6
- package/dist/strategy/WeightedStrategy.js.map +1 -1
- package/dist/strategy/WeightedStrategy.test.js +108 -18
- package/dist/strategy/WeightedStrategy.test.js.map +1 -1
- package/dist/strategy/index.d.ts +2 -0
- package/dist/strategy/index.d.ts.map +1 -1
- package/dist/strategy/index.js +2 -0
- package/dist/strategy/index.js.map +1 -1
- package/dist/test/helpers.d.ts +93 -3
- package/dist/test/helpers.d.ts.map +1 -1
- package/dist/test/helpers.js +267 -10
- package/dist/test/helpers.js.map +1 -1
- package/dist/tracking/ActionTracker.d.ts +49 -0
- package/dist/tracking/ActionTracker.d.ts.map +1 -0
- package/dist/tracking/ActionTracker.js +422 -0
- package/dist/tracking/ActionTracker.js.map +1 -0
- package/dist/tracking/ActionTracker.test.d.ts +2 -0
- package/dist/tracking/ActionTracker.test.d.ts.map +1 -0
- package/dist/tracking/ActionTracker.test.js +637 -0
- package/dist/tracking/ActionTracker.test.js.map +1 -0
- package/dist/tracking/IActionTracker.d.ts +101 -0
- package/dist/tracking/IActionTracker.d.ts.map +1 -0
- package/dist/tracking/IActionTracker.js +2 -0
- package/dist/tracking/IActionTracker.js.map +1 -0
- package/dist/tracking/InflightContextAdapter.d.ts +18 -0
- package/dist/tracking/InflightContextAdapter.d.ts.map +1 -0
- package/dist/tracking/InflightContextAdapter.js +35 -0
- package/dist/tracking/InflightContextAdapter.js.map +1 -0
- package/dist/tracking/InflightContextAdapter.test.d.ts +2 -0
- package/dist/tracking/InflightContextAdapter.test.d.ts.map +1 -0
- package/dist/tracking/InflightContextAdapter.test.js +172 -0
- package/dist/tracking/InflightContextAdapter.test.js.map +1 -0
- package/dist/tracking/index.d.ts +7 -0
- package/dist/tracking/index.d.ts.map +1 -0
- package/dist/tracking/index.js +6 -0
- package/dist/tracking/index.js.map +1 -0
- package/dist/tracking/store/IStore.d.ts +41 -0
- package/dist/tracking/store/IStore.d.ts.map +1 -0
- package/dist/tracking/store/IStore.js +2 -0
- package/dist/tracking/store/IStore.js.map +1 -0
- package/dist/tracking/store/InMemoryStore.d.ts +21 -0
- package/dist/tracking/store/InMemoryStore.d.ts.map +1 -0
- package/dist/tracking/store/InMemoryStore.js +40 -0
- package/dist/tracking/store/InMemoryStore.js.map +1 -0
- package/dist/tracking/store/InMemoryStore.test.d.ts +2 -0
- package/dist/tracking/store/InMemoryStore.test.d.ts.map +1 -0
- package/dist/tracking/store/InMemoryStore.test.js +290 -0
- package/dist/tracking/store/InMemoryStore.test.js.map +1 -0
- package/dist/tracking/store/index.d.ts +3 -0
- package/dist/tracking/store/index.d.ts.map +1 -0
- package/dist/tracking/store/index.js +2 -0
- package/dist/tracking/store/index.js.map +1 -0
- package/dist/tracking/types.d.ts +43 -0
- package/dist/tracking/types.d.ts.map +1 -0
- package/dist/tracking/types.js +2 -0
- package/dist/tracking/types.js.map +1 -0
- package/dist/utils/ExplorerClient.d.ts +39 -1
- package/dist/utils/ExplorerClient.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.js +205 -2
- package/dist/utils/ExplorerClient.js.map +1 -1
- package/dist/utils/balanceUtils.js +2 -2
- package/dist/utils/balanceUtils.js.map +1 -1
- package/dist/utils/balanceUtils.test.js +1 -0
- package/dist/utils/balanceUtils.test.js.map +1 -1
- package/dist/utils/bridgeUtils.d.ts +1 -3
- package/dist/utils/bridgeUtils.d.ts.map +1 -1
- package/dist/utils/bridgeUtils.js +1 -5
- package/dist/utils/bridgeUtils.js.map +1 -1
- package/dist/utils/bridgeUtils.test.js +3 -14
- package/dist/utils/bridgeUtils.test.js.map +1 -1
- package/package.json +11 -9
- package/src/config/RebalancerConfig.test.ts +459 -163
- package/src/config/RebalancerConfig.ts +5 -3
- package/src/config/types.ts +159 -52
- package/src/core/Rebalancer.test.ts +632 -0
- package/src/core/Rebalancer.ts +247 -157
- package/src/core/RebalancerService.test.ts +1144 -0
- package/src/core/RebalancerService.ts +245 -23
- package/src/factories/RebalancerContextFactory.ts +115 -14
- package/src/index.ts +16 -4
- package/src/interfaces/IMonitor.ts +15 -8
- package/src/interfaces/IRebalancer.ts +22 -4
- package/src/interfaces/IStrategy.ts +23 -2
- package/src/metrics/Metrics.ts +26 -5
- package/src/metrics/scripts/metrics.ts +14 -0
- package/src/monitor/Monitor.ts +109 -22
- package/src/strategy/BaseStrategy.ts +316 -26
- package/src/strategy/CollateralDeficitStrategy.test.ts +551 -0
- package/src/strategy/CollateralDeficitStrategy.ts +390 -0
- package/src/strategy/CompositeStrategy.test.ts +405 -0
- package/src/strategy/CompositeStrategy.ts +102 -0
- package/src/strategy/MinAmountStrategy.test.ts +189 -88
- package/src/strategy/MinAmountStrategy.ts +44 -13
- package/src/strategy/StrategyFactory.test.ts +2 -2
- package/src/strategy/StrategyFactory.ts +91 -8
- package/src/strategy/WeightedStrategy.test.ts +187 -72
- package/src/strategy/WeightedStrategy.ts +41 -7
- package/src/strategy/index.ts +2 -0
- package/src/test/helpers.ts +418 -14
- package/src/tracking/ActionTracker.test.ts +783 -0
- package/src/tracking/ActionTracker.ts +647 -0
- package/src/tracking/IActionTracker.ts +140 -0
- package/src/tracking/InflightContextAdapter.test.ts +203 -0
- package/src/tracking/InflightContextAdapter.ts +42 -0
- package/src/tracking/index.ts +36 -0
- package/src/tracking/store/IStore.ts +48 -0
- package/src/tracking/store/InMemoryStore.test.ts +338 -0
- package/src/tracking/store/InMemoryStore.ts +58 -0
- package/src/tracking/store/index.ts +2 -0
- package/src/tracking/types.ts +74 -0
- package/src/utils/ExplorerClient.ts +266 -3
- package/src/utils/balanceUtils.test.ts +1 -0
- package/src/utils/balanceUtils.ts +2 -2
- package/src/utils/bridgeUtils.test.ts +3 -15
- package/src/utils/bridgeUtils.ts +0 -10
- package/dist/core/WithInflightGuard.d.ts +0 -20
- package/dist/core/WithInflightGuard.d.ts.map +0 -1
- package/dist/core/WithInflightGuard.js +0 -47
- package/dist/core/WithInflightGuard.js.map +0 -1
- package/dist/core/WithInflightGuard.test.d.ts +0 -2
- package/dist/core/WithInflightGuard.test.d.ts.map +0 -1
- package/dist/core/WithInflightGuard.test.js +0 -64
- package/dist/core/WithInflightGuard.test.js.map +0 -1
- package/dist/core/WithSemaphore.d.ts +0 -22
- package/dist/core/WithSemaphore.d.ts.map +0 -1
- package/dist/core/WithSemaphore.js +0 -67
- package/dist/core/WithSemaphore.js.map +0 -1
- package/dist/core/WithSemaphore.test.d.ts +0 -2
- package/dist/core/WithSemaphore.test.d.ts.map +0 -1
- package/dist/core/WithSemaphore.test.js +0 -83
- package/dist/core/WithSemaphore.test.js.map +0 -1
- package/src/core/WithInflightGuard.test.ts +0 -131
- package/src/core/WithInflightGuard.ts +0 -67
- package/src/core/WithSemaphore.test.ts +0 -111
- package/src/core/WithSemaphore.ts +0 -92
|
@@ -8,40 +8,123 @@ import {
|
|
|
8
8
|
} from '../config/types.js';
|
|
9
9
|
import { type IStrategy } from '../interfaces/IStrategy.js';
|
|
10
10
|
import { type Metrics } from '../metrics/Metrics.js';
|
|
11
|
+
import type { BridgeConfigWithOverride } from '../utils/bridgeUtils.js';
|
|
11
12
|
|
|
13
|
+
import { CollateralDeficitStrategy } from './CollateralDeficitStrategy.js';
|
|
14
|
+
import { CompositeStrategy } from './CompositeStrategy.js';
|
|
12
15
|
import { MinAmountStrategy } from './MinAmountStrategy.js';
|
|
13
16
|
import { WeightedStrategy } from './WeightedStrategy.js';
|
|
14
17
|
|
|
15
18
|
export class StrategyFactory {
|
|
16
19
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @param
|
|
20
|
+
* Creates a strategy from an array of strategy configs.
|
|
21
|
+
* - Single strategy (array with 1 element): Creates that strategy directly
|
|
22
|
+
* - Multiple strategies (array with 2+ elements): Creates CompositeStrategy
|
|
23
|
+
*
|
|
24
|
+
* @param strategyConfigs Array of strategy configurations (always array format)
|
|
25
|
+
* @param tokensByChainName A map of chain->token to ease the lookup of token by chain
|
|
26
|
+
* @param initialTotalCollateral The initial total collateral of the rebalancer
|
|
27
|
+
* @param logger The logger to use for the strategy
|
|
28
|
+
* @param metrics The metrics to use for the strategy
|
|
22
29
|
* @returns A concrete strategy implementation
|
|
23
30
|
*/
|
|
24
31
|
static createStrategy(
|
|
32
|
+
strategyConfigs: StrategyConfig[],
|
|
33
|
+
tokensByChainName: ChainMap<Token>,
|
|
34
|
+
initialTotalCollateral: bigint,
|
|
35
|
+
logger: Logger,
|
|
36
|
+
metrics?: Metrics,
|
|
37
|
+
): IStrategy {
|
|
38
|
+
if (strategyConfigs.length === 0) {
|
|
39
|
+
throw new Error('At least one strategy must be configured');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Single strategy - create directly without CompositeStrategy wrapper
|
|
43
|
+
if (strategyConfigs.length === 1) {
|
|
44
|
+
return this.createSingleStrategy(
|
|
45
|
+
strategyConfigs[0],
|
|
46
|
+
tokensByChainName,
|
|
47
|
+
initialTotalCollateral,
|
|
48
|
+
logger,
|
|
49
|
+
metrics,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Multiple strategies - create CompositeStrategy
|
|
54
|
+
const subStrategies = strategyConfigs.map((config) =>
|
|
55
|
+
this.createSingleStrategy(
|
|
56
|
+
config,
|
|
57
|
+
tokensByChainName,
|
|
58
|
+
initialTotalCollateral,
|
|
59
|
+
logger,
|
|
60
|
+
metrics,
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
return new CompositeStrategy(subStrategies, logger);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a single strategy from config.
|
|
68
|
+
*/
|
|
69
|
+
private static createSingleStrategy(
|
|
25
70
|
strategyConfig: StrategyConfig,
|
|
26
71
|
tokensByChainName: ChainMap<Token>,
|
|
27
72
|
initialTotalCollateral: bigint,
|
|
28
73
|
logger: Logger,
|
|
29
74
|
metrics?: Metrics,
|
|
30
75
|
): IStrategy {
|
|
76
|
+
const bridgeConfigs = this.extractBridgeConfigs(strategyConfig);
|
|
77
|
+
|
|
31
78
|
switch (strategyConfig.rebalanceStrategy) {
|
|
32
|
-
case RebalancerStrategyOptions.Weighted:
|
|
33
|
-
return new WeightedStrategy(
|
|
34
|
-
|
|
79
|
+
case RebalancerStrategyOptions.Weighted: {
|
|
80
|
+
return new WeightedStrategy(
|
|
81
|
+
strategyConfig.chains,
|
|
82
|
+
logger,
|
|
83
|
+
bridgeConfigs,
|
|
84
|
+
metrics,
|
|
85
|
+
tokensByChainName,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
case RebalancerStrategyOptions.MinAmount: {
|
|
35
89
|
return new MinAmountStrategy(
|
|
36
90
|
strategyConfig.chains,
|
|
37
91
|
tokensByChainName,
|
|
38
92
|
initialTotalCollateral,
|
|
39
93
|
logger,
|
|
94
|
+
bridgeConfigs,
|
|
95
|
+
metrics,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
case RebalancerStrategyOptions.CollateralDeficit: {
|
|
99
|
+
return new CollateralDeficitStrategy(
|
|
100
|
+
strategyConfig.chains,
|
|
101
|
+
tokensByChainName,
|
|
102
|
+
logger,
|
|
103
|
+
bridgeConfigs,
|
|
40
104
|
metrics,
|
|
41
105
|
);
|
|
106
|
+
}
|
|
42
107
|
default: {
|
|
43
108
|
throw new Error('Unsupported strategy type');
|
|
44
109
|
}
|
|
45
110
|
}
|
|
46
111
|
}
|
|
112
|
+
|
|
113
|
+
private static extractBridgeConfigs(
|
|
114
|
+
strategyConfig: StrategyConfig,
|
|
115
|
+
): ChainMap<BridgeConfigWithOverride> {
|
|
116
|
+
const bridgeConfigs: ChainMap<BridgeConfigWithOverride> = {};
|
|
117
|
+
|
|
118
|
+
for (const [chain, config] of Object.entries(strategyConfig.chains)) {
|
|
119
|
+
bridgeConfigs[chain] = {
|
|
120
|
+
bridge: config.bridge,
|
|
121
|
+
bridgeMinAcceptedAmount: config.bridgeMinAcceptedAmount ?? 0,
|
|
122
|
+
override: config.override as ChainMap<
|
|
123
|
+
Partial<{ bridge: string; bridgeMinAcceptedAmount: string | number }>
|
|
124
|
+
>,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return bridgeConfigs;
|
|
129
|
+
}
|
|
47
130
|
}
|
|
@@ -2,9 +2,10 @@ import { expect } from 'chai';
|
|
|
2
2
|
import { ethers } from 'ethers';
|
|
3
3
|
import { pino } from 'pino';
|
|
4
4
|
|
|
5
|
-
import type { ChainName } from '@hyperlane-xyz/sdk';
|
|
5
|
+
import type { ChainMap, ChainName, Token } from '@hyperlane-xyz/sdk';
|
|
6
6
|
|
|
7
7
|
import type { RawBalances } from '../interfaces/IStrategy.js';
|
|
8
|
+
import { extractBridgeConfigs } from '../test/helpers.js';
|
|
8
9
|
|
|
9
10
|
import { WeightedStrategy } from './WeightedStrategy.js';
|
|
10
11
|
|
|
@@ -34,6 +35,7 @@ describe('WeightedStrategy', () => {
|
|
|
34
35
|
},
|
|
35
36
|
},
|
|
36
37
|
testLogger,
|
|
38
|
+
{},
|
|
37
39
|
),
|
|
38
40
|
).to.throw('At least two chains must be configured');
|
|
39
41
|
});
|
|
@@ -55,6 +57,7 @@ describe('WeightedStrategy', () => {
|
|
|
55
57
|
},
|
|
56
58
|
},
|
|
57
59
|
testLogger,
|
|
60
|
+
{},
|
|
58
61
|
),
|
|
59
62
|
).to.throw('Weight (-1) must not be negative for chain2');
|
|
60
63
|
});
|
|
@@ -76,6 +79,7 @@ describe('WeightedStrategy', () => {
|
|
|
76
79
|
},
|
|
77
80
|
},
|
|
78
81
|
testLogger,
|
|
82
|
+
{},
|
|
79
83
|
),
|
|
80
84
|
).to.throw('The total weight for all chains must be greater than 0');
|
|
81
85
|
});
|
|
@@ -97,6 +101,7 @@ describe('WeightedStrategy', () => {
|
|
|
97
101
|
},
|
|
98
102
|
},
|
|
99
103
|
testLogger,
|
|
104
|
+
{},
|
|
100
105
|
),
|
|
101
106
|
).to.throw('Tolerance (-1) must be between 0 and 100 for chain2');
|
|
102
107
|
|
|
@@ -116,6 +121,7 @@ describe('WeightedStrategy', () => {
|
|
|
116
121
|
},
|
|
117
122
|
},
|
|
118
123
|
testLogger,
|
|
124
|
+
{},
|
|
119
125
|
),
|
|
120
126
|
).to.throw('Tolerance (101) must be between 0 and 100 for chain2');
|
|
121
127
|
});
|
|
@@ -138,6 +144,7 @@ describe('WeightedStrategy', () => {
|
|
|
138
144
|
},
|
|
139
145
|
},
|
|
140
146
|
testLogger,
|
|
147
|
+
{},
|
|
141
148
|
).getRebalancingRoutes({
|
|
142
149
|
[chain1]: ethers.utils.parseEther('100').toBigInt(),
|
|
143
150
|
[chain2]: ethers.utils.parseEther('200').toBigInt(),
|
|
@@ -162,6 +169,7 @@ describe('WeightedStrategy', () => {
|
|
|
162
169
|
},
|
|
163
170
|
},
|
|
164
171
|
testLogger,
|
|
172
|
+
{},
|
|
165
173
|
).getRebalancingRoutes({
|
|
166
174
|
[chain1]: ethers.utils.parseEther('100').toBigInt(),
|
|
167
175
|
[chain3]: ethers.utils.parseEther('300').toBigInt(),
|
|
@@ -185,6 +193,7 @@ describe('WeightedStrategy', () => {
|
|
|
185
193
|
},
|
|
186
194
|
},
|
|
187
195
|
testLogger,
|
|
196
|
+
{},
|
|
188
197
|
).getRebalancingRoutes({
|
|
189
198
|
[chain1]: ethers.utils.parseEther('100').toBigInt(),
|
|
190
199
|
[chain2]: ethers.utils.parseEther('-200').toBigInt(),
|
|
@@ -207,6 +216,7 @@ describe('WeightedStrategy', () => {
|
|
|
207
216
|
},
|
|
208
217
|
},
|
|
209
218
|
testLogger,
|
|
219
|
+
{},
|
|
210
220
|
);
|
|
211
221
|
|
|
212
222
|
const rawBalances = {
|
|
@@ -220,21 +230,20 @@ describe('WeightedStrategy', () => {
|
|
|
220
230
|
});
|
|
221
231
|
|
|
222
232
|
it('should return a single route when a chain is unbalanced', () => {
|
|
223
|
-
const
|
|
224
|
-
{
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
bridgeLockTime: 1,
|
|
229
|
-
},
|
|
230
|
-
[chain2]: {
|
|
231
|
-
weighted: { weight: 100n, tolerance: 0n },
|
|
232
|
-
bridge: ethers.constants.AddressZero,
|
|
233
|
-
bridgeLockTime: 1,
|
|
234
|
-
},
|
|
233
|
+
const config = {
|
|
234
|
+
[chain1]: {
|
|
235
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
236
|
+
bridge: ethers.constants.AddressZero,
|
|
237
|
+
bridgeLockTime: 1,
|
|
235
238
|
},
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
[chain2]: {
|
|
240
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
241
|
+
bridge: ethers.constants.AddressZero,
|
|
242
|
+
bridgeLockTime: 1,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
const bridgeConfigs = extractBridgeConfigs(config);
|
|
246
|
+
const strategy = new WeightedStrategy(config, testLogger, bridgeConfigs);
|
|
238
247
|
|
|
239
248
|
const rawBalances = {
|
|
240
249
|
[chain1]: ethers.utils.parseEther('100').toBigInt(),
|
|
@@ -248,6 +257,7 @@ describe('WeightedStrategy', () => {
|
|
|
248
257
|
origin: chain2,
|
|
249
258
|
destination: chain1,
|
|
250
259
|
amount: ethers.utils.parseEther('50').toBigInt(),
|
|
260
|
+
bridge: ethers.constants.AddressZero,
|
|
251
261
|
},
|
|
252
262
|
]);
|
|
253
263
|
});
|
|
@@ -267,6 +277,7 @@ describe('WeightedStrategy', () => {
|
|
|
267
277
|
},
|
|
268
278
|
},
|
|
269
279
|
testLogger,
|
|
280
|
+
{},
|
|
270
281
|
);
|
|
271
282
|
|
|
272
283
|
const rawBalances = {
|
|
@@ -280,26 +291,25 @@ describe('WeightedStrategy', () => {
|
|
|
280
291
|
});
|
|
281
292
|
|
|
282
293
|
it('should return a single route when two chains are unbalanced and can be solved with a single transfer', () => {
|
|
283
|
-
const
|
|
284
|
-
{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
bridgeLockTime: 1,
|
|
289
|
-
},
|
|
290
|
-
[chain2]: {
|
|
291
|
-
weighted: { weight: 100n, tolerance: 0n },
|
|
292
|
-
bridge: ethers.constants.AddressZero,
|
|
293
|
-
bridgeLockTime: 1,
|
|
294
|
-
},
|
|
295
|
-
[chain3]: {
|
|
296
|
-
weighted: { weight: 100n, tolerance: 0n },
|
|
297
|
-
bridge: ethers.constants.AddressZero,
|
|
298
|
-
bridgeLockTime: 1,
|
|
299
|
-
},
|
|
294
|
+
const config = {
|
|
295
|
+
[chain1]: {
|
|
296
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
297
|
+
bridge: ethers.constants.AddressZero,
|
|
298
|
+
bridgeLockTime: 1,
|
|
300
299
|
},
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
[chain2]: {
|
|
301
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
302
|
+
bridge: ethers.constants.AddressZero,
|
|
303
|
+
bridgeLockTime: 1,
|
|
304
|
+
},
|
|
305
|
+
[chain3]: {
|
|
306
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
307
|
+
bridge: ethers.constants.AddressZero,
|
|
308
|
+
bridgeLockTime: 1,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
const bridgeConfigs = extractBridgeConfigs(config);
|
|
312
|
+
const strategy = new WeightedStrategy(config, testLogger, bridgeConfigs);
|
|
303
313
|
|
|
304
314
|
const rawBalances = {
|
|
305
315
|
[chain1]: ethers.utils.parseEther('100').toBigInt(),
|
|
@@ -314,30 +324,30 @@ describe('WeightedStrategy', () => {
|
|
|
314
324
|
origin: chain3,
|
|
315
325
|
destination: chain1,
|
|
316
326
|
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
327
|
+
bridge: ethers.constants.AddressZero,
|
|
317
328
|
},
|
|
318
329
|
]);
|
|
319
330
|
});
|
|
320
331
|
it('should return two routes when two chains are unbalanced and cannot be solved with a single transfer', () => {
|
|
321
|
-
const
|
|
322
|
-
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
bridgeLockTime: 1,
|
|
327
|
-
},
|
|
328
|
-
[chain2]: {
|
|
329
|
-
weighted: { weight: 100n, tolerance: 0n },
|
|
330
|
-
bridge: ethers.constants.AddressZero,
|
|
331
|
-
bridgeLockTime: 1,
|
|
332
|
-
},
|
|
333
|
-
[chain3]: {
|
|
334
|
-
weighted: { weight: 100n, tolerance: 0n },
|
|
335
|
-
bridge: ethers.constants.AddressZero,
|
|
336
|
-
bridgeLockTime: 1,
|
|
337
|
-
},
|
|
332
|
+
const config = {
|
|
333
|
+
[chain1]: {
|
|
334
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
335
|
+
bridge: ethers.constants.AddressZero,
|
|
336
|
+
bridgeLockTime: 1,
|
|
338
337
|
},
|
|
339
|
-
|
|
340
|
-
|
|
338
|
+
[chain2]: {
|
|
339
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
340
|
+
bridge: ethers.constants.AddressZero,
|
|
341
|
+
bridgeLockTime: 1,
|
|
342
|
+
},
|
|
343
|
+
[chain3]: {
|
|
344
|
+
weighted: { weight: 100n, tolerance: 0n },
|
|
345
|
+
bridge: ethers.constants.AddressZero,
|
|
346
|
+
bridgeLockTime: 1,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
const bridgeConfigs = extractBridgeConfigs(config);
|
|
350
|
+
const strategy = new WeightedStrategy(config, testLogger, bridgeConfigs);
|
|
341
351
|
|
|
342
352
|
const rawBalances = {
|
|
343
353
|
[chain1]: ethers.utils.parseEther('100').toBigInt(),
|
|
@@ -352,36 +362,37 @@ describe('WeightedStrategy', () => {
|
|
|
352
362
|
origin: chain3,
|
|
353
363
|
destination: chain1,
|
|
354
364
|
amount: 133333333333333333333n,
|
|
365
|
+
bridge: ethers.constants.AddressZero,
|
|
355
366
|
},
|
|
356
367
|
{
|
|
357
368
|
origin: chain3,
|
|
358
369
|
destination: chain2,
|
|
359
370
|
amount: 133333333333333333333n,
|
|
371
|
+
bridge: ethers.constants.AddressZero,
|
|
360
372
|
},
|
|
361
373
|
]);
|
|
362
374
|
});
|
|
363
375
|
|
|
364
376
|
it('should return routes to balance different weighted chains', () => {
|
|
365
|
-
const
|
|
366
|
-
{
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
bridgeLockTime: 1,
|
|
371
|
-
},
|
|
372
|
-
[chain2]: {
|
|
373
|
-
weighted: { weight: 25n, tolerance: 0n },
|
|
374
|
-
bridge: ethers.constants.AddressZero,
|
|
375
|
-
bridgeLockTime: 1,
|
|
376
|
-
},
|
|
377
|
-
[chain3]: {
|
|
378
|
-
weighted: { weight: 25n, tolerance: 0n },
|
|
379
|
-
bridge: ethers.constants.AddressZero,
|
|
380
|
-
bridgeLockTime: 1,
|
|
381
|
-
},
|
|
377
|
+
const config = {
|
|
378
|
+
[chain1]: {
|
|
379
|
+
weighted: { weight: 50n, tolerance: 0n },
|
|
380
|
+
bridge: ethers.constants.AddressZero,
|
|
381
|
+
bridgeLockTime: 1,
|
|
382
382
|
},
|
|
383
|
-
|
|
384
|
-
|
|
383
|
+
[chain2]: {
|
|
384
|
+
weighted: { weight: 25n, tolerance: 0n },
|
|
385
|
+
bridge: ethers.constants.AddressZero,
|
|
386
|
+
bridgeLockTime: 1,
|
|
387
|
+
},
|
|
388
|
+
[chain3]: {
|
|
389
|
+
weighted: { weight: 25n, tolerance: 0n },
|
|
390
|
+
bridge: ethers.constants.AddressZero,
|
|
391
|
+
bridgeLockTime: 1,
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
const bridgeConfigs = extractBridgeConfigs(config);
|
|
395
|
+
const strategy = new WeightedStrategy(config, testLogger, bridgeConfigs);
|
|
385
396
|
|
|
386
397
|
const rawBalances = {
|
|
387
398
|
[chain1]: ethers.utils.parseEther('100').toBigInt(),
|
|
@@ -396,13 +407,117 @@ describe('WeightedStrategy', () => {
|
|
|
396
407
|
origin: chain2,
|
|
397
408
|
destination: chain1,
|
|
398
409
|
amount: ethers.utils.parseEther('25').toBigInt(),
|
|
410
|
+
bridge: ethers.constants.AddressZero,
|
|
399
411
|
},
|
|
400
412
|
{
|
|
401
413
|
origin: chain3,
|
|
402
414
|
destination: chain1,
|
|
403
415
|
amount: ethers.utils.parseEther('25').toBigInt(),
|
|
416
|
+
bridge: ethers.constants.AddressZero,
|
|
404
417
|
},
|
|
405
418
|
]);
|
|
406
419
|
});
|
|
407
420
|
});
|
|
421
|
+
|
|
422
|
+
describe('bridgeMinAcceptedAmount filtering', () => {
|
|
423
|
+
function createMockToken(chainName: string, decimals = 18): Token {
|
|
424
|
+
return {
|
|
425
|
+
chainName,
|
|
426
|
+
decimals,
|
|
427
|
+
addressOrDenom: ethers.constants.AddressZero,
|
|
428
|
+
} as unknown as Token;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
it('should filter out routes below bridgeMinAcceptedAmount', () => {
|
|
432
|
+
const chain1 = 'chain1';
|
|
433
|
+
const chain2 = 'chain2';
|
|
434
|
+
|
|
435
|
+
const tokensByChainName: ChainMap<Token> = {
|
|
436
|
+
[chain1]: createMockToken(chain1),
|
|
437
|
+
[chain2]: createMockToken(chain2),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const config = {
|
|
441
|
+
[chain1]: {
|
|
442
|
+
weighted: { weight: 50n, tolerance: 0n },
|
|
443
|
+
bridge: ethers.constants.AddressZero,
|
|
444
|
+
bridgeLockTime: 1,
|
|
445
|
+
bridgeMinAcceptedAmount: '100', // 100 tokens minimum
|
|
446
|
+
},
|
|
447
|
+
[chain2]: {
|
|
448
|
+
weighted: { weight: 50n, tolerance: 0n },
|
|
449
|
+
bridge: ethers.constants.AddressZero,
|
|
450
|
+
bridgeLockTime: 1,
|
|
451
|
+
bridgeMinAcceptedAmount: '100',
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
const bridgeConfigs = extractBridgeConfigs(config);
|
|
455
|
+
const strategy = new WeightedStrategy(
|
|
456
|
+
config,
|
|
457
|
+
testLogger,
|
|
458
|
+
bridgeConfigs,
|
|
459
|
+
undefined,
|
|
460
|
+
tokensByChainName,
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
// chain1 has 150, chain2 has 50 (total 200, each should have 100)
|
|
464
|
+
// Would generate route: chain1 -> chain2, amount = 50
|
|
465
|
+
// But 50 < bridgeMinAcceptedAmount (100), so route should be filtered
|
|
466
|
+
const rawBalances: RawBalances = {
|
|
467
|
+
[chain1]: ethers.utils.parseEther('150').toBigInt(),
|
|
468
|
+
[chain2]: ethers.utils.parseEther('50').toBigInt(),
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const routes = strategy.getRebalancingRoutes(rawBalances);
|
|
472
|
+
|
|
473
|
+
expect(routes).to.have.lengthOf(0);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should keep routes at or above bridgeMinAcceptedAmount', () => {
|
|
477
|
+
const chain1 = 'chain1';
|
|
478
|
+
const chain2 = 'chain2';
|
|
479
|
+
|
|
480
|
+
const tokensByChainName: ChainMap<Token> = {
|
|
481
|
+
[chain1]: createMockToken(chain1),
|
|
482
|
+
[chain2]: createMockToken(chain2),
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const config = {
|
|
486
|
+
[chain1]: {
|
|
487
|
+
weighted: { weight: 50n, tolerance: 0n },
|
|
488
|
+
bridge: ethers.constants.AddressZero,
|
|
489
|
+
bridgeLockTime: 1,
|
|
490
|
+
bridgeMinAcceptedAmount: '50', // 50 tokens minimum
|
|
491
|
+
},
|
|
492
|
+
[chain2]: {
|
|
493
|
+
weighted: { weight: 50n, tolerance: 0n },
|
|
494
|
+
bridge: ethers.constants.AddressZero,
|
|
495
|
+
bridgeLockTime: 1,
|
|
496
|
+
bridgeMinAcceptedAmount: '50',
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
const bridgeConfigs = extractBridgeConfigs(config);
|
|
500
|
+
const strategy = new WeightedStrategy(
|
|
501
|
+
config,
|
|
502
|
+
testLogger,
|
|
503
|
+
bridgeConfigs,
|
|
504
|
+
undefined,
|
|
505
|
+
tokensByChainName,
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
// chain1 has 200, chain2 has 100 (total 300, each should have 150)
|
|
509
|
+
// Route: chain1 -> chain2, amount = 50 (equals minAcceptedAmount)
|
|
510
|
+
const rawBalances: RawBalances = {
|
|
511
|
+
[chain1]: ethers.utils.parseEther('200').toBigInt(),
|
|
512
|
+
[chain2]: ethers.utils.parseEther('100').toBigInt(),
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const routes = strategy.getRebalancingRoutes(rawBalances);
|
|
516
|
+
|
|
517
|
+
expect(routes).to.have.lengthOf(1);
|
|
518
|
+
expect(routes[0].amount).to.equal(
|
|
519
|
+
ethers.utils.parseEther('50').toBigInt(),
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
408
523
|
});
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { type Logger } from 'pino';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type { ChainMap, Token } from '@hyperlane-xyz/sdk';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
RebalancerStrategyOptions,
|
|
7
|
+
type WeightedStrategyConfig,
|
|
8
|
+
} from '../config/types.js';
|
|
9
|
+
import type {
|
|
10
|
+
RawBalances,
|
|
11
|
+
Route,
|
|
12
|
+
StrategyRoute,
|
|
13
|
+
} from '../interfaces/IStrategy.js';
|
|
5
14
|
import { type Metrics } from '../metrics/Metrics.js';
|
|
15
|
+
import type { BridgeConfigWithOverride } from '../utils/bridgeUtils.js';
|
|
6
16
|
|
|
7
17
|
import { BaseStrategy, type Delta } from './BaseStrategy.js';
|
|
8
18
|
|
|
@@ -11,6 +21,7 @@ import { BaseStrategy, type Delta } from './BaseStrategy.js';
|
|
|
11
21
|
* It distributes funds across chains based on their weights
|
|
12
22
|
*/
|
|
13
23
|
export class WeightedStrategy extends BaseStrategy {
|
|
24
|
+
readonly name = RebalancerStrategyOptions.Weighted;
|
|
14
25
|
private readonly config: WeightedStrategyConfig;
|
|
15
26
|
private readonly totalWeight: bigint;
|
|
16
27
|
protected readonly logger: Logger;
|
|
@@ -18,11 +29,13 @@ export class WeightedStrategy extends BaseStrategy {
|
|
|
18
29
|
constructor(
|
|
19
30
|
config: WeightedStrategyConfig,
|
|
20
31
|
logger: Logger,
|
|
32
|
+
bridgeConfigs: ChainMap<BridgeConfigWithOverride>,
|
|
21
33
|
metrics?: Metrics,
|
|
34
|
+
tokensByChainName?: ChainMap<Token>,
|
|
22
35
|
) {
|
|
23
36
|
const chains = Object.keys(config);
|
|
24
37
|
const log = logger.child({ class: WeightedStrategy.name });
|
|
25
|
-
super(chains, log, metrics);
|
|
38
|
+
super(chains, log, bridgeConfigs, metrics, tokensByChainName);
|
|
26
39
|
this.logger = log;
|
|
27
40
|
|
|
28
41
|
let totalWeight = 0n;
|
|
@@ -54,14 +67,35 @@ export class WeightedStrategy extends BaseStrategy {
|
|
|
54
67
|
|
|
55
68
|
/**
|
|
56
69
|
* Gets balances categorized by surplus and deficit based on weights
|
|
70
|
+
*
|
|
71
|
+
* Simulates both types of rebalances before calculating surpluses/deficits:
|
|
72
|
+
* - pendingRebalances: in-flight intents (origin tx confirmed, add to destination only)
|
|
73
|
+
* - proposedRebalances: routes from earlier strategies (subtract from origin AND add to destination)
|
|
74
|
+
*
|
|
75
|
+
* This prevents over-rebalancing when multiple strategies run in sequence.
|
|
57
76
|
*/
|
|
58
|
-
protected getCategorizedBalances(
|
|
77
|
+
protected getCategorizedBalances(
|
|
78
|
+
rawBalances: RawBalances,
|
|
79
|
+
pendingRebalances?: Route[],
|
|
80
|
+
proposedRebalances?: StrategyRoute[],
|
|
81
|
+
): {
|
|
59
82
|
surpluses: Delta[];
|
|
60
83
|
deficits: Delta[];
|
|
61
84
|
} {
|
|
62
|
-
//
|
|
85
|
+
// Step 1: Simulate pending rebalances (in-flight, origin already deducted on-chain)
|
|
86
|
+
let simulatedBalances = this.simulatePendingRebalances(
|
|
87
|
+
rawBalances,
|
|
88
|
+
pendingRebalances ?? [],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Step 2: Simulate proposed rebalances (from earlier strategies, not yet executed)
|
|
92
|
+
simulatedBalances = this.simulateProposedRebalances(
|
|
93
|
+
simulatedBalances,
|
|
94
|
+
proposedRebalances ?? [],
|
|
95
|
+
);
|
|
96
|
+
// Get the total balance from all chains (using simulated balances)
|
|
63
97
|
const total = this.chains.reduce(
|
|
64
|
-
(sum, chain) => sum +
|
|
98
|
+
(sum, chain) => sum + simulatedBalances[chain],
|
|
65
99
|
0n,
|
|
66
100
|
);
|
|
67
101
|
|
|
@@ -70,7 +104,7 @@ export class WeightedStrategy extends BaseStrategy {
|
|
|
70
104
|
const { weight, tolerance } = this.config[chain].weighted;
|
|
71
105
|
const target = (total * weight) / this.totalWeight;
|
|
72
106
|
const toleranceAmount = (target * tolerance) / 100n;
|
|
73
|
-
const balance =
|
|
107
|
+
const balance = simulatedBalances[chain];
|
|
74
108
|
|
|
75
109
|
// Apply the tolerance to deficits to prevent small imbalances
|
|
76
110
|
if (balance < target - toleranceAmount) {
|
package/src/strategy/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { BaseStrategy } from './BaseStrategy.js';
|
|
2
|
+
export { CollateralDeficitStrategy } from './CollateralDeficitStrategy.js';
|
|
3
|
+
export { CompositeStrategy } from './CompositeStrategy.js';
|
|
2
4
|
export { MinAmountStrategy } from './MinAmountStrategy.js';
|
|
3
5
|
export { StrategyFactory } from './StrategyFactory.js';
|
|
4
6
|
export { WeightedStrategy } from './WeightedStrategy.js';
|