@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.
- package/build/abi/api3-proxy.json +46 -0
- package/build/abi/api3-server-v1.json +463 -0
- package/build/abi/quick-perps/fast-price-events.json +70 -0
- package/build/abi/quick-perps/fast-price-feed.json +741 -0
- package/build/abi/quick-perps/reader.json +313 -0
- package/build/abi/quick-perps/vault-price-feed.json +323 -0
- package/build/abi/quick-perps/vault.json +1953 -0
- package/build/composed-event-subscriber.js +8 -2
- package/build/composed-event-subscriber.js.map +1 -1
- package/build/config.js +2 -2
- package/build/dex/index.js +2 -0
- package/build/dex/index.js.map +1 -1
- package/build/dex/quick-perps/config.d.ts +11 -0
- package/build/dex/quick-perps/config.js +27 -0
- package/build/dex/quick-perps/config.js.map +1 -0
- package/build/dex/quick-perps/fast-price-feed.d.ts +26 -0
- package/build/dex/quick-perps/fast-price-feed.js +176 -0
- package/build/dex/quick-perps/fast-price-feed.js.map +1 -0
- package/build/dex/quick-perps/pool.d.ts +21 -0
- package/build/dex/quick-perps/pool.js +210 -0
- package/build/dex/quick-perps/pool.js.map +1 -0
- package/build/dex/quick-perps/quick-perps.d.ts +52 -0
- package/build/dex/quick-perps/quick-perps.js +205 -0
- package/build/dex/quick-perps/quick-perps.js.map +1 -0
- package/build/dex/quick-perps/types.d.ts +98 -0
- package/build/dex/quick-perps/types.js +3 -0
- package/build/dex/quick-perps/types.js.map +1 -0
- package/build/dex/quick-perps/usdq.d.ts +15 -0
- package/build/dex/quick-perps/usdq.js +61 -0
- package/build/dex/quick-perps/usdq.js.map +1 -0
- package/build/dex/quick-perps/vault-price-feed.d.ts +43 -0
- package/build/dex/quick-perps/vault-price-feed.js +194 -0
- package/build/dex/quick-perps/vault-price-feed.js.map +1 -0
- package/build/dex/quick-perps/vault-utils.d.ts +8 -0
- package/build/dex/quick-perps/vault-utils.js +41 -0
- package/build/dex/quick-perps/vault-utils.js.map +1 -0
- package/build/dex/quick-perps/vault.d.ts +46 -0
- package/build/dex/quick-perps/vault.js +166 -0
- package/build/dex/quick-perps/vault.js.map +1 -0
- package/build/lib/api3-feed.d.ts +26 -0
- package/build/lib/api3-feed.js +82 -0
- package/build/lib/api3-feed.js.map +1 -0
- package/package.json +1 -1
- package/src/abi/api3-proxy.json +46 -0
- package/src/abi/api3-server-v1.json +463 -0
- package/src/abi/quick-perps/fast-price-events.json +70 -0
- package/src/abi/quick-perps/fast-price-feed.json +741 -0
- package/src/abi/quick-perps/reader.json +313 -0
- package/src/abi/quick-perps/vault-price-feed.json +323 -0
- package/src/abi/quick-perps/vault.json +1953 -0
- package/src/composed-event-subscriber.ts +16 -11
- package/src/config.ts +2 -2
- package/src/dex/index.ts +2 -0
- package/src/dex/quick-perps/config.ts +31 -0
- package/src/dex/quick-perps/fast-price-feed.ts +279 -0
- package/src/dex/quick-perps/pool.ts +372 -0
- package/src/dex/quick-perps/quick-perps-e2e.test.ts +65 -0
- package/src/dex/quick-perps/quick-perps-events.test.ts +101 -0
- package/src/dex/quick-perps/quick-perps-integration.test.ts +153 -0
- package/src/dex/quick-perps/quick-perps.ts +292 -0
- package/src/dex/quick-perps/types.ts +97 -0
- package/src/dex/quick-perps/usdq.ts +83 -0
- package/src/dex/quick-perps/vault-price-feed.ts +322 -0
- package/src/dex/quick-perps/vault-utils.ts +53 -0
- package/src/dex/quick-perps/vault.ts +290 -0
- package/src/lib/api3-feed.ts +140 -0
- package/tests/constants-e2e.ts +29 -0
- package/tests/tenderly-simulation.ts +46 -2
- 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
|
+
});
|