@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
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import chai, { expect } from 'chai';
|
|
2
|
+
import chaiAsPromised from 'chai-as-promised';
|
|
3
|
+
import { ethers } from 'ethers';
|
|
4
|
+
import { pino } from 'pino';
|
|
5
|
+
import Sinon from 'sinon';
|
|
6
|
+
|
|
7
|
+
import { HyperlaneCore } from '@hyperlane-xyz/sdk';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
buildTestRebalanceRoute,
|
|
11
|
+
createRebalancerTestContext,
|
|
12
|
+
} from '../test/helpers.js';
|
|
13
|
+
|
|
14
|
+
import { Rebalancer } from './Rebalancer.js';
|
|
15
|
+
|
|
16
|
+
chai.use(chaiAsPromised);
|
|
17
|
+
|
|
18
|
+
const testLogger = pino({ level: 'silent' });
|
|
19
|
+
|
|
20
|
+
describe('Rebalancer', () => {
|
|
21
|
+
let sandbox: Sinon.SinonSandbox;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
sandbox = Sinon.createSandbox();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
sandbox.restore();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('rebalance()', () => {
|
|
32
|
+
it('should return empty array for empty routes', async () => {
|
|
33
|
+
const ctx = createRebalancerTestContext();
|
|
34
|
+
const rebalancer = new Rebalancer(
|
|
35
|
+
ctx.warpCore,
|
|
36
|
+
ctx.chainMetadata,
|
|
37
|
+
ctx.tokensByChainName,
|
|
38
|
+
ctx.multiProvider as any,
|
|
39
|
+
testLogger,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const results = await rebalancer.rebalance([]);
|
|
43
|
+
|
|
44
|
+
expect(results).to.deep.equal([]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return success result for single valid route', async () => {
|
|
48
|
+
const ctx = createRebalancerTestContext();
|
|
49
|
+
|
|
50
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
51
|
+
{
|
|
52
|
+
id: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
53
|
+
} as any,
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const rebalancer = new Rebalancer(
|
|
57
|
+
ctx.warpCore,
|
|
58
|
+
ctx.chainMetadata,
|
|
59
|
+
ctx.tokensByChainName,
|
|
60
|
+
ctx.multiProvider as any,
|
|
61
|
+
testLogger,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const route = buildTestRebalanceRoute({
|
|
65
|
+
origin: 'ethereum',
|
|
66
|
+
destination: 'arbitrum',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const results = await rebalancer.rebalance([route]);
|
|
70
|
+
|
|
71
|
+
expect(results).to.have.lengthOf(1);
|
|
72
|
+
expect(results[0].success).to.be.true;
|
|
73
|
+
expect(results[0].route).to.deep.equal(route);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return failure results for routes that fail preparation', async () => {
|
|
77
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum'], {
|
|
78
|
+
ethereum: { isRebalancer: false },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const rebalancer = new Rebalancer(
|
|
82
|
+
ctx.warpCore,
|
|
83
|
+
ctx.chainMetadata,
|
|
84
|
+
ctx.tokensByChainName,
|
|
85
|
+
ctx.multiProvider as any,
|
|
86
|
+
testLogger,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const route = buildTestRebalanceRoute();
|
|
90
|
+
const results = await rebalancer.rebalance([route]);
|
|
91
|
+
|
|
92
|
+
expect(results).to.have.lengthOf(1);
|
|
93
|
+
expect(results[0].success).to.be.false;
|
|
94
|
+
expect(results[0].route).to.deep.equal(route);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should handle mixed success and failure results', async () => {
|
|
98
|
+
const ctx = createRebalancerTestContext(
|
|
99
|
+
['ethereum', 'arbitrum', 'optimism'],
|
|
100
|
+
{
|
|
101
|
+
ethereum: { isRebalancer: true },
|
|
102
|
+
optimism: { isRebalancer: false },
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
107
|
+
{
|
|
108
|
+
id: '0xMessageId111111111111111111111111111111111111111111111111111111',
|
|
109
|
+
} as any,
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const rebalancer = new Rebalancer(
|
|
113
|
+
ctx.warpCore,
|
|
114
|
+
{
|
|
115
|
+
...ctx.chainMetadata,
|
|
116
|
+
optimism: {
|
|
117
|
+
...ctx.chainMetadata.ethereum,
|
|
118
|
+
name: 'optimism',
|
|
119
|
+
domainId: 10,
|
|
120
|
+
} as any,
|
|
121
|
+
},
|
|
122
|
+
ctx.tokensByChainName,
|
|
123
|
+
ctx.multiProvider as any,
|
|
124
|
+
testLogger,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const routes = [
|
|
128
|
+
buildTestRebalanceRoute({
|
|
129
|
+
origin: 'ethereum',
|
|
130
|
+
destination: 'arbitrum',
|
|
131
|
+
}),
|
|
132
|
+
buildTestRebalanceRoute({
|
|
133
|
+
origin: 'optimism',
|
|
134
|
+
destination: 'arbitrum',
|
|
135
|
+
}),
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const results = await rebalancer.rebalance(routes);
|
|
139
|
+
|
|
140
|
+
expect(results).to.have.lengthOf(2);
|
|
141
|
+
const successResults = results.filter((r) => r.success);
|
|
142
|
+
const failureResults = results.filter((r) => !r.success);
|
|
143
|
+
expect(successResults).to.have.lengthOf(1);
|
|
144
|
+
expect(failureResults).to.have.lengthOf(1);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('validateRoute()', () => {
|
|
149
|
+
it('should fail when origin token not found', async () => {
|
|
150
|
+
const ctx = createRebalancerTestContext(['arbitrum']);
|
|
151
|
+
|
|
152
|
+
const rebalancer = new Rebalancer(
|
|
153
|
+
ctx.warpCore,
|
|
154
|
+
ctx.chainMetadata,
|
|
155
|
+
ctx.tokensByChainName,
|
|
156
|
+
ctx.multiProvider as any,
|
|
157
|
+
testLogger,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const route = buildTestRebalanceRoute({
|
|
161
|
+
origin: 'ethereum',
|
|
162
|
+
destination: 'arbitrum',
|
|
163
|
+
});
|
|
164
|
+
const results = await rebalancer.rebalance([route]);
|
|
165
|
+
|
|
166
|
+
expect(results).to.have.lengthOf(1);
|
|
167
|
+
expect(results[0].success).to.be.false;
|
|
168
|
+
expect(results[0].error).to.include('null');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should fail when destination token not found', async () => {
|
|
172
|
+
const ctx = createRebalancerTestContext(['ethereum']);
|
|
173
|
+
|
|
174
|
+
const rebalancer = new Rebalancer(
|
|
175
|
+
ctx.warpCore,
|
|
176
|
+
ctx.chainMetadata,
|
|
177
|
+
ctx.tokensByChainName,
|
|
178
|
+
ctx.multiProvider as any,
|
|
179
|
+
testLogger,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const route = buildTestRebalanceRoute({
|
|
183
|
+
origin: 'ethereum',
|
|
184
|
+
destination: 'arbitrum',
|
|
185
|
+
});
|
|
186
|
+
const results = await rebalancer.rebalance([route]);
|
|
187
|
+
|
|
188
|
+
expect(results).to.have.lengthOf(1);
|
|
189
|
+
expect(results[0].success).to.be.false;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should fail when signer is not a rebalancer', async () => {
|
|
193
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum'], {
|
|
194
|
+
ethereum: { isRebalancer: false },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const rebalancer = new Rebalancer(
|
|
198
|
+
ctx.warpCore,
|
|
199
|
+
ctx.chainMetadata,
|
|
200
|
+
ctx.tokensByChainName,
|
|
201
|
+
ctx.multiProvider as any,
|
|
202
|
+
testLogger,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const route = buildTestRebalanceRoute();
|
|
206
|
+
const results = await rebalancer.rebalance([route]);
|
|
207
|
+
|
|
208
|
+
expect(results).to.have.lengthOf(1);
|
|
209
|
+
expect(results[0].success).to.be.false;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should fail when destination is not in allowed list', async () => {
|
|
213
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum'], {
|
|
214
|
+
ethereum: {
|
|
215
|
+
allowedDestination: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const rebalancer = new Rebalancer(
|
|
220
|
+
ctx.warpCore,
|
|
221
|
+
ctx.chainMetadata,
|
|
222
|
+
ctx.tokensByChainName,
|
|
223
|
+
ctx.multiProvider as any,
|
|
224
|
+
testLogger,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const route = buildTestRebalanceRoute();
|
|
228
|
+
const results = await rebalancer.rebalance([route]);
|
|
229
|
+
|
|
230
|
+
expect(results).to.have.lengthOf(1);
|
|
231
|
+
expect(results[0].success).to.be.false;
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should fail when bridge is not allowed', async () => {
|
|
235
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum'], {
|
|
236
|
+
ethereum: { isBridgeAllowed: false },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const rebalancer = new Rebalancer(
|
|
240
|
+
ctx.warpCore,
|
|
241
|
+
ctx.chainMetadata,
|
|
242
|
+
ctx.tokensByChainName,
|
|
243
|
+
ctx.multiProvider as any,
|
|
244
|
+
testLogger,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const route = buildTestRebalanceRoute();
|
|
248
|
+
const results = await rebalancer.rebalance([route]);
|
|
249
|
+
|
|
250
|
+
expect(results).to.have.lengthOf(1);
|
|
251
|
+
expect(results[0].success).to.be.false;
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('prepareTransactions()', () => {
|
|
256
|
+
it('should create failure result when quote fetching throws', async () => {
|
|
257
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum'], {
|
|
258
|
+
ethereum: { throwOnQuotes: new Error('Quote fetch failed') },
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const rebalancer = new Rebalancer(
|
|
262
|
+
ctx.warpCore,
|
|
263
|
+
ctx.chainMetadata,
|
|
264
|
+
ctx.tokensByChainName,
|
|
265
|
+
ctx.multiProvider as any,
|
|
266
|
+
testLogger,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const route = buildTestRebalanceRoute();
|
|
270
|
+
const results = await rebalancer.rebalance([route]);
|
|
271
|
+
|
|
272
|
+
expect(results).to.have.lengthOf(1);
|
|
273
|
+
expect(results[0].success).to.be.false;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should create failure result when tx population throws', async () => {
|
|
277
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum'], {
|
|
278
|
+
ethereum: { throwOnPopulate: new Error('Populate failed') },
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const rebalancer = new Rebalancer(
|
|
282
|
+
ctx.warpCore,
|
|
283
|
+
ctx.chainMetadata,
|
|
284
|
+
ctx.tokensByChainName,
|
|
285
|
+
ctx.multiProvider as any,
|
|
286
|
+
testLogger,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const route = buildTestRebalanceRoute();
|
|
290
|
+
const results = await rebalancer.rebalance([route]);
|
|
291
|
+
|
|
292
|
+
expect(results).to.have.lengthOf(1);
|
|
293
|
+
expect(results[0].success).to.be.false;
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('executeTransactions()', () => {
|
|
298
|
+
it('should create failure result when gas estimation fails', async () => {
|
|
299
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum']);
|
|
300
|
+
ctx.multiProvider.estimateGas = Sinon.stub().rejects(
|
|
301
|
+
new Error('Gas estimation failed'),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
305
|
+
{
|
|
306
|
+
id: '0xMessageId111111111111111111111111111111111111111111111111111111',
|
|
307
|
+
} as any,
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
const rebalancer = new Rebalancer(
|
|
311
|
+
ctx.warpCore,
|
|
312
|
+
ctx.chainMetadata,
|
|
313
|
+
ctx.tokensByChainName,
|
|
314
|
+
ctx.multiProvider as any,
|
|
315
|
+
testLogger,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const route = buildTestRebalanceRoute();
|
|
319
|
+
const results = await rebalancer.rebalance([route]);
|
|
320
|
+
|
|
321
|
+
expect(results).to.have.lengthOf(1);
|
|
322
|
+
expect(results[0].success).to.be.false;
|
|
323
|
+
expect(results[0].error).to.include('Gas estimation failed');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should continue with other routes when one fails gas estimation', async () => {
|
|
327
|
+
const ctx = createRebalancerTestContext([
|
|
328
|
+
'ethereum',
|
|
329
|
+
'arbitrum',
|
|
330
|
+
'optimism',
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
let callCount = 0;
|
|
334
|
+
ctx.multiProvider.estimateGas = Sinon.stub().callsFake(() => {
|
|
335
|
+
callCount++;
|
|
336
|
+
if (callCount === 1) {
|
|
337
|
+
return Promise.reject(new Error('Gas estimation failed'));
|
|
338
|
+
}
|
|
339
|
+
return Promise.resolve(ethers.BigNumber.from(100000));
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
343
|
+
{
|
|
344
|
+
id: '0xMessageId111111111111111111111111111111111111111111111111111111',
|
|
345
|
+
} as any,
|
|
346
|
+
]);
|
|
347
|
+
|
|
348
|
+
const rebalancer = new Rebalancer(
|
|
349
|
+
ctx.warpCore,
|
|
350
|
+
{
|
|
351
|
+
...ctx.chainMetadata,
|
|
352
|
+
optimism: {
|
|
353
|
+
...ctx.chainMetadata.ethereum,
|
|
354
|
+
name: 'optimism',
|
|
355
|
+
domainId: 10,
|
|
356
|
+
} as any,
|
|
357
|
+
},
|
|
358
|
+
{ ...ctx.tokensByChainName, optimism: ctx.tokensByChainName.ethereum },
|
|
359
|
+
ctx.multiProvider as any,
|
|
360
|
+
testLogger,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const routes = [
|
|
364
|
+
buildTestRebalanceRoute({
|
|
365
|
+
origin: 'ethereum',
|
|
366
|
+
destination: 'arbitrum',
|
|
367
|
+
}),
|
|
368
|
+
buildTestRebalanceRoute({
|
|
369
|
+
origin: 'optimism',
|
|
370
|
+
destination: 'arbitrum',
|
|
371
|
+
}),
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
const results = await rebalancer.rebalance(routes);
|
|
375
|
+
|
|
376
|
+
expect(results).to.have.lengthOf(2);
|
|
377
|
+
const failures = results.filter((r) => !r.success);
|
|
378
|
+
const successes = results.filter((r) => r.success);
|
|
379
|
+
expect(failures).to.have.lengthOf(1);
|
|
380
|
+
expect(successes).to.have.lengthOf(1);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should group transactions by origin chain', async () => {
|
|
384
|
+
const ctx = createRebalancerTestContext([
|
|
385
|
+
'ethereum',
|
|
386
|
+
'arbitrum',
|
|
387
|
+
'optimism',
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
let sendCallCount = 0;
|
|
391
|
+
(ctx.multiProvider.sendTransaction as Sinon.SinonStub).callsFake(() => {
|
|
392
|
+
sendCallCount++;
|
|
393
|
+
return Promise.resolve({
|
|
394
|
+
transactionHash: `0x${sendCallCount.toString().padStart(64, '0')}`,
|
|
395
|
+
blockNumber: 100,
|
|
396
|
+
status: 1,
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
401
|
+
{
|
|
402
|
+
id: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
403
|
+
} as any,
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
const rebalancer = new Rebalancer(
|
|
407
|
+
ctx.warpCore,
|
|
408
|
+
ctx.chainMetadata,
|
|
409
|
+
ctx.tokensByChainName,
|
|
410
|
+
ctx.multiProvider as any,
|
|
411
|
+
testLogger,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
const routes = [
|
|
415
|
+
buildTestRebalanceRoute({
|
|
416
|
+
origin: 'ethereum',
|
|
417
|
+
destination: 'arbitrum',
|
|
418
|
+
}),
|
|
419
|
+
buildTestRebalanceRoute({
|
|
420
|
+
origin: 'ethereum',
|
|
421
|
+
destination: 'optimism',
|
|
422
|
+
}),
|
|
423
|
+
buildTestRebalanceRoute({
|
|
424
|
+
origin: 'optimism',
|
|
425
|
+
destination: 'arbitrum',
|
|
426
|
+
}),
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
await rebalancer.rebalance(routes);
|
|
430
|
+
|
|
431
|
+
expect(sendCallCount).to.equal(3);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe('sendTransactionsForChain()', () => {
|
|
436
|
+
it('should return error result when send fails', async () => {
|
|
437
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum']);
|
|
438
|
+
ctx.multiProvider.sendTransaction = Sinon.stub().rejects(
|
|
439
|
+
new Error('Send failed'),
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const rebalancer = new Rebalancer(
|
|
443
|
+
ctx.warpCore,
|
|
444
|
+
ctx.chainMetadata,
|
|
445
|
+
ctx.tokensByChainName,
|
|
446
|
+
ctx.multiProvider as any,
|
|
447
|
+
testLogger,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const route = buildTestRebalanceRoute();
|
|
451
|
+
const results = await rebalancer.rebalance([route]);
|
|
452
|
+
|
|
453
|
+
expect(results).to.have.lengthOf(1);
|
|
454
|
+
expect(results[0].success).to.be.false;
|
|
455
|
+
expect(results[0].error).to.include('Send failed');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should continue sending remaining transactions after one fails', async () => {
|
|
459
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum']);
|
|
460
|
+
|
|
461
|
+
let callCount = 0;
|
|
462
|
+
ctx.multiProvider.sendTransaction = Sinon.stub().callsFake(() => {
|
|
463
|
+
callCount++;
|
|
464
|
+
if (callCount === 1) {
|
|
465
|
+
return Promise.reject(new Error('First send failed'));
|
|
466
|
+
}
|
|
467
|
+
return Promise.resolve({
|
|
468
|
+
transactionHash:
|
|
469
|
+
'0xTxHash2222222222222222222222222222222222222222222222222222222222',
|
|
470
|
+
blockNumber: 100,
|
|
471
|
+
status: 1,
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
476
|
+
{
|
|
477
|
+
id: '0xMessageId111111111111111111111111111111111111111111111111111111',
|
|
478
|
+
} as any,
|
|
479
|
+
]);
|
|
480
|
+
|
|
481
|
+
const rebalancer = new Rebalancer(
|
|
482
|
+
ctx.warpCore,
|
|
483
|
+
ctx.chainMetadata,
|
|
484
|
+
ctx.tokensByChainName,
|
|
485
|
+
ctx.multiProvider as any,
|
|
486
|
+
testLogger,
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const routes = [
|
|
490
|
+
buildTestRebalanceRoute({
|
|
491
|
+
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
492
|
+
}),
|
|
493
|
+
buildTestRebalanceRoute({
|
|
494
|
+
amount: ethers.utils.parseEther('200').toBigInt(),
|
|
495
|
+
}),
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
const results = await rebalancer.rebalance(routes);
|
|
499
|
+
|
|
500
|
+
expect(results).to.have.lengthOf(2);
|
|
501
|
+
expect(results.filter((r) => !r.success)).to.have.lengthOf(1);
|
|
502
|
+
expect(results.filter((r) => r.success)).to.have.lengthOf(1);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('should send transactions sequentially within same origin chain', async () => {
|
|
506
|
+
const ctx = createRebalancerTestContext([
|
|
507
|
+
'ethereum',
|
|
508
|
+
'arbitrum',
|
|
509
|
+
'optimism',
|
|
510
|
+
]);
|
|
511
|
+
|
|
512
|
+
const callOrder: string[] = [];
|
|
513
|
+
ctx.multiProvider.sendTransaction = Sinon.stub().callsFake(
|
|
514
|
+
async (chain: string) => {
|
|
515
|
+
callOrder.push(chain);
|
|
516
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
517
|
+
return {
|
|
518
|
+
transactionHash: `0x${callOrder.length.toString().padStart(64, '0')}`,
|
|
519
|
+
blockNumber: 100,
|
|
520
|
+
status: 1,
|
|
521
|
+
};
|
|
522
|
+
},
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
526
|
+
{
|
|
527
|
+
id: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
528
|
+
} as any,
|
|
529
|
+
]);
|
|
530
|
+
|
|
531
|
+
const rebalancer = new Rebalancer(
|
|
532
|
+
ctx.warpCore,
|
|
533
|
+
ctx.chainMetadata,
|
|
534
|
+
ctx.tokensByChainName,
|
|
535
|
+
ctx.multiProvider as any,
|
|
536
|
+
testLogger,
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const routes = [
|
|
540
|
+
buildTestRebalanceRoute({
|
|
541
|
+
origin: 'ethereum',
|
|
542
|
+
destination: 'arbitrum',
|
|
543
|
+
amount: ethers.utils.parseEther('100').toBigInt(),
|
|
544
|
+
}),
|
|
545
|
+
buildTestRebalanceRoute({
|
|
546
|
+
origin: 'ethereum',
|
|
547
|
+
destination: 'optimism',
|
|
548
|
+
amount: ethers.utils.parseEther('200').toBigInt(),
|
|
549
|
+
}),
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
await rebalancer.rebalance(routes);
|
|
553
|
+
|
|
554
|
+
expect(callOrder).to.deep.equal(['ethereum', 'ethereum']);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
describe('result building', () => {
|
|
559
|
+
it('should include messageId when dispatch message found', async () => {
|
|
560
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum']);
|
|
561
|
+
|
|
562
|
+
const expectedMessageId =
|
|
563
|
+
'0xMessageId111111111111111111111111111111111111111111111111111111';
|
|
564
|
+
sandbox
|
|
565
|
+
.stub(HyperlaneCore, 'getDispatchedMessages')
|
|
566
|
+
.returns([{ id: expectedMessageId } as any]);
|
|
567
|
+
|
|
568
|
+
const rebalancer = new Rebalancer(
|
|
569
|
+
ctx.warpCore,
|
|
570
|
+
ctx.chainMetadata,
|
|
571
|
+
ctx.tokensByChainName,
|
|
572
|
+
ctx.multiProvider as any,
|
|
573
|
+
testLogger,
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const route = buildTestRebalanceRoute();
|
|
577
|
+
const results = await rebalancer.rebalance([route]);
|
|
578
|
+
|
|
579
|
+
expect(results).to.have.lengthOf(1);
|
|
580
|
+
expect(results[0].success).to.be.true;
|
|
581
|
+
expect(results[0].messageId).to.equal(expectedMessageId);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should return success: false when no Dispatch event found', async () => {
|
|
585
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum']);
|
|
586
|
+
|
|
587
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([]);
|
|
588
|
+
|
|
589
|
+
const rebalancer = new Rebalancer(
|
|
590
|
+
ctx.warpCore,
|
|
591
|
+
ctx.chainMetadata,
|
|
592
|
+
ctx.tokensByChainName,
|
|
593
|
+
ctx.multiProvider as any,
|
|
594
|
+
testLogger,
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const route = buildTestRebalanceRoute();
|
|
598
|
+
const results = await rebalancer.rebalance([route]);
|
|
599
|
+
|
|
600
|
+
expect(results).to.have.lengthOf(1);
|
|
601
|
+
expect(results[0].success).to.be.false;
|
|
602
|
+
expect(results[0].error).to.include('no Dispatch event found');
|
|
603
|
+
expect(results[0].messageId).to.be.undefined;
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('should include txHash in result', async () => {
|
|
607
|
+
const ctx = createRebalancerTestContext(['ethereum', 'arbitrum']);
|
|
608
|
+
|
|
609
|
+
const expectedTxHash =
|
|
610
|
+
'0x1111111111111111111111111111111111111111111111111111111111111111';
|
|
611
|
+
sandbox.stub(HyperlaneCore, 'getDispatchedMessages').returns([
|
|
612
|
+
{
|
|
613
|
+
id: '0x2222222222222222222222222222222222222222222222222222222222222222',
|
|
614
|
+
} as any,
|
|
615
|
+
]);
|
|
616
|
+
|
|
617
|
+
const rebalancer = new Rebalancer(
|
|
618
|
+
ctx.warpCore,
|
|
619
|
+
ctx.chainMetadata,
|
|
620
|
+
ctx.tokensByChainName,
|
|
621
|
+
ctx.multiProvider as any,
|
|
622
|
+
testLogger,
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
const route = buildTestRebalanceRoute();
|
|
626
|
+
const results = await rebalancer.rebalance([route]);
|
|
627
|
+
|
|
628
|
+
expect(results).to.have.lengthOf(1);
|
|
629
|
+
expect(results[0].txHash).to.equal(expectedTxHash);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
});
|