@paraswap/dex-lib 3.4.2-balancer-v2-single-swap.0 → 3.4.2

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 (110) hide show
  1. package/README.md +377 -377
  2. package/build/config.js +9 -5
  3. package/build/config.js.map +1 -1
  4. package/build/dex/algebra/algebra.js +25 -25
  5. package/build/dex/balancer-v1/balancer-v1.js +34 -34
  6. package/build/dex/balancer-v2/balancer-v2.d.ts +1 -1
  7. package/build/dex/balancer-v2/balancer-v2.js +65 -78
  8. package/build/dex/balancer-v2/balancer-v2.js.map +1 -1
  9. package/build/dex/camelot/camelot.js +27 -27
  10. package/build/dex/kyberdmm/kyberdmm.js +27 -27
  11. package/build/dex/pancakeswap-v3/pancakeswap-v3.js +25 -25
  12. package/build/dex/solidly/solidly.js +27 -27
  13. package/build/dex/solidly-v3/solidly-v3.js +25 -25
  14. package/build/dex/swaap-v2/config.js +15 -0
  15. package/build/dex/swaap-v2/config.js.map +1 -1
  16. package/build/dex/uniswap-v2/uniswap-v2.js +26 -26
  17. package/build/dex/uniswap-v3/uniswap-v3.js +25 -25
  18. package/funding.json +5 -5
  19. package/package.json +1 -1
  20. package/src/config.ts +500 -496
  21. package/src/constants.ts +67 -67
  22. package/src/dex/aave-v2/aave-v2.ts +409 -409
  23. package/src/dex/aave-v3/aave-v3.ts +320 -320
  24. package/src/dex/algebra/algebra.ts +1180 -1180
  25. package/src/dex/angle-staked-stable/angle-staked-stable.ts +347 -347
  26. package/src/dex/augustus-rfq.ts +180 -180
  27. package/src/dex/bProtocol/bProtocol.ts +108 -108
  28. package/src/dex/balancer-v1/balancer-v1.ts +572 -572
  29. package/src/dex/balancer-v2/balancer-v2-e2e.test.ts +1699 -1699
  30. package/src/dex/balancer-v2/balancer-v2.ts +1669 -1701
  31. package/src/dex/bancor/bancor.ts +128 -128
  32. package/src/dex/camelot/camelot.ts +873 -873
  33. package/src/dex/compound/compound.ts +107 -107
  34. package/src/dex/curve-v1/config.ts +1179 -1179
  35. package/src/dex/curve-v1/curve-v1-e2e.test.ts +507 -507
  36. package/src/dex/curve-v1/curve-v1.ts +1315 -1315
  37. package/src/dex/curve-v1-factory/config.ts +1018 -1018
  38. package/src/dex/curve-v1-factory/curve-v1-factory.ts +1288 -1288
  39. package/src/dex/curve-v2/curve-v2.ts +440 -440
  40. package/src/dex/dexalot/dexalot.ts +1100 -1100
  41. package/src/dex/dodo-v1/dodo-v1.ts +132 -132
  42. package/src/dex/dodo-v2.ts +266 -266
  43. package/src/dex/etherfi.ts +239 -239
  44. package/src/dex/generic-rfq/generic-rfq.ts +431 -431
  45. package/src/dex/gmx/gmx.ts +321 -321
  46. package/src/dex/hashflow/hashflow.ts +1135 -1135
  47. package/src/dex/idex.ts +254 -254
  48. package/src/dex/index.ts +357 -357
  49. package/src/dex/jarvis-v6/jarvis-v6.ts +509 -509
  50. package/src/dex/jarvis.ts +262 -262
  51. package/src/dex/kyberdmm/kyberdmm.ts +649 -649
  52. package/src/dex/lido/lido.ts +110 -110
  53. package/src/dex/maker-psm/maker-psm.ts +592 -592
  54. package/src/dex/maverick-v1/maverick-v1.ts +537 -537
  55. package/src/dex/nerve/config.ts +635 -635
  56. package/src/dex/nerve/nerve-e2e.test.ts +731 -731
  57. package/src/dex/nerve/nerve-events.test.ts +911 -911
  58. package/src/dex/nerve/nerve.ts +467 -467
  59. package/src/dex/onebit/onebit.ts +113 -113
  60. package/src/dex/pancakeswap-v3/pancakeswap-v3.ts +1150 -1150
  61. package/src/dex/paraswap-limit-orders/config.ts +60 -60
  62. package/src/dex/paraswap-limit-orders/paraswap-limit-orders-e2e.test.ts +137 -137
  63. package/src/dex/paraswap-limit-orders/paraswap-limit-orders-integration.test.ts +328 -328
  64. package/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts +631 -631
  65. package/src/dex/polygon-migrator/polygon-migrator.ts +194 -194
  66. package/src/dex/quick-perps/quick-perps.ts +337 -337
  67. package/src/dex/quickswap/config.ts +16 -16
  68. package/src/dex/quickswap/quickswap-e2e.test.ts +384 -384
  69. package/src/dex/smoothy/smoothy.ts +110 -110
  70. package/src/dex/solidly/config.ts +225 -225
  71. package/src/dex/solidly/solidly-e2e.test.ts +1421 -1421
  72. package/src/dex/solidly/solidly-integration.test.ts +1753 -1753
  73. package/src/dex/solidly/solidly.ts +687 -687
  74. package/src/dex/solidly-v3/solidly-v3.ts +968 -968
  75. package/src/dex/spark/spark.ts +284 -284
  76. package/src/dex/stable-pool/stable-pool.ts +129 -129
  77. package/src/dex/swaap-v2/config.ts +15 -0
  78. package/src/dex/swaap-v2/swaap-v2-e2e.test.ts +233 -17
  79. package/src/dex/swaap-v2/swaap-v2-integration.test.ts +1 -1
  80. package/src/dex/swaap-v2/swaap-v2.ts +1056 -1056
  81. package/src/dex/swell/swell.ts +312 -312
  82. package/src/dex/synthetix/synthetix.ts +420 -420
  83. package/src/dex/trader-joe-v2.1/trader-joe-v2.1.ts +180 -180
  84. package/src/dex/uniswap-v2/config.ts +637 -637
  85. package/src/dex/uniswap-v2/constants.ts +58 -58
  86. package/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts +1257 -1257
  87. package/src/dex/uniswap-v2/uniswap-v2-e2e-base.test.ts +42 -42
  88. package/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +1295 -1295
  89. package/src/dex/uniswap-v2/uniswap-v2-e2e-mainnet.test.ts +1834 -1834
  90. package/src/dex/uniswap-v2/uniswap-v2-e2e-polygon.test.ts +871 -871
  91. package/src/dex/uniswap-v2/uniswap-v2.ts +1117 -1117
  92. package/src/dex/uniswap-v3/uniswap-v3.ts +1501 -1501
  93. package/src/dex/uniswap-v3.ts +242 -242
  94. package/src/dex/weth/config.ts +73 -73
  95. package/src/dex/weth/weth.ts +230 -230
  96. package/src/dex/wombat/wombat.ts +467 -467
  97. package/src/dex/woo-fi-v2/woo-fi-v2.ts +529 -529
  98. package/src/dex/wsteth/wsteth.ts +304 -304
  99. package/src/dex/zerox/index.ts +328 -328
  100. package/src/executor/Executor01BytecodeBuilder.ts +474 -474
  101. package/src/executor/Executor02BytecodeBuilder.ts +1392 -1392
  102. package/src/executor/ExecutorBytecodeBuilder.ts +390 -390
  103. package/src/executor/ExecutorDetector.ts +131 -131
  104. package/src/executor/constants.ts +66 -66
  105. package/src/executor/executor01-bytecode-builder-e2e.test.ts +1478 -1478
  106. package/src/executor/utils.ts +62 -62
  107. package/src/router/simpleswap.ts +366 -366
  108. package/src/types.ts +342 -342
  109. package/tests/constants-e2e.ts +1632 -1622
  110. package/tests/generate-new-limit-order.ts +107 -107
@@ -1,1117 +1,1117 @@
1
- import { AbiCoder, Interface } from '@ethersproject/abi';
2
- import { pack } from '@ethersproject/solidity';
3
- import _ from 'lodash';
4
- import { AsyncOrSync, DeepReadonly } from 'ts-essentials';
5
- import erc20ABI from '../../abi/erc20.json';
6
- import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
7
- import {
8
- AdapterExchangeParam,
9
- Address,
10
- ExchangePrices,
11
- PoolPrices,
12
- Log,
13
- Logger,
14
- PoolLiquidity,
15
- SimpleExchangeParam,
16
- Token,
17
- TxInfo,
18
- TransferFeeParams,
19
- DexExchangeParam,
20
- } from '../../types';
21
- import {
22
- UniswapData,
23
- UniswapDataLegacy,
24
- UniswapParam,
25
- UniswapPool,
26
- UniswapV2Data,
27
- UniswapV2Functions,
28
- UniswapV2FunctionsV6,
29
- UniswapV2ParamsDirect,
30
- UniswapV2ParamsDirectBase,
31
- UniswapV2PoolOrderedParams,
32
- } from './types';
33
- import { IDex } from '../idex';
34
- import {
35
- DEST_TOKEN_PARASWAP_TRANSFERS,
36
- ETHER_ADDRESS,
37
- Network,
38
- NULL_ADDRESS,
39
- SRC_TOKEN_PARASWAP_TRANSFERS,
40
- SUBGRAPH_TIMEOUT,
41
- } from '../../constants';
42
- import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
43
- import { SimpleExchange } from '../simple-exchange';
44
- import { NumberAsString, SwapSide } from '@paraswap/core';
45
- import { IDexHelper } from '../../dex-helper';
46
- import {
47
- getDexKeysWithNetwork,
48
- isETHAddress,
49
- prependWithOx,
50
- getBigIntPow,
51
- uuidToBytes16,
52
- } from '../../utils';
53
- import uniswapV2ABI from '../../abi/uniswap-v2/uniswap-v2-pool.json';
54
- import uniswapV2factoryABI from '../../abi/uniswap-v2/uniswap-v2-factory.json';
55
- import ParaSwapABI from '../../abi/IParaswap.json';
56
- import UniswapV2ExchangeRouterABI from '../../abi/UniswapV2ExchangeRouter.json';
57
- import { Contract } from 'web3-eth-contract';
58
- import { UniswapV2Config, Adapters } from './config';
59
- import { Uniswapv2ConstantProductPool } from './uniswap-v2-constant-product-pool';
60
- import { applyTransferFee } from '../../lib/token-transfer-fee';
61
- import _rebaseTokens from '../../rebase-tokens.json';
62
- import { Flag, SpecialDex } from '../../executor/types';
63
- import {
64
- hexZeroPad,
65
- hexlify,
66
- solidityPack,
67
- hexDataLength,
68
- id,
69
- hexConcat,
70
- } from 'ethers/lib/utils';
71
- import { BigNumber } from 'ethers';
72
-
73
- const rebaseTokens = _rebaseTokens as { chainId: number; address: string }[];
74
-
75
- const rebaseTokensSetsByChain = rebaseTokens.reduce<{
76
- [chainId: number]: Set<string>;
77
- }>((acc, curr) => {
78
- if (!acc[curr.chainId]) {
79
- acc[curr.chainId] = new Set();
80
- }
81
-
82
- acc[curr.chainId].add(curr.address.toLowerCase());
83
-
84
- return acc;
85
- }, {});
86
-
87
- const DefaultUniswapV2PoolGasCost = 90 * 1000;
88
-
89
- export const RESERVE_LIMIT = 2n ** 112n - 1n;
90
-
91
- const LogCallTopics = [
92
- '0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1', // event Sync(uint112 reserve0, uint112 reserve1) // uni-V2 and most forks
93
- '0xcf2aa50876cdfbb541206f89af0ee78d44a2abf8d328e37fa4917f982149848a', // event Sync(uint256 reserve0, uint256 reserve1) // commonly seen in solidly & forks
94
- ];
95
-
96
- interface UniswapV2PoolState {
97
- reserves0: string;
98
- reserves1: string;
99
- feeCode: number;
100
- }
101
-
102
- const uniswapV2PoolIface = new Interface(uniswapV2ABI);
103
- const erc20iface = new Interface(erc20ABI);
104
- const coder = new AbiCoder();
105
-
106
- export const directUniswapFunctionName = [
107
- UniswapV2Functions.swapOnUniswap,
108
- UniswapV2Functions.buyOnUniswap,
109
- UniswapV2Functions.swapOnUniswapFork,
110
- UniswapV2Functions.buyOnUniswapFork,
111
- UniswapV2Functions.swapOnUniswapV2Fork,
112
- UniswapV2Functions.buyOnUniswapV2Fork,
113
- ];
114
-
115
- export const directUniswapFunctionNameV6 = [
116
- UniswapV2FunctionsV6.swap,
117
- UniswapV2FunctionsV6.buy,
118
- ];
119
-
120
- export interface UniswapV2Pair {
121
- token0: Token;
122
- token1: Token;
123
- exchange?: Address;
124
- pool?: UniswapV2EventPool;
125
- }
126
-
127
- export class UniswapV2EventPool extends StatefulEventSubscriber<UniswapV2PoolState> {
128
- decoder = (log: Log) => this.iface.parseLog(log);
129
-
130
- constructor(
131
- parentName: string,
132
- protected dexHelper: IDexHelper,
133
- private poolAddress: Address,
134
- private token0: Token,
135
- private token1: Token,
136
- // feeCode is ignored if DynamicFees is set to true
137
- private feeCode: number,
138
- logger: Logger,
139
- private dynamicFees = false,
140
- // feesMultiCallData is only used if dynamicFees is set to true
141
- private feesMultiCallEntry?: { target: Address; callData: string },
142
- private feesMultiCallDecoder?: (values: any[]) => number,
143
- private iface: Interface = uniswapV2PoolIface,
144
- ) {
145
- super(
146
- parentName,
147
- (token0.symbol || token0.address) +
148
- '-' +
149
- (token1.symbol || token1.address) +
150
- ' pool',
151
- dexHelper,
152
- logger,
153
- );
154
- }
155
-
156
- protected processLog(
157
- state: DeepReadonly<UniswapV2PoolState>,
158
- log: Readonly<Log>,
159
- ): AsyncOrSync<DeepReadonly<UniswapV2PoolState> | null> {
160
- if (!LogCallTopics.includes(log.topics[0])) return null;
161
-
162
- const event = this.decoder(log);
163
- switch (event.name) {
164
- case 'Sync':
165
- return {
166
- reserves0: event.args.reserve0.toString(),
167
- reserves1: event.args.reserve1.toString(),
168
- feeCode: state.feeCode,
169
- };
170
- }
171
- return null;
172
- }
173
-
174
- async generateState(
175
- blockNumber: number | 'latest' = 'latest',
176
- ): Promise<DeepReadonly<UniswapV2PoolState>> {
177
- let calldata = [
178
- {
179
- target: this.poolAddress,
180
- callData: this.iface.encodeFunctionData('getReserves', []),
181
- },
182
- ];
183
-
184
- if (this.dynamicFees) {
185
- calldata.push(this.feesMultiCallEntry!);
186
- }
187
-
188
- const data: { returnData: any[] } =
189
- await this.dexHelper.multiContract.methods
190
- .aggregate(calldata)
191
- .call({}, blockNumber);
192
-
193
- const decodedData = coder.decode(
194
- ['uint112', 'uint112', 'uint32'],
195
- data.returnData[0],
196
- );
197
-
198
- return {
199
- reserves0: decodedData[0].toString(),
200
- reserves1: decodedData[1].toString(),
201
- feeCode: this.dynamicFees
202
- ? this.feesMultiCallDecoder!(data.returnData[1])
203
- : this.feeCode,
204
- };
205
- }
206
- }
207
-
208
- function encodePools(
209
- pools: UniswapPool[],
210
- feeFactor: number,
211
- ): NumberAsString[] {
212
- return pools.map(({ fee, direction, address }) => {
213
- return (
214
- (BigInt(feeFactor - fee) << 161n) +
215
- ((direction ? 0n : 1n) << 160n) +
216
- BigInt(address)
217
- ).toString();
218
- });
219
- }
220
-
221
- export class UniswapV2
222
- extends SimpleExchange
223
- implements IDex<UniswapV2Data, UniswapParam | UniswapV2ParamsDirect>
224
- {
225
- pairs: { [key: string]: UniswapV2Pair } = {};
226
- feeFactor = 10000;
227
- factory: Contract;
228
-
229
- routerInterface: Interface;
230
- exchangeRouterInterface: Interface;
231
-
232
- needWrapNative = true;
233
-
234
- logger: Logger;
235
-
236
- static directFunctionName = directUniswapFunctionName;
237
- static directFunctionNameV6 = directUniswapFunctionNameV6;
238
-
239
- readonly hasConstantPriceLargeAmounts = false;
240
- readonly isFeeOnTransferSupported: boolean = true;
241
- readonly SRC_TOKEN_DEX_TRANSFERS = 1;
242
- readonly DEST_TOKEN_DEX_TRANSFERS = 1;
243
-
244
- public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
245
- getDexKeysWithNetwork(UniswapV2Config);
246
-
247
- constructor(
248
- protected network: Network,
249
- dexKey: string,
250
- protected dexHelper: IDexHelper,
251
- protected isDynamicFees = false,
252
- protected factoryAddress: Address = UniswapV2Config[dexKey][network]
253
- .factoryAddress,
254
- protected subgraphURL: string | undefined = UniswapV2Config[dexKey] &&
255
- UniswapV2Config[dexKey][network].subgraphURL,
256
- protected initCode: string = UniswapV2Config[dexKey][network].initCode,
257
- // feeCode is ignored when isDynamicFees is set to true
258
- protected feeCode: number = UniswapV2Config[dexKey][network].feeCode,
259
- protected poolGasCost: number = (UniswapV2Config[dexKey] &&
260
- UniswapV2Config[dexKey][network].poolGasCost) ??
261
- DefaultUniswapV2PoolGasCost,
262
- protected decoderIface: Interface = uniswapV2PoolIface,
263
- protected adapters = (UniswapV2Config[dexKey] &&
264
- UniswapV2Config[dexKey][network].adapters) ??
265
- Adapters[network],
266
- protected router = (UniswapV2Config[dexKey] &&
267
- UniswapV2Config[dexKey][network].router) ??
268
- dexHelper.config.data.uniswapV2ExchangeRouterAddress,
269
- ) {
270
- super(dexHelper, dexKey);
271
- this.logger = dexHelper.getLogger(dexKey);
272
-
273
- this.factory = new dexHelper.web3Provider.eth.Contract(
274
- uniswapV2factoryABI as any,
275
- factoryAddress,
276
- );
277
-
278
- this.routerInterface = new Interface(ParaSwapABI);
279
- this.exchangeRouterInterface = new Interface(UniswapV2ExchangeRouterABI);
280
- }
281
-
282
- // getFeesMultiCallData should be override
283
- // when isDynamicFees is set to true
284
- protected getFeesMultiCallData(pair: UniswapV2Pair):
285
- | undefined
286
- | {
287
- callEntry: { target: Address; callData: string };
288
- callDecoder: (values: any[]) => number;
289
- } {
290
- return undefined;
291
- }
292
-
293
- protected async addPool(
294
- pair: UniswapV2Pair,
295
- reserves0: string,
296
- reserves1: string,
297
- feeCode: number,
298
- blockNumber: number,
299
- ) {
300
- const { callEntry, callDecoder } = this.getFeesMultiCallData(pair) || {};
301
- pair.pool = new UniswapV2EventPool(
302
- this.dexKey,
303
- this.dexHelper,
304
- pair.exchange!,
305
- pair.token0,
306
- pair.token1,
307
- feeCode,
308
- this.logger,
309
- this.isDynamicFees,
310
- callEntry,
311
- callDecoder,
312
- this.decoderIface,
313
- );
314
- pair.pool.addressesSubscribed.push(pair.exchange!);
315
-
316
- await pair.pool.initialize(blockNumber, {
317
- state: { reserves0, reserves1, feeCode },
318
- });
319
- }
320
-
321
- async getBuyPrice(
322
- priceParams: UniswapV2PoolOrderedParams,
323
- destAmount: bigint,
324
- ): Promise<bigint> {
325
- return Uniswapv2ConstantProductPool.getBuyPrice(
326
- priceParams,
327
- destAmount,
328
- this.feeFactor,
329
- );
330
- }
331
-
332
- async getSellPrice(
333
- priceParams: UniswapV2PoolOrderedParams,
334
- srcAmount: bigint,
335
- ): Promise<bigint> {
336
- return Uniswapv2ConstantProductPool.getSellPrice(
337
- priceParams,
338
- srcAmount,
339
- this.feeFactor,
340
- );
341
- }
342
-
343
- async getBuyPricePath(
344
- amount: bigint,
345
- params: UniswapV2PoolOrderedParams[],
346
- ): Promise<bigint> {
347
- let price = amount;
348
- for (const param of params.reverse()) {
349
- price = await this.getBuyPrice(param, price);
350
- }
351
- return price;
352
- }
353
-
354
- async getSellPricePath(
355
- amount: bigint,
356
- params: UniswapV2PoolOrderedParams[],
357
- ): Promise<bigint> {
358
- let price = amount;
359
- for (const param of params) {
360
- price = await this.getSellPrice(param, price);
361
- }
362
- return price;
363
- }
364
-
365
- async findPair(from: Token, to: Token) {
366
- if (from.address.toLowerCase() === to.address.toLowerCase()) return null;
367
- const [token0, token1] =
368
- from.address.toLowerCase() < to.address.toLowerCase()
369
- ? [from, to]
370
- : [to, from];
371
-
372
- const key = `${token0.address.toLowerCase()}-${token1.address.toLowerCase()}`;
373
- let pair = this.pairs[key];
374
- if (pair) return pair;
375
- const exchange = await this.factory.methods
376
- .getPair(token0.address, token1.address)
377
- .call();
378
- if (exchange === NULL_ADDRESS) {
379
- pair = { token0, token1 };
380
- } else {
381
- pair = { token0, token1, exchange };
382
- }
383
- this.pairs[key] = pair;
384
- return pair;
385
- }
386
-
387
- async getManyPoolReserves(
388
- pairs: UniswapV2Pair[],
389
- blockNumber: number,
390
- ): Promise<UniswapV2PoolState[]> {
391
- try {
392
- const multiCallFeeData = pairs.map(pair =>
393
- this.getFeesMultiCallData(pair),
394
- );
395
- const calldata = pairs
396
- .map((pair, i) => {
397
- let calldata = [
398
- {
399
- target: pair.token0.address,
400
- callData: erc20iface.encodeFunctionData('balanceOf', [
401
- pair.exchange!,
402
- ]),
403
- },
404
- {
405
- target: pair.token1.address,
406
- callData: erc20iface.encodeFunctionData('balanceOf', [
407
- pair.exchange!,
408
- ]),
409
- },
410
- ];
411
- if (this.isDynamicFees) calldata.push(multiCallFeeData[i]!.callEntry);
412
- return calldata;
413
- })
414
- .flat();
415
-
416
- // const data: { returnData: any[] } =
417
- // await this.dexHelper.multiContract.callStatic.aggregate(calldata, {
418
- // blockTag: blockNumber,
419
- // });
420
-
421
- const data: { returnData: any[] } =
422
- await this.dexHelper.multiContract.methods
423
- .aggregate(calldata)
424
- .call({}, blockNumber);
425
-
426
- const returnData = _.chunk(data.returnData, this.isDynamicFees ? 3 : 2);
427
- return pairs.map((pair, i) => ({
428
- reserves0: coder.decode(['uint256'], returnData[i][0])[0].toString(),
429
- reserves1: coder.decode(['uint256'], returnData[i][1])[0].toString(),
430
- feeCode: this.isDynamicFees
431
- ? multiCallFeeData[i]!.callDecoder(returnData[i][2])
432
- : this.feeCode,
433
- }));
434
- } catch (e) {
435
- this.logger.error(
436
- `Error_getManyPoolReserves could not get reserves with error:`,
437
- e,
438
- );
439
- return [];
440
- }
441
- }
442
-
443
- async batchCatchUpPairs(pairs: [Token, Token][], blockNumber: number) {
444
- if (!blockNumber) return;
445
- const pairsToFetch: UniswapV2Pair[] = [];
446
- for (const _pair of pairs) {
447
- const pair = await this.findPair(_pair[0], _pair[1]);
448
- if (!(pair && pair.exchange)) continue;
449
- if (!pair.pool) {
450
- pairsToFetch.push(pair);
451
- } else if (!pair.pool.getState(blockNumber)) {
452
- pairsToFetch.push(pair);
453
- }
454
- }
455
-
456
- if (!pairsToFetch.length) return;
457
-
458
- const reserves = await this.getManyPoolReserves(pairsToFetch, blockNumber);
459
-
460
- if (reserves.length !== pairsToFetch.length) {
461
- this.logger.error(
462
- `Error_getManyPoolReserves didn't get any pool reserves`,
463
- );
464
- }
465
-
466
- for (let i = 0; i < pairsToFetch.length; i++) {
467
- const pairState = reserves[i];
468
- const pair = pairsToFetch[i];
469
- if (!pair.pool) {
470
- await this.addPool(
471
- pair,
472
- pairState.reserves0,
473
- pairState.reserves1,
474
- pairState.feeCode,
475
- blockNumber,
476
- );
477
- } else pair.pool.setState(pairState, blockNumber);
478
- }
479
- }
480
-
481
- async getPairOrderedParams(
482
- from: Token,
483
- to: Token,
484
- blockNumber: number,
485
- tokenDexTransferFee: number,
486
- ): Promise<UniswapV2PoolOrderedParams | null> {
487
- const pair = await this.findPair(from, to);
488
- if (!(pair && pair.pool && pair.exchange)) return null;
489
- const pairState = pair.pool.getState(blockNumber);
490
- if (!pairState) {
491
- this.logger.error(
492
- `Error_orderPairParams expected reserves, got none (maybe the pool doesn't exist) ${
493
- from.symbol || from.address
494
- } ${to.symbol || to.address}`,
495
- );
496
- return null;
497
- }
498
- const fee = (pairState.feeCode + tokenDexTransferFee).toString();
499
- const pairReversed =
500
- pair.token1.address.toLowerCase() === from.address.toLowerCase();
501
- if (pairReversed) {
502
- return {
503
- tokenIn: from.address,
504
- tokenOut: to.address,
505
- reservesIn: pairState.reserves1,
506
- reservesOut: pairState.reserves0,
507
- fee,
508
- direction: false,
509
- exchange: pair.exchange,
510
- };
511
- }
512
- return {
513
- tokenIn: from.address,
514
- tokenOut: to.address,
515
- reservesIn: pairState.reserves0,
516
- reservesOut: pairState.reserves1,
517
- fee,
518
- direction: true,
519
- exchange: pair.exchange,
520
- };
521
- }
522
-
523
- async getPoolIdentifiers(
524
- _from: Token,
525
- _to: Token,
526
- side: SwapSide,
527
- blockNumber: number,
528
- ): Promise<string[]> {
529
- const from = this.dexHelper.config.wrapETH(_from);
530
- const to = this.dexHelper.config.wrapETH(_to);
531
-
532
- if (from.address.toLowerCase() === to.address.toLowerCase()) {
533
- return [];
534
- }
535
-
536
- const tokenAddress = [from.address.toLowerCase(), to.address.toLowerCase()]
537
- .sort((a, b) => (a > b ? 1 : -1))
538
- .join('_');
539
-
540
- const poolIdentifier = `${this.dexKey}_${tokenAddress}`;
541
- return [poolIdentifier];
542
- }
543
-
544
- async getPricesVolume(
545
- _from: Token,
546
- _to: Token,
547
- amounts: bigint[],
548
- side: SwapSide,
549
- blockNumber: number,
550
- // list of pool identifiers to use for pricing, if undefined use all pools
551
- limitPools?: string[],
552
- transferFees: TransferFeeParams = {
553
- srcFee: 0,
554
- destFee: 0,
555
- srcDexFee: 0,
556
- destDexFee: 0,
557
- },
558
- ): Promise<ExchangePrices<UniswapV2Data> | null> {
559
- try {
560
- const from = this.dexHelper.config.wrapETH(_from);
561
- const to = this.dexHelper.config.wrapETH(_to);
562
-
563
- if (from.address.toLowerCase() === to.address.toLowerCase()) {
564
- return null;
565
- }
566
-
567
- const tokenAddress = [
568
- from.address.toLowerCase(),
569
- to.address.toLowerCase(),
570
- ]
571
- .sort((a, b) => (a > b ? 1 : -1))
572
- .join('_');
573
-
574
- const poolIdentifier = `${this.dexKey}_${tokenAddress}`;
575
-
576
- if (limitPools && limitPools.every(p => p !== poolIdentifier))
577
- return null;
578
-
579
- await this.batchCatchUpPairs([[from, to]], blockNumber);
580
- const isSell = side === SwapSide.SELL;
581
- const pairParam = await this.getPairOrderedParams(
582
- from,
583
- to,
584
- blockNumber,
585
- transferFees.srcDexFee,
586
- );
587
-
588
- if (!pairParam) return null;
589
-
590
- const unitAmount = getBigIntPow(isSell ? from.decimals : to.decimals);
591
-
592
- const [unitVolumeWithFee, ...amountsWithFee] = applyTransferFee(
593
- [unitAmount, ...amounts],
594
- side,
595
- isSell ? transferFees.srcFee : transferFees.destFee,
596
- isSell ? SRC_TOKEN_PARASWAP_TRANSFERS : DEST_TOKEN_PARASWAP_TRANSFERS,
597
- );
598
-
599
- const unit = isSell
600
- ? await this.getSellPricePath(unitVolumeWithFee, [pairParam])
601
- : await this.getBuyPricePath(unitVolumeWithFee, [pairParam]);
602
-
603
- const prices = isSell
604
- ? await Promise.all(
605
- amountsWithFee.map(amount =>
606
- this.getSellPricePath(amount, [pairParam]),
607
- ),
608
- )
609
- : await Promise.all(
610
- amountsWithFee.map(amount =>
611
- this.getBuyPricePath(amount, [pairParam]),
612
- ),
613
- );
614
-
615
- const [unitOutWithFee, ...outputsWithFee] = applyTransferFee(
616
- [unit, ...prices],
617
- side,
618
- // This part is confusing, because we treat differently SELL and BUY fees
619
- // If Buy, we should apply transfer fee on srcToken on top of dexFee applied earlier
620
- // But for Sell we should apply only one dexFee
621
- isSell ? transferFees.destDexFee : transferFees.srcFee,
622
- isSell ? this.DEST_TOKEN_DEX_TRANSFERS : SRC_TOKEN_PARASWAP_TRANSFERS,
623
- );
624
-
625
- // As uniswapv2 just has one pool per token pair
626
- return [
627
- {
628
- prices: outputsWithFee,
629
- unit: unitOutWithFee,
630
- data: {
631
- router: this.router,
632
- path: [from.address.toLowerCase(), to.address.toLowerCase()],
633
- factory: this.factoryAddress,
634
- initCode: this.initCode,
635
- feeFactor: this.feeFactor,
636
- pools: [
637
- {
638
- address: pairParam.exchange,
639
- fee: parseInt(pairParam.fee),
640
- direction: pairParam.direction,
641
- },
642
- ],
643
- },
644
- exchange: this.dexKey,
645
- poolIdentifier,
646
- gasCost: this.poolGasCost,
647
- poolAddresses: [pairParam.exchange],
648
- },
649
- ];
650
- } catch (e) {
651
- if (blockNumber === 0)
652
- this.logger.error(
653
- `Error_getPricesVolume: Aurelius block manager not yet instantiated`,
654
- );
655
- this.logger.error(`Error_getPrices:`, e);
656
- return null;
657
- }
658
- }
659
-
660
- // Returns estimated gas cost of calldata for this DEX in multiSwap
661
- getCalldataGasCost(poolPrices: PoolPrices<UniswapV2Data>): number | number[] {
662
- return (
663
- CALLDATA_GAS_COST.DEX_OVERHEAD +
664
- CALLDATA_GAS_COST.LENGTH_SMALL +
665
- // ParentStruct header
666
- CALLDATA_GAS_COST.OFFSET_SMALL +
667
- // ParentStruct -> weth
668
- CALLDATA_GAS_COST.ADDRESS +
669
- // ParentStruct -> pools[] header
670
- CALLDATA_GAS_COST.OFFSET_SMALL +
671
- // ParentStruct -> pools[]
672
- CALLDATA_GAS_COST.LENGTH_SMALL +
673
- // ParentStruct -> pools[0]
674
- CALLDATA_GAS_COST.wordNonZeroBytes(22)
675
- );
676
- }
677
-
678
- getAdapters(side: SwapSide): { name: string; index: number }[] | null {
679
- return this.adapters[side];
680
- }
681
-
682
- async getTopPoolsForToken(
683
- tokenAddress: Address,
684
- count: number,
685
- ): Promise<PoolLiquidity[]> {
686
- if (!this.subgraphURL) return [];
687
- const query = `
688
- query ($token: Bytes!, $count: Int) {
689
- pools0: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token0: $token, reserve0_gt: 1, reserve1_gt: 1}) {
690
- id
691
- token0 {
692
- id
693
- decimals
694
- }
695
- token1 {
696
- id
697
- decimals
698
- }
699
- reserveUSD
700
- }
701
- pools1: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token1: $token, reserve0_gt: 1, reserve1_gt: 1}) {
702
- id
703
- token0 {
704
- id
705
- decimals
706
- }
707
- token1 {
708
- id
709
- decimals
710
- }
711
- reserveUSD
712
- }
713
- }`;
714
-
715
- const { data } = await this.dexHelper.httpRequest.post(
716
- this.subgraphURL,
717
- {
718
- query,
719
- variables: { token: tokenAddress.toLowerCase(), count },
720
- },
721
- SUBGRAPH_TIMEOUT,
722
- );
723
-
724
- if (!(data && data.pools0 && data.pools1))
725
- throw new Error("Couldn't fetch the pools from the subgraph");
726
- const pools0 = _.map(data.pools0, pool => ({
727
- exchange: this.dexKey,
728
- address: pool.id.toLowerCase(),
729
- connectorTokens: [
730
- {
731
- address: pool.token1.id.toLowerCase(),
732
- decimals: parseInt(pool.token1.decimals),
733
- },
734
- ],
735
- liquidityUSD: parseFloat(pool.reserveUSD),
736
- }));
737
-
738
- const pools1 = _.map(data.pools1, pool => ({
739
- exchange: this.dexKey,
740
- address: pool.id.toLowerCase(),
741
- connectorTokens: [
742
- {
743
- address: pool.token0.id.toLowerCase(),
744
- decimals: parseInt(pool.token0.decimals),
745
- },
746
- ],
747
- liquidityUSD: parseFloat(pool.reserveUSD),
748
- }));
749
-
750
- return _.slice(
751
- _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]),
752
- 0,
753
- count,
754
- );
755
- }
756
-
757
- protected fixPath(path: Address[], srcToken: Address, destToken: Address) {
758
- return path.map((token: string, i: number) => {
759
- if (
760
- (i === 0 && srcToken.toLowerCase() === ETHER_ADDRESS.toLowerCase()) ||
761
- (i === path.length - 1 &&
762
- destToken.toLowerCase() === ETHER_ADDRESS.toLowerCase())
763
- )
764
- return ETHER_ADDRESS;
765
- return token;
766
- });
767
- }
768
-
769
- getWETHAddress(srcToken: Address, destToken: Address, weth?: Address) {
770
- if (!isETHAddress(srcToken) && !isETHAddress(destToken))
771
- return NULL_ADDRESS;
772
- return weth || this.dexHelper.config.data.wrappedNativeTokenAddress;
773
- }
774
-
775
- getAdapterParam(
776
- srcToken: Address,
777
- destToken: Address,
778
- srcAmount: NumberAsString,
779
- toAmount: NumberAsString, // required for buy case
780
- data: UniswapData,
781
- side: SwapSide,
782
- ): AdapterExchangeParam {
783
- const pools = encodePools(data.pools, this.feeFactor);
784
- const weth = this.getWETHAddress(srcToken, destToken, data.weth);
785
- const payload = this.abiCoder.encodeParameter(
786
- {
787
- ParentStruct: {
788
- weth: 'address',
789
- pools: 'uint256[]',
790
- },
791
- },
792
- { pools, weth },
793
- );
794
- return {
795
- targetExchange: data.router,
796
- payload,
797
- networkFee: '0',
798
- };
799
- }
800
-
801
- async getSimpleParam(
802
- src: Address,
803
- dest: Address,
804
- srcAmount: NumberAsString,
805
- destAmount: NumberAsString,
806
- data: UniswapData,
807
- side: SwapSide,
808
- ): Promise<SimpleExchangeParam> {
809
- const pools = encodePools(data.pools, this.feeFactor);
810
- const weth = this.getWETHAddress(src, dest, data.weth);
811
- const swapData = this.exchangeRouterInterface.encodeFunctionData(
812
- side === SwapSide.SELL ? UniswapV2Functions.swap : UniswapV2Functions.buy,
813
- [src, srcAmount, destAmount, weth, pools],
814
- );
815
-
816
- const hasRebaseTokenSrc = rebaseTokensSetsByChain[this.network]?.has(
817
- src.toLowerCase(),
818
- );
819
- const hasRebaseTokenDest = rebaseTokensSetsByChain[this.network]?.has(
820
- dest.toLowerCase(),
821
- );
822
-
823
- const maybeSyncCall =
824
- hasRebaseTokenSrc || hasRebaseTokenDest
825
- ? {
826
- callees: [
827
- hasRebaseTokenSrc
828
- ? data.pools[0].address
829
- : data.pools[data.pools.length - 1].address,
830
- ],
831
- calldata: [uniswapV2PoolIface.encodeFunctionData('sync')],
832
- values: ['0'],
833
- }
834
- : undefined;
835
-
836
- return this.buildSimpleParamWithoutWETHConversion(
837
- src,
838
- srcAmount,
839
- dest,
840
- destAmount,
841
- swapData,
842
- data.router,
843
- data.router,
844
- '0',
845
- maybeSyncCall,
846
- );
847
- }
848
-
849
- // TODO: Rebase tokens handling?
850
- getDexParam(
851
- srcToken: Address,
852
- destToken: Address,
853
- srcAmount: NumberAsString,
854
- destAmount: NumberAsString,
855
- recipient: Address,
856
- data: UniswapData,
857
- side: SwapSide,
858
- ): DexExchangeParam {
859
- const pools = encodePools(data.pools, this.feeFactor);
860
-
861
- let exchangeData: string;
862
- let specialDexFlag: SpecialDex;
863
- let transferSrcTokenBeforeSwap: Address | undefined;
864
- let targetExchange: Address;
865
- let dexFuncHasRecipient: boolean;
866
-
867
- // if (this.dexKey === 'BakerySwap') {
868
- // const weth = this.getWETHAddress(srcToken, destToken, data.weth);
869
-
870
- // exchangeData = this.exchangeRouterInterface.encodeFunctionData(
871
- // UniswapV2Functions.swap,
872
- // [srcToken, srcAmount, destAmount, weth, pools],
873
- // );
874
- // specialDexFlag = SpecialDex.DEFAULT;
875
- // targetExchange = data.router;
876
- // dexFuncHasRecipient = false;
877
- // } else if (side === SwapSide.SELL) {
878
-
879
- if (side === SwapSide.SELL) {
880
- // 28 bytes are prepended in the Bytecode builder
881
- const exchangeDataTypes = ['bytes4', 'bytes32', 'bytes32'];
882
- const exchangeDataToPack = [
883
- hexZeroPad(hexlify(0), 4),
884
- hexZeroPad(hexlify(data.pools.length), 32), // pool count
885
- hexZeroPad(hexlify(BigNumber.from(srcAmount)), 32),
886
- ];
887
- pools.forEach(pool => {
888
- exchangeDataTypes.push('bytes32');
889
- exchangeDataToPack.push(hexZeroPad(hexlify(BigNumber.from(pool)), 32));
890
- });
891
-
892
- exchangeData = solidityPack(exchangeDataTypes, exchangeDataToPack);
893
- specialDexFlag = SpecialDex.SWAP_ON_UNISWAP_V2_FORK;
894
- transferSrcTokenBeforeSwap = data.pools[0].address;
895
- targetExchange = recipient;
896
- dexFuncHasRecipient = true;
897
- } else {
898
- const weth = this.getWETHAddress(srcToken, destToken, data.weth);
899
-
900
- exchangeData = this.exchangeRouterInterface.encodeFunctionData(
901
- UniswapV2Functions.buy,
902
- [srcToken, srcAmount, destAmount, weth, pools],
903
- );
904
- specialDexFlag = SpecialDex.DEFAULT;
905
- targetExchange = data.router;
906
- dexFuncHasRecipient = false;
907
- }
908
-
909
- return {
910
- needWrapNative: this.needWrapNative,
911
- specialDexSupportsInsertFromAmount: true,
912
- dexFuncHasRecipient,
913
- exchangeData,
914
- targetExchange,
915
- specialDexFlag,
916
- transferSrcTokenBeforeSwap,
917
- returnAmountPos: undefined,
918
- };
919
- }
920
-
921
- // TODO: Move to new uniswapv2&forks router interface
922
- getDirectParam(
923
- srcToken: Address,
924
- destToken: Address,
925
- srcAmount: NumberAsString,
926
- destAmount: NumberAsString,
927
- expectedAmount: NumberAsString,
928
- _data: UniswapData,
929
- side: SwapSide,
930
- permit: string,
931
- uuid: string,
932
- feePercent: NumberAsString,
933
- deadline: NumberAsString,
934
- partner: string,
935
- beneficiary: string,
936
- contractMethod?: string,
937
- ): TxInfo<UniswapParam> {
938
- if (!contractMethod) throw new Error(`contractMethod need to be passed`);
939
- if (permit !== '0x') contractMethod += 'WithPermit';
940
-
941
- const swapParams = ((): UniswapParam => {
942
- const data = _data as unknown as UniswapDataLegacy;
943
- const path = this.fixPath(data.path, srcToken, destToken);
944
-
945
- switch (contractMethod) {
946
- case UniswapV2Functions.swapOnUniswap:
947
- case UniswapV2Functions.buyOnUniswap:
948
- return [srcAmount, destAmount, path];
949
-
950
- case UniswapV2Functions.swapOnUniswapFork:
951
- case UniswapV2Functions.buyOnUniswapFork:
952
- return [
953
- data.factory,
954
- prependWithOx(data.initCode),
955
- srcAmount,
956
- destAmount,
957
- path,
958
- ];
959
-
960
- case UniswapV2Functions.swapOnUniswapV2Fork:
961
- case UniswapV2Functions.buyOnUniswapV2Fork:
962
- return [
963
- srcToken,
964
- srcAmount,
965
- destAmount,
966
- this.getWETHAddress(srcToken, destToken, _data.weth),
967
- encodePools(_data.pools, this.feeFactor),
968
- ];
969
-
970
- case UniswapV2Functions.swapOnUniswapV2ForkWithPermit:
971
- case UniswapV2Functions.buyOnUniswapV2ForkWithPermit:
972
- return [
973
- srcToken,
974
- srcAmount,
975
- destAmount,
976
- this.getWETHAddress(srcToken, destToken, _data.weth),
977
- encodePools(_data.pools, this.feeFactor),
978
- permit,
979
- ];
980
-
981
- default:
982
- throw new Error(`contractMethod=${contractMethod} is not supported`);
983
- }
984
- })();
985
-
986
- const encoder = (...params: UniswapParam) =>
987
- this.routerInterface.encodeFunctionData(contractMethod!, params);
988
- return {
989
- params: swapParams,
990
- encoder,
991
- networkFee: '0',
992
- };
993
- }
994
-
995
- static getDirectFunctionName(): string[] {
996
- return this.directFunctionName;
997
- }
998
-
999
- getDirectParamV6(
1000
- srcToken: Address,
1001
- destToken: Address,
1002
- fromAmount: NumberAsString,
1003
- toAmount: NumberAsString,
1004
- quotedAmount: NumberAsString,
1005
- data: UniswapV2Data,
1006
- side: SwapSide,
1007
- permit: string,
1008
- uuid: string,
1009
- partnerAndFee: string,
1010
- beneficiary: string,
1011
- blockNumber: number,
1012
- contractMethod?: string,
1013
- ) {
1014
- if (!contractMethod) throw new Error(`contractMethod need to be passed`);
1015
- if (!UniswapV2.getDirectFunctionNameV6().includes(contractMethod!)) {
1016
- throw new Error(`Invalid contract method ${contractMethod}`);
1017
- }
1018
-
1019
- const { path, pools } = data;
1020
- const length = path.length;
1021
- const encodedPath = path.reduce((acc, _, i) => {
1022
- if (i >= length - 1) return acc;
1023
-
1024
- const p = this._encodePathV6(
1025
- {
1026
- srcToken: path[i],
1027
- destToken: path[i + 1],
1028
- direction: pools[i].direction,
1029
- },
1030
- side,
1031
- data.wethAddress,
1032
- ).replace('0x', '');
1033
-
1034
- return acc + p;
1035
- }, '0x');
1036
-
1037
- const metadata = hexConcat([
1038
- hexZeroPad(uuidToBytes16(uuid), 16),
1039
- hexZeroPad(hexlify(blockNumber), 16),
1040
- ]);
1041
-
1042
- const uniData: UniswapV2ParamsDirectBase = [
1043
- srcToken,
1044
- destToken,
1045
- fromAmount,
1046
- toAmount,
1047
- quotedAmount,
1048
- metadata,
1049
- beneficiary,
1050
- encodedPath,
1051
- ];
1052
-
1053
- const swapParams: UniswapV2ParamsDirect = [uniData, partnerAndFee, permit];
1054
-
1055
- const encoder = (...params: (string | UniswapV2ParamsDirect)[]) => {
1056
- return this.augustusV6Interface.encodeFunctionData(
1057
- side === SwapSide.SELL
1058
- ? UniswapV2FunctionsV6.swap
1059
- : UniswapV2FunctionsV6.buy,
1060
- [...params],
1061
- );
1062
- };
1063
-
1064
- return {
1065
- params: swapParams,
1066
- encoder,
1067
- networkFee: '0',
1068
- };
1069
- }
1070
-
1071
- static getDirectFunctionNameV6(): string[] {
1072
- return this.directFunctionNameV6;
1073
- }
1074
-
1075
- // univ2 always had 1 pool per pair
1076
- private _encodePathV6(
1077
- path: {
1078
- srcToken: Address;
1079
- destToken: Address;
1080
- direction: boolean;
1081
- },
1082
- side: SwapSide,
1083
- weth?: Address,
1084
- ): string {
1085
- if (path == null) {
1086
- this.logger.error(
1087
- `${this.dexKey}: Received invalid path=${path} for side=${side} to encode`,
1088
- );
1089
- return '0x';
1090
- }
1091
-
1092
- // v6 expects weth for eth in pools
1093
- if (isETHAddress(path.srcToken)) {
1094
- path.srcToken =
1095
- weth || this.dexHelper.config.data.wrappedNativeTokenAddress;
1096
- }
1097
-
1098
- if (isETHAddress(path.destToken)) {
1099
- path.destToken =
1100
- weth || this.dexHelper.config.data.wrappedNativeTokenAddress;
1101
- }
1102
-
1103
- // contract expects tokens to be sorted, and direction switched in case sorting changes src/dest order
1104
- const [srcTokenSorted, destTokenSorted] =
1105
- BigInt(path.srcToken) > BigInt(path.destToken)
1106
- ? [path.destToken, path.srcToken]
1107
- : [path.srcToken, path.destToken];
1108
-
1109
- const direction = srcTokenSorted === path.srcToken ? 1 : 0;
1110
-
1111
- const tokensEncoded = pack(
1112
- ['address', 'address'],
1113
- [srcTokenSorted, destTokenSorted],
1114
- );
1115
- return tokensEncoded + '0'.repeat(47) + direction;
1116
- }
1117
- }
1
+ import { AbiCoder, Interface } from '@ethersproject/abi';
2
+ import { pack } from '@ethersproject/solidity';
3
+ import _ from 'lodash';
4
+ import { AsyncOrSync, DeepReadonly } from 'ts-essentials';
5
+ import erc20ABI from '../../abi/erc20.json';
6
+ import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
7
+ import {
8
+ AdapterExchangeParam,
9
+ Address,
10
+ ExchangePrices,
11
+ PoolPrices,
12
+ Log,
13
+ Logger,
14
+ PoolLiquidity,
15
+ SimpleExchangeParam,
16
+ Token,
17
+ TxInfo,
18
+ TransferFeeParams,
19
+ DexExchangeParam,
20
+ } from '../../types';
21
+ import {
22
+ UniswapData,
23
+ UniswapDataLegacy,
24
+ UniswapParam,
25
+ UniswapPool,
26
+ UniswapV2Data,
27
+ UniswapV2Functions,
28
+ UniswapV2FunctionsV6,
29
+ UniswapV2ParamsDirect,
30
+ UniswapV2ParamsDirectBase,
31
+ UniswapV2PoolOrderedParams,
32
+ } from './types';
33
+ import { IDex } from '../idex';
34
+ import {
35
+ DEST_TOKEN_PARASWAP_TRANSFERS,
36
+ ETHER_ADDRESS,
37
+ Network,
38
+ NULL_ADDRESS,
39
+ SRC_TOKEN_PARASWAP_TRANSFERS,
40
+ SUBGRAPH_TIMEOUT,
41
+ } from '../../constants';
42
+ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
43
+ import { SimpleExchange } from '../simple-exchange';
44
+ import { NumberAsString, SwapSide } from '@paraswap/core';
45
+ import { IDexHelper } from '../../dex-helper';
46
+ import {
47
+ getDexKeysWithNetwork,
48
+ isETHAddress,
49
+ prependWithOx,
50
+ getBigIntPow,
51
+ uuidToBytes16,
52
+ } from '../../utils';
53
+ import uniswapV2ABI from '../../abi/uniswap-v2/uniswap-v2-pool.json';
54
+ import uniswapV2factoryABI from '../../abi/uniswap-v2/uniswap-v2-factory.json';
55
+ import ParaSwapABI from '../../abi/IParaswap.json';
56
+ import UniswapV2ExchangeRouterABI from '../../abi/UniswapV2ExchangeRouter.json';
57
+ import { Contract } from 'web3-eth-contract';
58
+ import { UniswapV2Config, Adapters } from './config';
59
+ import { Uniswapv2ConstantProductPool } from './uniswap-v2-constant-product-pool';
60
+ import { applyTransferFee } from '../../lib/token-transfer-fee';
61
+ import _rebaseTokens from '../../rebase-tokens.json';
62
+ import { Flag, SpecialDex } from '../../executor/types';
63
+ import {
64
+ hexZeroPad,
65
+ hexlify,
66
+ solidityPack,
67
+ hexDataLength,
68
+ id,
69
+ hexConcat,
70
+ } from 'ethers/lib/utils';
71
+ import { BigNumber } from 'ethers';
72
+
73
+ const rebaseTokens = _rebaseTokens as { chainId: number; address: string }[];
74
+
75
+ const rebaseTokensSetsByChain = rebaseTokens.reduce<{
76
+ [chainId: number]: Set<string>;
77
+ }>((acc, curr) => {
78
+ if (!acc[curr.chainId]) {
79
+ acc[curr.chainId] = new Set();
80
+ }
81
+
82
+ acc[curr.chainId].add(curr.address.toLowerCase());
83
+
84
+ return acc;
85
+ }, {});
86
+
87
+ const DefaultUniswapV2PoolGasCost = 90 * 1000;
88
+
89
+ export const RESERVE_LIMIT = 2n ** 112n - 1n;
90
+
91
+ const LogCallTopics = [
92
+ '0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1', // event Sync(uint112 reserve0, uint112 reserve1) // uni-V2 and most forks
93
+ '0xcf2aa50876cdfbb541206f89af0ee78d44a2abf8d328e37fa4917f982149848a', // event Sync(uint256 reserve0, uint256 reserve1) // commonly seen in solidly & forks
94
+ ];
95
+
96
+ interface UniswapV2PoolState {
97
+ reserves0: string;
98
+ reserves1: string;
99
+ feeCode: number;
100
+ }
101
+
102
+ const uniswapV2PoolIface = new Interface(uniswapV2ABI);
103
+ const erc20iface = new Interface(erc20ABI);
104
+ const coder = new AbiCoder();
105
+
106
+ export const directUniswapFunctionName = [
107
+ UniswapV2Functions.swapOnUniswap,
108
+ UniswapV2Functions.buyOnUniswap,
109
+ UniswapV2Functions.swapOnUniswapFork,
110
+ UniswapV2Functions.buyOnUniswapFork,
111
+ UniswapV2Functions.swapOnUniswapV2Fork,
112
+ UniswapV2Functions.buyOnUniswapV2Fork,
113
+ ];
114
+
115
+ export const directUniswapFunctionNameV6 = [
116
+ UniswapV2FunctionsV6.swap,
117
+ UniswapV2FunctionsV6.buy,
118
+ ];
119
+
120
+ export interface UniswapV2Pair {
121
+ token0: Token;
122
+ token1: Token;
123
+ exchange?: Address;
124
+ pool?: UniswapV2EventPool;
125
+ }
126
+
127
+ export class UniswapV2EventPool extends StatefulEventSubscriber<UniswapV2PoolState> {
128
+ decoder = (log: Log) => this.iface.parseLog(log);
129
+
130
+ constructor(
131
+ parentName: string,
132
+ protected dexHelper: IDexHelper,
133
+ private poolAddress: Address,
134
+ private token0: Token,
135
+ private token1: Token,
136
+ // feeCode is ignored if DynamicFees is set to true
137
+ private feeCode: number,
138
+ logger: Logger,
139
+ private dynamicFees = false,
140
+ // feesMultiCallData is only used if dynamicFees is set to true
141
+ private feesMultiCallEntry?: { target: Address; callData: string },
142
+ private feesMultiCallDecoder?: (values: any[]) => number,
143
+ private iface: Interface = uniswapV2PoolIface,
144
+ ) {
145
+ super(
146
+ parentName,
147
+ (token0.symbol || token0.address) +
148
+ '-' +
149
+ (token1.symbol || token1.address) +
150
+ ' pool',
151
+ dexHelper,
152
+ logger,
153
+ );
154
+ }
155
+
156
+ protected processLog(
157
+ state: DeepReadonly<UniswapV2PoolState>,
158
+ log: Readonly<Log>,
159
+ ): AsyncOrSync<DeepReadonly<UniswapV2PoolState> | null> {
160
+ if (!LogCallTopics.includes(log.topics[0])) return null;
161
+
162
+ const event = this.decoder(log);
163
+ switch (event.name) {
164
+ case 'Sync':
165
+ return {
166
+ reserves0: event.args.reserve0.toString(),
167
+ reserves1: event.args.reserve1.toString(),
168
+ feeCode: state.feeCode,
169
+ };
170
+ }
171
+ return null;
172
+ }
173
+
174
+ async generateState(
175
+ blockNumber: number | 'latest' = 'latest',
176
+ ): Promise<DeepReadonly<UniswapV2PoolState>> {
177
+ let calldata = [
178
+ {
179
+ target: this.poolAddress,
180
+ callData: this.iface.encodeFunctionData('getReserves', []),
181
+ },
182
+ ];
183
+
184
+ if (this.dynamicFees) {
185
+ calldata.push(this.feesMultiCallEntry!);
186
+ }
187
+
188
+ const data: { returnData: any[] } =
189
+ await this.dexHelper.multiContract.methods
190
+ .aggregate(calldata)
191
+ .call({}, blockNumber);
192
+
193
+ const decodedData = coder.decode(
194
+ ['uint112', 'uint112', 'uint32'],
195
+ data.returnData[0],
196
+ );
197
+
198
+ return {
199
+ reserves0: decodedData[0].toString(),
200
+ reserves1: decodedData[1].toString(),
201
+ feeCode: this.dynamicFees
202
+ ? this.feesMultiCallDecoder!(data.returnData[1])
203
+ : this.feeCode,
204
+ };
205
+ }
206
+ }
207
+
208
+ function encodePools(
209
+ pools: UniswapPool[],
210
+ feeFactor: number,
211
+ ): NumberAsString[] {
212
+ return pools.map(({ fee, direction, address }) => {
213
+ return (
214
+ (BigInt(feeFactor - fee) << 161n) +
215
+ ((direction ? 0n : 1n) << 160n) +
216
+ BigInt(address)
217
+ ).toString();
218
+ });
219
+ }
220
+
221
+ export class UniswapV2
222
+ extends SimpleExchange
223
+ implements IDex<UniswapV2Data, UniswapParam | UniswapV2ParamsDirect>
224
+ {
225
+ pairs: { [key: string]: UniswapV2Pair } = {};
226
+ feeFactor = 10000;
227
+ factory: Contract;
228
+
229
+ routerInterface: Interface;
230
+ exchangeRouterInterface: Interface;
231
+
232
+ needWrapNative = true;
233
+
234
+ logger: Logger;
235
+
236
+ static directFunctionName = directUniswapFunctionName;
237
+ static directFunctionNameV6 = directUniswapFunctionNameV6;
238
+
239
+ readonly hasConstantPriceLargeAmounts = false;
240
+ readonly isFeeOnTransferSupported: boolean = true;
241
+ readonly SRC_TOKEN_DEX_TRANSFERS = 1;
242
+ readonly DEST_TOKEN_DEX_TRANSFERS = 1;
243
+
244
+ public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
245
+ getDexKeysWithNetwork(UniswapV2Config);
246
+
247
+ constructor(
248
+ protected network: Network,
249
+ dexKey: string,
250
+ protected dexHelper: IDexHelper,
251
+ protected isDynamicFees = false,
252
+ protected factoryAddress: Address = UniswapV2Config[dexKey][network]
253
+ .factoryAddress,
254
+ protected subgraphURL: string | undefined = UniswapV2Config[dexKey] &&
255
+ UniswapV2Config[dexKey][network].subgraphURL,
256
+ protected initCode: string = UniswapV2Config[dexKey][network].initCode,
257
+ // feeCode is ignored when isDynamicFees is set to true
258
+ protected feeCode: number = UniswapV2Config[dexKey][network].feeCode,
259
+ protected poolGasCost: number = (UniswapV2Config[dexKey] &&
260
+ UniswapV2Config[dexKey][network].poolGasCost) ??
261
+ DefaultUniswapV2PoolGasCost,
262
+ protected decoderIface: Interface = uniswapV2PoolIface,
263
+ protected adapters = (UniswapV2Config[dexKey] &&
264
+ UniswapV2Config[dexKey][network].adapters) ??
265
+ Adapters[network],
266
+ protected router = (UniswapV2Config[dexKey] &&
267
+ UniswapV2Config[dexKey][network].router) ??
268
+ dexHelper.config.data.uniswapV2ExchangeRouterAddress,
269
+ ) {
270
+ super(dexHelper, dexKey);
271
+ this.logger = dexHelper.getLogger(dexKey);
272
+
273
+ this.factory = new dexHelper.web3Provider.eth.Contract(
274
+ uniswapV2factoryABI as any,
275
+ factoryAddress,
276
+ );
277
+
278
+ this.routerInterface = new Interface(ParaSwapABI);
279
+ this.exchangeRouterInterface = new Interface(UniswapV2ExchangeRouterABI);
280
+ }
281
+
282
+ // getFeesMultiCallData should be override
283
+ // when isDynamicFees is set to true
284
+ protected getFeesMultiCallData(pair: UniswapV2Pair):
285
+ | undefined
286
+ | {
287
+ callEntry: { target: Address; callData: string };
288
+ callDecoder: (values: any[]) => number;
289
+ } {
290
+ return undefined;
291
+ }
292
+
293
+ protected async addPool(
294
+ pair: UniswapV2Pair,
295
+ reserves0: string,
296
+ reserves1: string,
297
+ feeCode: number,
298
+ blockNumber: number,
299
+ ) {
300
+ const { callEntry, callDecoder } = this.getFeesMultiCallData(pair) || {};
301
+ pair.pool = new UniswapV2EventPool(
302
+ this.dexKey,
303
+ this.dexHelper,
304
+ pair.exchange!,
305
+ pair.token0,
306
+ pair.token1,
307
+ feeCode,
308
+ this.logger,
309
+ this.isDynamicFees,
310
+ callEntry,
311
+ callDecoder,
312
+ this.decoderIface,
313
+ );
314
+ pair.pool.addressesSubscribed.push(pair.exchange!);
315
+
316
+ await pair.pool.initialize(blockNumber, {
317
+ state: { reserves0, reserves1, feeCode },
318
+ });
319
+ }
320
+
321
+ async getBuyPrice(
322
+ priceParams: UniswapV2PoolOrderedParams,
323
+ destAmount: bigint,
324
+ ): Promise<bigint> {
325
+ return Uniswapv2ConstantProductPool.getBuyPrice(
326
+ priceParams,
327
+ destAmount,
328
+ this.feeFactor,
329
+ );
330
+ }
331
+
332
+ async getSellPrice(
333
+ priceParams: UniswapV2PoolOrderedParams,
334
+ srcAmount: bigint,
335
+ ): Promise<bigint> {
336
+ return Uniswapv2ConstantProductPool.getSellPrice(
337
+ priceParams,
338
+ srcAmount,
339
+ this.feeFactor,
340
+ );
341
+ }
342
+
343
+ async getBuyPricePath(
344
+ amount: bigint,
345
+ params: UniswapV2PoolOrderedParams[],
346
+ ): Promise<bigint> {
347
+ let price = amount;
348
+ for (const param of params.reverse()) {
349
+ price = await this.getBuyPrice(param, price);
350
+ }
351
+ return price;
352
+ }
353
+
354
+ async getSellPricePath(
355
+ amount: bigint,
356
+ params: UniswapV2PoolOrderedParams[],
357
+ ): Promise<bigint> {
358
+ let price = amount;
359
+ for (const param of params) {
360
+ price = await this.getSellPrice(param, price);
361
+ }
362
+ return price;
363
+ }
364
+
365
+ async findPair(from: Token, to: Token) {
366
+ if (from.address.toLowerCase() === to.address.toLowerCase()) return null;
367
+ const [token0, token1] =
368
+ from.address.toLowerCase() < to.address.toLowerCase()
369
+ ? [from, to]
370
+ : [to, from];
371
+
372
+ const key = `${token0.address.toLowerCase()}-${token1.address.toLowerCase()}`;
373
+ let pair = this.pairs[key];
374
+ if (pair) return pair;
375
+ const exchange = await this.factory.methods
376
+ .getPair(token0.address, token1.address)
377
+ .call();
378
+ if (exchange === NULL_ADDRESS) {
379
+ pair = { token0, token1 };
380
+ } else {
381
+ pair = { token0, token1, exchange };
382
+ }
383
+ this.pairs[key] = pair;
384
+ return pair;
385
+ }
386
+
387
+ async getManyPoolReserves(
388
+ pairs: UniswapV2Pair[],
389
+ blockNumber: number,
390
+ ): Promise<UniswapV2PoolState[]> {
391
+ try {
392
+ const multiCallFeeData = pairs.map(pair =>
393
+ this.getFeesMultiCallData(pair),
394
+ );
395
+ const calldata = pairs
396
+ .map((pair, i) => {
397
+ let calldata = [
398
+ {
399
+ target: pair.token0.address,
400
+ callData: erc20iface.encodeFunctionData('balanceOf', [
401
+ pair.exchange!,
402
+ ]),
403
+ },
404
+ {
405
+ target: pair.token1.address,
406
+ callData: erc20iface.encodeFunctionData('balanceOf', [
407
+ pair.exchange!,
408
+ ]),
409
+ },
410
+ ];
411
+ if (this.isDynamicFees) calldata.push(multiCallFeeData[i]!.callEntry);
412
+ return calldata;
413
+ })
414
+ .flat();
415
+
416
+ // const data: { returnData: any[] } =
417
+ // await this.dexHelper.multiContract.callStatic.aggregate(calldata, {
418
+ // blockTag: blockNumber,
419
+ // });
420
+
421
+ const data: { returnData: any[] } =
422
+ await this.dexHelper.multiContract.methods
423
+ .aggregate(calldata)
424
+ .call({}, blockNumber);
425
+
426
+ const returnData = _.chunk(data.returnData, this.isDynamicFees ? 3 : 2);
427
+ return pairs.map((pair, i) => ({
428
+ reserves0: coder.decode(['uint256'], returnData[i][0])[0].toString(),
429
+ reserves1: coder.decode(['uint256'], returnData[i][1])[0].toString(),
430
+ feeCode: this.isDynamicFees
431
+ ? multiCallFeeData[i]!.callDecoder(returnData[i][2])
432
+ : this.feeCode,
433
+ }));
434
+ } catch (e) {
435
+ this.logger.error(
436
+ `Error_getManyPoolReserves could not get reserves with error:`,
437
+ e,
438
+ );
439
+ return [];
440
+ }
441
+ }
442
+
443
+ async batchCatchUpPairs(pairs: [Token, Token][], blockNumber: number) {
444
+ if (!blockNumber) return;
445
+ const pairsToFetch: UniswapV2Pair[] = [];
446
+ for (const _pair of pairs) {
447
+ const pair = await this.findPair(_pair[0], _pair[1]);
448
+ if (!(pair && pair.exchange)) continue;
449
+ if (!pair.pool) {
450
+ pairsToFetch.push(pair);
451
+ } else if (!pair.pool.getState(blockNumber)) {
452
+ pairsToFetch.push(pair);
453
+ }
454
+ }
455
+
456
+ if (!pairsToFetch.length) return;
457
+
458
+ const reserves = await this.getManyPoolReserves(pairsToFetch, blockNumber);
459
+
460
+ if (reserves.length !== pairsToFetch.length) {
461
+ this.logger.error(
462
+ `Error_getManyPoolReserves didn't get any pool reserves`,
463
+ );
464
+ }
465
+
466
+ for (let i = 0; i < pairsToFetch.length; i++) {
467
+ const pairState = reserves[i];
468
+ const pair = pairsToFetch[i];
469
+ if (!pair.pool) {
470
+ await this.addPool(
471
+ pair,
472
+ pairState.reserves0,
473
+ pairState.reserves1,
474
+ pairState.feeCode,
475
+ blockNumber,
476
+ );
477
+ } else pair.pool.setState(pairState, blockNumber);
478
+ }
479
+ }
480
+
481
+ async getPairOrderedParams(
482
+ from: Token,
483
+ to: Token,
484
+ blockNumber: number,
485
+ tokenDexTransferFee: number,
486
+ ): Promise<UniswapV2PoolOrderedParams | null> {
487
+ const pair = await this.findPair(from, to);
488
+ if (!(pair && pair.pool && pair.exchange)) return null;
489
+ const pairState = pair.pool.getState(blockNumber);
490
+ if (!pairState) {
491
+ this.logger.error(
492
+ `Error_orderPairParams expected reserves, got none (maybe the pool doesn't exist) ${
493
+ from.symbol || from.address
494
+ } ${to.symbol || to.address}`,
495
+ );
496
+ return null;
497
+ }
498
+ const fee = (pairState.feeCode + tokenDexTransferFee).toString();
499
+ const pairReversed =
500
+ pair.token1.address.toLowerCase() === from.address.toLowerCase();
501
+ if (pairReversed) {
502
+ return {
503
+ tokenIn: from.address,
504
+ tokenOut: to.address,
505
+ reservesIn: pairState.reserves1,
506
+ reservesOut: pairState.reserves0,
507
+ fee,
508
+ direction: false,
509
+ exchange: pair.exchange,
510
+ };
511
+ }
512
+ return {
513
+ tokenIn: from.address,
514
+ tokenOut: to.address,
515
+ reservesIn: pairState.reserves0,
516
+ reservesOut: pairState.reserves1,
517
+ fee,
518
+ direction: true,
519
+ exchange: pair.exchange,
520
+ };
521
+ }
522
+
523
+ async getPoolIdentifiers(
524
+ _from: Token,
525
+ _to: Token,
526
+ side: SwapSide,
527
+ blockNumber: number,
528
+ ): Promise<string[]> {
529
+ const from = this.dexHelper.config.wrapETH(_from);
530
+ const to = this.dexHelper.config.wrapETH(_to);
531
+
532
+ if (from.address.toLowerCase() === to.address.toLowerCase()) {
533
+ return [];
534
+ }
535
+
536
+ const tokenAddress = [from.address.toLowerCase(), to.address.toLowerCase()]
537
+ .sort((a, b) => (a > b ? 1 : -1))
538
+ .join('_');
539
+
540
+ const poolIdentifier = `${this.dexKey}_${tokenAddress}`;
541
+ return [poolIdentifier];
542
+ }
543
+
544
+ async getPricesVolume(
545
+ _from: Token,
546
+ _to: Token,
547
+ amounts: bigint[],
548
+ side: SwapSide,
549
+ blockNumber: number,
550
+ // list of pool identifiers to use for pricing, if undefined use all pools
551
+ limitPools?: string[],
552
+ transferFees: TransferFeeParams = {
553
+ srcFee: 0,
554
+ destFee: 0,
555
+ srcDexFee: 0,
556
+ destDexFee: 0,
557
+ },
558
+ ): Promise<ExchangePrices<UniswapV2Data> | null> {
559
+ try {
560
+ const from = this.dexHelper.config.wrapETH(_from);
561
+ const to = this.dexHelper.config.wrapETH(_to);
562
+
563
+ if (from.address.toLowerCase() === to.address.toLowerCase()) {
564
+ return null;
565
+ }
566
+
567
+ const tokenAddress = [
568
+ from.address.toLowerCase(),
569
+ to.address.toLowerCase(),
570
+ ]
571
+ .sort((a, b) => (a > b ? 1 : -1))
572
+ .join('_');
573
+
574
+ const poolIdentifier = `${this.dexKey}_${tokenAddress}`;
575
+
576
+ if (limitPools && limitPools.every(p => p !== poolIdentifier))
577
+ return null;
578
+
579
+ await this.batchCatchUpPairs([[from, to]], blockNumber);
580
+ const isSell = side === SwapSide.SELL;
581
+ const pairParam = await this.getPairOrderedParams(
582
+ from,
583
+ to,
584
+ blockNumber,
585
+ transferFees.srcDexFee,
586
+ );
587
+
588
+ if (!pairParam) return null;
589
+
590
+ const unitAmount = getBigIntPow(isSell ? from.decimals : to.decimals);
591
+
592
+ const [unitVolumeWithFee, ...amountsWithFee] = applyTransferFee(
593
+ [unitAmount, ...amounts],
594
+ side,
595
+ isSell ? transferFees.srcFee : transferFees.destFee,
596
+ isSell ? SRC_TOKEN_PARASWAP_TRANSFERS : DEST_TOKEN_PARASWAP_TRANSFERS,
597
+ );
598
+
599
+ const unit = isSell
600
+ ? await this.getSellPricePath(unitVolumeWithFee, [pairParam])
601
+ : await this.getBuyPricePath(unitVolumeWithFee, [pairParam]);
602
+
603
+ const prices = isSell
604
+ ? await Promise.all(
605
+ amountsWithFee.map(amount =>
606
+ this.getSellPricePath(amount, [pairParam]),
607
+ ),
608
+ )
609
+ : await Promise.all(
610
+ amountsWithFee.map(amount =>
611
+ this.getBuyPricePath(amount, [pairParam]),
612
+ ),
613
+ );
614
+
615
+ const [unitOutWithFee, ...outputsWithFee] = applyTransferFee(
616
+ [unit, ...prices],
617
+ side,
618
+ // This part is confusing, because we treat differently SELL and BUY fees
619
+ // If Buy, we should apply transfer fee on srcToken on top of dexFee applied earlier
620
+ // But for Sell we should apply only one dexFee
621
+ isSell ? transferFees.destDexFee : transferFees.srcFee,
622
+ isSell ? this.DEST_TOKEN_DEX_TRANSFERS : SRC_TOKEN_PARASWAP_TRANSFERS,
623
+ );
624
+
625
+ // As uniswapv2 just has one pool per token pair
626
+ return [
627
+ {
628
+ prices: outputsWithFee,
629
+ unit: unitOutWithFee,
630
+ data: {
631
+ router: this.router,
632
+ path: [from.address.toLowerCase(), to.address.toLowerCase()],
633
+ factory: this.factoryAddress,
634
+ initCode: this.initCode,
635
+ feeFactor: this.feeFactor,
636
+ pools: [
637
+ {
638
+ address: pairParam.exchange,
639
+ fee: parseInt(pairParam.fee),
640
+ direction: pairParam.direction,
641
+ },
642
+ ],
643
+ },
644
+ exchange: this.dexKey,
645
+ poolIdentifier,
646
+ gasCost: this.poolGasCost,
647
+ poolAddresses: [pairParam.exchange],
648
+ },
649
+ ];
650
+ } catch (e) {
651
+ if (blockNumber === 0)
652
+ this.logger.error(
653
+ `Error_getPricesVolume: Aurelius block manager not yet instantiated`,
654
+ );
655
+ this.logger.error(`Error_getPrices:`, e);
656
+ return null;
657
+ }
658
+ }
659
+
660
+ // Returns estimated gas cost of calldata for this DEX in multiSwap
661
+ getCalldataGasCost(poolPrices: PoolPrices<UniswapV2Data>): number | number[] {
662
+ return (
663
+ CALLDATA_GAS_COST.DEX_OVERHEAD +
664
+ CALLDATA_GAS_COST.LENGTH_SMALL +
665
+ // ParentStruct header
666
+ CALLDATA_GAS_COST.OFFSET_SMALL +
667
+ // ParentStruct -> weth
668
+ CALLDATA_GAS_COST.ADDRESS +
669
+ // ParentStruct -> pools[] header
670
+ CALLDATA_GAS_COST.OFFSET_SMALL +
671
+ // ParentStruct -> pools[]
672
+ CALLDATA_GAS_COST.LENGTH_SMALL +
673
+ // ParentStruct -> pools[0]
674
+ CALLDATA_GAS_COST.wordNonZeroBytes(22)
675
+ );
676
+ }
677
+
678
+ getAdapters(side: SwapSide): { name: string; index: number }[] | null {
679
+ return this.adapters[side];
680
+ }
681
+
682
+ async getTopPoolsForToken(
683
+ tokenAddress: Address,
684
+ count: number,
685
+ ): Promise<PoolLiquidity[]> {
686
+ if (!this.subgraphURL) return [];
687
+ const query = `
688
+ query ($token: Bytes!, $count: Int) {
689
+ pools0: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token0: $token, reserve0_gt: 1, reserve1_gt: 1}) {
690
+ id
691
+ token0 {
692
+ id
693
+ decimals
694
+ }
695
+ token1 {
696
+ id
697
+ decimals
698
+ }
699
+ reserveUSD
700
+ }
701
+ pools1: pairs(first: $count, orderBy: reserveUSD, orderDirection: desc, where: {token1: $token, reserve0_gt: 1, reserve1_gt: 1}) {
702
+ id
703
+ token0 {
704
+ id
705
+ decimals
706
+ }
707
+ token1 {
708
+ id
709
+ decimals
710
+ }
711
+ reserveUSD
712
+ }
713
+ }`;
714
+
715
+ const { data } = await this.dexHelper.httpRequest.post(
716
+ this.subgraphURL,
717
+ {
718
+ query,
719
+ variables: { token: tokenAddress.toLowerCase(), count },
720
+ },
721
+ SUBGRAPH_TIMEOUT,
722
+ );
723
+
724
+ if (!(data && data.pools0 && data.pools1))
725
+ throw new Error("Couldn't fetch the pools from the subgraph");
726
+ const pools0 = _.map(data.pools0, pool => ({
727
+ exchange: this.dexKey,
728
+ address: pool.id.toLowerCase(),
729
+ connectorTokens: [
730
+ {
731
+ address: pool.token1.id.toLowerCase(),
732
+ decimals: parseInt(pool.token1.decimals),
733
+ },
734
+ ],
735
+ liquidityUSD: parseFloat(pool.reserveUSD),
736
+ }));
737
+
738
+ const pools1 = _.map(data.pools1, pool => ({
739
+ exchange: this.dexKey,
740
+ address: pool.id.toLowerCase(),
741
+ connectorTokens: [
742
+ {
743
+ address: pool.token0.id.toLowerCase(),
744
+ decimals: parseInt(pool.token0.decimals),
745
+ },
746
+ ],
747
+ liquidityUSD: parseFloat(pool.reserveUSD),
748
+ }));
749
+
750
+ return _.slice(
751
+ _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]),
752
+ 0,
753
+ count,
754
+ );
755
+ }
756
+
757
+ protected fixPath(path: Address[], srcToken: Address, destToken: Address) {
758
+ return path.map((token: string, i: number) => {
759
+ if (
760
+ (i === 0 && srcToken.toLowerCase() === ETHER_ADDRESS.toLowerCase()) ||
761
+ (i === path.length - 1 &&
762
+ destToken.toLowerCase() === ETHER_ADDRESS.toLowerCase())
763
+ )
764
+ return ETHER_ADDRESS;
765
+ return token;
766
+ });
767
+ }
768
+
769
+ getWETHAddress(srcToken: Address, destToken: Address, weth?: Address) {
770
+ if (!isETHAddress(srcToken) && !isETHAddress(destToken))
771
+ return NULL_ADDRESS;
772
+ return weth || this.dexHelper.config.data.wrappedNativeTokenAddress;
773
+ }
774
+
775
+ getAdapterParam(
776
+ srcToken: Address,
777
+ destToken: Address,
778
+ srcAmount: NumberAsString,
779
+ toAmount: NumberAsString, // required for buy case
780
+ data: UniswapData,
781
+ side: SwapSide,
782
+ ): AdapterExchangeParam {
783
+ const pools = encodePools(data.pools, this.feeFactor);
784
+ const weth = this.getWETHAddress(srcToken, destToken, data.weth);
785
+ const payload = this.abiCoder.encodeParameter(
786
+ {
787
+ ParentStruct: {
788
+ weth: 'address',
789
+ pools: 'uint256[]',
790
+ },
791
+ },
792
+ { pools, weth },
793
+ );
794
+ return {
795
+ targetExchange: data.router,
796
+ payload,
797
+ networkFee: '0',
798
+ };
799
+ }
800
+
801
+ async getSimpleParam(
802
+ src: Address,
803
+ dest: Address,
804
+ srcAmount: NumberAsString,
805
+ destAmount: NumberAsString,
806
+ data: UniswapData,
807
+ side: SwapSide,
808
+ ): Promise<SimpleExchangeParam> {
809
+ const pools = encodePools(data.pools, this.feeFactor);
810
+ const weth = this.getWETHAddress(src, dest, data.weth);
811
+ const swapData = this.exchangeRouterInterface.encodeFunctionData(
812
+ side === SwapSide.SELL ? UniswapV2Functions.swap : UniswapV2Functions.buy,
813
+ [src, srcAmount, destAmount, weth, pools],
814
+ );
815
+
816
+ const hasRebaseTokenSrc = rebaseTokensSetsByChain[this.network]?.has(
817
+ src.toLowerCase(),
818
+ );
819
+ const hasRebaseTokenDest = rebaseTokensSetsByChain[this.network]?.has(
820
+ dest.toLowerCase(),
821
+ );
822
+
823
+ const maybeSyncCall =
824
+ hasRebaseTokenSrc || hasRebaseTokenDest
825
+ ? {
826
+ callees: [
827
+ hasRebaseTokenSrc
828
+ ? data.pools[0].address
829
+ : data.pools[data.pools.length - 1].address,
830
+ ],
831
+ calldata: [uniswapV2PoolIface.encodeFunctionData('sync')],
832
+ values: ['0'],
833
+ }
834
+ : undefined;
835
+
836
+ return this.buildSimpleParamWithoutWETHConversion(
837
+ src,
838
+ srcAmount,
839
+ dest,
840
+ destAmount,
841
+ swapData,
842
+ data.router,
843
+ data.router,
844
+ '0',
845
+ maybeSyncCall,
846
+ );
847
+ }
848
+
849
+ // TODO: Rebase tokens handling?
850
+ getDexParam(
851
+ srcToken: Address,
852
+ destToken: Address,
853
+ srcAmount: NumberAsString,
854
+ destAmount: NumberAsString,
855
+ recipient: Address,
856
+ data: UniswapData,
857
+ side: SwapSide,
858
+ ): DexExchangeParam {
859
+ const pools = encodePools(data.pools, this.feeFactor);
860
+
861
+ let exchangeData: string;
862
+ let specialDexFlag: SpecialDex;
863
+ let transferSrcTokenBeforeSwap: Address | undefined;
864
+ let targetExchange: Address;
865
+ let dexFuncHasRecipient: boolean;
866
+
867
+ // if (this.dexKey === 'BakerySwap') {
868
+ // const weth = this.getWETHAddress(srcToken, destToken, data.weth);
869
+
870
+ // exchangeData = this.exchangeRouterInterface.encodeFunctionData(
871
+ // UniswapV2Functions.swap,
872
+ // [srcToken, srcAmount, destAmount, weth, pools],
873
+ // );
874
+ // specialDexFlag = SpecialDex.DEFAULT;
875
+ // targetExchange = data.router;
876
+ // dexFuncHasRecipient = false;
877
+ // } else if (side === SwapSide.SELL) {
878
+
879
+ if (side === SwapSide.SELL) {
880
+ // 28 bytes are prepended in the Bytecode builder
881
+ const exchangeDataTypes = ['bytes4', 'bytes32', 'bytes32'];
882
+ const exchangeDataToPack = [
883
+ hexZeroPad(hexlify(0), 4),
884
+ hexZeroPad(hexlify(data.pools.length), 32), // pool count
885
+ hexZeroPad(hexlify(BigNumber.from(srcAmount)), 32),
886
+ ];
887
+ pools.forEach(pool => {
888
+ exchangeDataTypes.push('bytes32');
889
+ exchangeDataToPack.push(hexZeroPad(hexlify(BigNumber.from(pool)), 32));
890
+ });
891
+
892
+ exchangeData = solidityPack(exchangeDataTypes, exchangeDataToPack);
893
+ specialDexFlag = SpecialDex.SWAP_ON_UNISWAP_V2_FORK;
894
+ transferSrcTokenBeforeSwap = data.pools[0].address;
895
+ targetExchange = recipient;
896
+ dexFuncHasRecipient = true;
897
+ } else {
898
+ const weth = this.getWETHAddress(srcToken, destToken, data.weth);
899
+
900
+ exchangeData = this.exchangeRouterInterface.encodeFunctionData(
901
+ UniswapV2Functions.buy,
902
+ [srcToken, srcAmount, destAmount, weth, pools],
903
+ );
904
+ specialDexFlag = SpecialDex.DEFAULT;
905
+ targetExchange = data.router;
906
+ dexFuncHasRecipient = false;
907
+ }
908
+
909
+ return {
910
+ needWrapNative: this.needWrapNative,
911
+ specialDexSupportsInsertFromAmount: true,
912
+ dexFuncHasRecipient,
913
+ exchangeData,
914
+ targetExchange,
915
+ specialDexFlag,
916
+ transferSrcTokenBeforeSwap,
917
+ returnAmountPos: undefined,
918
+ };
919
+ }
920
+
921
+ // TODO: Move to new uniswapv2&forks router interface
922
+ getDirectParam(
923
+ srcToken: Address,
924
+ destToken: Address,
925
+ srcAmount: NumberAsString,
926
+ destAmount: NumberAsString,
927
+ expectedAmount: NumberAsString,
928
+ _data: UniswapData,
929
+ side: SwapSide,
930
+ permit: string,
931
+ uuid: string,
932
+ feePercent: NumberAsString,
933
+ deadline: NumberAsString,
934
+ partner: string,
935
+ beneficiary: string,
936
+ contractMethod?: string,
937
+ ): TxInfo<UniswapParam> {
938
+ if (!contractMethod) throw new Error(`contractMethod need to be passed`);
939
+ if (permit !== '0x') contractMethod += 'WithPermit';
940
+
941
+ const swapParams = ((): UniswapParam => {
942
+ const data = _data as unknown as UniswapDataLegacy;
943
+ const path = this.fixPath(data.path, srcToken, destToken);
944
+
945
+ switch (contractMethod) {
946
+ case UniswapV2Functions.swapOnUniswap:
947
+ case UniswapV2Functions.buyOnUniswap:
948
+ return [srcAmount, destAmount, path];
949
+
950
+ case UniswapV2Functions.swapOnUniswapFork:
951
+ case UniswapV2Functions.buyOnUniswapFork:
952
+ return [
953
+ data.factory,
954
+ prependWithOx(data.initCode),
955
+ srcAmount,
956
+ destAmount,
957
+ path,
958
+ ];
959
+
960
+ case UniswapV2Functions.swapOnUniswapV2Fork:
961
+ case UniswapV2Functions.buyOnUniswapV2Fork:
962
+ return [
963
+ srcToken,
964
+ srcAmount,
965
+ destAmount,
966
+ this.getWETHAddress(srcToken, destToken, _data.weth),
967
+ encodePools(_data.pools, this.feeFactor),
968
+ ];
969
+
970
+ case UniswapV2Functions.swapOnUniswapV2ForkWithPermit:
971
+ case UniswapV2Functions.buyOnUniswapV2ForkWithPermit:
972
+ return [
973
+ srcToken,
974
+ srcAmount,
975
+ destAmount,
976
+ this.getWETHAddress(srcToken, destToken, _data.weth),
977
+ encodePools(_data.pools, this.feeFactor),
978
+ permit,
979
+ ];
980
+
981
+ default:
982
+ throw new Error(`contractMethod=${contractMethod} is not supported`);
983
+ }
984
+ })();
985
+
986
+ const encoder = (...params: UniswapParam) =>
987
+ this.routerInterface.encodeFunctionData(contractMethod!, params);
988
+ return {
989
+ params: swapParams,
990
+ encoder,
991
+ networkFee: '0',
992
+ };
993
+ }
994
+
995
+ static getDirectFunctionName(): string[] {
996
+ return this.directFunctionName;
997
+ }
998
+
999
+ getDirectParamV6(
1000
+ srcToken: Address,
1001
+ destToken: Address,
1002
+ fromAmount: NumberAsString,
1003
+ toAmount: NumberAsString,
1004
+ quotedAmount: NumberAsString,
1005
+ data: UniswapV2Data,
1006
+ side: SwapSide,
1007
+ permit: string,
1008
+ uuid: string,
1009
+ partnerAndFee: string,
1010
+ beneficiary: string,
1011
+ blockNumber: number,
1012
+ contractMethod?: string,
1013
+ ) {
1014
+ if (!contractMethod) throw new Error(`contractMethod need to be passed`);
1015
+ if (!UniswapV2.getDirectFunctionNameV6().includes(contractMethod!)) {
1016
+ throw new Error(`Invalid contract method ${contractMethod}`);
1017
+ }
1018
+
1019
+ const { path, pools } = data;
1020
+ const length = path.length;
1021
+ const encodedPath = path.reduce((acc, _, i) => {
1022
+ if (i >= length - 1) return acc;
1023
+
1024
+ const p = this._encodePathV6(
1025
+ {
1026
+ srcToken: path[i],
1027
+ destToken: path[i + 1],
1028
+ direction: pools[i].direction,
1029
+ },
1030
+ side,
1031
+ data.wethAddress,
1032
+ ).replace('0x', '');
1033
+
1034
+ return acc + p;
1035
+ }, '0x');
1036
+
1037
+ const metadata = hexConcat([
1038
+ hexZeroPad(uuidToBytes16(uuid), 16),
1039
+ hexZeroPad(hexlify(blockNumber), 16),
1040
+ ]);
1041
+
1042
+ const uniData: UniswapV2ParamsDirectBase = [
1043
+ srcToken,
1044
+ destToken,
1045
+ fromAmount,
1046
+ toAmount,
1047
+ quotedAmount,
1048
+ metadata,
1049
+ beneficiary,
1050
+ encodedPath,
1051
+ ];
1052
+
1053
+ const swapParams: UniswapV2ParamsDirect = [uniData, partnerAndFee, permit];
1054
+
1055
+ const encoder = (...params: (string | UniswapV2ParamsDirect)[]) => {
1056
+ return this.augustusV6Interface.encodeFunctionData(
1057
+ side === SwapSide.SELL
1058
+ ? UniswapV2FunctionsV6.swap
1059
+ : UniswapV2FunctionsV6.buy,
1060
+ [...params],
1061
+ );
1062
+ };
1063
+
1064
+ return {
1065
+ params: swapParams,
1066
+ encoder,
1067
+ networkFee: '0',
1068
+ };
1069
+ }
1070
+
1071
+ static getDirectFunctionNameV6(): string[] {
1072
+ return this.directFunctionNameV6;
1073
+ }
1074
+
1075
+ // univ2 always had 1 pool per pair
1076
+ private _encodePathV6(
1077
+ path: {
1078
+ srcToken: Address;
1079
+ destToken: Address;
1080
+ direction: boolean;
1081
+ },
1082
+ side: SwapSide,
1083
+ weth?: Address,
1084
+ ): string {
1085
+ if (path == null) {
1086
+ this.logger.error(
1087
+ `${this.dexKey}: Received invalid path=${path} for side=${side} to encode`,
1088
+ );
1089
+ return '0x';
1090
+ }
1091
+
1092
+ // v6 expects weth for eth in pools
1093
+ if (isETHAddress(path.srcToken)) {
1094
+ path.srcToken =
1095
+ weth || this.dexHelper.config.data.wrappedNativeTokenAddress;
1096
+ }
1097
+
1098
+ if (isETHAddress(path.destToken)) {
1099
+ path.destToken =
1100
+ weth || this.dexHelper.config.data.wrappedNativeTokenAddress;
1101
+ }
1102
+
1103
+ // contract expects tokens to be sorted, and direction switched in case sorting changes src/dest order
1104
+ const [srcTokenSorted, destTokenSorted] =
1105
+ BigInt(path.srcToken) > BigInt(path.destToken)
1106
+ ? [path.destToken, path.srcToken]
1107
+ : [path.srcToken, path.destToken];
1108
+
1109
+ const direction = srcTokenSorted === path.srcToken ? 1 : 0;
1110
+
1111
+ const tokensEncoded = pack(
1112
+ ['address', 'address'],
1113
+ [srcTokenSorted, destTokenSorted],
1114
+ );
1115
+ return tokensEncoded + '0'.repeat(47) + direction;
1116
+ }
1117
+ }