@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,292 @@
1
+ import { Interface } from '@ethersproject/abi';
2
+ import _ from 'lodash';
3
+ import {
4
+ Token,
5
+ Address,
6
+ ExchangePrices,
7
+ PoolPrices,
8
+ AdapterExchangeParam,
9
+ SimpleExchangeParam,
10
+ PoolLiquidity,
11
+ Logger,
12
+ } from '../../types';
13
+ import { SwapSide, Network } from '../../constants';
14
+ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
15
+ import { getDexKeysWithNetwork, getBigIntPow } from '../../utils';
16
+ import { IDex } from '../idex';
17
+ import { IDexHelper } from '../../dex-helper/idex-helper';
18
+ import { QuickPerpsData, DexParams } from './types';
19
+ import { QuickPerpsEventPool } from './pool';
20
+ import { SimpleExchange } from '../simple-exchange';
21
+ import { QuickPerpsConfig, Adapters } from './config';
22
+ import { Vault } from './vault';
23
+ import ERC20ABI from '../../abi/erc20.json';
24
+
25
+ const QuickPerpsGasCost = 300 * 1000;
26
+
27
+ export class QuickPerps extends SimpleExchange implements IDex<QuickPerpsData> {
28
+ protected pool: QuickPerpsEventPool | null = null;
29
+ protected supportedTokensMap: { [address: string]: boolean } = {};
30
+ // supportedTokens is only used by the pooltracker
31
+ protected supportedTokens: Token[] = [];
32
+
33
+ readonly hasConstantPriceLargeAmounts = false;
34
+ readonly needWrapNative = true;
35
+ readonly isFeeOnTransferSupported = false;
36
+
37
+ public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
38
+ getDexKeysWithNetwork(QuickPerpsConfig);
39
+
40
+ public static erc20Interface = new Interface(ERC20ABI);
41
+
42
+ vaultUSDBalance: number = 0;
43
+
44
+ logger: Logger;
45
+
46
+ constructor(
47
+ protected network: Network,
48
+ dexKey: string,
49
+ protected dexHelper: IDexHelper,
50
+ protected adapters = Adapters[network],
51
+ protected params: DexParams = QuickPerpsConfig[dexKey][network],
52
+ ) {
53
+ super(dexHelper, dexKey);
54
+ this.logger = dexHelper.getLogger(dexKey);
55
+ }
56
+
57
+ async initializePricing(blockNumber: number) {
58
+ const config = await QuickPerpsEventPool.getConfig(
59
+ this.params,
60
+ blockNumber,
61
+ this.dexHelper.multiContract,
62
+ );
63
+ config.tokenAddresses.forEach(
64
+ (token: Address) => (this.supportedTokensMap[token] = true),
65
+ );
66
+ this.pool = new QuickPerpsEventPool(
67
+ this.dexKey,
68
+ this.network,
69
+ this.dexHelper,
70
+ config,
71
+ );
72
+ await this.pool.initialize(blockNumber);
73
+ }
74
+
75
+ // Returns the list of contract adapters (name and index)
76
+ // for a buy/sell. Return null if there are no adapters.
77
+ getAdapters(side: SwapSide): { name: string; index: number }[] | null {
78
+ return this.adapters[side];
79
+ }
80
+
81
+ // Returns list of pool identifiers that can be used
82
+ // for a given swap. poolIdentifiers must be unique
83
+ // across DEXes.
84
+ async getPoolIdentifiers(
85
+ srcToken: Token,
86
+ destToken: Token,
87
+ side: SwapSide,
88
+ blockNumber: number,
89
+ ): Promise<string[]> {
90
+ if (side === SwapSide.BUY || !this.pool) return [];
91
+ const srcAddress = this.dexHelper.config
92
+ .wrapETH(srcToken)
93
+ .address.toLowerCase();
94
+ const destAddress = this.dexHelper.config
95
+ .wrapETH(destToken)
96
+ .address.toLowerCase();
97
+ if (
98
+ srcAddress !== destAddress &&
99
+ this.supportedTokensMap[srcAddress] &&
100
+ this.supportedTokensMap[destAddress]
101
+ ) {
102
+ return [`${this.dexKey}_${srcAddress}`, `${this.dexKey}_${destAddress}`];
103
+ }
104
+ return [];
105
+ }
106
+
107
+ // Returns pool prices for amounts.
108
+ // If limitPools is defined only pools in limitPools
109
+ // should be used. If limitPools is undefined then
110
+ // any pools can be used.
111
+ async getPricesVolume(
112
+ srcToken: Token,
113
+ destToken: Token,
114
+ amounts: bigint[],
115
+ side: SwapSide,
116
+ blockNumber: number,
117
+ limitPools?: string[],
118
+ ): Promise<null | ExchangePrices<QuickPerpsData>> {
119
+ if (side === SwapSide.BUY || !this.pool) return null;
120
+ const srcAddress = this.dexHelper.config
121
+ .wrapETH(srcToken)
122
+ .address.toLowerCase();
123
+ const destAddress = this.dexHelper.config
124
+ .wrapETH(destToken)
125
+ .address.toLowerCase();
126
+ if (
127
+ srcAddress === destAddress ||
128
+ !(
129
+ this.supportedTokensMap[srcAddress] &&
130
+ this.supportedTokensMap[destAddress]
131
+ )
132
+ )
133
+ return null;
134
+ const srcPoolIdentifier = `${this.dexKey}_${srcAddress}`;
135
+ const destPoolIdentifier = `${this.dexKey}_${destAddress}`;
136
+ const pools = [srcPoolIdentifier, destPoolIdentifier];
137
+ if (limitPools && pools.some(p => !limitPools.includes(p))) return null;
138
+
139
+ const unitVolume = getBigIntPow(srcToken.decimals);
140
+ const prices = await this.pool.getAmountOut(
141
+ srcAddress,
142
+ destAddress,
143
+ [unitVolume, ...amounts],
144
+ blockNumber,
145
+ );
146
+
147
+ if (!prices) return null;
148
+
149
+ return [
150
+ {
151
+ prices: prices.slice(1),
152
+ unit: prices[0],
153
+ gasCost: QuickPerpsGasCost,
154
+ exchange: this.dexKey,
155
+ data: {},
156
+ poolAddresses: [this.params.vault],
157
+ },
158
+ ];
159
+ }
160
+
161
+ // Returns estimated gas cost of calldata for this DEX in multiSwap
162
+ getCalldataGasCost(
163
+ poolPrices: PoolPrices<QuickPerpsData>,
164
+ ): number | number[] {
165
+ return CALLDATA_GAS_COST.DEX_NO_PAYLOAD;
166
+ }
167
+
168
+ getAdapterParam(
169
+ srcToken: string,
170
+ destToken: string,
171
+ srcAmount: string,
172
+ destAmount: string,
173
+ data: QuickPerpsData,
174
+ side: SwapSide,
175
+ ): AdapterExchangeParam {
176
+ return {
177
+ targetExchange: this.params.vault,
178
+ payload: '0x',
179
+ networkFee: '0',
180
+ };
181
+ }
182
+
183
+ getSimpleParam(
184
+ srcToken: string,
185
+ destToken: string,
186
+ srcAmount: string,
187
+ destAmount: string,
188
+ data: QuickPerpsData,
189
+ side: SwapSide,
190
+ ): SimpleExchangeParam {
191
+ return {
192
+ callees: [srcToken, this.params.vault],
193
+ calldata: [
194
+ QuickPerps.erc20Interface.encodeFunctionData('transfer', [
195
+ this.params.vault,
196
+ srcAmount,
197
+ ]),
198
+ Vault.interface.encodeFunctionData('swap', [
199
+ srcToken,
200
+ destToken,
201
+ this.augustusAddress,
202
+ ]),
203
+ ],
204
+ values: ['0', '0'],
205
+ networkFee: '0',
206
+ };
207
+ }
208
+
209
+ async updatePoolState(): Promise<void> {
210
+ if (!this.supportedTokens.length) {
211
+ const tokenAddresses = await QuickPerpsEventPool.getWhitelistedTokens(
212
+ this.params.vault,
213
+ 'latest',
214
+ this.dexHelper.multiContract,
215
+ );
216
+
217
+ const decimalsCallData =
218
+ QuickPerps.erc20Interface.encodeFunctionData('decimals');
219
+ const tokenBalanceMultiCall = tokenAddresses.map(t => ({
220
+ target: t,
221
+ callData: decimalsCallData,
222
+ }));
223
+ const res = (
224
+ await this.dexHelper.multiContract.methods
225
+ .aggregate(tokenBalanceMultiCall)
226
+ .call()
227
+ ).returnData;
228
+
229
+ const tokenDecimals = res.map((r: any) =>
230
+ parseInt(
231
+ QuickPerps.erc20Interface
232
+ .decodeFunctionResult('decimals', r)[0]
233
+ .toString(),
234
+ ),
235
+ );
236
+
237
+ this.supportedTokens = tokenAddresses.map((t, i) => ({
238
+ address: t,
239
+ decimals: tokenDecimals[i],
240
+ }));
241
+ }
242
+
243
+ const erc20BalanceCalldata = QuickPerps.erc20Interface.encodeFunctionData(
244
+ 'balanceOf',
245
+ [this.params.vault],
246
+ );
247
+ const tokenBalanceMultiCall = this.supportedTokens.map(t => ({
248
+ target: t.address,
249
+ callData: erc20BalanceCalldata,
250
+ }));
251
+ const res = (
252
+ await this.dexHelper.multiContract.methods
253
+ .aggregate(tokenBalanceMultiCall)
254
+ .call()
255
+ ).returnData;
256
+ const tokenBalances = res.map((r: any) =>
257
+ BigInt(
258
+ QuickPerps.erc20Interface
259
+ .decodeFunctionResult('balanceOf', r)[0]
260
+ .toString(),
261
+ ),
262
+ );
263
+ const tokenBalancesUSD = await Promise.all(
264
+ this.supportedTokens.map((t, i) =>
265
+ this.dexHelper.getTokenUSDPrice(t, tokenBalances[i]),
266
+ ),
267
+ );
268
+ this.vaultUSDBalance = tokenBalancesUSD.reduce(
269
+ (sum: number, curr: number) => sum + curr,
270
+ );
271
+ }
272
+
273
+ // Returns list of top pools based on liquidity. Max
274
+ // limit number pools should be returned.
275
+ async getTopPoolsForToken(
276
+ _tokenAddress: Address,
277
+ limit: number,
278
+ ): Promise<PoolLiquidity[]> {
279
+ const tokenAddress = _tokenAddress.toLowerCase();
280
+ if (!this.supportedTokens.some(t => t.address === tokenAddress)) return [];
281
+ return [
282
+ {
283
+ exchange: this.dexKey,
284
+ address: this.params.vault,
285
+ connectorTokens: this.supportedTokens.filter(
286
+ t => t.address !== tokenAddress,
287
+ ),
288
+ liquidityUSD: this.vaultUSDBalance,
289
+ },
290
+ ];
291
+ }
292
+ }
@@ -0,0 +1,97 @@
1
+ import { Address } from '../../types';
2
+ import { Api3FeedSubscriberState } from '../../lib/api3-feed';
3
+
4
+ export type PoolState = {
5
+ primaryPrices: { [poolAddress: string]: Api3FeedSubscriberState };
6
+ secondaryPrices: FastPriceFeedState;
7
+ vault: VaultState;
8
+ usdq: USDQState;
9
+ };
10
+
11
+ export type FastPriceFeedState = {
12
+ lastUpdatedAt: number;
13
+ prices: { [tokenAddress: string]: bigint };
14
+ };
15
+
16
+ export type VaultState = {
17
+ usdqAmounts: { [tokenAddress: string]: bigint };
18
+ };
19
+
20
+ export type USDQState = {
21
+ totalSupply: bigint;
22
+ };
23
+
24
+ export type QuickPerpsData = {
25
+ // TODO: QuickPerpsData is the dex data that is
26
+ // returned by the API that can be used for
27
+ // tx building. The data structure should be minimal.
28
+ // Complete me!
29
+ };
30
+
31
+ export type DexParams = {
32
+ vault: Address;
33
+ reader: Address;
34
+ priceFeed: Address;
35
+ fastPriceFeed: Address;
36
+ fastPriceEvents: Address;
37
+ // Last three param can be fetched on chain by calling
38
+ // vaultAddress.priceFeed() => priceFeed
39
+ // priceFeed.secondaryPriceFeed() => fastPriceFeed
40
+ // fastPriceFeed.fastPriceEvents() => fastPriceEvents
41
+ // It is added as constants to avoid unnecessary
42
+ // sequential onchain calls
43
+ usdq: Address;
44
+ };
45
+
46
+ export type FastPriceFeedConfig = {
47
+ priceDuration: number;
48
+ maxDeviationBasisPoints: bigint;
49
+ favorFastPrice: Record<string, boolean>;
50
+ spreadBasisPointsIfInactive: bigint;
51
+ spreadBasisPointsIfChainError: bigint;
52
+ maxPriceUpdateDelay: number;
53
+ };
54
+
55
+ export type VaultPriceFeedConfig = {
56
+ isSecondaryPriceEnabled: boolean;
57
+ strictStableTokens: { [address: string]: boolean };
58
+ spreadBasisPoints: { [address: string]: bigint };
59
+ adjustmentBasisPoints: { [address: string]: bigint };
60
+ isAdjustmentAdditive: { [address: string]: boolean };
61
+ priceDecimals: { [address: string]: number };
62
+ maxStrictPriceDeviation: bigint;
63
+ };
64
+
65
+ export type VaultConfig = {
66
+ tokenDecimals: { [address: string]: number };
67
+ stableTokens: { [address: string]: boolean };
68
+ tokenWeights: { [address: string]: bigint };
69
+ stableSwapFeeBasisPoints: bigint;
70
+ swapFeeBasisPoints: bigint;
71
+ stableTaxBasisPoints: bigint;
72
+ taxBasisPoints: bigint;
73
+ hasDynamicFees: bigint;
74
+ includeAmmPrice: boolean;
75
+ useSwapPricing: boolean;
76
+ totalTokenWeights: bigint;
77
+ };
78
+
79
+ export type PoolConfig = {
80
+ vaultAddress: Address;
81
+ readerAddress: Address;
82
+ priceFeed: Address;
83
+ fastPriceFeed: Address;
84
+ fastPriceEvents: Address;
85
+ usdqAddress: Address;
86
+ tokenAddresses: Address[];
87
+ vaultConfig: VaultConfig;
88
+ vaultPriceFeedConfig: VaultPriceFeedConfig;
89
+ fastPriceFeedConfig: FastPriceFeedConfig;
90
+ api3ServerV1: {
91
+ [address: string]: {
92
+ proxy: Address;
93
+ api3ServerV1: Address;
94
+ dataFeedId: string;
95
+ };
96
+ };
97
+ };
@@ -0,0 +1,83 @@
1
+ import _ from 'lodash';
2
+ import { Interface } from '@ethersproject/abi';
3
+ import { DeepReadonly } from 'ts-essentials';
4
+ import { PartialEventSubscriber } from '../../composed-event-subscriber';
5
+ import {
6
+ Address,
7
+ MultiCallInput,
8
+ MultiCallOutput,
9
+ Logger,
10
+ Log,
11
+ BlockHeader,
12
+ } from '../../types';
13
+ import { USDQState } from './types';
14
+ import ERC20ABI from '../../abi/erc20.json';
15
+ import { Lens } from '../../lens';
16
+ import { NULL_ADDRESS } from '../../constants';
17
+
18
+ export class USDQ<State> extends PartialEventSubscriber<State, USDQState> {
19
+ static readonly interface = new Interface(ERC20ABI);
20
+
21
+ constructor(
22
+ private usdqAddress: Address,
23
+ lens: Lens<DeepReadonly<State>, DeepReadonly<USDQState>>,
24
+ logger: Logger,
25
+ ) {
26
+ super([usdqAddress], lens, logger);
27
+ }
28
+
29
+ getTotalSupply(state: DeepReadonly<State>) {
30
+ return this.lens.get()(state).totalSupply;
31
+ }
32
+
33
+ public processLog(
34
+ state: DeepReadonly<USDQState>,
35
+ log: Readonly<Log>,
36
+ blockHeader: Readonly<BlockHeader>,
37
+ ): DeepReadonly<USDQState> | null {
38
+ try {
39
+ const parsed = USDQ.interface.parseLog(log);
40
+ const _state: USDQState = _.cloneDeep(state);
41
+ switch (parsed.name) {
42
+ case 'Transfer': {
43
+ const fromAddress = parsed.args.src;
44
+ const toAddress = parsed.args.dst;
45
+ if (fromAddress === NULL_ADDRESS) {
46
+ _state.totalSupply += BigInt(parsed.args.wad.toString());
47
+ } else if (toAddress === NULL_ADDRESS) {
48
+ _state.totalSupply -= BigInt(parsed.args.wad.toString());
49
+ }
50
+ return _state;
51
+ }
52
+ default:
53
+ return null;
54
+ }
55
+ } catch (e) {
56
+ this.logger.error('Failed to parse log', e);
57
+ return null;
58
+ }
59
+ }
60
+
61
+ public getGenerateStateMultiCallInputs(): MultiCallInput[] {
62
+ return [
63
+ {
64
+ target: this.usdqAddress,
65
+ callData: USDQ.interface.encodeFunctionData('totalSupply'),
66
+ },
67
+ ];
68
+ }
69
+
70
+ public generateState(
71
+ multicallOutputs: MultiCallOutput[],
72
+ blockNumber?: number | 'latest',
73
+ ): DeepReadonly<USDQState> {
74
+ return {
75
+ totalSupply: BigInt(
76
+ USDQ.interface.decodeFunctionResult(
77
+ 'totalSupply',
78
+ multicallOutputs[0],
79
+ )[0],
80
+ ),
81
+ };
82
+ }
83
+ }