@paraswap/dex-lib 2.34.0 → 2.35.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.
Files changed (69) hide show
  1. package/build/abi/api3-proxy.json +46 -0
  2. package/build/abi/api3-server-v1.json +463 -0
  3. package/build/abi/quick-perps/fast-price-events.json +70 -0
  4. package/build/abi/quick-perps/fast-price-feed.json +741 -0
  5. package/build/abi/quick-perps/reader.json +313 -0
  6. package/build/abi/quick-perps/vault-price-feed.json +323 -0
  7. package/build/abi/quick-perps/vault.json +1953 -0
  8. package/build/composed-event-subscriber.js +8 -2
  9. package/build/composed-event-subscriber.js.map +1 -1
  10. package/build/config.js +2 -2
  11. package/build/dex/index.js +2 -0
  12. package/build/dex/index.js.map +1 -1
  13. package/build/dex/quick-perps/config.d.ts +11 -0
  14. package/build/dex/quick-perps/config.js +27 -0
  15. package/build/dex/quick-perps/config.js.map +1 -0
  16. package/build/dex/quick-perps/fast-price-feed.d.ts +26 -0
  17. package/build/dex/quick-perps/fast-price-feed.js +176 -0
  18. package/build/dex/quick-perps/fast-price-feed.js.map +1 -0
  19. package/build/dex/quick-perps/pool.d.ts +21 -0
  20. package/build/dex/quick-perps/pool.js +210 -0
  21. package/build/dex/quick-perps/pool.js.map +1 -0
  22. package/build/dex/quick-perps/quick-perps.d.ts +52 -0
  23. package/build/dex/quick-perps/quick-perps.js +205 -0
  24. package/build/dex/quick-perps/quick-perps.js.map +1 -0
  25. package/build/dex/quick-perps/types.d.ts +98 -0
  26. package/build/dex/quick-perps/types.js +3 -0
  27. package/build/dex/quick-perps/types.js.map +1 -0
  28. package/build/dex/quick-perps/usdq.d.ts +15 -0
  29. package/build/dex/quick-perps/usdq.js +61 -0
  30. package/build/dex/quick-perps/usdq.js.map +1 -0
  31. package/build/dex/quick-perps/vault-price-feed.d.ts +43 -0
  32. package/build/dex/quick-perps/vault-price-feed.js +194 -0
  33. package/build/dex/quick-perps/vault-price-feed.js.map +1 -0
  34. package/build/dex/quick-perps/vault-utils.d.ts +8 -0
  35. package/build/dex/quick-perps/vault-utils.js +41 -0
  36. package/build/dex/quick-perps/vault-utils.js.map +1 -0
  37. package/build/dex/quick-perps/vault.d.ts +46 -0
  38. package/build/dex/quick-perps/vault.js +166 -0
  39. package/build/dex/quick-perps/vault.js.map +1 -0
  40. package/build/lib/api3-feed.d.ts +26 -0
  41. package/build/lib/api3-feed.js +82 -0
  42. package/build/lib/api3-feed.js.map +1 -0
  43. package/package.json +1 -1
  44. package/src/abi/api3-proxy.json +46 -0
  45. package/src/abi/api3-server-v1.json +463 -0
  46. package/src/abi/quick-perps/fast-price-events.json +70 -0
  47. package/src/abi/quick-perps/fast-price-feed.json +741 -0
  48. package/src/abi/quick-perps/reader.json +313 -0
  49. package/src/abi/quick-perps/vault-price-feed.json +323 -0
  50. package/src/abi/quick-perps/vault.json +1953 -0
  51. package/src/composed-event-subscriber.ts +16 -11
  52. package/src/config.ts +2 -2
  53. package/src/dex/index.ts +2 -0
  54. package/src/dex/quick-perps/config.ts +31 -0
  55. package/src/dex/quick-perps/fast-price-feed.ts +279 -0
  56. package/src/dex/quick-perps/pool.ts +372 -0
  57. package/src/dex/quick-perps/quick-perps-e2e.test.ts +65 -0
  58. package/src/dex/quick-perps/quick-perps-events.test.ts +101 -0
  59. package/src/dex/quick-perps/quick-perps-integration.test.ts +153 -0
  60. package/src/dex/quick-perps/quick-perps.ts +292 -0
  61. package/src/dex/quick-perps/types.ts +97 -0
  62. package/src/dex/quick-perps/usdq.ts +83 -0
  63. package/src/dex/quick-perps/vault-price-feed.ts +322 -0
  64. package/src/dex/quick-perps/vault-utils.ts +53 -0
  65. package/src/dex/quick-perps/vault.ts +290 -0
  66. package/src/lib/api3-feed.ts +140 -0
  67. package/tests/constants-e2e.ts +29 -0
  68. package/tests/tenderly-simulation.ts +46 -2
  69. package/tests/utils-e2e.ts +18 -10
@@ -0,0 +1,372 @@
1
+ import { DeepReadonly } from 'ts-essentials';
2
+ import { lens } from '../../lens';
3
+ import { Address, Logger, MultiCallInput } from '../../types';
4
+ import { ComposedEventSubscriber } from '../../composed-event-subscriber';
5
+ import { IDexHelper } from '../../dex-helper/idex-helper';
6
+ import { PoolState, DexParams, PoolConfig } from './types';
7
+ import { Api3FeedSubscriber } from '../../lib/api3-feed';
8
+ import { FastPriceFeed } from './fast-price-feed';
9
+ import { VaultPriceFeed } from './vault-price-feed';
10
+ import { Vault } from './vault';
11
+ import { USDQ } from './usdq';
12
+ import { Contract } from 'web3-eth-contract';
13
+ import ReaderABI from '../../abi/quick-perps/reader.json';
14
+
15
+ const MAX_AMOUNT_IN_CACHE_TTL = 5 * 60;
16
+
17
+ export class QuickPerpsEventPool extends ComposedEventSubscriber<PoolState> {
18
+ PRICE_PRECISION = 10n ** 30n;
19
+ USDQ_DECIMALS = 18;
20
+ BASIS_POINTS_DIVISOR = 10000n;
21
+
22
+ vault: Vault<PoolState>;
23
+ reader: Contract;
24
+
25
+ constructor(
26
+ parentName: string,
27
+ protected network: number,
28
+ protected dexHelper: IDexHelper,
29
+ config: PoolConfig,
30
+ ) {
31
+ const api3ServerV1Map = Object.entries(config.api3ServerV1).reduce(
32
+ (
33
+ acc: { [address: string]: Api3FeedSubscriber<PoolState> },
34
+ [key, value],
35
+ ) => {
36
+ acc[key] = new Api3FeedSubscriber<PoolState>(
37
+ value.proxy,
38
+ value.api3ServerV1,
39
+ value.dataFeedId,
40
+ lens<DeepReadonly<PoolState>>().primaryPrices[key],
41
+ dexHelper.getLogger(
42
+ `${key} Api3FeedSubscriber for ${parentName}-${network}`,
43
+ ),
44
+ );
45
+ return acc;
46
+ },
47
+ {},
48
+ );
49
+ const fastPriceFeed = new FastPriceFeed(
50
+ config.fastPriceFeed,
51
+ config.fastPriceEvents,
52
+ config.tokenAddresses,
53
+ config.fastPriceFeedConfig,
54
+ lens<DeepReadonly<PoolState>>().secondaryPrices,
55
+ dexHelper.getLogger(`${parentName}-${network} fastPriceFeed`),
56
+ );
57
+ const vaultPriceFeed = new VaultPriceFeed(
58
+ config.vaultPriceFeedConfig,
59
+ api3ServerV1Map,
60
+ fastPriceFeed,
61
+ );
62
+ const usdq = new USDQ(
63
+ config.usdqAddress,
64
+ lens<DeepReadonly<PoolState>>().usdq,
65
+ dexHelper.getLogger(`${parentName}-${network} USDQ`),
66
+ );
67
+ const vault = new Vault(
68
+ config.vaultAddress,
69
+ config.tokenAddresses,
70
+ config.vaultConfig,
71
+ vaultPriceFeed,
72
+ usdq,
73
+ lens<DeepReadonly<PoolState>>().vault,
74
+ dexHelper.getLogger(`${parentName}-${network} vault`),
75
+ );
76
+ super(
77
+ parentName,
78
+ 'pool',
79
+ dexHelper.getLogger(`${parentName}-${network}`),
80
+ dexHelper,
81
+ [...Object.values(api3ServerV1Map), fastPriceFeed, usdq, vault],
82
+ {
83
+ primaryPrices: {},
84
+ secondaryPrices: {
85
+ lastUpdatedAt: 0,
86
+ prices: {},
87
+ },
88
+ vault: {
89
+ usdqAmounts: {},
90
+ },
91
+ usdq: {
92
+ totalSupply: 0n,
93
+ },
94
+ },
95
+ );
96
+ this.vault = vault;
97
+ this.reader = new this.dexHelper.web3Provider.eth.Contract(
98
+ ReaderABI as any,
99
+ config.readerAddress,
100
+ );
101
+ }
102
+
103
+ async getStateOrGenerate(blockNumber: number): Promise<Readonly<PoolState>> {
104
+ const evenState = this.getState(blockNumber);
105
+ if (evenState) return evenState;
106
+ const onChainState = await this.generateState(blockNumber);
107
+ this.setState(onChainState, blockNumber);
108
+ return onChainState;
109
+ }
110
+
111
+ async getMaxAmountIn(_tokenIn: Address, _tokenOut: Address): Promise<bigint> {
112
+ const cacheKey = `maxAmountIn_${_tokenIn}_${_tokenOut}`;
113
+ const maxAmountCached = await this.dexHelper.cache.get(
114
+ this.parentName,
115
+ this.network,
116
+ cacheKey,
117
+ );
118
+ if (maxAmountCached) return BigInt(maxAmountCached);
119
+ const maxAmount: string = await this.reader.methods
120
+ .getMaxAmountIn(this.vault.vaultAddress, _tokenIn, _tokenOut)
121
+ .call();
122
+ this.dexHelper.cache.setex(
123
+ this.parentName,
124
+ this.network,
125
+ cacheKey,
126
+ MAX_AMOUNT_IN_CACHE_TTL,
127
+ maxAmount,
128
+ );
129
+ return BigInt(maxAmount);
130
+ }
131
+
132
+ // Reference to the original implementation
133
+ // https://github.com/gmx-io/gmx-contracts/blob/master/contracts/peripherals/Reader.sol#L71
134
+ async getAmountOut(
135
+ _tokenIn: Address,
136
+ _tokenOut: Address,
137
+ _amountsIn: bigint[],
138
+ blockNumber: number,
139
+ ): Promise<bigint[] | null> {
140
+ const maxAmountIn = await this.getMaxAmountIn(_tokenIn, _tokenOut);
141
+ const state = await this.getStateOrGenerate(blockNumber);
142
+ const priceIn = this.vault.getMinPrice(state, _tokenIn);
143
+ const priceOut = this.vault.getMaxPrice(state, _tokenOut);
144
+
145
+ const tokenInDecimals = this.vault.tokenDecimals[_tokenIn];
146
+ const tokenOutDecimals = this.vault.tokenDecimals[_tokenOut];
147
+
148
+ const isStableSwap =
149
+ this.vault.stableTokens[_tokenIn] && this.vault.stableTokens[_tokenOut];
150
+ const baseBps = isStableSwap
151
+ ? this.vault.stableSwapFeeBasisPoints
152
+ : this.vault.swapFeeBasisPoints;
153
+ const taxBps = isStableSwap
154
+ ? this.vault.stableTaxBasisPoints
155
+ : this.vault.taxBasisPoints;
156
+ const USDQUnit = BigInt(10 ** this.USDQ_DECIMALS);
157
+ const tokenInUnit = BigInt(10 ** tokenInDecimals);
158
+ const tokenOutUnit = BigInt(10 ** tokenOutDecimals);
159
+
160
+ return _amountsIn.map(_amountIn => {
161
+ if (_amountIn > maxAmountIn) return 0n;
162
+ let feeBasisPoints;
163
+ {
164
+ let usdqAmount = (_amountIn * priceIn) / this.PRICE_PRECISION;
165
+ usdqAmount = (usdqAmount * USDQUnit) / tokenInUnit;
166
+
167
+ const feesBasisPoints0 = this.vault.getFeeBasisPoints(
168
+ state,
169
+ _tokenIn,
170
+ usdqAmount,
171
+ baseBps,
172
+ taxBps,
173
+ true,
174
+ );
175
+ const feesBasisPoints1 = this.vault.getFeeBasisPoints(
176
+ state,
177
+ _tokenOut,
178
+ usdqAmount,
179
+ baseBps,
180
+ taxBps,
181
+ false,
182
+ );
183
+ // use the higher of the two fee basis points
184
+ feeBasisPoints =
185
+ feesBasisPoints0 > feesBasisPoints1
186
+ ? feesBasisPoints0
187
+ : feesBasisPoints1;
188
+ }
189
+
190
+ let amountOut = (_amountIn * priceIn) / priceOut;
191
+ amountOut = (amountOut * tokenOutUnit) / tokenInUnit;
192
+
193
+ const amountOutAfterFees =
194
+ (amountOut * (this.BASIS_POINTS_DIVISOR - feeBasisPoints)) /
195
+ this.BASIS_POINTS_DIVISOR;
196
+ return amountOutAfterFees;
197
+ });
198
+ }
199
+
200
+ static async getWhitelistedTokens(
201
+ vaultAddress: Address,
202
+ blockNumber: number | 'latest',
203
+ multiContract: Contract,
204
+ ) {
205
+ // get tokens count
206
+ const tokenCountResult = (
207
+ await multiContract.methods
208
+ .aggregate([
209
+ {
210
+ callData: Vault.interface.encodeFunctionData(
211
+ 'allWhitelistedTokensLength',
212
+ ),
213
+ target: vaultAddress,
214
+ },
215
+ ])
216
+ .call({}, blockNumber)
217
+ ).returnData;
218
+ const tokensCount = parseInt(
219
+ Vault.interface
220
+ .decodeFunctionResult('allWhitelistedTokensLength', tokenCountResult[0])
221
+ .toString(),
222
+ );
223
+
224
+ // get tokens
225
+ const getTokensCalldata = new Array(tokensCount).fill(0).map((_, i) => {
226
+ return {
227
+ callData: Vault.interface.encodeFunctionData('allWhitelistedTokens', [
228
+ i,
229
+ ]),
230
+ target: vaultAddress,
231
+ };
232
+ });
233
+ const tokensResult = (
234
+ await multiContract.methods
235
+ .aggregate(getTokensCalldata)
236
+ .call({}, blockNumber)
237
+ ).returnData;
238
+ const tokens: Address[] = tokensResult.map((t: any) =>
239
+ Vault.interface
240
+ .decodeFunctionResult('allWhitelistedTokens', t)[0]
241
+ .toLowerCase(),
242
+ );
243
+ return tokens;
244
+ }
245
+
246
+ static async getConfig(
247
+ dexParams: DexParams,
248
+ blockNumber: number | 'latest',
249
+ multiContract: Contract,
250
+ ): Promise<PoolConfig> {
251
+ const tokens = await this.getWhitelistedTokens(
252
+ dexParams.vault,
253
+ blockNumber,
254
+ multiContract,
255
+ );
256
+
257
+ // get price chainlink price feed
258
+ const getPriceFeedCalldata = tokens.map(t => {
259
+ return {
260
+ callData: VaultPriceFeed.interface.encodeFunctionData(
261
+ 'priceFeedProxies',
262
+ [t],
263
+ ),
264
+ target: dexParams.priceFeed,
265
+ };
266
+ });
267
+ const priceFeedResult = (
268
+ await multiContract.methods
269
+ .aggregate(getPriceFeedCalldata)
270
+ .call({}, blockNumber)
271
+ ).returnData;
272
+ const priceFeeds = priceFeedResult.map((p: any) =>
273
+ VaultPriceFeed.interface
274
+ .decodeFunctionResult('priceFeedProxies', p)[0]
275
+ .toString()
276
+ .toLowerCase(),
277
+ );
278
+
279
+ // get config for all event listeners
280
+ let multicallSlices: [number, number][] = [];
281
+ let multiCallData: MultiCallInput[] = [];
282
+ let i = 0;
283
+ for (let priceFeed of priceFeeds) {
284
+ const api3ServerAddressCallData =
285
+ Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed);
286
+ const dataFeedIdCallData = Api3FeedSubscriber.getDataFeedId(priceFeed);
287
+ multiCallData.push(...[api3ServerAddressCallData, dataFeedIdCallData]);
288
+ multicallSlices.push([i, i + 2]);
289
+ i += 2;
290
+ }
291
+
292
+ const fastPriceFeedConfigCallData = FastPriceFeed.getConfigMulticallInputs(
293
+ dexParams.fastPriceFeed,
294
+ tokens,
295
+ );
296
+ multiCallData.push(...fastPriceFeedConfigCallData);
297
+ multicallSlices.push([i, i + fastPriceFeedConfigCallData.length]);
298
+ i += fastPriceFeedConfigCallData.length;
299
+
300
+ const vaultPriceFeedConfigCallData =
301
+ VaultPriceFeed.getConfigMulticallInputs(dexParams.priceFeed, tokens);
302
+ multiCallData.push(...vaultPriceFeedConfigCallData);
303
+ multicallSlices.push([i, i + vaultPriceFeedConfigCallData.length]);
304
+ i += vaultPriceFeedConfigCallData.length;
305
+
306
+ const vaultConfigCallData = Vault.getConfigMulticallInputs(
307
+ dexParams.vault,
308
+ tokens,
309
+ );
310
+ multiCallData.push(...vaultConfigCallData);
311
+ multicallSlices.push([i, i + vaultConfigCallData.length]);
312
+ i += vaultConfigCallData.length;
313
+
314
+ const configResults = (
315
+ await multiContract.methods.aggregate(multiCallData).call({}, blockNumber)
316
+ ).returnData;
317
+
318
+ const api3ServerV1: {
319
+ [address: string]: {
320
+ proxy: Address;
321
+ api3ServerV1: Address;
322
+ dataFeedId: string;
323
+ };
324
+ } = {};
325
+ for (let token of tokens) {
326
+ const [api3ServerAddressRes, dataFeedIdRes] = configResults.slice(
327
+ ...multicallSlices.shift()!,
328
+ );
329
+ const serverV1Address =
330
+ Api3FeedSubscriber.decodeApi3ServerV1Result(api3ServerAddressRes);
331
+ const dataFeedId = Api3FeedSubscriber.decodeDataFeedId(dataFeedIdRes);
332
+ api3ServerV1[token] = {
333
+ proxy: priceFeeds.shift(),
334
+ api3ServerV1: serverV1Address,
335
+ dataFeedId,
336
+ };
337
+ }
338
+
339
+ const fastPriceFeedConfigResults = configResults.slice(
340
+ ...multicallSlices.shift()!,
341
+ );
342
+ const fastPriceFeedConfig = FastPriceFeed.getConfig(
343
+ fastPriceFeedConfigResults,
344
+ tokens,
345
+ );
346
+
347
+ const vaultPriceFeedConfigResults = configResults.slice(
348
+ ...multicallSlices.shift()!,
349
+ );
350
+ const vaultPriceFeedConfig = VaultPriceFeed.getConfig(
351
+ vaultPriceFeedConfigResults,
352
+ tokens,
353
+ );
354
+
355
+ const vaultConfigResults = configResults.slice(...multicallSlices.shift()!);
356
+ const vaultConfig = Vault.getConfig(vaultConfigResults, tokens);
357
+
358
+ return {
359
+ vaultAddress: dexParams.vault,
360
+ readerAddress: dexParams.reader,
361
+ priceFeed: dexParams.priceFeed,
362
+ fastPriceFeed: dexParams.fastPriceFeed,
363
+ fastPriceEvents: dexParams.fastPriceEvents,
364
+ usdqAddress: dexParams.usdq,
365
+ tokenAddresses: tokens,
366
+ vaultConfig,
367
+ vaultPriceFeedConfig,
368
+ fastPriceFeedConfig,
369
+ api3ServerV1,
370
+ };
371
+ }
372
+ }
@@ -0,0 +1,65 @@
1
+ import dotenv from 'dotenv';
2
+ dotenv.config();
3
+
4
+ import { testE2E } from '../../../tests/utils-e2e';
5
+ import { Tokens, Holders } from '../../../tests/constants-e2e';
6
+ import { Network, ContractMethod, SwapSide } from '../../constants';
7
+ import { StaticJsonRpcProvider } from '@ethersproject/providers';
8
+ import { generateConfig } from '../../config';
9
+
10
+ describe('QuickPerps E2E', () => {
11
+ const dexKey = 'QuickPerps';
12
+
13
+ describe('QuickPerps zkEVM', () => {
14
+ const network = Network.ZKEVM;
15
+ const tokens = Tokens[network];
16
+ const holders = Holders[network];
17
+ const provider = new StaticJsonRpcProvider(
18
+ generateConfig(network).privateHttpProvider,
19
+ network,
20
+ );
21
+
22
+ const tokenASymbol: string = 'ETH';
23
+ const tokenBSymbol: string = 'MATIC';
24
+
25
+ const tokenAAmount: string = '100000000000000000';
26
+ const tokenBAmount: string = '10000000000000000000';
27
+
28
+ const sideToContractMethods = new Map([
29
+ [
30
+ SwapSide.SELL,
31
+ [
32
+ ContractMethod.simpleSwap,
33
+ ContractMethod.multiSwap,
34
+ ContractMethod.megaSwap,
35
+ ],
36
+ ],
37
+ ]);
38
+
39
+ sideToContractMethods.forEach((contractMethods, side) =>
40
+ contractMethods.forEach((contractMethod: ContractMethod) => {
41
+ describe(`${contractMethod}`, () => {
42
+ it('ETH -> MATIC', async () => {
43
+ await testE2E(
44
+ tokens[tokenASymbol],
45
+ tokens[tokenBSymbol],
46
+ holders[tokenASymbol],
47
+ side === SwapSide.SELL ? tokenAAmount : tokenBAmount,
48
+ side,
49
+ dexKey,
50
+ contractMethod,
51
+ network,
52
+ provider,
53
+ undefined,
54
+ undefined,
55
+ undefined,
56
+ undefined,
57
+ undefined,
58
+ true,
59
+ );
60
+ });
61
+ });
62
+ }),
63
+ );
64
+ });
65
+ });
@@ -0,0 +1,101 @@
1
+ import dotenv from 'dotenv';
2
+ dotenv.config();
3
+
4
+ import { QuickPerpsEventPool } from './pool';
5
+ import { QuickPerpsConfig } from './config';
6
+ import { Network } from '../../constants';
7
+ import { DummyDexHelper } from '../../dex-helper/index';
8
+ import { testEventSubscriber } from '../../../tests/utils-events';
9
+ import { PoolConfig, PoolState } from './types';
10
+
11
+ jest.setTimeout(50 * 1000);
12
+ const dexKey = 'QuickPerps';
13
+ const network = Network.ZKEVM;
14
+ const params = QuickPerpsConfig[dexKey][network];
15
+ const dexHelper = new DummyDexHelper(network);
16
+ const logger = dexHelper.getLogger(dexKey);
17
+
18
+ async function fetchPoolState(
19
+ quickPerpsPool: QuickPerpsEventPool,
20
+ blockNumber: number,
21
+ ): Promise<PoolState> {
22
+ return quickPerpsPool.generateState(blockNumber);
23
+ }
24
+
25
+ // timestamp can't be compared exactly as the event released
26
+ // doesn't have the timestamp. It is safe to consider the
27
+ // timestamp as the blockTime as the max deviation is bounded
28
+ // on the contract
29
+ const stateWithoutTimestamp = (state: PoolState) => ({
30
+ ...state,
31
+ secondaryPrices: {
32
+ prices: state.secondaryPrices.prices,
33
+ },
34
+ });
35
+
36
+ function compareState(state: PoolState, expectedState: PoolState) {
37
+ expect(stateWithoutTimestamp(state)).toEqual(
38
+ stateWithoutTimestamp(expectedState),
39
+ );
40
+ }
41
+
42
+ describe('QuickPerps Event', function () {
43
+ const blockNumbers: { [eventName: string]: number[] } = {
44
+ IncreaseUsdqAmount: [
45
+ 4960808, 4960808, 4961034, 4961037, 4961046, 4961052, 4961055, 4961062,
46
+ 4961153, 4961167, 4961190, 4961194, 4961215, 4961220, 4961353, 4961472,
47
+ 4961476, 4961521, 4961628, 4961629, 4961648, 4961664, 4961683, 4961710,
48
+ 4961716, 4961848, 4961853, 4961863, 4961866, 4962156, 4962180, 4962200,
49
+ 4962296,
50
+ ],
51
+ DecreaseUsdqAmount: [
52
+ 4960808, 4960808, 4961034, 4961037, 4961046, 4961052, 4961055, 4961062,
53
+ 4961153, 4961167, 4961190, 4961194, 4961215, 4961220, 4961353, 4961472,
54
+ 4961476, 4961521, 4961628, 4961629, 4961648, 4961664, 4961683, 4961710,
55
+ 4961716, 4961848, 4961853, 4961863, 4961866, 4962156, 4962180, 4962200,
56
+ 4962296,
57
+ ],
58
+ Transfer: [4958541, 4959994, 4959998, 4960452, 4960452],
59
+ PriceUpdate: [
60
+ 4960534, 4960569, 4960584, 4960609, 4960637, 4960660, 4960694, 4960700,
61
+ ],
62
+ };
63
+
64
+ describe('QuickPerpsEventPool', function () {
65
+ let config: PoolConfig;
66
+ let quickPerpsPool: QuickPerpsEventPool;
67
+ let blockNumber: number;
68
+ beforeAll(async function () {
69
+ blockNumber = await dexHelper.web3Provider.eth.getBlockNumber();
70
+
71
+ config = await QuickPerpsEventPool.getConfig(
72
+ params,
73
+ blockNumber,
74
+ dexHelper.multiContract,
75
+ );
76
+ });
77
+ Object.keys(blockNumbers).forEach((event: string) => {
78
+ blockNumbers[event].forEach((blockNumber: number) => {
79
+ it(`Should return the correct state after the ${blockNumber}:${event}`, async function () {
80
+ quickPerpsPool = new QuickPerpsEventPool(
81
+ dexKey,
82
+ network,
83
+ dexHelper,
84
+ logger,
85
+ config,
86
+ );
87
+ await testEventSubscriber(
88
+ quickPerpsPool,
89
+ quickPerpsPool.addressesSubscribed,
90
+ (_blockNumber: number) =>
91
+ fetchPoolState(quickPerpsPool, _blockNumber),
92
+ blockNumber,
93
+ `${dexKey}_${params.vault}`,
94
+ dexHelper.provider,
95
+ compareState,
96
+ );
97
+ });
98
+ });
99
+ });
100
+ });
101
+ });
@@ -0,0 +1,153 @@
1
+ /* eslint-disable no-console */
2
+ import dotenv from 'dotenv';
3
+ dotenv.config();
4
+
5
+ import { Interface } from '@ethersproject/abi';
6
+ import { DummyDexHelper } from '../../dex-helper/index';
7
+ import { Network, SwapSide } from '../../constants';
8
+ import { QuickPerps } from './quick-perps';
9
+ import { QuickPerpsConfig } from './config';
10
+ import {
11
+ checkPoolPrices,
12
+ checkPoolsLiquidity,
13
+ checkConstantPoolPrices,
14
+ } from '../../../tests/utils';
15
+ import { Tokens } from '../../../tests/constants-e2e';
16
+ import ReaderABI from '../../abi/quick-perps/reader.json';
17
+
18
+ const network = Network.ZKEVM;
19
+ const dexKey = 'QuickPerps';
20
+ const params = QuickPerpsConfig[dexKey][network];
21
+ const readerInterface = new Interface(ReaderABI);
22
+ const readerAddress = '0xf1CFB75854DE535475B88Bb6FBad317eea98c0F9';
23
+
24
+ function testsForCase(
25
+ tokenASymbol: string,
26
+ tokenBSymbol: string,
27
+ amounts: bigint[],
28
+ ) {
29
+ const TokenA = Tokens[network][tokenASymbol];
30
+ const TokenB = Tokens[network][tokenBSymbol];
31
+
32
+ it('getPoolIdentifiers and getPricesVolume SELL', async function () {
33
+ const dexHelper = new DummyDexHelper(network);
34
+ const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber();
35
+ const quickPerps = new QuickPerps(network, dexKey, dexHelper);
36
+
37
+ await quickPerps.initializePricing(blocknumber);
38
+
39
+ const pools = await quickPerps.getPoolIdentifiers(
40
+ TokenA,
41
+ TokenB,
42
+ SwapSide.SELL,
43
+ blocknumber,
44
+ );
45
+ console.log(`${tokenASymbol} <> ${tokenBSymbol} Pool Identifiers: `, pools);
46
+
47
+ expect(pools.length).toBeGreaterThan(0);
48
+
49
+ const poolPrices = await quickPerps.getPricesVolume(
50
+ TokenA,
51
+ TokenB,
52
+ amounts,
53
+ SwapSide.SELL,
54
+ blocknumber,
55
+ pools,
56
+ );
57
+ console.log(`${tokenASymbol} <> ${tokenBSymbol} Pool Prices: `, poolPrices);
58
+
59
+ expect(poolPrices).not.toBeNull();
60
+ if (quickPerps.hasConstantPriceLargeAmounts) {
61
+ checkConstantPoolPrices(poolPrices!, amounts, dexKey);
62
+ } else {
63
+ checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey);
64
+ }
65
+
66
+ // Do on chain pricing based on reader to compare
67
+ const readerCallData = amounts.map(a => ({
68
+ target: readerAddress,
69
+ callData: readerInterface.encodeFunctionData('getAmountOut', [
70
+ params.vault,
71
+ TokenA.address,
72
+ TokenB.address,
73
+ a.toString(),
74
+ ]),
75
+ }));
76
+
77
+ const readerResult = (
78
+ await dexHelper.multiContract.methods
79
+ .aggregate(readerCallData)
80
+ .call({}, blocknumber)
81
+ ).returnData;
82
+ const expectedPrices = readerResult.map((p: any) =>
83
+ BigInt(
84
+ readerInterface.decodeFunctionResult('getAmountOut', p)[0].toString(),
85
+ ),
86
+ );
87
+
88
+ expect(poolPrices![0].prices).toEqual(expectedPrices);
89
+ });
90
+
91
+ it('getTopPoolsForToken', async function () {
92
+ const dexHelper = new DummyDexHelper(network);
93
+ const quickPerps = new QuickPerps(network, dexKey, dexHelper);
94
+
95
+ await quickPerps.updatePoolState();
96
+ const poolLiquidity = await quickPerps.getTopPoolsForToken(
97
+ TokenA.address,
98
+ 10,
99
+ );
100
+ console.log(
101
+ `${tokenASymbol} Top Pools:`,
102
+ JSON.stringify(poolLiquidity, null, 2),
103
+ );
104
+
105
+ if (!quickPerps.hasConstantPriceLargeAmounts) {
106
+ checkPoolsLiquidity(poolLiquidity, TokenA.address, dexKey);
107
+ }
108
+ });
109
+ }
110
+
111
+ describe('QuickPerps', function () {
112
+ describe('WETH -> MATIC', function () {
113
+ const TokenASymbol = 'WETH';
114
+ const TokenBSymbol = 'MATIC';
115
+
116
+ const amounts = [
117
+ 0n,
118
+ 100000000000000000n,
119
+ 200000000000000000n,
120
+ 300000000000000000n,
121
+ 400000000000000000n,
122
+ 500000000000000000n,
123
+ 600000000000000000n,
124
+ 700000000000000000n,
125
+ 800000000000000000n,
126
+ 900000000000000000n,
127
+ 1000000000000000000n,
128
+ ];
129
+
130
+ testsForCase(TokenASymbol, TokenBSymbol, amounts);
131
+ });
132
+
133
+ describe('WBTC -> USDC', function () {
134
+ const TokenASymbol = 'WBTC';
135
+ const TokenBSymbol = 'USDC';
136
+
137
+ const amounts = [
138
+ 0n,
139
+ 100000000n,
140
+ 200000000n,
141
+ 300000000n,
142
+ 400000000n,
143
+ 500000000n,
144
+ 600000000n,
145
+ 700000000n,
146
+ 800000000n,
147
+ 900000000n,
148
+ 1000000000n,
149
+ ];
150
+
151
+ testsForCase(TokenASymbol, TokenBSymbol, amounts);
152
+ });
153
+ });