@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,1315 +1,1315 @@
1
- import { Interface, AbiCoder, JsonFragment } from '@ethersproject/abi';
2
- import _ from 'lodash';
3
- import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
4
- import { assert } from 'ts-essentials';
5
- import BigNumber from 'bignumber.js';
6
- import CurveABI from '../../abi/Curve.json';
7
- import DirectSwapABI from '../../abi/DirectSwap.json';
8
- import { Adapters, CurveV1Config } from './config';
9
-
10
- import {
11
- AdapterExchangeParam,
12
- Address,
13
- DexExchangeParam,
14
- ExchangePrices,
15
- ExchangeTxInfo,
16
- PoolLiquidity,
17
- PoolPrices,
18
- PreprocessTransactionOptions,
19
- SimpleExchangeParam,
20
- Token,
21
- TransferFeeParams,
22
- TxInfo,
23
- } from '../../types';
24
- import {
25
- Network,
26
- NULL_ADDRESS,
27
- SRC_TOKEN_PARASWAP_TRANSFERS,
28
- ETHER_ADDRESS,
29
- SwapSide,
30
- } from '../../constants';
31
- import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
32
- import StableSwapBBTC from '../../abi/curve-v1/StableSwapBBTC.json';
33
- import { CurvePool } from './pools/curve-pool';
34
- import { CurveMetapool } from './pools/curve-metapool';
35
- import { ThreePool, address as ThreePoolAddress } from './pools/3pool';
36
- import { SUSDPool, address as SUSDPoolAddress } from './pools/sUSDpool';
37
- import { HBTCPool, address as HBTCPoolAddress } from './pools/hBTCpool';
38
- import { RenPool, address as RenPoolAddress } from './pools/renpool';
39
- import { SBTCPool, address as SBTCPoolAddress } from './pools/sBTCpool';
40
- import { SETHPool, address as SETHPoolAddress } from './pools/sETHpool';
41
- import { STETHPool, address as STETHPoolAddress } from './pools/stETHpool';
42
- import { EURSPool, address as EURSPoolAddress } from './pools/EURSpool';
43
- import { DUSDPool, address as DUSDPoolAddress } from './pools/DUSDpool';
44
- import { BBTCPool, address as BBTCPoolAddress } from './pools/bBTCpool';
45
- import { GUSDPool, address as GUSDPoolAddress } from './pools/GUSDpool';
46
- import { HUSDPool, address as HUSDPoolAddress } from './pools/HUSDpool';
47
- import {
48
- LinkUSDPool,
49
- address as LinkUSDPoolAddress,
50
- } from './pools/LinkUSDpool';
51
- import { MUSDPool, address as MUSDPoolAddress } from './pools/MUSDpool';
52
- import { OBTCPool, address as OBTCPoolAddress } from './pools/oBTCpool';
53
- import { PBTCPool, address as PBTCPoolAddress } from './pools/pBTCpool';
54
- import { RSVPool, address as RSVPoolAddress } from './pools/rsvpool';
55
- import { TBTCPool, address as TBTCPoolAddress } from './pools/tBTCpool';
56
- import { USDKPool, address as USDKPoolAddress } from './pools/usdkpool';
57
- import { USTPool, address as USTPoolAddress } from './pools/ustpool';
58
- import { SLINKPool, address as SLINKPoolAddress } from './pools/sLinkpool';
59
- import {
60
- getBigIntPow,
61
- getDexKeysWithNetwork,
62
- interpolate,
63
- isDestTokenTransferFeeToBeExchanged,
64
- isETHAddress,
65
- isSrcTokenTransferFeeToBeExchanged,
66
- Utils,
67
- uuidToBytes16,
68
- } from '../../utils';
69
- import { BN_0 } from '../../bignumber-constants';
70
- import {
71
- getLocalDeadlineAsFriendlyPlaceholder,
72
- SimpleExchange,
73
- } from '../simple-exchange';
74
- import { IDex } from '../idex';
75
- import { IDexHelper } from '../../dex-helper';
76
- import { Logger } from 'log4js';
77
- import { uin128DecodeToInt, uin256DecodeToFloat } from '../../lib/decoders';
78
- import { getManyPoolStates } from './pools/getstate-multicall';
79
- import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper';
80
- import {
81
- PoolConfig,
82
- CurveV1Data,
83
- TokenWithReasonableVolume,
84
- CurveSwapFunctions,
85
- CurveV1SwapType,
86
- DirectCurveV1Param,
87
- DirectCurveV1ParamV6,
88
- CurveV1DirectSwap,
89
- } from './types';
90
- import { erc20Iface } from '../../lib/utils-interfaces';
91
- import { applyTransferFee } from '../../lib/token-transfer-fee';
92
- import { DIRECT_METHOD_NAME, DIRECT_METHOD_NAME_V6 } from './constants';
93
- import { packCurveData } from '../../lib/curve/encoder';
94
- import { encodeCurveAssets } from './packer';
95
- import { hexConcat, hexZeroPad, hexlify } from 'ethers/lib/utils';
96
-
97
- const CURVE_DEFAULT_CHUNKS = 10;
98
-
99
- const CURVE_LENDING_POOl_GAS = 340 * 1000;
100
- const CURVE_POOL_GAS = 200 * 1000;
101
-
102
- const coder = new AbiCoder();
103
-
104
- export class CurveV1
105
- extends SimpleExchange
106
- implements IDex<CurveV1Data, DirectCurveV1Param | CurveV1DirectSwap>
107
- {
108
- exchangeRouterInterface: Interface;
109
- minConversionRate = '1';
110
-
111
- eventPools = new Array<CurvePool | CurveMetapool>();
112
- public poolInterface: Interface;
113
-
114
- private logger: Logger;
115
-
116
- readonly hasConstantPriceLargeAmounts = false;
117
- readonly isFeeOnTransferSupported = true;
118
-
119
- private decimalsCoinsAndUnderlying: Record<string, number> = {};
120
-
121
- protected pools: Record<string, PoolConfig>;
122
- protected eventSupportedPools: string[];
123
- protected baseTokens: Record<string, TokenWithReasonableVolume>;
124
-
125
- protected disableFeeOnTransferTokenAddresses: Set<string>;
126
-
127
- readonly directSwapIface = new Interface(DirectSwapABI);
128
-
129
- readonly SRC_TOKEN_DEX_TRANSFERS = 1;
130
- readonly DEST_TOKEN_DEX_TRANSFERS = 1;
131
-
132
- public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
133
- getDexKeysWithNetwork(CurveV1Config);
134
-
135
- constructor(
136
- protected network: Network,
137
- dexKey: string,
138
- protected dexHelper: IDexHelper,
139
- dexConfig = CurveV1Config[dexKey][network],
140
-
141
- protected adapters = Adapters[network],
142
- ) {
143
- super(dexHelper, dexKey);
144
- this.pools = Object.keys(dexConfig.pools).reduce<
145
- Record<string, PoolConfig>
146
- >((acc, key) => {
147
- const poolConf = dexConfig.pools[key];
148
-
149
- acc[key] = {
150
- underlying: poolConf.underlying.map(t => t.toLowerCase()),
151
- coins: poolConf.coins.map(t => t.toLowerCase()),
152
- address: poolConf.address.toLowerCase(),
153
- name: poolConf.name,
154
- type: poolConf.type,
155
- version: poolConf.version,
156
- isLending: poolConf.isLending,
157
- isMetapool: poolConf.isMetapool,
158
- baseToken: poolConf.baseToken
159
- ? poolConf.baseToken.toLowerCase()
160
- : poolConf.baseToken,
161
- liquidityUSD: poolConf.liquidityUSD,
162
- trackCoins: poolConf.trackCoins,
163
- useLending: poolConf.useLending,
164
- precisionMul: poolConf.precisionMul,
165
- tokenAddress: poolConf.tokenAddress
166
- ? poolConf.tokenAddress.toLowerCase()
167
- : undefined,
168
- isFeeOnTransferSupported: poolConf.isFeeOnTransferSupported,
169
- };
170
-
171
- return acc;
172
- }, {});
173
-
174
- this.eventSupportedPools = dexConfig.eventSupportedPools.map(addr =>
175
- addr.toLowerCase(),
176
- );
177
-
178
- this.baseTokens = Object.keys(dexConfig.baseTokens).reduce<
179
- Record<string, TokenWithReasonableVolume>
180
- >((acc, key) => {
181
- const baseToken = dexConfig.baseTokens[key];
182
- acc[key.toLowerCase()] = {
183
- address: baseToken.address.toLowerCase(),
184
- decimals: baseToken.decimals,
185
- reasonableVolume: baseToken.reasonableVolume,
186
- };
187
- return acc;
188
- }, {});
189
-
190
- this.disableFeeOnTransferTokenAddresses =
191
- dexConfig.disableFeeOnTransferTokenAddresses
192
- ? new Set<string>(
193
- Array.from(
194
- dexConfig.disableFeeOnTransferTokenAddresses.values(),
195
- ).map(addr => addr.toLowerCase()),
196
- )
197
- : new Set<string>();
198
-
199
- this.exchangeRouterInterface = new Interface(CurveABI as JsonFragment[]);
200
-
201
- this.logger = dexHelper.getLogger(dexKey);
202
-
203
- this.poolInterface = new Interface(StableSwapBBTC as any);
204
- }
205
-
206
- protected getPoolByAddress(address: Address) {
207
- return (
208
- this.eventPools.find(
209
- p => p.address.toLowerCase() == address.toLowerCase(),
210
- ) || null
211
- );
212
- }
213
-
214
- protected getPoolConfigByAddress(address: Address) {
215
- return Object.values(this.pools).find(p => p.address == address);
216
- }
217
-
218
- getPoolConfig(from: Token, to: Token): PoolConfig | undefined {
219
- return this.getPoolConfigs(from, to)[0];
220
- }
221
-
222
- getPoolConfigs(from: Token, to: Token) {
223
- const fromAddress = from.address.toLowerCase();
224
- const toAddress = to.address.toLowerCase();
225
- return Object.values(this.pools).filter(config => {
226
- const underlying = config.underlying.map((add: string) =>
227
- add.toLowerCase(),
228
- );
229
- const coins = config.coins.map((add: string) => add.toLowerCase());
230
-
231
- // Case 0: If the swap is between the underlying first token and any underlying token
232
- if (
233
- underlying.length &&
234
- ((underlying[0] === fromAddress && underlying.includes(toAddress)) ||
235
- (underlying[0] === toAddress && underlying.includes(fromAddress)))
236
- )
237
- return true;
238
- // Case 1: If it not a metapool and the swap is between any underlying tokens
239
- if (
240
- config.isLending &&
241
- underlying.includes(toAddress) &&
242
- underlying.includes(fromAddress)
243
- )
244
- return true;
245
-
246
- // Case 2: If it a swap between any direct tokens
247
- if (coins.includes(toAddress) && coins.includes(fromAddress)) return true;
248
-
249
- return false;
250
- });
251
- }
252
-
253
- async getPoolIdentifiers(
254
- _from: Token,
255
- _to: Token,
256
- side: SwapSide,
257
- blockNumber: number,
258
- ): Promise<string[]> {
259
- const pools = this.getPoolConfigs(_from, _to);
260
- return pools.map(pool => this.getPoolIdentifier(pool.name));
261
- }
262
-
263
- getContractAddress(srcToken: Token, to: Token) {
264
- const pool = this.getPoolConfig(srcToken, to);
265
- return pool && pool.address;
266
- }
267
-
268
- poolConfigsByAddress() {
269
- return Object.keys(this.pools).reduce((acc: any, poolName: string) => {
270
- acc[this.pools[poolName].address.toLowerCase()] = this.pools[poolName];
271
- return acc;
272
- }, {});
273
- }
274
-
275
- // TODO: revisit to verify the chunks for BTC pools vs USD pools
276
- getOnChainChunks(
277
- from: Token,
278
- to: Token,
279
- srcAmount: bigint,
280
- defaultChunks: number,
281
- ) {
282
- return CURVE_DEFAULT_CHUNKS;
283
- //return srcAmount >= 10000n * getBigIntPow(from.decimals)
284
- // ? defaultChunks
285
- // : CURVE_DEFAULT_CHUNKS;
286
- }
287
-
288
- getEventPoolInstance(poolAddress: Address): CurvePool | CurveMetapool | null {
289
- switch (poolAddress.toLowerCase()) {
290
- case ThreePoolAddress:
291
- return new ThreePool(this.dexKey, this.dexHelper);
292
- case SUSDPoolAddress:
293
- return new SUSDPool(this.dexKey, this.dexHelper);
294
- case HBTCPoolAddress:
295
- return new HBTCPool(this.dexKey, this.dexHelper);
296
- case RenPoolAddress:
297
- return new RenPool(this.dexKey, this.dexHelper);
298
- case SBTCPoolAddress:
299
- return new SBTCPool(this.dexKey, this.dexHelper);
300
- case SETHPoolAddress:
301
- return new SETHPool(this.dexKey, this.dexHelper);
302
- case STETHPoolAddress:
303
- return new STETHPool(this.dexKey, this.dexHelper);
304
- case EURSPoolAddress:
305
- return new EURSPool(this.dexKey, this.dexHelper);
306
- case DUSDPoolAddress:
307
- return new DUSDPool(this.dexKey, this.dexHelper);
308
- case BBTCPoolAddress:
309
- return new BBTCPool(this.dexKey, this.dexHelper);
310
- case GUSDPoolAddress:
311
- return new GUSDPool(this.dexKey, this.dexHelper);
312
- case HUSDPoolAddress:
313
- return new HUSDPool(this.dexKey, this.dexHelper);
314
- case LinkUSDPoolAddress:
315
- return new LinkUSDPool(this.dexKey, this.dexHelper);
316
- case MUSDPoolAddress:
317
- return new MUSDPool(this.dexKey, this.dexHelper);
318
- case OBTCPoolAddress:
319
- return new OBTCPool(this.dexKey, this.dexHelper);
320
- case PBTCPoolAddress:
321
- return new PBTCPool(this.dexKey, this.dexHelper);
322
- case RSVPoolAddress:
323
- return new RSVPool(this.dexKey, this.dexHelper);
324
- case TBTCPoolAddress:
325
- return new TBTCPool(this.dexKey, this.dexHelper);
326
- case USDKPoolAddress:
327
- return new USDKPool(this.dexKey, this.dexHelper);
328
- case USTPoolAddress:
329
- return new USTPool(this.dexKey, this.dexHelper);
330
- case SLINKPoolAddress:
331
- return new SLINKPool(this.dexKey, this.dexHelper);
332
- default:
333
- return null;
334
- }
335
- }
336
-
337
- // Makes sure all the pools from poolAddress are being tracked. If a pool in not tracked yet
338
- // fetch the state and setup the pool, if they are being watched but don't have a valid state
339
- // add it to the list of unavailable pools. Return the list of unavailable pools.
340
- async batchCatchUpPools(
341
- poolAddresses: string[],
342
- blockNumber: number,
343
- ): Promise<string[]> {
344
- const unavailablePools = new Array<string>();
345
- if (!blockNumber) return unavailablePools;
346
- let poolsToFetch: (CurvePool | CurveMetapool)[] = [];
347
- for (const poolAddress of poolAddresses) {
348
- // Check if the pool is already tracked
349
- let pool = this.getPoolByAddress(poolAddress);
350
- // If the pool is not stracked add it to the list of pools to fetch
351
- if (!pool) {
352
- let newPool = this.getEventPoolInstance(poolAddress);
353
- if (!newPool)
354
- throw new Error(
355
- `Error_${this.dexKey} requested unsupported event pool with address ${poolAddress}`,
356
- );
357
-
358
- const addressesSubscribed = newPool.addressesSubscribed.map(address =>
359
- address.toLowerCase(),
360
- );
361
- if (!addressesSubscribed.includes(poolAddress)) {
362
- newPool.addressesSubscribed.push(poolAddress);
363
- }
364
-
365
- await newPool.initialize(blockNumber);
366
- poolsToFetch.push(newPool);
367
- } else if (!pool.getState(blockNumber)) {
368
- unavailablePools.push(poolAddress);
369
- }
370
- }
371
-
372
- if (!poolsToFetch.length) return unavailablePools;
373
-
374
- try {
375
- const poolStates = await getManyPoolStates(
376
- poolsToFetch,
377
- this.dexHelper.multiContract,
378
- blockNumber,
379
- );
380
-
381
- for (let i = 0; i < poolsToFetch.length; i++) {
382
- await poolsToFetch[i].setup(blockNumber, poolStates[i] as any);
383
- this.eventPools.push(poolsToFetch[i]);
384
- }
385
- return unavailablePools;
386
- } catch (e) {
387
- this.logger.error(`Error_batchCatchUpPools`, e);
388
- throw e;
389
- }
390
- }
391
-
392
- async getRatesEventPools(
393
- fromToken: Token,
394
- toToken: Token,
395
- amounts: BigNumber[],
396
- fromBigNumbers: number,
397
- pools: string[],
398
- indexes: [number, number, number][],
399
- blockNumber: number,
400
- ) {
401
- const unavailablePools = await this.batchCatchUpPools(pools, blockNumber);
402
-
403
- if (unavailablePools.length) {
404
- this.logger.error(
405
- `Couldn't use events for ${unavailablePools.length} ${this.dexKey} pools!`,
406
- );
407
- pools = pools.filter(p => !unavailablePools.includes(p));
408
- }
409
-
410
- let rates = new Array(pools.length);
411
- for (let i = 0; i < pools.length; i++) {
412
- rates[i] = {
413
- exchange: pools[i],
414
- rates: this.getRatesEventPool(
415
- pools[i],
416
- indexes[i],
417
- amounts,
418
- fromBigNumbers,
419
- blockNumber,
420
- ),
421
- };
422
- }
423
- return rates;
424
- }
425
-
426
- async getRatesOnChain(
427
- fromToken: Token,
428
- toToken: Token,
429
- amounts: bigint[],
430
- pools: string[],
431
- indexes: [number, number, number][],
432
- blockNumber: number,
433
- ) {
434
- const chunks = amounts.length - 1;
435
- const srcAmount = amounts[chunks]!;
436
-
437
- // The number of chunks used onchain to minimize the full node calls
438
- const onChainChunks = this.getOnChainChunks(
439
- fromToken,
440
- toToken,
441
- srcAmount,
442
- chunks,
443
- );
444
-
445
- const _width = Math.floor(chunks / onChainChunks);
446
-
447
- const unitVolume = amounts[0];
448
- amounts[0] = 0n;
449
-
450
- const _amounts = [unitVolume].concat(
451
- Array.from(Array(onChainChunks).keys()).map(
452
- i => amounts[(i + 1) * _width],
453
- ),
454
- );
455
-
456
- const calldata = pools.map((p, i) =>
457
- _amounts.map(_amount => ({
458
- target: p,
459
- callData: this.poolInterface.encodeFunctionData(
460
- indexes[i][2] === 1 ? 'get_dy_underlying' : 'get_dy',
461
- [indexes[i][0], indexes[i][1], _amount],
462
- ),
463
- })),
464
- );
465
-
466
- //const data = await this.multi.methods
467
- // .tryAggregate(false, calldata.flat())
468
- // .call({}, 'latest');
469
- const data = (
470
- await Promise.all(
471
- calldata.map(async poolCalldata => {
472
- try {
473
- const result = await Utils.timeoutPromise<MultiResult<any>[]>(
474
- this.dexHelper.multiContract.methods
475
- .tryAggregate(false, poolCalldata)
476
- .call({}, 'latest'),
477
- 2000,
478
- `Timed out multicall for curve pool ${poolCalldata[0].target}`,
479
- );
480
- return result;
481
- } catch (e) {
482
- this.logger.error(e);
483
- return Array<MultiResult<any>>(poolCalldata.length).fill({
484
- success: false,
485
- returnData: '0x',
486
- });
487
- }
488
- }),
489
- )
490
- ).flat();
491
-
492
- // Convert the on chain rates to the requested chunks, using simple interpolation
493
- const correctChunks = (_r: bigint[]): bigint[] => [
494
- _r[0],
495
- ...interpolate(
496
- _amounts.slice(1),
497
- _r.slice(1),
498
- amounts.slice(1),
499
- SwapSide.SELL,
500
- ),
501
- ];
502
-
503
- const decode = (j: number) => {
504
- if (!data[j].success) return 0n;
505
- const decoded = coder.decode(['uint256'], data[j].returnData);
506
- return BigInt(decoded[0].toString());
507
- };
508
-
509
- let i = 0;
510
- // chunks !== onChainChunks || !isAmountsLinear
511
- const ratesAllPools = pools.map(p => {
512
- const rates = _amounts.map(a => decode(i++));
513
- return {
514
- rates: correctChunks(rates),
515
- exchange: p,
516
- };
517
- });
518
-
519
- return ratesAllPools;
520
- }
521
-
522
- protected noMorePrice(prices: BigNumber[], index: number): boolean {
523
- return index > 1 && prices[index - 1].eq(BN_0);
524
- }
525
-
526
- getRatesEventPool(
527
- exchange: string,
528
- curveIndexes: number[],
529
- amounts: BigNumber[],
530
- fromBigNumbers: number,
531
- blockNumber: number,
532
- ) {
533
- let rates = new Array<BigNumber>(amounts.length).fill(BN_0);
534
-
535
- let fromIndex = curveIndexes[0];
536
- let toIndex = curveIndexes[1];
537
- let isTokenSwap = curveIndexes[2] === 1;
538
-
539
- let pool = this.getPoolByAddress(exchange);
540
- if (!pool) {
541
- throw new Error(
542
- `Error_${this.dexKey}_getRatesEvent pool is not initialised`,
543
- );
544
- }
545
-
546
- const state = pool.getState(blockNumber);
547
- if (!state)
548
- throw new Error(
549
- `Error_${this.dexKey}_${pool.name} expected state at blocknumber ${blockNumber}, got none`,
550
- );
551
-
552
- if (isTokenSwap) {
553
- rates[0] = pool.get_dy_underlying(
554
- fromIndex,
555
- toIndex,
556
- amounts[0],
557
- state as any,
558
- );
559
-
560
- for (let i = 1; i < amounts.length; i++) {
561
- rates[i] = this.noMorePrice(rates, i)
562
- ? BN_0
563
- : pool.get_dy_underlying(
564
- fromIndex,
565
- toIndex,
566
- amounts[i],
567
- state as any,
568
- );
569
- }
570
- } else {
571
- rates[0] = pool.get_dy(fromIndex, toIndex, amounts[0], state as any);
572
-
573
- for (let i = 1; i < amounts.length; i++) {
574
- rates[i] = this.noMorePrice(rates, i)
575
- ? BN_0
576
- : pool.get_dy(fromIndex, toIndex, amounts[i], state as any);
577
- }
578
- }
579
-
580
- let floatRates = rates.map(rate => BigInt(rate.toFixed(0)));
581
- return floatRates;
582
- }
583
-
584
- isEventPoolSupported(poolAddress: Address): Boolean {
585
- return this.eventSupportedPools.some(p => p === poolAddress.toLowerCase());
586
- }
587
-
588
- allocPools(
589
- from: Token,
590
- to: Token,
591
- side: SwapSide,
592
- routeID: number,
593
- usedPools: { [poolIdentifier: string]: number },
594
- isFirstSwap: boolean,
595
- ) {
596
- if (side === SwapSide.BUY) {
597
- return;
598
- }
599
-
600
- this.getPoolConfigs(from, to).forEach(p => {
601
- const poolIdentifier = this.getPoolIdentifier(p.name);
602
- if (!(poolIdentifier in usedPools)) {
603
- usedPools[poolIdentifier] = routeID;
604
- }
605
- });
606
- }
607
-
608
- public getPoolIdentifier(name: string) {
609
- return `${this.dexKey}_${name.toLowerCase()}`;
610
- }
611
-
612
- async getPricesVolume(
613
- _from: Token,
614
- _to: Token,
615
- amounts: bigint[],
616
- side: SwapSide,
617
- blockNumber: number,
618
- // list of pool identifiers to use for pricing, if undefined use all pools
619
- limitPools?: string[],
620
- transferFees: TransferFeeParams = {
621
- srcFee: 0,
622
- destFee: 0,
623
- srcDexFee: 0,
624
- destDexFee: 0,
625
- },
626
- ): Promise<ExchangePrices<CurveV1Data> | null> {
627
- try {
628
- if (side === SwapSide.BUY) {
629
- return null;
630
- }
631
-
632
- _from.address = _from.address.toLowerCase();
633
- _to.address = _to.address.toLowerCase();
634
-
635
- const _isSrcTokenTransferFeeToBeExchanged =
636
- this.disableFeeOnTransferTokenAddresses.has(_from.address)
637
- ? false
638
- : isSrcTokenTransferFeeToBeExchanged(transferFees);
639
-
640
- const _isDestTokenTransferFeeToBeExchanged =
641
- this.disableFeeOnTransferTokenAddresses.has(_to.address)
642
- ? false
643
- : isDestTokenTransferFeeToBeExchanged(transferFees);
644
-
645
- // We first filter out pools which were explicitly excluded and pools which are already used
646
- // then for the good pools we set the boolean to be true for used pools
647
- // and for each pool we take the address.
648
- const goodPoolConfigs = this.getPoolConfigs(_from, _to).filter(p => {
649
- if (limitPools !== undefined) {
650
- const id = this.getPoolIdentifier(p.name);
651
- if (!limitPools.includes(id)) {
652
- return false;
653
- }
654
- }
655
-
656
- if (_isSrcTokenTransferFeeToBeExchanged) {
657
- // Fee on transfers supported only when flag is specified
658
- return !!p.isFeeOnTransferSupported;
659
- }
660
- return true;
661
- });
662
-
663
- const goodPoolAddress = goodPoolConfigs.map(p => p.address);
664
- const indexesByAddress = goodPoolConfigs.reduce(
665
- (acc: { [address: string]: [number, number, number] }, pc) => {
666
- acc[pc.address] = this.getSwapIndexes(_from, _to, pc);
667
- return acc;
668
- },
669
- {},
670
- );
671
-
672
- if (goodPoolAddress.length === 0) {
673
- return null;
674
- }
675
-
676
- const amountsWithUnit = [
677
- getBigIntPow(_from.decimals),
678
- ...amounts.slice(1),
679
- ];
680
- const amountsWithUnitAndFee = _isSrcTokenTransferFeeToBeExchanged
681
- ? applyTransferFee(
682
- applyTransferFee(
683
- amountsWithUnit,
684
- side,
685
- transferFees.srcFee,
686
- SRC_TOKEN_PARASWAP_TRANSFERS,
687
- ),
688
- side,
689
- transferFees.srcDexFee,
690
- this.SRC_TOKEN_DEX_TRANSFERS,
691
- )
692
- : amountsWithUnit;
693
-
694
- let _prices = new Array();
695
-
696
- const eventPools = goodPoolAddress.filter(p =>
697
- this.isEventPoolSupported(p),
698
- );
699
- const eventPoolIndexes = eventPools.map(ep => indexesByAddress[ep]);
700
- if (eventPools) {
701
- const pricesEvents = await this.getRatesEventPools(
702
- _from,
703
- _to,
704
- amountsWithUnitAndFee.map(a => new BigNumber(a.toString())),
705
- _from.decimals,
706
- eventPools,
707
- eventPoolIndexes,
708
- blockNumber,
709
- );
710
- _prices = _prices.concat(pricesEvents);
711
- }
712
-
713
- // if the pool rates was not available in the event pool then fallback to the onchain pool query
714
- const isPoolNotInPrices = (poolAddress: Address) =>
715
- !_prices.find(
716
- existingPrice =>
717
- existingPrice.exchange.toLowerCase() === poolAddress.toLowerCase(),
718
- );
719
- const onChainPools = goodPoolAddress.filter(isPoolNotInPrices);
720
-
721
- const onChainPoolIndexes = onChainPools.map(op => indexesByAddress[op]);
722
-
723
- // onChainPools are only supported for pools with linear price chunks
724
- if (onChainPools.length) {
725
- const pricesOnChain = await this.getRatesOnChain(
726
- _from,
727
- _to,
728
- amountsWithUnitAndFee,
729
- onChainPools,
730
- onChainPoolIndexes,
731
- blockNumber,
732
- );
733
- _prices = _prices.concat(pricesOnChain);
734
- }
735
-
736
- const poolConfigsByAddress = this.poolConfigsByAddress();
737
-
738
- const poolPrices = _prices.reduce(
739
- (acc: PoolPrices<CurveV1Data>[], _price: any, idx: number) => {
740
- if (
741
- _price.exchange === NULL_ADDRESS ||
742
- !(_price.exchange.toLowerCase() in poolConfigsByAddress)
743
- )
744
- return acc;
745
-
746
- const poolConfig =
747
- poolConfigsByAddress[_price.exchange.toLowerCase()];
748
- const indexes = this.getSwapIndexes(_from, _to, poolConfig);
749
- const [i, j, swapType] = indexes;
750
-
751
- if (_isDestTokenTransferFeeToBeExchanged) {
752
- _price.rates = applyTransferFee(
753
- _price.rates,
754
- side,
755
- transferFees.destDexFee,
756
- this.DEST_TOKEN_DEX_TRANSFERS,
757
- );
758
- }
759
-
760
- acc.push({
761
- prices: [0n, ..._price.rates.slice(1)],
762
- unit: _price.rates[0],
763
- data: {
764
- exchange: _price.exchange,
765
- i,
766
- j,
767
- underlyingSwap: swapType === 1,
768
- deadline: 0,
769
- },
770
- exchange: this.dexKey,
771
- poolIdentifier: `${this.dexKey}_${poolConfig.name.toLowerCase()}`,
772
- gasCost: poolConfig.isLending
773
- ? CURVE_LENDING_POOl_GAS
774
- : CURVE_POOL_GAS,
775
- poolAddresses: [_price.exchange],
776
- });
777
- return acc;
778
- },
779
- [],
780
- );
781
- return poolPrices;
782
- } catch (e) {
783
- if (blockNumber === 0)
784
- this.logger.error(
785
- `Error_${this.dexKey}_getPrices: Aurelius block manager not yet instantiated`,
786
- );
787
- this.logger.error(`Error_${this.dexKey}_getPrices`, e);
788
- return null;
789
- }
790
- }
791
-
792
- getSwapIndexes(
793
- from: Token,
794
- to: Token,
795
- pool?: PoolConfig,
796
- ): [number, number, number] {
797
- pool = pool || this.getPoolConfig(from, to);
798
- if (!pool) {
799
- return [-1, -1, -1];
800
- }
801
-
802
- const fromAddress = from.address.toLowerCase();
803
- const toAddress = to.address.toLowerCase();
804
- const isUnderlyingSwap =
805
- pool.underlying.length &&
806
- pool.underlying.some(t => t.toLowerCase() === fromAddress) &&
807
- pool.underlying.some(t => t.toLowerCase() === toAddress);
808
-
809
- const fromIndex = isUnderlyingSwap
810
- ? pool.underlying.findIndex(t => t.toLowerCase() === fromAddress)
811
- : pool.coins.findIndex(t => t.toLowerCase() === fromAddress);
812
-
813
- const toIndex = isUnderlyingSwap
814
- ? pool.underlying.findIndex(t => t.toLowerCase() === toAddress)
815
- : pool.coins.findIndex(t => t.toLowerCase() === toAddress);
816
-
817
- const swapType = isUnderlyingSwap ? 1 : 2;
818
-
819
- return [fromIndex, toIndex, swapType];
820
- }
821
-
822
- getAdapterParam(
823
- srcToken: string,
824
- destToken: string,
825
- srcAmount: string,
826
- destAmount: string,
827
- data: CurveV1Data,
828
- side: SwapSide,
829
- ): AdapterExchangeParam {
830
- if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
831
-
832
- const { i, j, deadline, underlyingSwap } = data;
833
- const payload = this.abiCoder.encodeParameter(
834
- {
835
- ParentStruct: {
836
- i: 'int128',
837
- j: 'int128',
838
- deadline: 'uint256',
839
- underlyingSwap: 'bool',
840
- },
841
- },
842
- { i, j, deadline, underlyingSwap },
843
- );
844
-
845
- return {
846
- targetExchange: data.exchange,
847
- payload,
848
- networkFee: '0',
849
- };
850
- }
851
-
852
- getCalldataGasCost(poolPrices: PoolPrices<CurveV1Data>): number | number[] {
853
- return (
854
- CALLDATA_GAS_COST.DEX_OVERHEAD +
855
- CALLDATA_GAS_COST.LENGTH_SMALL +
856
- CALLDATA_GAS_COST.INDEX +
857
- CALLDATA_GAS_COST.INDEX +
858
- CALLDATA_GAS_COST.TIMESTAMP +
859
- CALLDATA_GAS_COST.BOOL
860
- );
861
- }
862
-
863
- getAdapters(side: SwapSide): { name: string; index: number }[] | null {
864
- return this.adapters[side];
865
- }
866
-
867
- static getDirectFunctionName(): string[] {
868
- return [DIRECT_METHOD_NAME];
869
- }
870
-
871
- getTokenFromAddress(address: Address): Token {
872
- // In this Dex decimals are not used
873
- return { address, decimals: 0 };
874
- }
875
-
876
- async preProcessTransaction(
877
- optimalSwapExchange: OptimalSwapExchange<CurveV1Data>,
878
- srcToken: Token,
879
- _0: Token,
880
- _1: SwapSide,
881
- options: PreprocessTransactionOptions,
882
- ): Promise<[OptimalSwapExchange<CurveV1Data>, ExchangeTxInfo]> {
883
- if (!options.isDirectMethod) {
884
- return [
885
- optimalSwapExchange,
886
- {
887
- deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
888
- },
889
- ];
890
- }
891
-
892
- assert(
893
- optimalSwapExchange.data !== undefined,
894
- `preProcessTransaction: data field is missing`,
895
- );
896
-
897
- let isApproved: boolean | undefined;
898
-
899
- try {
900
- isApproved = await this.dexHelper.augustusApprovals.hasApproval(
901
- options.executionContractAddress,
902
- this.dexHelper.config.wrapETH(srcToken).address,
903
- optimalSwapExchange.data.exchange,
904
- );
905
- } catch (e) {
906
- this.logger.error(
907
- `preProcessTransaction failed to retrieve allowance info: `,
908
- e,
909
- );
910
- }
911
-
912
- return [
913
- {
914
- ...optimalSwapExchange,
915
- data: {
916
- ...optimalSwapExchange.data,
917
- isApproved,
918
- },
919
- },
920
- {
921
- deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
922
- },
923
- ];
924
- }
925
-
926
- getDirectParam(
927
- srcToken: Address,
928
- destToken: Address,
929
- srcAmount: NumberAsString,
930
- destAmount: NumberAsString,
931
- expectedAmount: NumberAsString,
932
- data: CurveV1Data,
933
- side: SwapSide,
934
- permit: string,
935
- uuid: string,
936
- feePercent: NumberAsString,
937
- deadline: NumberAsString,
938
- partner: string,
939
- beneficiary: string,
940
- contractMethod?: string,
941
- ): TxInfo<DirectCurveV1Param> {
942
- if (contractMethod !== DIRECT_METHOD_NAME) {
943
- throw new Error(`Invalid contract method ${contractMethod}`);
944
- }
945
- assert(side === SwapSide.SELL, 'Buy not supported');
946
-
947
- let isApproved: boolean = !!data.isApproved;
948
- if (data.isApproved === undefined) {
949
- this.logger.warn(`isApproved is undefined, defaulting to false`);
950
- }
951
-
952
- const swapParams: DirectCurveV1Param = [
953
- srcToken,
954
- destToken,
955
- data.exchange,
956
- srcAmount,
957
- destAmount,
958
- expectedAmount,
959
- feePercent,
960
- data.i.toString(),
961
- data.j.toString(),
962
- partner,
963
- isApproved,
964
- data.underlyingSwap
965
- ? CurveV1SwapType.EXCHANGE_UNDERLYING
966
- : CurveV1SwapType.EXCHANGE,
967
- beneficiary,
968
- // For CurveV1 we work as it is, without wrapping and unwrapping
969
- false,
970
- permit,
971
- uuidToBytes16(uuid),
972
- ];
973
-
974
- const encoder = (...params: DirectCurveV1Param) => {
975
- return this.directSwapIface.encodeFunctionData(DIRECT_METHOD_NAME, [
976
- params,
977
- ]);
978
- };
979
-
980
- return {
981
- params: swapParams,
982
- encoder,
983
- networkFee: '0',
984
- };
985
- }
986
-
987
- getDirectParamV6(
988
- srcToken: Address,
989
- destToken: Address,
990
- fromAmount: NumberAsString,
991
- toAmount: NumberAsString,
992
- quotedAmount: NumberAsString,
993
- data: CurveV1Data,
994
- side: SwapSide,
995
- permit: string,
996
- uuid: string,
997
- partnerAndFee: string,
998
- beneficiary: string,
999
- blockNumber: number,
1000
- contractMethod?: string,
1001
- ) {
1002
- if (contractMethod !== DIRECT_METHOD_NAME_V6) {
1003
- throw new Error(`Invalid contract method ${contractMethod}`);
1004
- }
1005
- assert(side === SwapSide.SELL, 'Buy not supported');
1006
-
1007
- const metadata = hexConcat([
1008
- hexZeroPad(uuidToBytes16(uuid), 16),
1009
- hexZeroPad(hexlify(blockNumber), 16),
1010
- ]);
1011
-
1012
- const swapParams: DirectCurveV1ParamV6 = [
1013
- packCurveData(
1014
- data.exchange,
1015
- !data.isApproved, // approve flag, if not approved then set to true
1016
- isETHAddress(destToken) ? 0 : isETHAddress(srcToken) ? 3 : 0,
1017
- data.underlyingSwap
1018
- ? CurveV1SwapType.EXCHANGE_UNDERLYING
1019
- : CurveV1SwapType.EXCHANGE,
1020
- ).toString(),
1021
- encodeCurveAssets(data.i, data.j).toString(),
1022
- srcToken,
1023
- destToken,
1024
- fromAmount,
1025
- toAmount,
1026
- quotedAmount,
1027
- metadata,
1028
- beneficiary,
1029
- ];
1030
-
1031
- const encodeParams: CurveV1DirectSwap = [swapParams, partnerAndFee, permit];
1032
-
1033
- const encoder = (...params: CurveV1DirectSwap) => {
1034
- return this.augustusV6Interface.encodeFunctionData(
1035
- DIRECT_METHOD_NAME_V6,
1036
- [...params],
1037
- );
1038
- };
1039
-
1040
- return {
1041
- encoder,
1042
- params: encodeParams,
1043
- networkFee: '0',
1044
- };
1045
- }
1046
-
1047
- static getDirectFunctionNameV6(): string[] {
1048
- return [DIRECT_METHOD_NAME_V6];
1049
- }
1050
-
1051
- getDexParam(
1052
- srcToken: Address,
1053
- destToken: Address,
1054
- srcAmount: NumberAsString,
1055
- destAmount: NumberAsString,
1056
- recipient: Address,
1057
- data: CurveV1Data,
1058
- side: SwapSide,
1059
- ): DexExchangeParam {
1060
- if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
1061
-
1062
- const { exchange, i, j, underlyingSwap } = data;
1063
- const defaultArgs = [i, j, srcAmount, this.minConversionRate];
1064
- const swapMethod = underlyingSwap
1065
- ? CurveSwapFunctions.exchange_underlying
1066
- : CurveSwapFunctions.exchange;
1067
- const exchangeData = this.exchangeRouterInterface.encodeFunctionData(
1068
- swapMethod,
1069
- defaultArgs,
1070
- );
1071
-
1072
- return {
1073
- exchangeData,
1074
- needWrapNative: this.needWrapNative,
1075
- sendEthButSupportsInsertFromAmount: true,
1076
- dexFuncHasRecipient: false,
1077
- targetExchange: exchange,
1078
- returnAmountPos: undefined,
1079
- };
1080
- }
1081
-
1082
- async getSimpleParam(
1083
- srcToken: string,
1084
- destToken: string,
1085
- srcAmount: string,
1086
- destAmount: string,
1087
- data: CurveV1Data,
1088
- side: SwapSide,
1089
- ): Promise<SimpleExchangeParam> {
1090
- if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
1091
-
1092
- const { exchange, i, j, underlyingSwap } = data;
1093
- const defaultArgs = [i, j, srcAmount, this.minConversionRate];
1094
- const swapMethod = underlyingSwap
1095
- ? CurveSwapFunctions.exchange_underlying
1096
- : CurveSwapFunctions.exchange;
1097
- const swapData = this.exchangeRouterInterface.encodeFunctionData(
1098
- swapMethod,
1099
- defaultArgs,
1100
- );
1101
-
1102
- return this.buildSimpleParamWithoutWETHConversion(
1103
- srcToken,
1104
- srcAmount,
1105
- destToken,
1106
- destAmount,
1107
- swapData,
1108
- exchange,
1109
- );
1110
- }
1111
-
1112
- async updateBaseTokenPrice() {
1113
- for (let baseToken of Object.values(this.baseTokens)) {
1114
- const usdPrice = await this.dexHelper.getTokenUSDPrice(
1115
- baseToken,
1116
- baseToken.reasonableVolume,
1117
- );
1118
-
1119
- const denominator =
1120
- Number(baseToken.reasonableVolume) / 10 ** baseToken.decimals;
1121
-
1122
- baseToken.tokenPrice = usdPrice / denominator;
1123
- }
1124
- }
1125
-
1126
- async fetchDecimals() {
1127
- const calls = Object.values(this.pools).reduce<MultiCallParams<any>[]>(
1128
- (acc, pool) => {
1129
- const underlyingDecimalsCalls = pool.underlying.reduce<
1130
- MultiCallParams<any>[]
1131
- >((_acc, _token) => {
1132
- const token = _token.toLowerCase();
1133
- if (token === ETHER_ADDRESS) {
1134
- this.decimalsCoinsAndUnderlying[token] = 18;
1135
- } else {
1136
- _acc.push({
1137
- target: token,
1138
- callData: erc20Iface.encodeFunctionData('decimals'),
1139
- decodeFunction: uin128DecodeToInt,
1140
- cb: (decimals: number) => {
1141
- this.decimalsCoinsAndUnderlying[token.toLowerCase()] = decimals;
1142
- },
1143
- });
1144
- }
1145
-
1146
- return _acc;
1147
- }, []);
1148
-
1149
- const coinsDecimalsCalls = pool.coins.reduce<MultiCallParams<any>[]>(
1150
- (_acc, _coin) => {
1151
- const coin = _coin.toLowerCase();
1152
- if (coin === ETHER_ADDRESS) {
1153
- this.decimalsCoinsAndUnderlying[coin] = 18;
1154
- } else {
1155
- _acc.push({
1156
- target: coin,
1157
- callData: erc20Iface.encodeFunctionData('decimals'),
1158
- decodeFunction: uin128DecodeToInt,
1159
- cb: (decimals: number) => {
1160
- this.decimalsCoinsAndUnderlying[coin.toLowerCase()] =
1161
- decimals;
1162
- },
1163
- });
1164
- }
1165
- return _acc;
1166
- },
1167
- [],
1168
- );
1169
-
1170
- acc.push(...underlyingDecimalsCalls, ...coinsDecimalsCalls);
1171
- return acc;
1172
- },
1173
- [],
1174
- );
1175
-
1176
- return this.dexHelper.multiWrapper!.tryAggregate(true, calls);
1177
- }
1178
-
1179
- async fetchAllPools() {
1180
- try {
1181
- // Hack: We choose to check the balance of the token for the pool instead of calling
1182
- // the balances array in the pool as it was a mess to have each curve abi
1183
- // Some have balances[uint128] some have balances[uint256]
1184
- // One of the consequence for such a hack is below for ETH Pools
1185
- // TODO: below can be highly optimized
1186
- // * we don't need to query token decimals every iteration
1187
- // * we can use the decimals from the tokens list using the api
1188
-
1189
- const calls = Object.values(this.pools).reduce<MultiCallParams<number>[]>(
1190
- (acc, pool) => {
1191
- const coinCalls = pool.coins.map<MultiCallParams<number>>(coin => {
1192
- const coinLowerCase = coin.toLowerCase();
1193
- if (coinLowerCase === ETHER_ADDRESS) {
1194
- return {
1195
- target:
1196
- this.dexHelper.config.data.multicallV2Address.toLowerCase(),
1197
- callData: this.dexHelper.multiContract.methods
1198
- .getEthBalance(pool.address.toLowerCase())
1199
- .encodeABI(),
1200
- decodeFunction: uin256DecodeToFloat,
1201
- };
1202
- } else {
1203
- return {
1204
- target: coinLowerCase,
1205
- callData: erc20Iface.encodeFunctionData('balanceOf', [
1206
- pool.address,
1207
- ]),
1208
- decodeFunction: uin256DecodeToFloat,
1209
- };
1210
- }
1211
- });
1212
- acc.push(...coinCalls);
1213
- return acc;
1214
- },
1215
- [],
1216
- );
1217
-
1218
- const results = await this.dexHelper.multiWrapper!.tryAggregate(
1219
- true,
1220
- calls,
1221
- );
1222
-
1223
- let index = 0;
1224
- Object.values(this.pools).forEach(pool => {
1225
- const balances = results.slice(index, index + pool.coins.length);
1226
- index += pool.coins.length;
1227
-
1228
- const sumLiquidity = balances.reduce((acc, balance, i) => {
1229
- const coin = pool.coins[i].toLowerCase();
1230
- const decimals = this.decimalsCoinsAndUnderlying[coin];
1231
-
1232
- return acc + balance.returnData / 10 ** decimals;
1233
- }, 0);
1234
-
1235
- let liquidityUSD = 0;
1236
- if (pool.baseToken) {
1237
- if (!this.baseTokens[pool.baseToken]) {
1238
- throw new Error(
1239
- `missing base token ${pool.baseToken} for ${this.dexKey}`,
1240
- );
1241
- }
1242
- liquidityUSD =
1243
- sumLiquidity * Number(this.baseTokens[pool.baseToken].tokenPrice!);
1244
- }
1245
-
1246
- pool.liquidityUSD = liquidityUSD;
1247
- });
1248
- } catch (e) {
1249
- this.logger.error(`fetchAllPools ${this.dexKey}`, e);
1250
- throw e;
1251
- }
1252
- }
1253
-
1254
- async updatePoolState(): Promise<void> {
1255
- if (!Object.keys(this.decimalsCoinsAndUnderlying).length) {
1256
- await this.fetchDecimals();
1257
- }
1258
-
1259
- await this.updateBaseTokenPrice();
1260
- await this.fetchAllPools();
1261
- }
1262
-
1263
- async getTopPoolsForToken(
1264
- tokenAddress: Address,
1265
- limit: number,
1266
- ): Promise<PoolLiquidity[]> {
1267
- const _tokenAddress = this.dexHelper.config.unwrapETH({
1268
- address: tokenAddress,
1269
- decimals: 18,
1270
- }).address;
1271
- // In general case a token can be in the coins or underlying
1272
- // In case of the metapool the token might be in both at the same time
1273
- // It is important to note that the connector tokens might not be
1274
- // compatible for exchange among themselves.
1275
- const selectedPool = Object.values(this.pools).reduce<PoolLiquidity[]>(
1276
- (acc, pool) => {
1277
- const inCoins = pool.coins.some(
1278
- _token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
1279
- );
1280
- const inUnderlying = pool.underlying.some(
1281
- _token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
1282
- );
1283
- let connectorTokens = inCoins ? pool.coins : [];
1284
- connectorTokens = inUnderlying
1285
- ? _.concat(connectorTokens, pool.underlying)
1286
- : connectorTokens;
1287
-
1288
- if (connectorTokens.length) {
1289
- acc.push({
1290
- exchange: this.dexKey,
1291
- address: pool.address,
1292
- liquidityUSD: pool.liquidityUSD!,
1293
- connectorTokens: _.uniq(connectorTokens)
1294
- .filter(
1295
- (_token: string) =>
1296
- _token.toLowerCase() !== _tokenAddress.toLowerCase(),
1297
- )
1298
- .map(tokenAddress => {
1299
- const address = tokenAddress.toLowerCase();
1300
- return {
1301
- address,
1302
- decimals: this.decimalsCoinsAndUnderlying[address],
1303
- };
1304
- }),
1305
- });
1306
- }
1307
- return acc;
1308
- },
1309
- [],
1310
- );
1311
- return selectedPool
1312
- .sort((a, b) => b.liquidityUSD - a.liquidityUSD)
1313
- .slice(0, limit);
1314
- }
1315
- }
1
+ import { Interface, AbiCoder, JsonFragment } from '@ethersproject/abi';
2
+ import _ from 'lodash';
3
+ import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
4
+ import { assert } from 'ts-essentials';
5
+ import BigNumber from 'bignumber.js';
6
+ import CurveABI from '../../abi/Curve.json';
7
+ import DirectSwapABI from '../../abi/DirectSwap.json';
8
+ import { Adapters, CurveV1Config } from './config';
9
+
10
+ import {
11
+ AdapterExchangeParam,
12
+ Address,
13
+ DexExchangeParam,
14
+ ExchangePrices,
15
+ ExchangeTxInfo,
16
+ PoolLiquidity,
17
+ PoolPrices,
18
+ PreprocessTransactionOptions,
19
+ SimpleExchangeParam,
20
+ Token,
21
+ TransferFeeParams,
22
+ TxInfo,
23
+ } from '../../types';
24
+ import {
25
+ Network,
26
+ NULL_ADDRESS,
27
+ SRC_TOKEN_PARASWAP_TRANSFERS,
28
+ ETHER_ADDRESS,
29
+ SwapSide,
30
+ } from '../../constants';
31
+ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
32
+ import StableSwapBBTC from '../../abi/curve-v1/StableSwapBBTC.json';
33
+ import { CurvePool } from './pools/curve-pool';
34
+ import { CurveMetapool } from './pools/curve-metapool';
35
+ import { ThreePool, address as ThreePoolAddress } from './pools/3pool';
36
+ import { SUSDPool, address as SUSDPoolAddress } from './pools/sUSDpool';
37
+ import { HBTCPool, address as HBTCPoolAddress } from './pools/hBTCpool';
38
+ import { RenPool, address as RenPoolAddress } from './pools/renpool';
39
+ import { SBTCPool, address as SBTCPoolAddress } from './pools/sBTCpool';
40
+ import { SETHPool, address as SETHPoolAddress } from './pools/sETHpool';
41
+ import { STETHPool, address as STETHPoolAddress } from './pools/stETHpool';
42
+ import { EURSPool, address as EURSPoolAddress } from './pools/EURSpool';
43
+ import { DUSDPool, address as DUSDPoolAddress } from './pools/DUSDpool';
44
+ import { BBTCPool, address as BBTCPoolAddress } from './pools/bBTCpool';
45
+ import { GUSDPool, address as GUSDPoolAddress } from './pools/GUSDpool';
46
+ import { HUSDPool, address as HUSDPoolAddress } from './pools/HUSDpool';
47
+ import {
48
+ LinkUSDPool,
49
+ address as LinkUSDPoolAddress,
50
+ } from './pools/LinkUSDpool';
51
+ import { MUSDPool, address as MUSDPoolAddress } from './pools/MUSDpool';
52
+ import { OBTCPool, address as OBTCPoolAddress } from './pools/oBTCpool';
53
+ import { PBTCPool, address as PBTCPoolAddress } from './pools/pBTCpool';
54
+ import { RSVPool, address as RSVPoolAddress } from './pools/rsvpool';
55
+ import { TBTCPool, address as TBTCPoolAddress } from './pools/tBTCpool';
56
+ import { USDKPool, address as USDKPoolAddress } from './pools/usdkpool';
57
+ import { USTPool, address as USTPoolAddress } from './pools/ustpool';
58
+ import { SLINKPool, address as SLINKPoolAddress } from './pools/sLinkpool';
59
+ import {
60
+ getBigIntPow,
61
+ getDexKeysWithNetwork,
62
+ interpolate,
63
+ isDestTokenTransferFeeToBeExchanged,
64
+ isETHAddress,
65
+ isSrcTokenTransferFeeToBeExchanged,
66
+ Utils,
67
+ uuidToBytes16,
68
+ } from '../../utils';
69
+ import { BN_0 } from '../../bignumber-constants';
70
+ import {
71
+ getLocalDeadlineAsFriendlyPlaceholder,
72
+ SimpleExchange,
73
+ } from '../simple-exchange';
74
+ import { IDex } from '../idex';
75
+ import { IDexHelper } from '../../dex-helper';
76
+ import { Logger } from 'log4js';
77
+ import { uin128DecodeToInt, uin256DecodeToFloat } from '../../lib/decoders';
78
+ import { getManyPoolStates } from './pools/getstate-multicall';
79
+ import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper';
80
+ import {
81
+ PoolConfig,
82
+ CurveV1Data,
83
+ TokenWithReasonableVolume,
84
+ CurveSwapFunctions,
85
+ CurveV1SwapType,
86
+ DirectCurveV1Param,
87
+ DirectCurveV1ParamV6,
88
+ CurveV1DirectSwap,
89
+ } from './types';
90
+ import { erc20Iface } from '../../lib/utils-interfaces';
91
+ import { applyTransferFee } from '../../lib/token-transfer-fee';
92
+ import { DIRECT_METHOD_NAME, DIRECT_METHOD_NAME_V6 } from './constants';
93
+ import { packCurveData } from '../../lib/curve/encoder';
94
+ import { encodeCurveAssets } from './packer';
95
+ import { hexConcat, hexZeroPad, hexlify } from 'ethers/lib/utils';
96
+
97
+ const CURVE_DEFAULT_CHUNKS = 10;
98
+
99
+ const CURVE_LENDING_POOl_GAS = 340 * 1000;
100
+ const CURVE_POOL_GAS = 200 * 1000;
101
+
102
+ const coder = new AbiCoder();
103
+
104
+ export class CurveV1
105
+ extends SimpleExchange
106
+ implements IDex<CurveV1Data, DirectCurveV1Param | CurveV1DirectSwap>
107
+ {
108
+ exchangeRouterInterface: Interface;
109
+ minConversionRate = '1';
110
+
111
+ eventPools = new Array<CurvePool | CurveMetapool>();
112
+ public poolInterface: Interface;
113
+
114
+ private logger: Logger;
115
+
116
+ readonly hasConstantPriceLargeAmounts = false;
117
+ readonly isFeeOnTransferSupported = true;
118
+
119
+ private decimalsCoinsAndUnderlying: Record<string, number> = {};
120
+
121
+ protected pools: Record<string, PoolConfig>;
122
+ protected eventSupportedPools: string[];
123
+ protected baseTokens: Record<string, TokenWithReasonableVolume>;
124
+
125
+ protected disableFeeOnTransferTokenAddresses: Set<string>;
126
+
127
+ readonly directSwapIface = new Interface(DirectSwapABI);
128
+
129
+ readonly SRC_TOKEN_DEX_TRANSFERS = 1;
130
+ readonly DEST_TOKEN_DEX_TRANSFERS = 1;
131
+
132
+ public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
133
+ getDexKeysWithNetwork(CurveV1Config);
134
+
135
+ constructor(
136
+ protected network: Network,
137
+ dexKey: string,
138
+ protected dexHelper: IDexHelper,
139
+ dexConfig = CurveV1Config[dexKey][network],
140
+
141
+ protected adapters = Adapters[network],
142
+ ) {
143
+ super(dexHelper, dexKey);
144
+ this.pools = Object.keys(dexConfig.pools).reduce<
145
+ Record<string, PoolConfig>
146
+ >((acc, key) => {
147
+ const poolConf = dexConfig.pools[key];
148
+
149
+ acc[key] = {
150
+ underlying: poolConf.underlying.map(t => t.toLowerCase()),
151
+ coins: poolConf.coins.map(t => t.toLowerCase()),
152
+ address: poolConf.address.toLowerCase(),
153
+ name: poolConf.name,
154
+ type: poolConf.type,
155
+ version: poolConf.version,
156
+ isLending: poolConf.isLending,
157
+ isMetapool: poolConf.isMetapool,
158
+ baseToken: poolConf.baseToken
159
+ ? poolConf.baseToken.toLowerCase()
160
+ : poolConf.baseToken,
161
+ liquidityUSD: poolConf.liquidityUSD,
162
+ trackCoins: poolConf.trackCoins,
163
+ useLending: poolConf.useLending,
164
+ precisionMul: poolConf.precisionMul,
165
+ tokenAddress: poolConf.tokenAddress
166
+ ? poolConf.tokenAddress.toLowerCase()
167
+ : undefined,
168
+ isFeeOnTransferSupported: poolConf.isFeeOnTransferSupported,
169
+ };
170
+
171
+ return acc;
172
+ }, {});
173
+
174
+ this.eventSupportedPools = dexConfig.eventSupportedPools.map(addr =>
175
+ addr.toLowerCase(),
176
+ );
177
+
178
+ this.baseTokens = Object.keys(dexConfig.baseTokens).reduce<
179
+ Record<string, TokenWithReasonableVolume>
180
+ >((acc, key) => {
181
+ const baseToken = dexConfig.baseTokens[key];
182
+ acc[key.toLowerCase()] = {
183
+ address: baseToken.address.toLowerCase(),
184
+ decimals: baseToken.decimals,
185
+ reasonableVolume: baseToken.reasonableVolume,
186
+ };
187
+ return acc;
188
+ }, {});
189
+
190
+ this.disableFeeOnTransferTokenAddresses =
191
+ dexConfig.disableFeeOnTransferTokenAddresses
192
+ ? new Set<string>(
193
+ Array.from(
194
+ dexConfig.disableFeeOnTransferTokenAddresses.values(),
195
+ ).map(addr => addr.toLowerCase()),
196
+ )
197
+ : new Set<string>();
198
+
199
+ this.exchangeRouterInterface = new Interface(CurveABI as JsonFragment[]);
200
+
201
+ this.logger = dexHelper.getLogger(dexKey);
202
+
203
+ this.poolInterface = new Interface(StableSwapBBTC as any);
204
+ }
205
+
206
+ protected getPoolByAddress(address: Address) {
207
+ return (
208
+ this.eventPools.find(
209
+ p => p.address.toLowerCase() == address.toLowerCase(),
210
+ ) || null
211
+ );
212
+ }
213
+
214
+ protected getPoolConfigByAddress(address: Address) {
215
+ return Object.values(this.pools).find(p => p.address == address);
216
+ }
217
+
218
+ getPoolConfig(from: Token, to: Token): PoolConfig | undefined {
219
+ return this.getPoolConfigs(from, to)[0];
220
+ }
221
+
222
+ getPoolConfigs(from: Token, to: Token) {
223
+ const fromAddress = from.address.toLowerCase();
224
+ const toAddress = to.address.toLowerCase();
225
+ return Object.values(this.pools).filter(config => {
226
+ const underlying = config.underlying.map((add: string) =>
227
+ add.toLowerCase(),
228
+ );
229
+ const coins = config.coins.map((add: string) => add.toLowerCase());
230
+
231
+ // Case 0: If the swap is between the underlying first token and any underlying token
232
+ if (
233
+ underlying.length &&
234
+ ((underlying[0] === fromAddress && underlying.includes(toAddress)) ||
235
+ (underlying[0] === toAddress && underlying.includes(fromAddress)))
236
+ )
237
+ return true;
238
+ // Case 1: If it not a metapool and the swap is between any underlying tokens
239
+ if (
240
+ config.isLending &&
241
+ underlying.includes(toAddress) &&
242
+ underlying.includes(fromAddress)
243
+ )
244
+ return true;
245
+
246
+ // Case 2: If it a swap between any direct tokens
247
+ if (coins.includes(toAddress) && coins.includes(fromAddress)) return true;
248
+
249
+ return false;
250
+ });
251
+ }
252
+
253
+ async getPoolIdentifiers(
254
+ _from: Token,
255
+ _to: Token,
256
+ side: SwapSide,
257
+ blockNumber: number,
258
+ ): Promise<string[]> {
259
+ const pools = this.getPoolConfigs(_from, _to);
260
+ return pools.map(pool => this.getPoolIdentifier(pool.name));
261
+ }
262
+
263
+ getContractAddress(srcToken: Token, to: Token) {
264
+ const pool = this.getPoolConfig(srcToken, to);
265
+ return pool && pool.address;
266
+ }
267
+
268
+ poolConfigsByAddress() {
269
+ return Object.keys(this.pools).reduce((acc: any, poolName: string) => {
270
+ acc[this.pools[poolName].address.toLowerCase()] = this.pools[poolName];
271
+ return acc;
272
+ }, {});
273
+ }
274
+
275
+ // TODO: revisit to verify the chunks for BTC pools vs USD pools
276
+ getOnChainChunks(
277
+ from: Token,
278
+ to: Token,
279
+ srcAmount: bigint,
280
+ defaultChunks: number,
281
+ ) {
282
+ return CURVE_DEFAULT_CHUNKS;
283
+ //return srcAmount >= 10000n * getBigIntPow(from.decimals)
284
+ // ? defaultChunks
285
+ // : CURVE_DEFAULT_CHUNKS;
286
+ }
287
+
288
+ getEventPoolInstance(poolAddress: Address): CurvePool | CurveMetapool | null {
289
+ switch (poolAddress.toLowerCase()) {
290
+ case ThreePoolAddress:
291
+ return new ThreePool(this.dexKey, this.dexHelper);
292
+ case SUSDPoolAddress:
293
+ return new SUSDPool(this.dexKey, this.dexHelper);
294
+ case HBTCPoolAddress:
295
+ return new HBTCPool(this.dexKey, this.dexHelper);
296
+ case RenPoolAddress:
297
+ return new RenPool(this.dexKey, this.dexHelper);
298
+ case SBTCPoolAddress:
299
+ return new SBTCPool(this.dexKey, this.dexHelper);
300
+ case SETHPoolAddress:
301
+ return new SETHPool(this.dexKey, this.dexHelper);
302
+ case STETHPoolAddress:
303
+ return new STETHPool(this.dexKey, this.dexHelper);
304
+ case EURSPoolAddress:
305
+ return new EURSPool(this.dexKey, this.dexHelper);
306
+ case DUSDPoolAddress:
307
+ return new DUSDPool(this.dexKey, this.dexHelper);
308
+ case BBTCPoolAddress:
309
+ return new BBTCPool(this.dexKey, this.dexHelper);
310
+ case GUSDPoolAddress:
311
+ return new GUSDPool(this.dexKey, this.dexHelper);
312
+ case HUSDPoolAddress:
313
+ return new HUSDPool(this.dexKey, this.dexHelper);
314
+ case LinkUSDPoolAddress:
315
+ return new LinkUSDPool(this.dexKey, this.dexHelper);
316
+ case MUSDPoolAddress:
317
+ return new MUSDPool(this.dexKey, this.dexHelper);
318
+ case OBTCPoolAddress:
319
+ return new OBTCPool(this.dexKey, this.dexHelper);
320
+ case PBTCPoolAddress:
321
+ return new PBTCPool(this.dexKey, this.dexHelper);
322
+ case RSVPoolAddress:
323
+ return new RSVPool(this.dexKey, this.dexHelper);
324
+ case TBTCPoolAddress:
325
+ return new TBTCPool(this.dexKey, this.dexHelper);
326
+ case USDKPoolAddress:
327
+ return new USDKPool(this.dexKey, this.dexHelper);
328
+ case USTPoolAddress:
329
+ return new USTPool(this.dexKey, this.dexHelper);
330
+ case SLINKPoolAddress:
331
+ return new SLINKPool(this.dexKey, this.dexHelper);
332
+ default:
333
+ return null;
334
+ }
335
+ }
336
+
337
+ // Makes sure all the pools from poolAddress are being tracked. If a pool in not tracked yet
338
+ // fetch the state and setup the pool, if they are being watched but don't have a valid state
339
+ // add it to the list of unavailable pools. Return the list of unavailable pools.
340
+ async batchCatchUpPools(
341
+ poolAddresses: string[],
342
+ blockNumber: number,
343
+ ): Promise<string[]> {
344
+ const unavailablePools = new Array<string>();
345
+ if (!blockNumber) return unavailablePools;
346
+ let poolsToFetch: (CurvePool | CurveMetapool)[] = [];
347
+ for (const poolAddress of poolAddresses) {
348
+ // Check if the pool is already tracked
349
+ let pool = this.getPoolByAddress(poolAddress);
350
+ // If the pool is not stracked add it to the list of pools to fetch
351
+ if (!pool) {
352
+ let newPool = this.getEventPoolInstance(poolAddress);
353
+ if (!newPool)
354
+ throw new Error(
355
+ `Error_${this.dexKey} requested unsupported event pool with address ${poolAddress}`,
356
+ );
357
+
358
+ const addressesSubscribed = newPool.addressesSubscribed.map(address =>
359
+ address.toLowerCase(),
360
+ );
361
+ if (!addressesSubscribed.includes(poolAddress)) {
362
+ newPool.addressesSubscribed.push(poolAddress);
363
+ }
364
+
365
+ await newPool.initialize(blockNumber);
366
+ poolsToFetch.push(newPool);
367
+ } else if (!pool.getState(blockNumber)) {
368
+ unavailablePools.push(poolAddress);
369
+ }
370
+ }
371
+
372
+ if (!poolsToFetch.length) return unavailablePools;
373
+
374
+ try {
375
+ const poolStates = await getManyPoolStates(
376
+ poolsToFetch,
377
+ this.dexHelper.multiContract,
378
+ blockNumber,
379
+ );
380
+
381
+ for (let i = 0; i < poolsToFetch.length; i++) {
382
+ await poolsToFetch[i].setup(blockNumber, poolStates[i] as any);
383
+ this.eventPools.push(poolsToFetch[i]);
384
+ }
385
+ return unavailablePools;
386
+ } catch (e) {
387
+ this.logger.error(`Error_batchCatchUpPools`, e);
388
+ throw e;
389
+ }
390
+ }
391
+
392
+ async getRatesEventPools(
393
+ fromToken: Token,
394
+ toToken: Token,
395
+ amounts: BigNumber[],
396
+ fromBigNumbers: number,
397
+ pools: string[],
398
+ indexes: [number, number, number][],
399
+ blockNumber: number,
400
+ ) {
401
+ const unavailablePools = await this.batchCatchUpPools(pools, blockNumber);
402
+
403
+ if (unavailablePools.length) {
404
+ this.logger.error(
405
+ `Couldn't use events for ${unavailablePools.length} ${this.dexKey} pools!`,
406
+ );
407
+ pools = pools.filter(p => !unavailablePools.includes(p));
408
+ }
409
+
410
+ let rates = new Array(pools.length);
411
+ for (let i = 0; i < pools.length; i++) {
412
+ rates[i] = {
413
+ exchange: pools[i],
414
+ rates: this.getRatesEventPool(
415
+ pools[i],
416
+ indexes[i],
417
+ amounts,
418
+ fromBigNumbers,
419
+ blockNumber,
420
+ ),
421
+ };
422
+ }
423
+ return rates;
424
+ }
425
+
426
+ async getRatesOnChain(
427
+ fromToken: Token,
428
+ toToken: Token,
429
+ amounts: bigint[],
430
+ pools: string[],
431
+ indexes: [number, number, number][],
432
+ blockNumber: number,
433
+ ) {
434
+ const chunks = amounts.length - 1;
435
+ const srcAmount = amounts[chunks]!;
436
+
437
+ // The number of chunks used onchain to minimize the full node calls
438
+ const onChainChunks = this.getOnChainChunks(
439
+ fromToken,
440
+ toToken,
441
+ srcAmount,
442
+ chunks,
443
+ );
444
+
445
+ const _width = Math.floor(chunks / onChainChunks);
446
+
447
+ const unitVolume = amounts[0];
448
+ amounts[0] = 0n;
449
+
450
+ const _amounts = [unitVolume].concat(
451
+ Array.from(Array(onChainChunks).keys()).map(
452
+ i => amounts[(i + 1) * _width],
453
+ ),
454
+ );
455
+
456
+ const calldata = pools.map((p, i) =>
457
+ _amounts.map(_amount => ({
458
+ target: p,
459
+ callData: this.poolInterface.encodeFunctionData(
460
+ indexes[i][2] === 1 ? 'get_dy_underlying' : 'get_dy',
461
+ [indexes[i][0], indexes[i][1], _amount],
462
+ ),
463
+ })),
464
+ );
465
+
466
+ //const data = await this.multi.methods
467
+ // .tryAggregate(false, calldata.flat())
468
+ // .call({}, 'latest');
469
+ const data = (
470
+ await Promise.all(
471
+ calldata.map(async poolCalldata => {
472
+ try {
473
+ const result = await Utils.timeoutPromise<MultiResult<any>[]>(
474
+ this.dexHelper.multiContract.methods
475
+ .tryAggregate(false, poolCalldata)
476
+ .call({}, 'latest'),
477
+ 2000,
478
+ `Timed out multicall for curve pool ${poolCalldata[0].target}`,
479
+ );
480
+ return result;
481
+ } catch (e) {
482
+ this.logger.error(e);
483
+ return Array<MultiResult<any>>(poolCalldata.length).fill({
484
+ success: false,
485
+ returnData: '0x',
486
+ });
487
+ }
488
+ }),
489
+ )
490
+ ).flat();
491
+
492
+ // Convert the on chain rates to the requested chunks, using simple interpolation
493
+ const correctChunks = (_r: bigint[]): bigint[] => [
494
+ _r[0],
495
+ ...interpolate(
496
+ _amounts.slice(1),
497
+ _r.slice(1),
498
+ amounts.slice(1),
499
+ SwapSide.SELL,
500
+ ),
501
+ ];
502
+
503
+ const decode = (j: number) => {
504
+ if (!data[j].success) return 0n;
505
+ const decoded = coder.decode(['uint256'], data[j].returnData);
506
+ return BigInt(decoded[0].toString());
507
+ };
508
+
509
+ let i = 0;
510
+ // chunks !== onChainChunks || !isAmountsLinear
511
+ const ratesAllPools = pools.map(p => {
512
+ const rates = _amounts.map(a => decode(i++));
513
+ return {
514
+ rates: correctChunks(rates),
515
+ exchange: p,
516
+ };
517
+ });
518
+
519
+ return ratesAllPools;
520
+ }
521
+
522
+ protected noMorePrice(prices: BigNumber[], index: number): boolean {
523
+ return index > 1 && prices[index - 1].eq(BN_0);
524
+ }
525
+
526
+ getRatesEventPool(
527
+ exchange: string,
528
+ curveIndexes: number[],
529
+ amounts: BigNumber[],
530
+ fromBigNumbers: number,
531
+ blockNumber: number,
532
+ ) {
533
+ let rates = new Array<BigNumber>(amounts.length).fill(BN_0);
534
+
535
+ let fromIndex = curveIndexes[0];
536
+ let toIndex = curveIndexes[1];
537
+ let isTokenSwap = curveIndexes[2] === 1;
538
+
539
+ let pool = this.getPoolByAddress(exchange);
540
+ if (!pool) {
541
+ throw new Error(
542
+ `Error_${this.dexKey}_getRatesEvent pool is not initialised`,
543
+ );
544
+ }
545
+
546
+ const state = pool.getState(blockNumber);
547
+ if (!state)
548
+ throw new Error(
549
+ `Error_${this.dexKey}_${pool.name} expected state at blocknumber ${blockNumber}, got none`,
550
+ );
551
+
552
+ if (isTokenSwap) {
553
+ rates[0] = pool.get_dy_underlying(
554
+ fromIndex,
555
+ toIndex,
556
+ amounts[0],
557
+ state as any,
558
+ );
559
+
560
+ for (let i = 1; i < amounts.length; i++) {
561
+ rates[i] = this.noMorePrice(rates, i)
562
+ ? BN_0
563
+ : pool.get_dy_underlying(
564
+ fromIndex,
565
+ toIndex,
566
+ amounts[i],
567
+ state as any,
568
+ );
569
+ }
570
+ } else {
571
+ rates[0] = pool.get_dy(fromIndex, toIndex, amounts[0], state as any);
572
+
573
+ for (let i = 1; i < amounts.length; i++) {
574
+ rates[i] = this.noMorePrice(rates, i)
575
+ ? BN_0
576
+ : pool.get_dy(fromIndex, toIndex, amounts[i], state as any);
577
+ }
578
+ }
579
+
580
+ let floatRates = rates.map(rate => BigInt(rate.toFixed(0)));
581
+ return floatRates;
582
+ }
583
+
584
+ isEventPoolSupported(poolAddress: Address): Boolean {
585
+ return this.eventSupportedPools.some(p => p === poolAddress.toLowerCase());
586
+ }
587
+
588
+ allocPools(
589
+ from: Token,
590
+ to: Token,
591
+ side: SwapSide,
592
+ routeID: number,
593
+ usedPools: { [poolIdentifier: string]: number },
594
+ isFirstSwap: boolean,
595
+ ) {
596
+ if (side === SwapSide.BUY) {
597
+ return;
598
+ }
599
+
600
+ this.getPoolConfigs(from, to).forEach(p => {
601
+ const poolIdentifier = this.getPoolIdentifier(p.name);
602
+ if (!(poolIdentifier in usedPools)) {
603
+ usedPools[poolIdentifier] = routeID;
604
+ }
605
+ });
606
+ }
607
+
608
+ public getPoolIdentifier(name: string) {
609
+ return `${this.dexKey}_${name.toLowerCase()}`;
610
+ }
611
+
612
+ async getPricesVolume(
613
+ _from: Token,
614
+ _to: Token,
615
+ amounts: bigint[],
616
+ side: SwapSide,
617
+ blockNumber: number,
618
+ // list of pool identifiers to use for pricing, if undefined use all pools
619
+ limitPools?: string[],
620
+ transferFees: TransferFeeParams = {
621
+ srcFee: 0,
622
+ destFee: 0,
623
+ srcDexFee: 0,
624
+ destDexFee: 0,
625
+ },
626
+ ): Promise<ExchangePrices<CurveV1Data> | null> {
627
+ try {
628
+ if (side === SwapSide.BUY) {
629
+ return null;
630
+ }
631
+
632
+ _from.address = _from.address.toLowerCase();
633
+ _to.address = _to.address.toLowerCase();
634
+
635
+ const _isSrcTokenTransferFeeToBeExchanged =
636
+ this.disableFeeOnTransferTokenAddresses.has(_from.address)
637
+ ? false
638
+ : isSrcTokenTransferFeeToBeExchanged(transferFees);
639
+
640
+ const _isDestTokenTransferFeeToBeExchanged =
641
+ this.disableFeeOnTransferTokenAddresses.has(_to.address)
642
+ ? false
643
+ : isDestTokenTransferFeeToBeExchanged(transferFees);
644
+
645
+ // We first filter out pools which were explicitly excluded and pools which are already used
646
+ // then for the good pools we set the boolean to be true for used pools
647
+ // and for each pool we take the address.
648
+ const goodPoolConfigs = this.getPoolConfigs(_from, _to).filter(p => {
649
+ if (limitPools !== undefined) {
650
+ const id = this.getPoolIdentifier(p.name);
651
+ if (!limitPools.includes(id)) {
652
+ return false;
653
+ }
654
+ }
655
+
656
+ if (_isSrcTokenTransferFeeToBeExchanged) {
657
+ // Fee on transfers supported only when flag is specified
658
+ return !!p.isFeeOnTransferSupported;
659
+ }
660
+ return true;
661
+ });
662
+
663
+ const goodPoolAddress = goodPoolConfigs.map(p => p.address);
664
+ const indexesByAddress = goodPoolConfigs.reduce(
665
+ (acc: { [address: string]: [number, number, number] }, pc) => {
666
+ acc[pc.address] = this.getSwapIndexes(_from, _to, pc);
667
+ return acc;
668
+ },
669
+ {},
670
+ );
671
+
672
+ if (goodPoolAddress.length === 0) {
673
+ return null;
674
+ }
675
+
676
+ const amountsWithUnit = [
677
+ getBigIntPow(_from.decimals),
678
+ ...amounts.slice(1),
679
+ ];
680
+ const amountsWithUnitAndFee = _isSrcTokenTransferFeeToBeExchanged
681
+ ? applyTransferFee(
682
+ applyTransferFee(
683
+ amountsWithUnit,
684
+ side,
685
+ transferFees.srcFee,
686
+ SRC_TOKEN_PARASWAP_TRANSFERS,
687
+ ),
688
+ side,
689
+ transferFees.srcDexFee,
690
+ this.SRC_TOKEN_DEX_TRANSFERS,
691
+ )
692
+ : amountsWithUnit;
693
+
694
+ let _prices = new Array();
695
+
696
+ const eventPools = goodPoolAddress.filter(p =>
697
+ this.isEventPoolSupported(p),
698
+ );
699
+ const eventPoolIndexes = eventPools.map(ep => indexesByAddress[ep]);
700
+ if (eventPools) {
701
+ const pricesEvents = await this.getRatesEventPools(
702
+ _from,
703
+ _to,
704
+ amountsWithUnitAndFee.map(a => new BigNumber(a.toString())),
705
+ _from.decimals,
706
+ eventPools,
707
+ eventPoolIndexes,
708
+ blockNumber,
709
+ );
710
+ _prices = _prices.concat(pricesEvents);
711
+ }
712
+
713
+ // if the pool rates was not available in the event pool then fallback to the onchain pool query
714
+ const isPoolNotInPrices = (poolAddress: Address) =>
715
+ !_prices.find(
716
+ existingPrice =>
717
+ existingPrice.exchange.toLowerCase() === poolAddress.toLowerCase(),
718
+ );
719
+ const onChainPools = goodPoolAddress.filter(isPoolNotInPrices);
720
+
721
+ const onChainPoolIndexes = onChainPools.map(op => indexesByAddress[op]);
722
+
723
+ // onChainPools are only supported for pools with linear price chunks
724
+ if (onChainPools.length) {
725
+ const pricesOnChain = await this.getRatesOnChain(
726
+ _from,
727
+ _to,
728
+ amountsWithUnitAndFee,
729
+ onChainPools,
730
+ onChainPoolIndexes,
731
+ blockNumber,
732
+ );
733
+ _prices = _prices.concat(pricesOnChain);
734
+ }
735
+
736
+ const poolConfigsByAddress = this.poolConfigsByAddress();
737
+
738
+ const poolPrices = _prices.reduce(
739
+ (acc: PoolPrices<CurveV1Data>[], _price: any, idx: number) => {
740
+ if (
741
+ _price.exchange === NULL_ADDRESS ||
742
+ !(_price.exchange.toLowerCase() in poolConfigsByAddress)
743
+ )
744
+ return acc;
745
+
746
+ const poolConfig =
747
+ poolConfigsByAddress[_price.exchange.toLowerCase()];
748
+ const indexes = this.getSwapIndexes(_from, _to, poolConfig);
749
+ const [i, j, swapType] = indexes;
750
+
751
+ if (_isDestTokenTransferFeeToBeExchanged) {
752
+ _price.rates = applyTransferFee(
753
+ _price.rates,
754
+ side,
755
+ transferFees.destDexFee,
756
+ this.DEST_TOKEN_DEX_TRANSFERS,
757
+ );
758
+ }
759
+
760
+ acc.push({
761
+ prices: [0n, ..._price.rates.slice(1)],
762
+ unit: _price.rates[0],
763
+ data: {
764
+ exchange: _price.exchange,
765
+ i,
766
+ j,
767
+ underlyingSwap: swapType === 1,
768
+ deadline: 0,
769
+ },
770
+ exchange: this.dexKey,
771
+ poolIdentifier: `${this.dexKey}_${poolConfig.name.toLowerCase()}`,
772
+ gasCost: poolConfig.isLending
773
+ ? CURVE_LENDING_POOl_GAS
774
+ : CURVE_POOL_GAS,
775
+ poolAddresses: [_price.exchange],
776
+ });
777
+ return acc;
778
+ },
779
+ [],
780
+ );
781
+ return poolPrices;
782
+ } catch (e) {
783
+ if (blockNumber === 0)
784
+ this.logger.error(
785
+ `Error_${this.dexKey}_getPrices: Aurelius block manager not yet instantiated`,
786
+ );
787
+ this.logger.error(`Error_${this.dexKey}_getPrices`, e);
788
+ return null;
789
+ }
790
+ }
791
+
792
+ getSwapIndexes(
793
+ from: Token,
794
+ to: Token,
795
+ pool?: PoolConfig,
796
+ ): [number, number, number] {
797
+ pool = pool || this.getPoolConfig(from, to);
798
+ if (!pool) {
799
+ return [-1, -1, -1];
800
+ }
801
+
802
+ const fromAddress = from.address.toLowerCase();
803
+ const toAddress = to.address.toLowerCase();
804
+ const isUnderlyingSwap =
805
+ pool.underlying.length &&
806
+ pool.underlying.some(t => t.toLowerCase() === fromAddress) &&
807
+ pool.underlying.some(t => t.toLowerCase() === toAddress);
808
+
809
+ const fromIndex = isUnderlyingSwap
810
+ ? pool.underlying.findIndex(t => t.toLowerCase() === fromAddress)
811
+ : pool.coins.findIndex(t => t.toLowerCase() === fromAddress);
812
+
813
+ const toIndex = isUnderlyingSwap
814
+ ? pool.underlying.findIndex(t => t.toLowerCase() === toAddress)
815
+ : pool.coins.findIndex(t => t.toLowerCase() === toAddress);
816
+
817
+ const swapType = isUnderlyingSwap ? 1 : 2;
818
+
819
+ return [fromIndex, toIndex, swapType];
820
+ }
821
+
822
+ getAdapterParam(
823
+ srcToken: string,
824
+ destToken: string,
825
+ srcAmount: string,
826
+ destAmount: string,
827
+ data: CurveV1Data,
828
+ side: SwapSide,
829
+ ): AdapterExchangeParam {
830
+ if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
831
+
832
+ const { i, j, deadline, underlyingSwap } = data;
833
+ const payload = this.abiCoder.encodeParameter(
834
+ {
835
+ ParentStruct: {
836
+ i: 'int128',
837
+ j: 'int128',
838
+ deadline: 'uint256',
839
+ underlyingSwap: 'bool',
840
+ },
841
+ },
842
+ { i, j, deadline, underlyingSwap },
843
+ );
844
+
845
+ return {
846
+ targetExchange: data.exchange,
847
+ payload,
848
+ networkFee: '0',
849
+ };
850
+ }
851
+
852
+ getCalldataGasCost(poolPrices: PoolPrices<CurveV1Data>): number | number[] {
853
+ return (
854
+ CALLDATA_GAS_COST.DEX_OVERHEAD +
855
+ CALLDATA_GAS_COST.LENGTH_SMALL +
856
+ CALLDATA_GAS_COST.INDEX +
857
+ CALLDATA_GAS_COST.INDEX +
858
+ CALLDATA_GAS_COST.TIMESTAMP +
859
+ CALLDATA_GAS_COST.BOOL
860
+ );
861
+ }
862
+
863
+ getAdapters(side: SwapSide): { name: string; index: number }[] | null {
864
+ return this.adapters[side];
865
+ }
866
+
867
+ static getDirectFunctionName(): string[] {
868
+ return [DIRECT_METHOD_NAME];
869
+ }
870
+
871
+ getTokenFromAddress(address: Address): Token {
872
+ // In this Dex decimals are not used
873
+ return { address, decimals: 0 };
874
+ }
875
+
876
+ async preProcessTransaction(
877
+ optimalSwapExchange: OptimalSwapExchange<CurveV1Data>,
878
+ srcToken: Token,
879
+ _0: Token,
880
+ _1: SwapSide,
881
+ options: PreprocessTransactionOptions,
882
+ ): Promise<[OptimalSwapExchange<CurveV1Data>, ExchangeTxInfo]> {
883
+ if (!options.isDirectMethod) {
884
+ return [
885
+ optimalSwapExchange,
886
+ {
887
+ deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
888
+ },
889
+ ];
890
+ }
891
+
892
+ assert(
893
+ optimalSwapExchange.data !== undefined,
894
+ `preProcessTransaction: data field is missing`,
895
+ );
896
+
897
+ let isApproved: boolean | undefined;
898
+
899
+ try {
900
+ isApproved = await this.dexHelper.augustusApprovals.hasApproval(
901
+ options.executionContractAddress,
902
+ this.dexHelper.config.wrapETH(srcToken).address,
903
+ optimalSwapExchange.data.exchange,
904
+ );
905
+ } catch (e) {
906
+ this.logger.error(
907
+ `preProcessTransaction failed to retrieve allowance info: `,
908
+ e,
909
+ );
910
+ }
911
+
912
+ return [
913
+ {
914
+ ...optimalSwapExchange,
915
+ data: {
916
+ ...optimalSwapExchange.data,
917
+ isApproved,
918
+ },
919
+ },
920
+ {
921
+ deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
922
+ },
923
+ ];
924
+ }
925
+
926
+ getDirectParam(
927
+ srcToken: Address,
928
+ destToken: Address,
929
+ srcAmount: NumberAsString,
930
+ destAmount: NumberAsString,
931
+ expectedAmount: NumberAsString,
932
+ data: CurveV1Data,
933
+ side: SwapSide,
934
+ permit: string,
935
+ uuid: string,
936
+ feePercent: NumberAsString,
937
+ deadline: NumberAsString,
938
+ partner: string,
939
+ beneficiary: string,
940
+ contractMethod?: string,
941
+ ): TxInfo<DirectCurveV1Param> {
942
+ if (contractMethod !== DIRECT_METHOD_NAME) {
943
+ throw new Error(`Invalid contract method ${contractMethod}`);
944
+ }
945
+ assert(side === SwapSide.SELL, 'Buy not supported');
946
+
947
+ let isApproved: boolean = !!data.isApproved;
948
+ if (data.isApproved === undefined) {
949
+ this.logger.warn(`isApproved is undefined, defaulting to false`);
950
+ }
951
+
952
+ const swapParams: DirectCurveV1Param = [
953
+ srcToken,
954
+ destToken,
955
+ data.exchange,
956
+ srcAmount,
957
+ destAmount,
958
+ expectedAmount,
959
+ feePercent,
960
+ data.i.toString(),
961
+ data.j.toString(),
962
+ partner,
963
+ isApproved,
964
+ data.underlyingSwap
965
+ ? CurveV1SwapType.EXCHANGE_UNDERLYING
966
+ : CurveV1SwapType.EXCHANGE,
967
+ beneficiary,
968
+ // For CurveV1 we work as it is, without wrapping and unwrapping
969
+ false,
970
+ permit,
971
+ uuidToBytes16(uuid),
972
+ ];
973
+
974
+ const encoder = (...params: DirectCurveV1Param) => {
975
+ return this.directSwapIface.encodeFunctionData(DIRECT_METHOD_NAME, [
976
+ params,
977
+ ]);
978
+ };
979
+
980
+ return {
981
+ params: swapParams,
982
+ encoder,
983
+ networkFee: '0',
984
+ };
985
+ }
986
+
987
+ getDirectParamV6(
988
+ srcToken: Address,
989
+ destToken: Address,
990
+ fromAmount: NumberAsString,
991
+ toAmount: NumberAsString,
992
+ quotedAmount: NumberAsString,
993
+ data: CurveV1Data,
994
+ side: SwapSide,
995
+ permit: string,
996
+ uuid: string,
997
+ partnerAndFee: string,
998
+ beneficiary: string,
999
+ blockNumber: number,
1000
+ contractMethod?: string,
1001
+ ) {
1002
+ if (contractMethod !== DIRECT_METHOD_NAME_V6) {
1003
+ throw new Error(`Invalid contract method ${contractMethod}`);
1004
+ }
1005
+ assert(side === SwapSide.SELL, 'Buy not supported');
1006
+
1007
+ const metadata = hexConcat([
1008
+ hexZeroPad(uuidToBytes16(uuid), 16),
1009
+ hexZeroPad(hexlify(blockNumber), 16),
1010
+ ]);
1011
+
1012
+ const swapParams: DirectCurveV1ParamV6 = [
1013
+ packCurveData(
1014
+ data.exchange,
1015
+ !data.isApproved, // approve flag, if not approved then set to true
1016
+ isETHAddress(destToken) ? 0 : isETHAddress(srcToken) ? 3 : 0,
1017
+ data.underlyingSwap
1018
+ ? CurveV1SwapType.EXCHANGE_UNDERLYING
1019
+ : CurveV1SwapType.EXCHANGE,
1020
+ ).toString(),
1021
+ encodeCurveAssets(data.i, data.j).toString(),
1022
+ srcToken,
1023
+ destToken,
1024
+ fromAmount,
1025
+ toAmount,
1026
+ quotedAmount,
1027
+ metadata,
1028
+ beneficiary,
1029
+ ];
1030
+
1031
+ const encodeParams: CurveV1DirectSwap = [swapParams, partnerAndFee, permit];
1032
+
1033
+ const encoder = (...params: CurveV1DirectSwap) => {
1034
+ return this.augustusV6Interface.encodeFunctionData(
1035
+ DIRECT_METHOD_NAME_V6,
1036
+ [...params],
1037
+ );
1038
+ };
1039
+
1040
+ return {
1041
+ encoder,
1042
+ params: encodeParams,
1043
+ networkFee: '0',
1044
+ };
1045
+ }
1046
+
1047
+ static getDirectFunctionNameV6(): string[] {
1048
+ return [DIRECT_METHOD_NAME_V6];
1049
+ }
1050
+
1051
+ getDexParam(
1052
+ srcToken: Address,
1053
+ destToken: Address,
1054
+ srcAmount: NumberAsString,
1055
+ destAmount: NumberAsString,
1056
+ recipient: Address,
1057
+ data: CurveV1Data,
1058
+ side: SwapSide,
1059
+ ): DexExchangeParam {
1060
+ if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
1061
+
1062
+ const { exchange, i, j, underlyingSwap } = data;
1063
+ const defaultArgs = [i, j, srcAmount, this.minConversionRate];
1064
+ const swapMethod = underlyingSwap
1065
+ ? CurveSwapFunctions.exchange_underlying
1066
+ : CurveSwapFunctions.exchange;
1067
+ const exchangeData = this.exchangeRouterInterface.encodeFunctionData(
1068
+ swapMethod,
1069
+ defaultArgs,
1070
+ );
1071
+
1072
+ return {
1073
+ exchangeData,
1074
+ needWrapNative: this.needWrapNative,
1075
+ sendEthButSupportsInsertFromAmount: true,
1076
+ dexFuncHasRecipient: false,
1077
+ targetExchange: exchange,
1078
+ returnAmountPos: undefined,
1079
+ };
1080
+ }
1081
+
1082
+ async getSimpleParam(
1083
+ srcToken: string,
1084
+ destToken: string,
1085
+ srcAmount: string,
1086
+ destAmount: string,
1087
+ data: CurveV1Data,
1088
+ side: SwapSide,
1089
+ ): Promise<SimpleExchangeParam> {
1090
+ if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
1091
+
1092
+ const { exchange, i, j, underlyingSwap } = data;
1093
+ const defaultArgs = [i, j, srcAmount, this.minConversionRate];
1094
+ const swapMethod = underlyingSwap
1095
+ ? CurveSwapFunctions.exchange_underlying
1096
+ : CurveSwapFunctions.exchange;
1097
+ const swapData = this.exchangeRouterInterface.encodeFunctionData(
1098
+ swapMethod,
1099
+ defaultArgs,
1100
+ );
1101
+
1102
+ return this.buildSimpleParamWithoutWETHConversion(
1103
+ srcToken,
1104
+ srcAmount,
1105
+ destToken,
1106
+ destAmount,
1107
+ swapData,
1108
+ exchange,
1109
+ );
1110
+ }
1111
+
1112
+ async updateBaseTokenPrice() {
1113
+ for (let baseToken of Object.values(this.baseTokens)) {
1114
+ const usdPrice = await this.dexHelper.getTokenUSDPrice(
1115
+ baseToken,
1116
+ baseToken.reasonableVolume,
1117
+ );
1118
+
1119
+ const denominator =
1120
+ Number(baseToken.reasonableVolume) / 10 ** baseToken.decimals;
1121
+
1122
+ baseToken.tokenPrice = usdPrice / denominator;
1123
+ }
1124
+ }
1125
+
1126
+ async fetchDecimals() {
1127
+ const calls = Object.values(this.pools).reduce<MultiCallParams<any>[]>(
1128
+ (acc, pool) => {
1129
+ const underlyingDecimalsCalls = pool.underlying.reduce<
1130
+ MultiCallParams<any>[]
1131
+ >((_acc, _token) => {
1132
+ const token = _token.toLowerCase();
1133
+ if (token === ETHER_ADDRESS) {
1134
+ this.decimalsCoinsAndUnderlying[token] = 18;
1135
+ } else {
1136
+ _acc.push({
1137
+ target: token,
1138
+ callData: erc20Iface.encodeFunctionData('decimals'),
1139
+ decodeFunction: uin128DecodeToInt,
1140
+ cb: (decimals: number) => {
1141
+ this.decimalsCoinsAndUnderlying[token.toLowerCase()] = decimals;
1142
+ },
1143
+ });
1144
+ }
1145
+
1146
+ return _acc;
1147
+ }, []);
1148
+
1149
+ const coinsDecimalsCalls = pool.coins.reduce<MultiCallParams<any>[]>(
1150
+ (_acc, _coin) => {
1151
+ const coin = _coin.toLowerCase();
1152
+ if (coin === ETHER_ADDRESS) {
1153
+ this.decimalsCoinsAndUnderlying[coin] = 18;
1154
+ } else {
1155
+ _acc.push({
1156
+ target: coin,
1157
+ callData: erc20Iface.encodeFunctionData('decimals'),
1158
+ decodeFunction: uin128DecodeToInt,
1159
+ cb: (decimals: number) => {
1160
+ this.decimalsCoinsAndUnderlying[coin.toLowerCase()] =
1161
+ decimals;
1162
+ },
1163
+ });
1164
+ }
1165
+ return _acc;
1166
+ },
1167
+ [],
1168
+ );
1169
+
1170
+ acc.push(...underlyingDecimalsCalls, ...coinsDecimalsCalls);
1171
+ return acc;
1172
+ },
1173
+ [],
1174
+ );
1175
+
1176
+ return this.dexHelper.multiWrapper!.tryAggregate(true, calls);
1177
+ }
1178
+
1179
+ async fetchAllPools() {
1180
+ try {
1181
+ // Hack: We choose to check the balance of the token for the pool instead of calling
1182
+ // the balances array in the pool as it was a mess to have each curve abi
1183
+ // Some have balances[uint128] some have balances[uint256]
1184
+ // One of the consequence for such a hack is below for ETH Pools
1185
+ // TODO: below can be highly optimized
1186
+ // * we don't need to query token decimals every iteration
1187
+ // * we can use the decimals from the tokens list using the api
1188
+
1189
+ const calls = Object.values(this.pools).reduce<MultiCallParams<number>[]>(
1190
+ (acc, pool) => {
1191
+ const coinCalls = pool.coins.map<MultiCallParams<number>>(coin => {
1192
+ const coinLowerCase = coin.toLowerCase();
1193
+ if (coinLowerCase === ETHER_ADDRESS) {
1194
+ return {
1195
+ target:
1196
+ this.dexHelper.config.data.multicallV2Address.toLowerCase(),
1197
+ callData: this.dexHelper.multiContract.methods
1198
+ .getEthBalance(pool.address.toLowerCase())
1199
+ .encodeABI(),
1200
+ decodeFunction: uin256DecodeToFloat,
1201
+ };
1202
+ } else {
1203
+ return {
1204
+ target: coinLowerCase,
1205
+ callData: erc20Iface.encodeFunctionData('balanceOf', [
1206
+ pool.address,
1207
+ ]),
1208
+ decodeFunction: uin256DecodeToFloat,
1209
+ };
1210
+ }
1211
+ });
1212
+ acc.push(...coinCalls);
1213
+ return acc;
1214
+ },
1215
+ [],
1216
+ );
1217
+
1218
+ const results = await this.dexHelper.multiWrapper!.tryAggregate(
1219
+ true,
1220
+ calls,
1221
+ );
1222
+
1223
+ let index = 0;
1224
+ Object.values(this.pools).forEach(pool => {
1225
+ const balances = results.slice(index, index + pool.coins.length);
1226
+ index += pool.coins.length;
1227
+
1228
+ const sumLiquidity = balances.reduce((acc, balance, i) => {
1229
+ const coin = pool.coins[i].toLowerCase();
1230
+ const decimals = this.decimalsCoinsAndUnderlying[coin];
1231
+
1232
+ return acc + balance.returnData / 10 ** decimals;
1233
+ }, 0);
1234
+
1235
+ let liquidityUSD = 0;
1236
+ if (pool.baseToken) {
1237
+ if (!this.baseTokens[pool.baseToken]) {
1238
+ throw new Error(
1239
+ `missing base token ${pool.baseToken} for ${this.dexKey}`,
1240
+ );
1241
+ }
1242
+ liquidityUSD =
1243
+ sumLiquidity * Number(this.baseTokens[pool.baseToken].tokenPrice!);
1244
+ }
1245
+
1246
+ pool.liquidityUSD = liquidityUSD;
1247
+ });
1248
+ } catch (e) {
1249
+ this.logger.error(`fetchAllPools ${this.dexKey}`, e);
1250
+ throw e;
1251
+ }
1252
+ }
1253
+
1254
+ async updatePoolState(): Promise<void> {
1255
+ if (!Object.keys(this.decimalsCoinsAndUnderlying).length) {
1256
+ await this.fetchDecimals();
1257
+ }
1258
+
1259
+ await this.updateBaseTokenPrice();
1260
+ await this.fetchAllPools();
1261
+ }
1262
+
1263
+ async getTopPoolsForToken(
1264
+ tokenAddress: Address,
1265
+ limit: number,
1266
+ ): Promise<PoolLiquidity[]> {
1267
+ const _tokenAddress = this.dexHelper.config.unwrapETH({
1268
+ address: tokenAddress,
1269
+ decimals: 18,
1270
+ }).address;
1271
+ // In general case a token can be in the coins or underlying
1272
+ // In case of the metapool the token might be in both at the same time
1273
+ // It is important to note that the connector tokens might not be
1274
+ // compatible for exchange among themselves.
1275
+ const selectedPool = Object.values(this.pools).reduce<PoolLiquidity[]>(
1276
+ (acc, pool) => {
1277
+ const inCoins = pool.coins.some(
1278
+ _token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
1279
+ );
1280
+ const inUnderlying = pool.underlying.some(
1281
+ _token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
1282
+ );
1283
+ let connectorTokens = inCoins ? pool.coins : [];
1284
+ connectorTokens = inUnderlying
1285
+ ? _.concat(connectorTokens, pool.underlying)
1286
+ : connectorTokens;
1287
+
1288
+ if (connectorTokens.length) {
1289
+ acc.push({
1290
+ exchange: this.dexKey,
1291
+ address: pool.address,
1292
+ liquidityUSD: pool.liquidityUSD!,
1293
+ connectorTokens: _.uniq(connectorTokens)
1294
+ .filter(
1295
+ (_token: string) =>
1296
+ _token.toLowerCase() !== _tokenAddress.toLowerCase(),
1297
+ )
1298
+ .map(tokenAddress => {
1299
+ const address = tokenAddress.toLowerCase();
1300
+ return {
1301
+ address,
1302
+ decimals: this.decimalsCoinsAndUnderlying[address],
1303
+ };
1304
+ }),
1305
+ });
1306
+ }
1307
+ return acc;
1308
+ },
1309
+ [],
1310
+ );
1311
+ return selectedPool
1312
+ .sort((a, b) => b.liquidityUSD - a.liquidityUSD)
1313
+ .slice(0, limit);
1314
+ }
1315
+ }