@paraswap/dex-lib 3.11.0 → 3.11.1-algebra.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/build/dex/algebra/algebra-pool-v1_1.js +5 -5
- package/build/dex/algebra/algebra-pool-v1_1.js.map +1 -1
- package/build/dex/algebra/lib/AlgebraMath.js +1 -4
- package/build/dex/algebra/lib/AlgebraMath.js.map +1 -1
- package/build/dex/balancer-v3/abi/balancerBatchRouter.d.ts +729 -0
- package/build/dex/balancer-v3/abi/balancerBatchRouter.js +732 -0
- package/build/dex/balancer-v3/abi/balancerBatchRouter.js.map +1 -0
- package/build/dex/balancer-v3/abi/balancerRouter.d.ts +1406 -0
- package/build/dex/balancer-v3/abi/balancerRouter.js +1175 -0
- package/build/dex/balancer-v3/abi/balancerRouter.js.map +1 -0
- package/build/dex/balancer-v3/abi/vaultExtension.V3.d.ts +2081 -0
- package/build/dex/balancer-v3/abi/vaultExtension.V3.js +1851 -0
- package/build/dex/balancer-v3/abi/vaultExtension.V3.js.map +1 -0
- package/build/dex/balancer-v3/balancer-v3-pool.d.ts +76 -0
- package/build/dex/balancer-v3/balancer-v3-pool.js +383 -0
- package/build/dex/balancer-v3/balancer-v3-pool.js.map +1 -0
- package/build/dex/balancer-v3/balancer-v3.d.ts +57 -0
- package/build/dex/balancer-v3/balancer-v3.js +373 -0
- package/build/dex/balancer-v3/balancer-v3.js.map +1 -0
- package/build/dex/balancer-v3/config.d.ts +8 -0
- package/build/dex/balancer-v3/config.js +24 -0
- package/build/dex/balancer-v3/config.js.map +1 -0
- package/build/dex/balancer-v3/getOnChainState.d.ts +10 -0
- package/build/dex/balancer-v3/getOnChainState.js +158 -0
- package/build/dex/balancer-v3/getOnChainState.js.map +1 -0
- package/build/dex/balancer-v3/getPoolsApi.d.ts +2 -0
- package/build/dex/balancer-v3/getPoolsApi.js +81 -0
- package/build/dex/balancer-v3/getPoolsApi.js.map +1 -0
- package/build/dex/balancer-v3/getTopPoolsApi.d.ts +13 -0
- package/build/dex/balancer-v3/getTopPoolsApi.js +62 -0
- package/build/dex/balancer-v3/getTopPoolsApi.js.map +1 -0
- package/build/dex/balancer-v3/types.d.ts +63 -0
- package/build/dex/balancer-v3/types.js +3 -0
- package/build/dex/balancer-v3/types.js.map +1 -0
- package/build/dex/fluid-dex/fluid-dex.js +1 -1
- package/build/dex/fluid-dex/fluid-dex.js.map +1 -1
- package/package.json +1 -1
- package/src/dex/algebra/algebra-pool-v1_1.ts +5 -5
- package/src/dex/algebra/lib/AlgebraMath.ts +1 -5
- package/src/dex/algebra/scripts/validate-state.test.ts +384 -0
- package/src/dex/fluid-dex/fluid-dex.ts +0 -1
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
dotenv.config();
|
|
4
|
+
|
|
5
|
+
import { Network } from '../../../constants';
|
|
6
|
+
import { DummyDexHelper } from '../../../dex-helper';
|
|
7
|
+
import { Algebra } from '../algebra';
|
|
8
|
+
import { uint256ToBigInt } from '../../../lib/decoders';
|
|
9
|
+
import {
|
|
10
|
+
DecodedStateMultiCallResultWithRelativeBitmapsV1_1,
|
|
11
|
+
DecodedStateMultiCallResultWithRelativeBitmapsV1_9,
|
|
12
|
+
PoolState_v1_9,
|
|
13
|
+
} from '../types';
|
|
14
|
+
import {
|
|
15
|
+
decodeStateMultiCallResultWithRelativeBitmapsV1_1,
|
|
16
|
+
decodeStateMultiCallResultWithRelativeBitmapsV1_9,
|
|
17
|
+
} from '../utils';
|
|
18
|
+
import { MultiCallParams, MultiWrapper } from '../../../lib/multi-wrapper';
|
|
19
|
+
import Web3 from 'web3';
|
|
20
|
+
import multiABIV2 from '../../../abi/multi-v2.json';
|
|
21
|
+
import ERC20ABI from '../../../abi/erc20.json';
|
|
22
|
+
import { getLogger } from '../../../lib/log4js';
|
|
23
|
+
import { Interface } from 'ethers/lib/utils';
|
|
24
|
+
import { AbiItem } from 'web3-utils';
|
|
25
|
+
import AlgebraStateMulticallABI from '../../../abi/algebra/AlgebraStateMulticall.abi.json';
|
|
26
|
+
import { TICK_BITMAP_TO_USE, TICK_BITMAP_BUFFER } from '../constants';
|
|
27
|
+
import { BlockHeader } from 'web3-eth';
|
|
28
|
+
import { ethers } from 'ethers';
|
|
29
|
+
import assert from 'assert';
|
|
30
|
+
|
|
31
|
+
// public RPC, replace to RPC with archive node
|
|
32
|
+
const web3Provider = new Web3(
|
|
33
|
+
'https://empty-muddy-breeze.xdai.quiknode.pro/f1ccb72e430860bc3be568120bb2d8c028ba2633/',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// pool and tokens for specific broken tx - 0x9a296a13f7c5eb8ce838e15ecbe3888e8998a14803a1ec46838611e1ff118d6e
|
|
37
|
+
const factoryAddress = '0xA0864cCA6E114013AB0e27cbd5B6f4c8947da766';
|
|
38
|
+
const poolAddress = '0xfd24c5c19df9f124f385b3c0f38f8c6c72f5a137';
|
|
39
|
+
const srcToken = {
|
|
40
|
+
address: `0xaf204776c7245bF4147c2612BF6e5972Ee483701`,
|
|
41
|
+
decimals: 18,
|
|
42
|
+
};
|
|
43
|
+
const destToken = {
|
|
44
|
+
address: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d',
|
|
45
|
+
decimals: 6,
|
|
46
|
+
};
|
|
47
|
+
const blockHeaders: Record<number, BlockHeader> = {};
|
|
48
|
+
|
|
49
|
+
const multiContract = new web3Provider.eth.Contract(
|
|
50
|
+
multiABIV2 as any,
|
|
51
|
+
'0xca11bde05977b3631167028862be2a173976ca11',
|
|
52
|
+
);
|
|
53
|
+
const stateMultiContract = new web3Provider.eth.Contract(
|
|
54
|
+
AlgebraStateMulticallABI as AbiItem[],
|
|
55
|
+
'0x49C46f7f88110ccA234ef27Cd664510f7bbF5998',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const multiWrapper = new MultiWrapper(multiContract, getLogger(`Ticks`));
|
|
59
|
+
const erc20Interface = new Interface(ERC20ABI);
|
|
60
|
+
|
|
61
|
+
function getBitmapRangeToRequest() {
|
|
62
|
+
return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER;
|
|
63
|
+
// return 2n;
|
|
64
|
+
// return 0n;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function _fetchPoolState_v1_9SingleStep(
|
|
68
|
+
blockNumber: number,
|
|
69
|
+
): Promise<
|
|
70
|
+
[bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_9]
|
|
71
|
+
> {
|
|
72
|
+
const callData: MultiCallParams<
|
|
73
|
+
bigint | DecodedStateMultiCallResultWithRelativeBitmapsV1_9
|
|
74
|
+
>[] = [
|
|
75
|
+
{
|
|
76
|
+
target: srcToken.address,
|
|
77
|
+
callData: erc20Interface.encodeFunctionData('balanceOf', [poolAddress]),
|
|
78
|
+
decodeFunction: uint256ToBigInt,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
target: destToken.address,
|
|
82
|
+
callData: erc20Interface.encodeFunctionData('balanceOf', [poolAddress]),
|
|
83
|
+
decodeFunction: uint256ToBigInt,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
target: stateMultiContract.options.address,
|
|
87
|
+
callData: stateMultiContract.methods
|
|
88
|
+
.getFullStateWithRelativeBitmaps(
|
|
89
|
+
factoryAddress,
|
|
90
|
+
srcToken.address,
|
|
91
|
+
destToken.address,
|
|
92
|
+
getBitmapRangeToRequest(),
|
|
93
|
+
getBitmapRangeToRequest(),
|
|
94
|
+
)
|
|
95
|
+
.encodeABI(),
|
|
96
|
+
decodeFunction: decodeStateMultiCallResultWithRelativeBitmapsV1_9,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const [resBalance0, resBalance1, resState] = await multiWrapper.tryAggregate<
|
|
101
|
+
bigint | DecodedStateMultiCallResultWithRelativeBitmapsV1_9
|
|
102
|
+
>(false, callData, blockNumber, multiWrapper.defaultBatchSize, false);
|
|
103
|
+
|
|
104
|
+
const [balance0, balance1, _state] = [
|
|
105
|
+
resBalance0.returnData,
|
|
106
|
+
resBalance1.returnData,
|
|
107
|
+
resState.returnData,
|
|
108
|
+
] as [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_9];
|
|
109
|
+
|
|
110
|
+
return [balance0, balance1, _state];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function fetchPoolStateSingleStep(
|
|
114
|
+
blockNumber: number,
|
|
115
|
+
): Promise<
|
|
116
|
+
[bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1]
|
|
117
|
+
> {
|
|
118
|
+
const callData: MultiCallParams<
|
|
119
|
+
bigint | DecodedStateMultiCallResultWithRelativeBitmapsV1_1
|
|
120
|
+
>[] = [
|
|
121
|
+
{
|
|
122
|
+
target: srcToken.address,
|
|
123
|
+
callData: erc20Interface.encodeFunctionData('balanceOf', [poolAddress]),
|
|
124
|
+
decodeFunction: uint256ToBigInt,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
target: destToken.address,
|
|
128
|
+
callData: erc20Interface.encodeFunctionData('balanceOf', [poolAddress]),
|
|
129
|
+
decodeFunction: uint256ToBigInt,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
target: stateMultiContract.options.address,
|
|
133
|
+
callData: stateMultiContract.methods
|
|
134
|
+
.getFullStateWithRelativeBitmaps(
|
|
135
|
+
factoryAddress,
|
|
136
|
+
srcToken.address,
|
|
137
|
+
destToken.address,
|
|
138
|
+
getBitmapRangeToRequest(),
|
|
139
|
+
getBitmapRangeToRequest(),
|
|
140
|
+
)
|
|
141
|
+
.encodeABI(),
|
|
142
|
+
decodeFunction: decodeStateMultiCallResultWithRelativeBitmapsV1_1,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const [resBalance0, resBalance1, resState] = await multiWrapper.tryAggregate<
|
|
147
|
+
bigint | DecodedStateMultiCallResultWithRelativeBitmapsV1_1
|
|
148
|
+
>(false, callData, blockNumber, multiWrapper.defaultBatchSize, false);
|
|
149
|
+
|
|
150
|
+
assert(resState.success, 'Pool does not exist');
|
|
151
|
+
|
|
152
|
+
const [balance0, balance1, _state] = [
|
|
153
|
+
resBalance0.returnData,
|
|
154
|
+
resBalance1.returnData,
|
|
155
|
+
resState.returnData,
|
|
156
|
+
] as [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1];
|
|
157
|
+
|
|
158
|
+
return [balance0, balance1, _state];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function preprocessField(value: any): string {
|
|
162
|
+
// const _v = ethers.BigNumber.from(value);
|
|
163
|
+
return value.toString();
|
|
164
|
+
// if (
|
|
165
|
+
// typeof value === 'bigint' ||
|
|
166
|
+
// typeof value === 'number' ||
|
|
167
|
+
// ethers.BigNumber.isBigNumber(value)
|
|
168
|
+
// ) {
|
|
169
|
+
// return value.toString();
|
|
170
|
+
// }
|
|
171
|
+
// return value;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function compareAndLogDifferences<T extends object, Y extends object>(
|
|
175
|
+
obj1: T,
|
|
176
|
+
obj2: Y,
|
|
177
|
+
keyNames: Array<keyof T & keyof Y>,
|
|
178
|
+
checkEachKey = false,
|
|
179
|
+
) {
|
|
180
|
+
let isValid = true;
|
|
181
|
+
let keys = keyNames;
|
|
182
|
+
|
|
183
|
+
if (checkEachKey) {
|
|
184
|
+
// find common keys
|
|
185
|
+
const keys1 = Object.keys(obj1);
|
|
186
|
+
const keys2 = new Set(Object.keys(obj2));
|
|
187
|
+
keys = keys1.filter(key => keys2.has(key)) as Array<keyof T & keyof Y>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (let fieldName of keys) {
|
|
191
|
+
const value1 = preprocessField(obj1[fieldName]);
|
|
192
|
+
const value2 = preprocessField(obj2[fieldName]);
|
|
193
|
+
|
|
194
|
+
if (value1 !== value2) {
|
|
195
|
+
console.log(
|
|
196
|
+
`${fieldName.toString()} mismatch: actual: ${value1} vs pool: ${value2}`,
|
|
197
|
+
);
|
|
198
|
+
isValid = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return isValid;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function isPoolStateEqualToReal(
|
|
206
|
+
state: PoolState_v1_9,
|
|
207
|
+
blockNumber: number,
|
|
208
|
+
) {
|
|
209
|
+
// const [balance0, balance1, contractState] =
|
|
210
|
+
// await _fetchPoolState_v1_9SingleStep(blockNumber);
|
|
211
|
+
|
|
212
|
+
const [balance0, balance1, contractState] = await fetchPoolStateSingleStep(
|
|
213
|
+
blockNumber,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const isValidBalances = compareAndLogDifferences(
|
|
217
|
+
state,
|
|
218
|
+
{ balance0, balance1, ...contractState },
|
|
219
|
+
['balance0', 'balance1', 'liquidity', 'tickSpacing'],
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const isValidGlobalState = compareAndLogDifferences(
|
|
223
|
+
state.globalState,
|
|
224
|
+
contractState.globalState,
|
|
225
|
+
// can check only for one of them, because they are directly co-related
|
|
226
|
+
['price', 'tick'],
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const isValidTickBitmap = compareAndLogDifferences(
|
|
230
|
+
state.tickBitmap,
|
|
231
|
+
contractState.tickBitmap.reduce((acc, val) => {
|
|
232
|
+
// @ts-ignore
|
|
233
|
+
acc[val.index] = val.value;
|
|
234
|
+
return acc;
|
|
235
|
+
}, {} as Record<string, bigint>),
|
|
236
|
+
[],
|
|
237
|
+
true,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
let isValidTicks = true;
|
|
241
|
+
for (let tick of contractState.ticks) {
|
|
242
|
+
const stateTick = state?.ticks[tick.index];
|
|
243
|
+
if (stateTick) {
|
|
244
|
+
const isValidTick = compareAndLogDifferences(stateTick, tick.value, [
|
|
245
|
+
'liquidityGross',
|
|
246
|
+
'liquidityNet',
|
|
247
|
+
// next fields doesn't affect pricing, so skip checks
|
|
248
|
+
// 'initialized',
|
|
249
|
+
// 'secondsOutside',
|
|
250
|
+
// 'secondsPerLiquidityOutsideX128',
|
|
251
|
+
// 'tickCumulativeOutside'
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
if (isValidTicks && !isValidTick) {
|
|
255
|
+
isValidTicks = false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
isValidBalances && isValidGlobalState && isValidTicks && isValidTickBitmap
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function checkPoolStateForBlockRange(
|
|
266
|
+
startBlockNumber: number,
|
|
267
|
+
endBlockNumber: number,
|
|
268
|
+
): Promise<boolean> {
|
|
269
|
+
// const network = Network.ARBITRUM;
|
|
270
|
+
// const dexKey = 'CamelotV3';
|
|
271
|
+
const network = Network.GNOSIS;
|
|
272
|
+
const dexKey = 'SwaprV3';
|
|
273
|
+
const dexHelper = new DummyDexHelper(network);
|
|
274
|
+
|
|
275
|
+
const algebra = new Algebra(network, dexKey, dexHelper);
|
|
276
|
+
const pool = await algebra.getPool(
|
|
277
|
+
srcToken.address,
|
|
278
|
+
destToken.address,
|
|
279
|
+
startBlockNumber,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const logsToDispatch = await dexHelper.provider.getLogs({
|
|
283
|
+
fromBlock: startBlockNumber,
|
|
284
|
+
toBlock: endBlockNumber,
|
|
285
|
+
address: poolAddress,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
console.log(logsToDispatch.length);
|
|
289
|
+
|
|
290
|
+
// group logs by block number
|
|
291
|
+
const logsByBlockNumber: Record<number, any[]> = {};
|
|
292
|
+
for (let log of logsToDispatch) {
|
|
293
|
+
if (!logsByBlockNumber[log.blockNumber]) {
|
|
294
|
+
logsByBlockNumber[log.blockNumber] = [];
|
|
295
|
+
}
|
|
296
|
+
logsByBlockNumber[log.blockNumber].push(log);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const sortedBlocks = Object.keys(logsByBlockNumber)
|
|
300
|
+
.map(Number)
|
|
301
|
+
.sort((a, b) => Number(a) - Number(b));
|
|
302
|
+
|
|
303
|
+
for (let blockNumber of sortedBlocks) {
|
|
304
|
+
if (!blockHeaders[blockNumber]) {
|
|
305
|
+
blockHeaders[blockNumber] = await dexHelper.web3Provider.eth.getBlock(
|
|
306
|
+
blockNumber,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// await pool?.update(logsByBlockNumber[blockNumber], {
|
|
311
|
+
// [blockNumber]: blockHeaders[blockNumber],
|
|
312
|
+
// });
|
|
313
|
+
|
|
314
|
+
const stateBeforeLog1 = pool?.getState(blockNumber) as PoolState_v1_9;
|
|
315
|
+
await pool?.update([logsByBlockNumber[blockNumber][0]], {
|
|
316
|
+
[blockNumber]: blockHeaders[blockNumber],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const stateAfterLog1 = pool?.getState(blockNumber) as PoolState_v1_9;
|
|
320
|
+
await pool?.update([logsByBlockNumber[blockNumber][1]], {
|
|
321
|
+
[blockNumber]: blockHeaders[blockNumber],
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const stateAfterLog2 = pool?.getState(blockNumber) as PoolState_v1_9;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const state = pool?.getState(startBlockNumber) as PoolState_v1_9;
|
|
328
|
+
return isPoolStateEqualToReal(state, endBlockNumber);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function findBreakingBlock(startBlock: number, endBlock: number) {
|
|
332
|
+
let left = startBlock;
|
|
333
|
+
let right = endBlock;
|
|
334
|
+
|
|
335
|
+
// If the state is valid at the start, then there's no breaking block in the range
|
|
336
|
+
if (await checkPoolStateForBlockRange(left, right)) {
|
|
337
|
+
return -1; // Indicates no breaking block found
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
while (left <= right) {
|
|
341
|
+
const mid = left + Math.floor((right - left) / 2);
|
|
342
|
+
const isValid = await checkPoolStateForBlockRange(startBlock, mid);
|
|
343
|
+
|
|
344
|
+
if (isValid) {
|
|
345
|
+
// If the state is valid up to mid, the issue must be in the second half
|
|
346
|
+
left = mid + 1;
|
|
347
|
+
} else {
|
|
348
|
+
// If the state is not valid up to mid, the issue is in the first half
|
|
349
|
+
// But we need to check if mid is the first occurrence of the issue
|
|
350
|
+
if (
|
|
351
|
+
mid === startBlock ||
|
|
352
|
+
(await checkPoolStateForBlockRange(startBlock, mid - 1))
|
|
353
|
+
) {
|
|
354
|
+
return mid; // Found the breaking block
|
|
355
|
+
}
|
|
356
|
+
right = mid - 1;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return -1; // Should not reach here if there's a breaking block
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// set timeout to 5 min
|
|
364
|
+
jest.setTimeout(300000);
|
|
365
|
+
|
|
366
|
+
describe('Algebra Pool State Validation', () => {
|
|
367
|
+
it('should validate pool state for a block range', async () => {
|
|
368
|
+
const isValid = await checkPoolStateForBlockRange(36886183, 36886186);
|
|
369
|
+
expect(isValid).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
async function main() {
|
|
374
|
+
// use findBreakingBlock to find the block where the state is broken
|
|
375
|
+
// console.log(await findBreakingBlock(startBlockNumber, endBlockNumber));
|
|
376
|
+
// previously broken block 150502863
|
|
377
|
+
console.log(await checkPoolStateForBlockRange(36886183, 36886186));
|
|
378
|
+
// console.log(await checkPoolStateForBlockRange(152087800, 152287800));
|
|
379
|
+
// console.log(await checkPoolStateForBlockRange(152100945, 152100947));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// main()
|
|
383
|
+
// .then(() => console.log('Done'))
|
|
384
|
+
// .catch(e => console.error(e));
|