@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.
- package/README.md +377 -377
- package/build/config.js +9 -5
- package/build/config.js.map +1 -1
- package/build/dex/algebra/algebra.js +25 -25
- package/build/dex/balancer-v1/balancer-v1.js +34 -34
- package/build/dex/balancer-v2/balancer-v2.d.ts +1 -1
- package/build/dex/balancer-v2/balancer-v2.js +65 -78
- package/build/dex/balancer-v2/balancer-v2.js.map +1 -1
- package/build/dex/camelot/camelot.js +27 -27
- package/build/dex/kyberdmm/kyberdmm.js +27 -27
- package/build/dex/pancakeswap-v3/pancakeswap-v3.js +25 -25
- package/build/dex/solidly/solidly.js +27 -27
- package/build/dex/solidly-v3/solidly-v3.js +25 -25
- package/build/dex/swaap-v2/config.js +15 -0
- package/build/dex/swaap-v2/config.js.map +1 -1
- package/build/dex/uniswap-v2/uniswap-v2.js +26 -26
- package/build/dex/uniswap-v3/uniswap-v3.js +25 -25
- package/funding.json +5 -5
- package/package.json +1 -1
- package/src/config.ts +500 -496
- package/src/constants.ts +67 -67
- package/src/dex/aave-v2/aave-v2.ts +409 -409
- package/src/dex/aave-v3/aave-v3.ts +320 -320
- package/src/dex/algebra/algebra.ts +1180 -1180
- package/src/dex/angle-staked-stable/angle-staked-stable.ts +347 -347
- package/src/dex/augustus-rfq.ts +180 -180
- package/src/dex/bProtocol/bProtocol.ts +108 -108
- package/src/dex/balancer-v1/balancer-v1.ts +572 -572
- package/src/dex/balancer-v2/balancer-v2-e2e.test.ts +1699 -1699
- package/src/dex/balancer-v2/balancer-v2.ts +1669 -1701
- package/src/dex/bancor/bancor.ts +128 -128
- package/src/dex/camelot/camelot.ts +873 -873
- package/src/dex/compound/compound.ts +107 -107
- package/src/dex/curve-v1/config.ts +1179 -1179
- package/src/dex/curve-v1/curve-v1-e2e.test.ts +507 -507
- package/src/dex/curve-v1/curve-v1.ts +1315 -1315
- package/src/dex/curve-v1-factory/config.ts +1018 -1018
- package/src/dex/curve-v1-factory/curve-v1-factory.ts +1288 -1288
- package/src/dex/curve-v2/curve-v2.ts +440 -440
- package/src/dex/dexalot/dexalot.ts +1100 -1100
- package/src/dex/dodo-v1/dodo-v1.ts +132 -132
- package/src/dex/dodo-v2.ts +266 -266
- package/src/dex/etherfi.ts +239 -239
- package/src/dex/generic-rfq/generic-rfq.ts +431 -431
- package/src/dex/gmx/gmx.ts +321 -321
- package/src/dex/hashflow/hashflow.ts +1135 -1135
- package/src/dex/idex.ts +254 -254
- package/src/dex/index.ts +357 -357
- package/src/dex/jarvis-v6/jarvis-v6.ts +509 -509
- package/src/dex/jarvis.ts +262 -262
- package/src/dex/kyberdmm/kyberdmm.ts +649 -649
- package/src/dex/lido/lido.ts +110 -110
- package/src/dex/maker-psm/maker-psm.ts +592 -592
- package/src/dex/maverick-v1/maverick-v1.ts +537 -537
- package/src/dex/nerve/config.ts +635 -635
- package/src/dex/nerve/nerve-e2e.test.ts +731 -731
- package/src/dex/nerve/nerve-events.test.ts +911 -911
- package/src/dex/nerve/nerve.ts +467 -467
- package/src/dex/onebit/onebit.ts +113 -113
- package/src/dex/pancakeswap-v3/pancakeswap-v3.ts +1150 -1150
- package/src/dex/paraswap-limit-orders/config.ts +60 -60
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders-e2e.test.ts +137 -137
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders-integration.test.ts +328 -328
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts +631 -631
- package/src/dex/polygon-migrator/polygon-migrator.ts +194 -194
- package/src/dex/quick-perps/quick-perps.ts +337 -337
- package/src/dex/quickswap/config.ts +16 -16
- package/src/dex/quickswap/quickswap-e2e.test.ts +384 -384
- package/src/dex/smoothy/smoothy.ts +110 -110
- package/src/dex/solidly/config.ts +225 -225
- package/src/dex/solidly/solidly-e2e.test.ts +1421 -1421
- package/src/dex/solidly/solidly-integration.test.ts +1753 -1753
- package/src/dex/solidly/solidly.ts +687 -687
- package/src/dex/solidly-v3/solidly-v3.ts +968 -968
- package/src/dex/spark/spark.ts +284 -284
- package/src/dex/stable-pool/stable-pool.ts +129 -129
- package/src/dex/swaap-v2/config.ts +15 -0
- package/src/dex/swaap-v2/swaap-v2-e2e.test.ts +233 -17
- package/src/dex/swaap-v2/swaap-v2-integration.test.ts +1 -1
- package/src/dex/swaap-v2/swaap-v2.ts +1056 -1056
- package/src/dex/swell/swell.ts +312 -312
- package/src/dex/synthetix/synthetix.ts +420 -420
- package/src/dex/trader-joe-v2.1/trader-joe-v2.1.ts +180 -180
- package/src/dex/uniswap-v2/config.ts +637 -637
- package/src/dex/uniswap-v2/constants.ts +58 -58
- package/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts +1257 -1257
- package/src/dex/uniswap-v2/uniswap-v2-e2e-base.test.ts +42 -42
- package/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +1295 -1295
- package/src/dex/uniswap-v2/uniswap-v2-e2e-mainnet.test.ts +1834 -1834
- package/src/dex/uniswap-v2/uniswap-v2-e2e-polygon.test.ts +871 -871
- package/src/dex/uniswap-v2/uniswap-v2.ts +1117 -1117
- package/src/dex/uniswap-v3/uniswap-v3.ts +1501 -1501
- package/src/dex/uniswap-v3.ts +242 -242
- package/src/dex/weth/config.ts +73 -73
- package/src/dex/weth/weth.ts +230 -230
- package/src/dex/wombat/wombat.ts +467 -467
- package/src/dex/woo-fi-v2/woo-fi-v2.ts +529 -529
- package/src/dex/wsteth/wsteth.ts +304 -304
- package/src/dex/zerox/index.ts +328 -328
- package/src/executor/Executor01BytecodeBuilder.ts +474 -474
- package/src/executor/Executor02BytecodeBuilder.ts +1392 -1392
- package/src/executor/ExecutorBytecodeBuilder.ts +390 -390
- package/src/executor/ExecutorDetector.ts +131 -131
- package/src/executor/constants.ts +66 -66
- package/src/executor/executor01-bytecode-builder-e2e.test.ts +1478 -1478
- package/src/executor/utils.ts +62 -62
- package/src/router/simpleswap.ts +366 -366
- package/src/types.ts +342 -342
- package/tests/constants-e2e.ts +1632 -1622
- package/tests/generate-new-limit-order.ts +107 -107
|
@@ -1,1288 +1,1288 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import { AbiItem } from 'web3-utils';
|
|
3
|
-
import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
|
|
4
|
-
import { assert, AsyncOrSync } from 'ts-essentials';
|
|
5
|
-
import { Interface, JsonFragment } from '@ethersproject/abi';
|
|
6
|
-
import {
|
|
7
|
-
Token,
|
|
8
|
-
Address,
|
|
9
|
-
ExchangePrices,
|
|
10
|
-
PoolPrices,
|
|
11
|
-
AdapterExchangeParam,
|
|
12
|
-
SimpleExchangeParam,
|
|
13
|
-
PoolLiquidity,
|
|
14
|
-
Logger,
|
|
15
|
-
TransferFeeParams,
|
|
16
|
-
TxInfo,
|
|
17
|
-
PreprocessTransactionOptions,
|
|
18
|
-
ExchangeTxInfo,
|
|
19
|
-
DexExchangeParam,
|
|
20
|
-
} from '../../types';
|
|
21
|
-
import {
|
|
22
|
-
SwapSide,
|
|
23
|
-
Network,
|
|
24
|
-
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
25
|
-
NULL_ADDRESS,
|
|
26
|
-
} from '../../constants';
|
|
27
|
-
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
28
|
-
import {
|
|
29
|
-
getBigIntPow,
|
|
30
|
-
getDexKeysWithNetwork,
|
|
31
|
-
isETHAddress,
|
|
32
|
-
isSrcTokenTransferFeeToBeExchanged,
|
|
33
|
-
uuidToBytes16,
|
|
34
|
-
} from '../../utils';
|
|
35
|
-
import { IDex } from '../idex';
|
|
36
|
-
import { IDexHelper } from '../../dex-helper/idex-helper';
|
|
37
|
-
import {
|
|
38
|
-
CurveSwapFunctions,
|
|
39
|
-
CurveV1FactoryData,
|
|
40
|
-
CurveV1FactoryIfaces,
|
|
41
|
-
CurveV1SwapType,
|
|
42
|
-
CustomImplementationNames,
|
|
43
|
-
ImplementationNames,
|
|
44
|
-
PoolConstants,
|
|
45
|
-
DirectCurveV1Param,
|
|
46
|
-
CurveV1FactoryDirectSwap,
|
|
47
|
-
DirectCurveV1FactoryParamV6,
|
|
48
|
-
} from './types';
|
|
49
|
-
import {
|
|
50
|
-
getLocalDeadlineAsFriendlyPlaceholder,
|
|
51
|
-
SimpleExchange,
|
|
52
|
-
} from '../simple-exchange';
|
|
53
|
-
import { CurveV1FactoryConfig, Adapters } from './config';
|
|
54
|
-
import {
|
|
55
|
-
DIRECT_METHOD_NAME,
|
|
56
|
-
FACTORY_MAX_PLAIN_COINS,
|
|
57
|
-
FACTORY_MAX_PLAIN_IMPLEMENTATIONS_FOR_COIN,
|
|
58
|
-
MIN_AMOUNT_TO_RECEIVE,
|
|
59
|
-
POOL_EXCHANGE_GAS_COST,
|
|
60
|
-
} from './constants';
|
|
61
|
-
import { CurveV1FactoryPoolManager } from './curve-v1-pool-manager';
|
|
62
|
-
import CurveABI from '../../abi/Curve.json';
|
|
63
|
-
import DirectSwapABI from '../../abi/DirectSwap.json';
|
|
64
|
-
import FactoryCurveV1ABI from '../../abi/curve-v1-factory/FactoryCurveV1.json';
|
|
65
|
-
import ThreePoolABI from '../../abi/curve-v1-factory/ThreePool.json';
|
|
66
|
-
import ERC20ABI from '../../abi/erc20.json';
|
|
67
|
-
import {
|
|
68
|
-
addressDecode,
|
|
69
|
-
generalDecoder,
|
|
70
|
-
uint256DecodeToNumber,
|
|
71
|
-
uint8ToNumber,
|
|
72
|
-
} from '../../lib/decoders';
|
|
73
|
-
import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper';
|
|
74
|
-
import { BigNumber, BytesLike } from 'ethers';
|
|
75
|
-
import { FactoryStateHandler } from './state-polling-pools/factory-pool-polling';
|
|
76
|
-
import { PoolPollingBase } from './state-polling-pools/pool-polling-base';
|
|
77
|
-
import { CustomBasePoolForFactory } from './state-polling-pools/custom-pool-polling';
|
|
78
|
-
import ImplementationConstants from './price-handlers/functions/constants';
|
|
79
|
-
import { applyTransferFee } from '../../lib/token-transfer-fee';
|
|
80
|
-
import { PriceHandler } from './price-handlers/price-handler';
|
|
81
|
-
import { hexConcat, hexZeroPad, hexlify } from 'ethers/lib/utils';
|
|
82
|
-
import { packCurveData } from '../../lib/curve/encoder';
|
|
83
|
-
import { encodeCurveAssets } from '../curve-v1/packer';
|
|
84
|
-
|
|
85
|
-
import { DIRECT_METHOD_NAME_V6 } from './constants';
|
|
86
|
-
|
|
87
|
-
const DefaultCoinsABI: AbiItem = {
|
|
88
|
-
type: 'function',
|
|
89
|
-
name: 'coins',
|
|
90
|
-
inputs: [
|
|
91
|
-
{
|
|
92
|
-
name: '',
|
|
93
|
-
type: 'uint256',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
outputs: [
|
|
97
|
-
{
|
|
98
|
-
name: '',
|
|
99
|
-
type: 'address',
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export class CurveV1Factory
|
|
105
|
-
extends SimpleExchange
|
|
106
|
-
implements
|
|
107
|
-
IDex<CurveV1FactoryData, DirectCurveV1Param | CurveV1FactoryDirectSwap>
|
|
108
|
-
{
|
|
109
|
-
readonly hasConstantPriceLargeAmounts = false;
|
|
110
|
-
readonly needWrapNative = false;
|
|
111
|
-
readonly isFeeOnTransferSupported = true;
|
|
112
|
-
readonly isStatePollingDex = true;
|
|
113
|
-
|
|
114
|
-
private factoryAddresses: string[];
|
|
115
|
-
|
|
116
|
-
readonly poolManager: CurveV1FactoryPoolManager;
|
|
117
|
-
readonly ifaces: CurveV1FactoryIfaces;
|
|
118
|
-
|
|
119
|
-
private areFactoryPoolsFetched: boolean = false;
|
|
120
|
-
private areCustomPoolsFetched: boolean = false;
|
|
121
|
-
|
|
122
|
-
readonly SRC_TOKEN_DEX_TRANSFERS = 1;
|
|
123
|
-
readonly DEST_TOKEN_DEX_TRANSFERS = 1;
|
|
124
|
-
|
|
125
|
-
readonly directSwapIface = new Interface(DirectSwapABI);
|
|
126
|
-
|
|
127
|
-
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
128
|
-
getDexKeysWithNetwork(CurveV1FactoryConfig);
|
|
129
|
-
|
|
130
|
-
logger: Logger;
|
|
131
|
-
|
|
132
|
-
constructor(
|
|
133
|
-
readonly network: Network,
|
|
134
|
-
readonly dexKey: string,
|
|
135
|
-
readonly dexHelper: IDexHelper,
|
|
136
|
-
protected adapters = Adapters[network] || {},
|
|
137
|
-
protected config = CurveV1FactoryConfig[dexKey][network],
|
|
138
|
-
// This type is used to support different encoding for uint128 and uint256 args
|
|
139
|
-
private coinsTypeTemplate: AbiItem = DefaultCoinsABI,
|
|
140
|
-
) {
|
|
141
|
-
super(dexHelper, dexKey);
|
|
142
|
-
this.logger = dexHelper.getLogger(`${this.dexKey}-${this.network}`);
|
|
143
|
-
this.factoryAddresses =
|
|
144
|
-
this.config.factories?.map(e => e.address.toLowerCase()) || [];
|
|
145
|
-
this.ifaces = {
|
|
146
|
-
exchangeRouter: new Interface(CurveABI),
|
|
147
|
-
factory: new Interface(FactoryCurveV1ABI as JsonFragment[]),
|
|
148
|
-
erc20: new Interface(ERC20ABI as JsonFragment[]),
|
|
149
|
-
threePool: new Interface(ThreePoolABI as JsonFragment[]),
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// I had to put this initialization into CurveV1 because I have to have access to mapping
|
|
153
|
-
// from address to custom pool
|
|
154
|
-
const allPriceHandlers = Object.values(
|
|
155
|
-
this.config.factoryPoolImplementations,
|
|
156
|
-
).reduce<Record<string, PriceHandler>>((acc, curr) => {
|
|
157
|
-
let baseImplementationName: ImplementationNames | undefined;
|
|
158
|
-
if (curr.basePoolAddress) {
|
|
159
|
-
const customBasePool = this.config.customPools[curr.basePoolAddress];
|
|
160
|
-
if (customBasePool === undefined) {
|
|
161
|
-
this.logger.error(
|
|
162
|
-
`${this.dexKey} on ${this.dexHelper.config.data.network}: can not get customBasePool from basePoolAddress`,
|
|
163
|
-
);
|
|
164
|
-
return acc;
|
|
165
|
-
} else {
|
|
166
|
-
baseImplementationName = customBasePool.name;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
acc[curr.address] = new PriceHandler(
|
|
170
|
-
this.logger,
|
|
171
|
-
curr.name,
|
|
172
|
-
baseImplementationName,
|
|
173
|
-
);
|
|
174
|
-
return acc;
|
|
175
|
-
}, {});
|
|
176
|
-
|
|
177
|
-
Object.values(this.config.customPools).reduce((acc, curr) => {
|
|
178
|
-
if (curr.useForPricing) {
|
|
179
|
-
acc[curr.address] = new PriceHandler(this.logger, curr.name);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return acc;
|
|
183
|
-
}, allPriceHandlers);
|
|
184
|
-
|
|
185
|
-
this.poolManager = new CurveV1FactoryPoolManager(
|
|
186
|
-
this.dexKey,
|
|
187
|
-
dexHelper.getLogger(`${this.dexKey}-state-manager`),
|
|
188
|
-
dexHelper,
|
|
189
|
-
allPriceHandlers,
|
|
190
|
-
this.config.stateUpdatePeriodMs,
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async initializePricing(blockNumber: number) {
|
|
195
|
-
// This is only to start timer, each pool is initialized with updated state
|
|
196
|
-
this.poolManager.initializePollingPools();
|
|
197
|
-
await this.fetchFactoryPools(blockNumber);
|
|
198
|
-
await this.poolManager.fetchLiquiditiesFromApi();
|
|
199
|
-
await this.poolManager.updatePollingPoolsInBatch();
|
|
200
|
-
this.logger.info(`${this.dexKey}: successfully initialized`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async initializeCustomPollingPools(
|
|
204
|
-
factoryAddresses: string[],
|
|
205
|
-
blockNumber?: number,
|
|
206
|
-
// We don't want to initialize state for PoolTracker. It doesn't make any sense
|
|
207
|
-
initializeInitialState: boolean = true,
|
|
208
|
-
) {
|
|
209
|
-
if (this.areCustomPoolsFetched) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
await Promise.all(
|
|
214
|
-
factoryAddresses.map(async factoryAddress => {
|
|
215
|
-
try {
|
|
216
|
-
await Promise.all(
|
|
217
|
-
Object.values(this.config.customPools).map(async customPool => {
|
|
218
|
-
const poolIdentifier = this.getPoolIdentifier(
|
|
219
|
-
customPool.address,
|
|
220
|
-
false,
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
const poolContextConstants =
|
|
224
|
-
ImplementationConstants[customPool.name];
|
|
225
|
-
const {
|
|
226
|
-
N_COINS: nCoins,
|
|
227
|
-
USE_LENDING: useLending,
|
|
228
|
-
isLending,
|
|
229
|
-
} = poolContextConstants;
|
|
230
|
-
|
|
231
|
-
const coinsAndImplementations =
|
|
232
|
-
await this.dexHelper.multiWrapper.aggregate(
|
|
233
|
-
_.range(0, nCoins)
|
|
234
|
-
.map(i => ({
|
|
235
|
-
target: customPool.address,
|
|
236
|
-
callData: this.abiCoder.encodeFunctionCall(
|
|
237
|
-
this._getCoinsABI(customPool.coinsInputType),
|
|
238
|
-
[i.toString()],
|
|
239
|
-
),
|
|
240
|
-
decodeFunction: addressDecode,
|
|
241
|
-
}))
|
|
242
|
-
.concat([
|
|
243
|
-
{
|
|
244
|
-
target: factoryAddress,
|
|
245
|
-
callData: this.ifaces.factory.encodeFunctionData(
|
|
246
|
-
'get_implementation_address',
|
|
247
|
-
[customPool.address],
|
|
248
|
-
),
|
|
249
|
-
decodeFunction: addressDecode,
|
|
250
|
-
},
|
|
251
|
-
]),
|
|
252
|
-
);
|
|
253
|
-
const COINS = coinsAndImplementations.slice(0, -1);
|
|
254
|
-
const implementationAddress =
|
|
255
|
-
coinsAndImplementations.slice(-1)[0];
|
|
256
|
-
|
|
257
|
-
const coins_decimals = (
|
|
258
|
-
await this.dexHelper.multiWrapper.tryAggregate(
|
|
259
|
-
true,
|
|
260
|
-
COINS.map(c => ({
|
|
261
|
-
target: c,
|
|
262
|
-
callData: this.ifaces.erc20.encodeFunctionData(
|
|
263
|
-
'decimals',
|
|
264
|
-
[],
|
|
265
|
-
),
|
|
266
|
-
decodeFunction: uint8ToNumber,
|
|
267
|
-
})),
|
|
268
|
-
)
|
|
269
|
-
).map(r => r.returnData);
|
|
270
|
-
|
|
271
|
-
const poolConstants: PoolConstants = {
|
|
272
|
-
COINS,
|
|
273
|
-
coins_decimals,
|
|
274
|
-
rate_multipliers: this._calcRateMultipliers(coins_decimals),
|
|
275
|
-
lpTokenAddress: customPool.lpTokenAddress,
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
let newPool: PoolPollingBase;
|
|
279
|
-
if (
|
|
280
|
-
Object.values<ImplementationNames>(
|
|
281
|
-
CustomImplementationNames,
|
|
282
|
-
).includes(customPool.name)
|
|
283
|
-
) {
|
|
284
|
-
// We don't want custom pools to be used for pricing, unless explicitly specified
|
|
285
|
-
newPool = new CustomBasePoolForFactory(
|
|
286
|
-
this.logger,
|
|
287
|
-
this.dexKey,
|
|
288
|
-
this.dexHelper.config.data.network,
|
|
289
|
-
this.cacheStateKey,
|
|
290
|
-
customPool.name,
|
|
291
|
-
implementationAddress,
|
|
292
|
-
customPool.address,
|
|
293
|
-
this.config.stateUpdatePeriodMs,
|
|
294
|
-
poolIdentifier,
|
|
295
|
-
poolConstants,
|
|
296
|
-
poolContextConstants,
|
|
297
|
-
customPool.liquidityApiSlug,
|
|
298
|
-
customPool.lpTokenAddress,
|
|
299
|
-
isLending,
|
|
300
|
-
customPool.balancesInputType,
|
|
301
|
-
useLending,
|
|
302
|
-
customPool.useForPricing,
|
|
303
|
-
);
|
|
304
|
-
} else {
|
|
305
|
-
// Use for pricing pools from factory
|
|
306
|
-
newPool = new CustomBasePoolForFactory(
|
|
307
|
-
this.logger,
|
|
308
|
-
this.dexKey,
|
|
309
|
-
this.dexHelper.config.data.network,
|
|
310
|
-
this.cacheStateKey,
|
|
311
|
-
customPool.name,
|
|
312
|
-
implementationAddress,
|
|
313
|
-
customPool.address,
|
|
314
|
-
this.config.stateUpdatePeriodMs,
|
|
315
|
-
poolIdentifier,
|
|
316
|
-
poolConstants,
|
|
317
|
-
poolContextConstants,
|
|
318
|
-
customPool.liquidityApiSlug,
|
|
319
|
-
customPool.lpTokenAddress,
|
|
320
|
-
isLending,
|
|
321
|
-
customPool.balancesInputType,
|
|
322
|
-
useLending,
|
|
323
|
-
true,
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
this.poolManager.initializeNewPoolForState(
|
|
328
|
-
poolIdentifier,
|
|
329
|
-
newPool,
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
if (initializeInitialState) {
|
|
333
|
-
await this.poolManager.initializeIndividualPollingPoolState(
|
|
334
|
-
poolIdentifier,
|
|
335
|
-
CustomBasePoolForFactory.IS_SRC_FEE_ON_TRANSFER_SUPPORTED,
|
|
336
|
-
blockNumber,
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
}),
|
|
340
|
-
);
|
|
341
|
-
} catch (e) {
|
|
342
|
-
this.logger.error(
|
|
343
|
-
`Error initializing custom polling pools for factory ${factoryAddress}: `,
|
|
344
|
-
e,
|
|
345
|
-
);
|
|
346
|
-
throw e;
|
|
347
|
-
}
|
|
348
|
-
}),
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
this.areCustomPoolsFetched = true;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
async fetchFactoryPools(
|
|
355
|
-
blockNumber?: number,
|
|
356
|
-
// Variable initializeInitialState is only for poolTracker. We don't want to keep state updated with scheduler
|
|
357
|
-
// We just want to initialize factory pools and send request to CurveAPI
|
|
358
|
-
// Other values are not used
|
|
359
|
-
initializeInitialState: boolean = false,
|
|
360
|
-
) {
|
|
361
|
-
if (this.areFactoryPoolsFetched) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (!this.factoryAddresses || this.factoryAddresses.length == 0) {
|
|
366
|
-
this.logger.warn(`No factory address specified in configs`);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// There is no scenario when we need to call initialize custom pools without factory pools
|
|
371
|
-
// So I put it here to not forget call, because custom pools must be initialised before factory pools
|
|
372
|
-
// This function may be called multiple times, but will execute only once
|
|
373
|
-
await this.initializeCustomPollingPools(
|
|
374
|
-
this.factoryAddresses,
|
|
375
|
-
blockNumber,
|
|
376
|
-
initializeInitialState,
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
await Promise.all(
|
|
380
|
-
this.factoryAddresses.map(async factoryAddress => {
|
|
381
|
-
try {
|
|
382
|
-
const poolCountResult =
|
|
383
|
-
await this.dexHelper.multiWrapper!.tryAggregate(true, [
|
|
384
|
-
{
|
|
385
|
-
target: factoryAddress,
|
|
386
|
-
callData: this.ifaces.factory.encodeFunctionData('pool_count'),
|
|
387
|
-
decodeFunction: uint256DecodeToNumber,
|
|
388
|
-
},
|
|
389
|
-
// This is used later to request all available implementations. In particular meta implementations
|
|
390
|
-
{
|
|
391
|
-
target: factoryAddress,
|
|
392
|
-
callData:
|
|
393
|
-
this.ifaces.factory.encodeFunctionData('base_pool_count'),
|
|
394
|
-
decodeFunction: uint256DecodeToNumber,
|
|
395
|
-
},
|
|
396
|
-
]);
|
|
397
|
-
|
|
398
|
-
const poolCount = poolCountResult[0].returnData;
|
|
399
|
-
const basePoolCount = poolCountResult[1].returnData;
|
|
400
|
-
|
|
401
|
-
const calldataGetPoolAddresses = _.range(0, poolCount).map(i => ({
|
|
402
|
-
target: factoryAddress,
|
|
403
|
-
callData: this.ifaces.factory.encodeFunctionData('pool_list', [i]),
|
|
404
|
-
decodeFunction: addressDecode,
|
|
405
|
-
}));
|
|
406
|
-
|
|
407
|
-
const calldataGetBasePoolAddresses = _.range(0, basePoolCount).map(
|
|
408
|
-
i => ({
|
|
409
|
-
target: factoryAddress,
|
|
410
|
-
callData: this.ifaces.factory.encodeFunctionData(
|
|
411
|
-
'base_pool_list',
|
|
412
|
-
[i],
|
|
413
|
-
),
|
|
414
|
-
decodeFunction: addressDecode,
|
|
415
|
-
}),
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
const allPoolAddresses = (
|
|
419
|
-
await this.dexHelper.multiWrapper.tryAggregate(
|
|
420
|
-
true,
|
|
421
|
-
calldataGetPoolAddresses.concat(calldataGetBasePoolAddresses),
|
|
422
|
-
)
|
|
423
|
-
).map(e => e.returnData);
|
|
424
|
-
|
|
425
|
-
const poolAddresses = allPoolAddresses.slice(0, poolCount);
|
|
426
|
-
const basePoolAddresses = allPoolAddresses.slice(poolCount);
|
|
427
|
-
|
|
428
|
-
const customPoolAddresses = Object.values(
|
|
429
|
-
this.config.customPools,
|
|
430
|
-
).map(customPool => customPool.address);
|
|
431
|
-
basePoolAddresses.forEach(basePool => {
|
|
432
|
-
if (
|
|
433
|
-
!customPoolAddresses.includes(basePool) &&
|
|
434
|
-
!this.config.disabledPools.has(basePool)
|
|
435
|
-
) {
|
|
436
|
-
this._reportForUnspecifiedCustomPool(basePool);
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
const factoryConfig = this.config.factories?.find(
|
|
441
|
-
({ address }) =>
|
|
442
|
-
factoryAddress.toLowerCase() === address.toLowerCase(),
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
let callDataFromFactoryPools: MultiCallParams<
|
|
446
|
-
string[] | number[] | string
|
|
447
|
-
>[] = poolAddresses
|
|
448
|
-
.map(p => [
|
|
449
|
-
{
|
|
450
|
-
target: factoryAddress,
|
|
451
|
-
callData: this.ifaces.factory.encodeFunctionData(
|
|
452
|
-
'get_implementation_address',
|
|
453
|
-
[p],
|
|
454
|
-
),
|
|
455
|
-
decodeFunction: addressDecode,
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
target: factoryAddress,
|
|
459
|
-
callData: this.ifaces.factory.encodeFunctionData('get_coins', [
|
|
460
|
-
p,
|
|
461
|
-
]),
|
|
462
|
-
decodeFunction: (
|
|
463
|
-
result: MultiResult<BytesLike> | BytesLike,
|
|
464
|
-
): string[] =>
|
|
465
|
-
generalDecoder<string[]>(
|
|
466
|
-
result,
|
|
467
|
-
[
|
|
468
|
-
`address[${
|
|
469
|
-
factoryConfig?.maxPlainCoins?.toString() || ''
|
|
470
|
-
}]`,
|
|
471
|
-
],
|
|
472
|
-
new Array(4).fill(NULL_ADDRESS),
|
|
473
|
-
parsed => parsed[0].map((p: string) => p.toLowerCase()),
|
|
474
|
-
),
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
target: factoryAddress,
|
|
478
|
-
callData: this.ifaces.factory.encodeFunctionData(
|
|
479
|
-
'get_decimals',
|
|
480
|
-
[p],
|
|
481
|
-
),
|
|
482
|
-
decodeFunction: (
|
|
483
|
-
result: MultiResult<BytesLike> | BytesLike,
|
|
484
|
-
): number[] =>
|
|
485
|
-
generalDecoder<number[]>(
|
|
486
|
-
result,
|
|
487
|
-
[
|
|
488
|
-
`uint256[${
|
|
489
|
-
factoryConfig?.maxPlainCoins?.toString() || ''
|
|
490
|
-
}]`,
|
|
491
|
-
],
|
|
492
|
-
[0, 0, 0, 0],
|
|
493
|
-
parsed =>
|
|
494
|
-
parsed[0].map((p: BigNumber) => Number(p.toString())),
|
|
495
|
-
),
|
|
496
|
-
},
|
|
497
|
-
])
|
|
498
|
-
.flat();
|
|
499
|
-
|
|
500
|
-
// This is divider between pools related results and implementations
|
|
501
|
-
const factoryResultsDivider = callDataFromFactoryPools.length;
|
|
502
|
-
|
|
503
|
-
const metaPoolImplementations = factoryConfig?.isStableNg
|
|
504
|
-
? []
|
|
505
|
-
: basePoolAddresses.map(basePoolAddress => ({
|
|
506
|
-
target: factoryAddress,
|
|
507
|
-
callData: this.ifaces.factory.encodeFunctionData(
|
|
508
|
-
'metapool_implementations',
|
|
509
|
-
[basePoolAddress],
|
|
510
|
-
),
|
|
511
|
-
decodeFunction: (
|
|
512
|
-
result: MultiResult<BytesLike> | BytesLike,
|
|
513
|
-
): string[] =>
|
|
514
|
-
generalDecoder<string[]>(
|
|
515
|
-
result,
|
|
516
|
-
['address[10]'],
|
|
517
|
-
new Array(10).fill(NULL_ADDRESS),
|
|
518
|
-
parsed => parsed[0].map((p: string) => p.toLowerCase()),
|
|
519
|
-
),
|
|
520
|
-
}));
|
|
521
|
-
|
|
522
|
-
const plainImplementations = factoryConfig?.isStableNg
|
|
523
|
-
? []
|
|
524
|
-
: _.flattenDeep(
|
|
525
|
-
_.range(2, FACTORY_MAX_PLAIN_COINS + 1).map(coinNumber =>
|
|
526
|
-
_.range(FACTORY_MAX_PLAIN_IMPLEMENTATIONS_FOR_COIN).map(
|
|
527
|
-
implInd => ({
|
|
528
|
-
target: factoryAddress,
|
|
529
|
-
callData: this.ifaces.factory.encodeFunctionData(
|
|
530
|
-
'plain_implementations',
|
|
531
|
-
[coinNumber, implInd],
|
|
532
|
-
),
|
|
533
|
-
decodeFunction: addressDecode,
|
|
534
|
-
}),
|
|
535
|
-
),
|
|
536
|
-
),
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
// Implementations must be requested from factory, but it accepts as arg basePool address
|
|
540
|
-
// for metaPools
|
|
541
|
-
callDataFromFactoryPools = callDataFromFactoryPools.concat(
|
|
542
|
-
...metaPoolImplementations,
|
|
543
|
-
// To receive plain pool implementation address, you have to call plain_implementations
|
|
544
|
-
// with two variables: N_COINS and implementations_index
|
|
545
|
-
// N_COINS is between 2-4. Currently more than 4 coins is not supported
|
|
546
|
-
// as for implementation index, there are only 0-9 indexes
|
|
547
|
-
...plainImplementations,
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
const allResultsFromFactory = (
|
|
551
|
-
await this.dexHelper.multiWrapper.tryAggregate<
|
|
552
|
-
string[] | number[] | string
|
|
553
|
-
>(true, callDataFromFactoryPools)
|
|
554
|
-
).map(r => r.returnData);
|
|
555
|
-
|
|
556
|
-
const resultsFromFactory = allResultsFromFactory.slice(
|
|
557
|
-
0,
|
|
558
|
-
factoryResultsDivider,
|
|
559
|
-
);
|
|
560
|
-
|
|
561
|
-
const allAvailableImplementations = _.flattenDeep(
|
|
562
|
-
allResultsFromFactory.slice(factoryResultsDivider) as string[],
|
|
563
|
-
).filter(
|
|
564
|
-
implementation =>
|
|
565
|
-
implementation !== NULL_ADDRESS &&
|
|
566
|
-
!this.config.disabledImplementations.has(implementation),
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
allAvailableImplementations.forEach(implementation => {
|
|
570
|
-
const currentImplementation =
|
|
571
|
-
this.config.factoryPoolImplementations[implementation];
|
|
572
|
-
if (currentImplementation === undefined) {
|
|
573
|
-
this._reportForUnspecifiedImplementation(implementation);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
const stateInitializePromises: Promise<void>[] = [];
|
|
578
|
-
_.chunk(resultsFromFactory, 3).forEach((result, i) => {
|
|
579
|
-
if (this.config.disabledPools.has(poolAddresses[i])) {
|
|
580
|
-
this.logger.trace(`Filtering disabled pool ${poolAddresses[i]}`);
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
let [implementationAddress, coins, coins_decimals] = result as [
|
|
585
|
-
string,
|
|
586
|
-
string[],
|
|
587
|
-
number[],
|
|
588
|
-
];
|
|
589
|
-
|
|
590
|
-
implementationAddress = implementationAddress.toLowerCase();
|
|
591
|
-
coins = coins
|
|
592
|
-
.map(c => c.toLowerCase())
|
|
593
|
-
.filter(c => c !== NULL_ADDRESS);
|
|
594
|
-
coins_decimals = coins_decimals.filter(cd => cd !== 0);
|
|
595
|
-
|
|
596
|
-
const factoryImplementationFromConfig =
|
|
597
|
-
this.config.factoryPoolImplementations[implementationAddress];
|
|
598
|
-
|
|
599
|
-
if (
|
|
600
|
-
factoryImplementationFromConfig === undefined &&
|
|
601
|
-
!this.config.disabledImplementations.has(implementationAddress)
|
|
602
|
-
) {
|
|
603
|
-
this._reportForUnspecifiedImplementation(
|
|
604
|
-
implementationAddress,
|
|
605
|
-
poolAddresses[i],
|
|
606
|
-
);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const factoryImplementationConstants =
|
|
611
|
-
ImplementationConstants[factoryImplementationFromConfig.name];
|
|
612
|
-
|
|
613
|
-
let isMeta: boolean = false;
|
|
614
|
-
let basePoolStateFetcher: PoolPollingBase | undefined;
|
|
615
|
-
if (factoryImplementationFromConfig.basePoolAddress !== undefined) {
|
|
616
|
-
isMeta = true;
|
|
617
|
-
const basePoolIdentifier = this.getPoolIdentifier(
|
|
618
|
-
factoryImplementationFromConfig.basePoolAddress,
|
|
619
|
-
false,
|
|
620
|
-
);
|
|
621
|
-
const basePool = this.poolManager.getPool(
|
|
622
|
-
basePoolIdentifier,
|
|
623
|
-
false,
|
|
624
|
-
);
|
|
625
|
-
if (basePool === null) {
|
|
626
|
-
this.logger.error(
|
|
627
|
-
`${this.dexKey}_${this.dexHelper.config.data.network}: custom base pool ${basePoolIdentifier} was not initialized properly. ` +
|
|
628
|
-
`You must call initializeCustomPollingPools before fetching factory`,
|
|
629
|
-
);
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
basePoolStateFetcher = basePool;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const poolConstants: PoolConstants = {
|
|
636
|
-
COINS: coins,
|
|
637
|
-
coins_decimals,
|
|
638
|
-
rate_multipliers: this._calcRateMultipliers(coins_decimals),
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
const poolIdentifier = this.getPoolIdentifier(
|
|
642
|
-
poolAddresses[i],
|
|
643
|
-
isMeta,
|
|
644
|
-
);
|
|
645
|
-
|
|
646
|
-
const newPool = new FactoryStateHandler(
|
|
647
|
-
this.logger,
|
|
648
|
-
this.dexKey,
|
|
649
|
-
this.dexHelper.config.data.network,
|
|
650
|
-
this.cacheStateKey,
|
|
651
|
-
factoryImplementationFromConfig.name,
|
|
652
|
-
implementationAddress.toLowerCase(),
|
|
653
|
-
poolAddresses[i],
|
|
654
|
-
this.config,
|
|
655
|
-
factoryAddress,
|
|
656
|
-
poolIdentifier,
|
|
657
|
-
poolConstants,
|
|
658
|
-
factoryImplementationConstants,
|
|
659
|
-
factoryImplementationConstants.isFeeOnTransferSupported,
|
|
660
|
-
factoryImplementationFromConfig.liquidityApiSlug ?? '/factory',
|
|
661
|
-
basePoolStateFetcher,
|
|
662
|
-
factoryImplementationFromConfig.customGasCost,
|
|
663
|
-
factoryImplementationFromConfig.isStoreRateSupported,
|
|
664
|
-
);
|
|
665
|
-
|
|
666
|
-
this.poolManager.initializeNewPool(poolIdentifier, newPool);
|
|
667
|
-
|
|
668
|
-
if (initializeInitialState) {
|
|
669
|
-
stateInitializePromises.push(
|
|
670
|
-
this.poolManager.initializeIndividualPollingPoolState(
|
|
671
|
-
poolIdentifier,
|
|
672
|
-
factoryImplementationConstants.isFeeOnTransferSupported,
|
|
673
|
-
),
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
await Promise.all(stateInitializePromises);
|
|
679
|
-
} catch (e) {
|
|
680
|
-
this.logger.error(
|
|
681
|
-
`Error fetching factory pools for ${factoryAddress}: `,
|
|
682
|
-
e,
|
|
683
|
-
);
|
|
684
|
-
throw e;
|
|
685
|
-
}
|
|
686
|
-
}),
|
|
687
|
-
);
|
|
688
|
-
|
|
689
|
-
this.areFactoryPoolsFetched = true;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
693
|
-
return this.adapters[side] ? this.adapters[side] : null;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
getPoolIdentifier(poolAddress: string, isMeta: boolean): string {
|
|
697
|
-
return `${this.dexKey}_${poolAddress}_${isMeta}`;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
async getPoolIdentifiers(
|
|
701
|
-
srcToken: Token,
|
|
702
|
-
destToken: Token,
|
|
703
|
-
side: SwapSide,
|
|
704
|
-
blockNumber: number,
|
|
705
|
-
): Promise<string[]> {
|
|
706
|
-
if (side === SwapSide.BUY) {
|
|
707
|
-
return [];
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const srcTokenAddress = srcToken.address.toLowerCase();
|
|
711
|
-
const destTokenAddress = destToken.address.toLowerCase();
|
|
712
|
-
|
|
713
|
-
if (srcTokenAddress === destTokenAddress) {
|
|
714
|
-
return [];
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const pools = this.poolManager.getPoolsForPair(
|
|
718
|
-
srcTokenAddress,
|
|
719
|
-
destTokenAddress,
|
|
720
|
-
);
|
|
721
|
-
|
|
722
|
-
return pools.map(pool =>
|
|
723
|
-
this.getPoolIdentifier(pool.address, pool.isMetaPool),
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
async getPricesVolume(
|
|
728
|
-
srcToken: Token,
|
|
729
|
-
destToken: Token,
|
|
730
|
-
amounts: bigint[],
|
|
731
|
-
side: SwapSide,
|
|
732
|
-
blockNumber: number,
|
|
733
|
-
limitPools?: string[],
|
|
734
|
-
transferFees: TransferFeeParams = {
|
|
735
|
-
srcFee: 0,
|
|
736
|
-
destFee: 0,
|
|
737
|
-
srcDexFee: 0,
|
|
738
|
-
destDexFee: 0,
|
|
739
|
-
},
|
|
740
|
-
): Promise<null | ExchangePrices<CurveV1FactoryData>> {
|
|
741
|
-
try {
|
|
742
|
-
if (side === SwapSide.BUY) {
|
|
743
|
-
return null;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
const _isSrcTokenTransferFeeToBeExchanged =
|
|
747
|
-
isSrcTokenTransferFeeToBeExchanged(transferFees);
|
|
748
|
-
|
|
749
|
-
const srcTokenAddress = srcToken.address.toLowerCase();
|
|
750
|
-
const destTokenAddress = destToken.address.toLowerCase();
|
|
751
|
-
|
|
752
|
-
if (srcTokenAddress === destTokenAddress) {
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
let pools: PoolPollingBase[] = [];
|
|
757
|
-
if (limitPools !== undefined) {
|
|
758
|
-
pools = limitPools
|
|
759
|
-
.map(poolIdentifier =>
|
|
760
|
-
this.poolManager.getPool(
|
|
761
|
-
poolIdentifier,
|
|
762
|
-
_isSrcTokenTransferFeeToBeExchanged,
|
|
763
|
-
),
|
|
764
|
-
)
|
|
765
|
-
.filter(
|
|
766
|
-
(pool): pool is PoolPollingBase =>
|
|
767
|
-
pool !== null &&
|
|
768
|
-
pool.getPoolData(srcTokenAddress, destTokenAddress) !== null,
|
|
769
|
-
);
|
|
770
|
-
} else {
|
|
771
|
-
pools = this.poolManager.getPoolsForPair(
|
|
772
|
-
srcTokenAddress,
|
|
773
|
-
destTokenAddress,
|
|
774
|
-
_isSrcTokenTransferFeeToBeExchanged,
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
if (pools.length <= 0) {
|
|
779
|
-
return null;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if (pools.length > 10) {
|
|
783
|
-
// I expect this to not happen
|
|
784
|
-
this.logger.warn(
|
|
785
|
-
`${this.dexKey}: There are more than 10 pools found for pair: ${srcTokenAddress}-${destTokenAddress}`,
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const amountsWithUnit = [
|
|
790
|
-
getBigIntPow(srcToken.decimals),
|
|
791
|
-
...amounts.slice(1),
|
|
792
|
-
];
|
|
793
|
-
const amountsWithUnitAndFee = _isSrcTokenTransferFeeToBeExchanged
|
|
794
|
-
? applyTransferFee(
|
|
795
|
-
applyTransferFee(
|
|
796
|
-
amountsWithUnit,
|
|
797
|
-
side,
|
|
798
|
-
transferFees.srcFee,
|
|
799
|
-
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
800
|
-
),
|
|
801
|
-
side,
|
|
802
|
-
transferFees.srcDexFee,
|
|
803
|
-
this.SRC_TOKEN_DEX_TRANSFERS,
|
|
804
|
-
)
|
|
805
|
-
: amountsWithUnit;
|
|
806
|
-
|
|
807
|
-
const results = await Promise.all(
|
|
808
|
-
pools.map(
|
|
809
|
-
async (pool): Promise<PoolPrices<CurveV1FactoryData> | null> => {
|
|
810
|
-
let state = pool.getState();
|
|
811
|
-
if (!state) {
|
|
812
|
-
await this.poolManager.updateManuallyPollingPools(
|
|
813
|
-
pool.baseStatePoolPolling
|
|
814
|
-
? [pool.baseStatePoolPolling, pool]
|
|
815
|
-
: [pool],
|
|
816
|
-
);
|
|
817
|
-
state = pool.getState();
|
|
818
|
-
if (!state) {
|
|
819
|
-
return null;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (state.balances.every(b => b === 0n)) {
|
|
824
|
-
this.logger.trace(
|
|
825
|
-
`${this.dexKey} on ${this.dexHelper.config.data.network}: State balances equal to 0 in pool ${pool.address}`,
|
|
826
|
-
);
|
|
827
|
-
return null;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
const poolData = pool.getPoolData(
|
|
831
|
-
srcTokenAddress,
|
|
832
|
-
destTokenAddress,
|
|
833
|
-
);
|
|
834
|
-
|
|
835
|
-
if (poolData === null) {
|
|
836
|
-
this.logger.error(
|
|
837
|
-
`${pool.fullName}: one or both tokens can not be exchanged in pool ${pool.address}: ${srcTokenAddress} -> ${destTokenAddress}`,
|
|
838
|
-
);
|
|
839
|
-
return null;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
let outputs: bigint[] = this.poolManager
|
|
843
|
-
.getPriceHandler(pool.implementationAddress)
|
|
844
|
-
.getOutputs(
|
|
845
|
-
state,
|
|
846
|
-
amountsWithUnitAndFee,
|
|
847
|
-
poolData.i,
|
|
848
|
-
poolData.j,
|
|
849
|
-
poolData.underlyingSwap,
|
|
850
|
-
);
|
|
851
|
-
|
|
852
|
-
outputs = applyTransferFee(
|
|
853
|
-
outputs,
|
|
854
|
-
side,
|
|
855
|
-
transferFees.destDexFee,
|
|
856
|
-
this.DEST_TOKEN_DEX_TRANSFERS,
|
|
857
|
-
);
|
|
858
|
-
|
|
859
|
-
return {
|
|
860
|
-
prices: [0n, ...outputs.slice(1)],
|
|
861
|
-
unit: outputs[0],
|
|
862
|
-
data: poolData,
|
|
863
|
-
exchange: this.dexKey,
|
|
864
|
-
poolIdentifier: pool.poolIdentifier,
|
|
865
|
-
gasCost: POOL_EXCHANGE_GAS_COST,
|
|
866
|
-
poolAddresses: [pool.address],
|
|
867
|
-
};
|
|
868
|
-
},
|
|
869
|
-
),
|
|
870
|
-
);
|
|
871
|
-
|
|
872
|
-
return results.filter(
|
|
873
|
-
(
|
|
874
|
-
r: PoolPrices<CurveV1FactoryData> | null,
|
|
875
|
-
): r is PoolPrices<CurveV1FactoryData> => r !== null,
|
|
876
|
-
);
|
|
877
|
-
} catch (e) {
|
|
878
|
-
if (blockNumber === 0)
|
|
879
|
-
this.logger.error(
|
|
880
|
-
`Error_${this.dexKey}_${this.dexHelper.config.data.network}_getPrices: Aurelius block manager not yet instantiated`,
|
|
881
|
-
);
|
|
882
|
-
this.logger.error(
|
|
883
|
-
`Error_${this.dexKey}_${this.dexHelper.config.data.network}_getPrices`,
|
|
884
|
-
e,
|
|
885
|
-
);
|
|
886
|
-
return null;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
getCalldataGasCost(
|
|
891
|
-
poolPrices: PoolPrices<CurveV1FactoryData>,
|
|
892
|
-
): number | number[] {
|
|
893
|
-
return (
|
|
894
|
-
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
895
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
896
|
-
CALLDATA_GAS_COST.INDEX +
|
|
897
|
-
CALLDATA_GAS_COST.INDEX +
|
|
898
|
-
CALLDATA_GAS_COST.TIMESTAMP +
|
|
899
|
-
CALLDATA_GAS_COST.BOOL
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
getAdapterParam(
|
|
904
|
-
srcToken: string,
|
|
905
|
-
destToken: string,
|
|
906
|
-
srcAmount: string,
|
|
907
|
-
destAmount: string,
|
|
908
|
-
data: CurveV1FactoryData,
|
|
909
|
-
side: SwapSide,
|
|
910
|
-
): AdapterExchangeParam {
|
|
911
|
-
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
912
|
-
|
|
913
|
-
const { i, j, underlyingSwap } = data;
|
|
914
|
-
const payload = this.abiCoder.encodeParameter(
|
|
915
|
-
{
|
|
916
|
-
ParentStruct: {
|
|
917
|
-
i: 'int128',
|
|
918
|
-
j: 'int128',
|
|
919
|
-
deadline: 'uint256',
|
|
920
|
-
underlyingSwap: 'bool',
|
|
921
|
-
},
|
|
922
|
-
},
|
|
923
|
-
{ i, j, deadline: 0, underlyingSwap },
|
|
924
|
-
);
|
|
925
|
-
|
|
926
|
-
return {
|
|
927
|
-
targetExchange: data.exchange,
|
|
928
|
-
payload,
|
|
929
|
-
networkFee: '0',
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
getTokenFromAddress(address: Address): Token {
|
|
934
|
-
// In this Dex decimals are not used
|
|
935
|
-
return { address, decimals: 0 };
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
async preProcessTransaction(
|
|
939
|
-
optimalSwapExchange: OptimalSwapExchange<CurveV1FactoryData>,
|
|
940
|
-
srcToken: Token,
|
|
941
|
-
_0: Token,
|
|
942
|
-
_1: SwapSide,
|
|
943
|
-
options: PreprocessTransactionOptions,
|
|
944
|
-
): Promise<[OptimalSwapExchange<CurveV1FactoryData>, ExchangeTxInfo]> {
|
|
945
|
-
if (!options.isDirectMethod) {
|
|
946
|
-
return [
|
|
947
|
-
optimalSwapExchange,
|
|
948
|
-
{
|
|
949
|
-
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
950
|
-
},
|
|
951
|
-
];
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
assert(
|
|
955
|
-
optimalSwapExchange.data !== undefined,
|
|
956
|
-
`preProcessTransaction: data field is missing`,
|
|
957
|
-
);
|
|
958
|
-
|
|
959
|
-
let isApproved: boolean | undefined;
|
|
960
|
-
|
|
961
|
-
try {
|
|
962
|
-
isApproved = await this.dexHelper.augustusApprovals.hasApproval(
|
|
963
|
-
options.executionContractAddress,
|
|
964
|
-
this.dexHelper.config.wrapETH(srcToken).address,
|
|
965
|
-
optimalSwapExchange.data.exchange,
|
|
966
|
-
);
|
|
967
|
-
} catch (e) {
|
|
968
|
-
this.logger.error(
|
|
969
|
-
`preProcessTransaction failed to retrieve allowance info: `,
|
|
970
|
-
e,
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
return [
|
|
975
|
-
{
|
|
976
|
-
...optimalSwapExchange,
|
|
977
|
-
data: {
|
|
978
|
-
...optimalSwapExchange.data,
|
|
979
|
-
isApproved,
|
|
980
|
-
},
|
|
981
|
-
},
|
|
982
|
-
{
|
|
983
|
-
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
984
|
-
},
|
|
985
|
-
];
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
getDirectParam(
|
|
989
|
-
srcToken: Address,
|
|
990
|
-
destToken: Address,
|
|
991
|
-
srcAmount: NumberAsString,
|
|
992
|
-
destAmount: NumberAsString,
|
|
993
|
-
expectedAmount: NumberAsString,
|
|
994
|
-
data: CurveV1FactoryData,
|
|
995
|
-
side: SwapSide,
|
|
996
|
-
permit: string,
|
|
997
|
-
uuid: string,
|
|
998
|
-
feePercent: NumberAsString,
|
|
999
|
-
deadline: NumberAsString,
|
|
1000
|
-
partner: string,
|
|
1001
|
-
beneficiary: string,
|
|
1002
|
-
contractMethod?: string,
|
|
1003
|
-
): TxInfo<DirectCurveV1Param> {
|
|
1004
|
-
if (contractMethod !== DIRECT_METHOD_NAME) {
|
|
1005
|
-
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1006
|
-
}
|
|
1007
|
-
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
1008
|
-
|
|
1009
|
-
let isApproved: boolean = !!data.isApproved;
|
|
1010
|
-
if (data.isApproved === undefined) {
|
|
1011
|
-
this.logger.warn(`isApproved is undefined, defaulting to false`);
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
const swapParams: DirectCurveV1Param = [
|
|
1015
|
-
srcToken,
|
|
1016
|
-
destToken,
|
|
1017
|
-
data.exchange,
|
|
1018
|
-
srcAmount,
|
|
1019
|
-
destAmount,
|
|
1020
|
-
expectedAmount,
|
|
1021
|
-
feePercent,
|
|
1022
|
-
data.i.toString(),
|
|
1023
|
-
data.j.toString(),
|
|
1024
|
-
partner,
|
|
1025
|
-
isApproved,
|
|
1026
|
-
data.underlyingSwap
|
|
1027
|
-
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
1028
|
-
: CurveV1SwapType.EXCHANGE,
|
|
1029
|
-
beneficiary,
|
|
1030
|
-
// For CurveV1 we work as it is, without wrapping and unwrapping
|
|
1031
|
-
false,
|
|
1032
|
-
permit,
|
|
1033
|
-
uuidToBytes16(uuid),
|
|
1034
|
-
];
|
|
1035
|
-
|
|
1036
|
-
const encoder = (...params: DirectCurveV1Param) => {
|
|
1037
|
-
return this.directSwapIface.encodeFunctionData(DIRECT_METHOD_NAME, [
|
|
1038
|
-
params,
|
|
1039
|
-
]);
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
return {
|
|
1043
|
-
params: swapParams,
|
|
1044
|
-
encoder,
|
|
1045
|
-
networkFee: '0',
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
getDirectParamV6(
|
|
1050
|
-
srcToken: Address,
|
|
1051
|
-
destToken: Address,
|
|
1052
|
-
fromAmount: NumberAsString,
|
|
1053
|
-
toAmount: NumberAsString,
|
|
1054
|
-
quotedAmount: NumberAsString,
|
|
1055
|
-
data: CurveV1FactoryData,
|
|
1056
|
-
side: SwapSide,
|
|
1057
|
-
permit: string,
|
|
1058
|
-
uuid: string,
|
|
1059
|
-
partnerAndFee: string,
|
|
1060
|
-
beneficiary: string,
|
|
1061
|
-
blockNumber: number,
|
|
1062
|
-
contractMethod?: string,
|
|
1063
|
-
) {
|
|
1064
|
-
if (contractMethod !== DIRECT_METHOD_NAME_V6) {
|
|
1065
|
-
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1066
|
-
}
|
|
1067
|
-
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
1068
|
-
|
|
1069
|
-
const metadata = hexConcat([
|
|
1070
|
-
hexZeroPad(uuidToBytes16(uuid), 16),
|
|
1071
|
-
hexZeroPad(hexlify(blockNumber), 16),
|
|
1072
|
-
]);
|
|
1073
|
-
|
|
1074
|
-
const swapParams: DirectCurveV1FactoryParamV6 = [
|
|
1075
|
-
packCurveData(
|
|
1076
|
-
data.exchange,
|
|
1077
|
-
!data.isApproved, // approve flag, if not approved then set to true
|
|
1078
|
-
isETHAddress(destToken) ? 0 : isETHAddress(srcToken) ? 3 : 0,
|
|
1079
|
-
data.underlyingSwap
|
|
1080
|
-
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
1081
|
-
: CurveV1SwapType.EXCHANGE,
|
|
1082
|
-
).toString(),
|
|
1083
|
-
encodeCurveAssets(data.i, data.j).toString(),
|
|
1084
|
-
srcToken,
|
|
1085
|
-
destToken,
|
|
1086
|
-
fromAmount,
|
|
1087
|
-
toAmount,
|
|
1088
|
-
quotedAmount,
|
|
1089
|
-
metadata,
|
|
1090
|
-
beneficiary,
|
|
1091
|
-
];
|
|
1092
|
-
|
|
1093
|
-
const encodeParams: CurveV1FactoryDirectSwap = [
|
|
1094
|
-
swapParams,
|
|
1095
|
-
partnerAndFee,
|
|
1096
|
-
permit,
|
|
1097
|
-
];
|
|
1098
|
-
|
|
1099
|
-
const encoder = (...params: CurveV1FactoryDirectSwap) => {
|
|
1100
|
-
return this.augustusV6Interface.encodeFunctionData(
|
|
1101
|
-
DIRECT_METHOD_NAME_V6,
|
|
1102
|
-
[...params],
|
|
1103
|
-
);
|
|
1104
|
-
};
|
|
1105
|
-
|
|
1106
|
-
return {
|
|
1107
|
-
encoder,
|
|
1108
|
-
params: encodeParams,
|
|
1109
|
-
networkFee: '0',
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
static getDirectFunctionName(): string[] {
|
|
1114
|
-
return [DIRECT_METHOD_NAME];
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
async getSimpleParam(
|
|
1118
|
-
srcToken: string,
|
|
1119
|
-
destToken: string,
|
|
1120
|
-
srcAmount: string,
|
|
1121
|
-
destAmount: string,
|
|
1122
|
-
data: CurveV1FactoryData,
|
|
1123
|
-
side: SwapSide,
|
|
1124
|
-
): Promise<SimpleExchangeParam> {
|
|
1125
|
-
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1126
|
-
|
|
1127
|
-
const { exchange, i, j, underlyingSwap } = data;
|
|
1128
|
-
const defaultArgs = [i, j, srcAmount, MIN_AMOUNT_TO_RECEIVE];
|
|
1129
|
-
const swapMethod = underlyingSwap
|
|
1130
|
-
? CurveSwapFunctions.exchange_underlying
|
|
1131
|
-
: CurveSwapFunctions.exchange;
|
|
1132
|
-
|
|
1133
|
-
const swapData = this.ifaces.exchangeRouter.encodeFunctionData(
|
|
1134
|
-
swapMethod,
|
|
1135
|
-
defaultArgs,
|
|
1136
|
-
);
|
|
1137
|
-
|
|
1138
|
-
return this.buildSimpleParamWithoutWETHConversion(
|
|
1139
|
-
srcToken,
|
|
1140
|
-
srcAmount,
|
|
1141
|
-
destToken,
|
|
1142
|
-
destAmount,
|
|
1143
|
-
swapData,
|
|
1144
|
-
exchange,
|
|
1145
|
-
);
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
static getDirectFunctionNameV6(): string[] {
|
|
1149
|
-
return [DIRECT_METHOD_NAME_V6];
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
getDexParam(
|
|
1153
|
-
srcToken: Address,
|
|
1154
|
-
destToken: Address,
|
|
1155
|
-
srcAmount: NumberAsString,
|
|
1156
|
-
destAmount: NumberAsString,
|
|
1157
|
-
recipient: Address,
|
|
1158
|
-
data: CurveV1FactoryData,
|
|
1159
|
-
side: SwapSide,
|
|
1160
|
-
): DexExchangeParam {
|
|
1161
|
-
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1162
|
-
|
|
1163
|
-
const { exchange, i, j, underlyingSwap } = data;
|
|
1164
|
-
const defaultArgs = [i, j, srcAmount, MIN_AMOUNT_TO_RECEIVE];
|
|
1165
|
-
const swapMethod = underlyingSwap
|
|
1166
|
-
? CurveSwapFunctions.exchange_underlying
|
|
1167
|
-
: CurveSwapFunctions.exchange;
|
|
1168
|
-
const exchangeData = this.ifaces.exchangeRouter.encodeFunctionData(
|
|
1169
|
-
swapMethod,
|
|
1170
|
-
defaultArgs,
|
|
1171
|
-
);
|
|
1172
|
-
|
|
1173
|
-
return {
|
|
1174
|
-
exchangeData,
|
|
1175
|
-
needWrapNative: this.needWrapNative,
|
|
1176
|
-
sendEthButSupportsInsertFromAmount: true,
|
|
1177
|
-
dexFuncHasRecipient: false,
|
|
1178
|
-
targetExchange: exchange,
|
|
1179
|
-
returnAmountPos: undefined,
|
|
1180
|
-
};
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
async updatePoolState(): Promise<void> {
|
|
1184
|
-
if (!this.areFactoryPoolsFetched) {
|
|
1185
|
-
await this.fetchFactoryPools(undefined, false);
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
await this.poolManager.fetchLiquiditiesFromApi();
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
async getTopPoolsForToken(
|
|
1192
|
-
tokenAddress: Address,
|
|
1193
|
-
limit: number,
|
|
1194
|
-
): Promise<PoolLiquidity[]> {
|
|
1195
|
-
if (!this.areFactoryPoolsFetched) {
|
|
1196
|
-
this.logger.error(
|
|
1197
|
-
`${this.dexKey}: received getTopPools while factory pools are not fetched`,
|
|
1198
|
-
);
|
|
1199
|
-
return [];
|
|
1200
|
-
}
|
|
1201
|
-
const _tokenAddress = tokenAddress.toLowerCase();
|
|
1202
|
-
const poolsWithToken = this.poolManager.getPoolsWithToken(_tokenAddress);
|
|
1203
|
-
|
|
1204
|
-
if (poolsWithToken.length === 0) {
|
|
1205
|
-
return [];
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
return poolsWithToken
|
|
1209
|
-
.reduce<PoolLiquidity[]>((acc, pool) => {
|
|
1210
|
-
let inCoins = false;
|
|
1211
|
-
let inUnderlying = false;
|
|
1212
|
-
const inCoinConnectors: Token[] = [];
|
|
1213
|
-
for (const [i, coin] of pool.poolConstants.COINS.entries()) {
|
|
1214
|
-
if (coin === _tokenAddress) {
|
|
1215
|
-
inCoins = true;
|
|
1216
|
-
} else {
|
|
1217
|
-
inCoinConnectors.push({
|
|
1218
|
-
address: coin,
|
|
1219
|
-
decimals: pool.poolConstants.coins_decimals[i],
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
const underlyingConnectors: Token[] = [];
|
|
1225
|
-
for (const [underlying, i] of Object.entries(
|
|
1226
|
-
pool.underlyingCoinsToIndices,
|
|
1227
|
-
)) {
|
|
1228
|
-
if (underlying === _tokenAddress) {
|
|
1229
|
-
inUnderlying = true;
|
|
1230
|
-
} else {
|
|
1231
|
-
underlyingConnectors.push({
|
|
1232
|
-
address: underlying,
|
|
1233
|
-
decimals: pool.underlyingDecimals[i],
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
let connectorTokens: Token[] = [];
|
|
1238
|
-
if (inCoins) {
|
|
1239
|
-
connectorTokens = connectorTokens.concat(inCoinConnectors);
|
|
1240
|
-
}
|
|
1241
|
-
if (inUnderlying) {
|
|
1242
|
-
connectorTokens = connectorTokens.concat(underlyingConnectors);
|
|
1243
|
-
}
|
|
1244
|
-
if (connectorTokens.length) {
|
|
1245
|
-
acc.push({
|
|
1246
|
-
exchange: this.dexKey,
|
|
1247
|
-
address: pool.address,
|
|
1248
|
-
liquidityUSD: pool.liquidityUSD,
|
|
1249
|
-
connectorTokens: _.uniqBy(connectorTokens, 'address'),
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
return acc;
|
|
1253
|
-
}, [])
|
|
1254
|
-
.sort((a, b) => b.liquidityUSD - a.liquidityUSD)
|
|
1255
|
-
.slice(0, limit);
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
releaseResources(): AsyncOrSync<void> {
|
|
1259
|
-
this.poolManager.releaseResources();
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
private _calcRateMultipliers(coins_decimals: number[]): bigint[] {
|
|
1263
|
-
return coins_decimals.map(coinDecimal => getBigIntPow(36 - coinDecimal));
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
private _reportForUnspecifiedImplementation(
|
|
1267
|
-
implementation: Address,
|
|
1268
|
-
pool?: Address,
|
|
1269
|
-
) {
|
|
1270
|
-
this.logger.warn(
|
|
1271
|
-
`${this.dexKey}: on network ${this.dexHelper.config.data.network} ` +
|
|
1272
|
-
`found unspecified implementation: ${implementation} for ${pool} pool. Skipping pool`,
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
private _reportForUnspecifiedCustomPool(pool: Address) {
|
|
1277
|
-
this.logger.error(
|
|
1278
|
-
`${this.dexKey}: on network ${this.dexHelper.config.data.network} ` +
|
|
1279
|
-
`found unspecified custom pool: ${pool}`,
|
|
1280
|
-
);
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
private _getCoinsABI(type: string): AbiItem {
|
|
1284
|
-
const newCoinsType = _.cloneDeep(this.coinsTypeTemplate);
|
|
1285
|
-
newCoinsType.inputs![0].type = type;
|
|
1286
|
-
return newCoinsType;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { AbiItem } from 'web3-utils';
|
|
3
|
+
import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
|
|
4
|
+
import { assert, AsyncOrSync } from 'ts-essentials';
|
|
5
|
+
import { Interface, JsonFragment } from '@ethersproject/abi';
|
|
6
|
+
import {
|
|
7
|
+
Token,
|
|
8
|
+
Address,
|
|
9
|
+
ExchangePrices,
|
|
10
|
+
PoolPrices,
|
|
11
|
+
AdapterExchangeParam,
|
|
12
|
+
SimpleExchangeParam,
|
|
13
|
+
PoolLiquidity,
|
|
14
|
+
Logger,
|
|
15
|
+
TransferFeeParams,
|
|
16
|
+
TxInfo,
|
|
17
|
+
PreprocessTransactionOptions,
|
|
18
|
+
ExchangeTxInfo,
|
|
19
|
+
DexExchangeParam,
|
|
20
|
+
} from '../../types';
|
|
21
|
+
import {
|
|
22
|
+
SwapSide,
|
|
23
|
+
Network,
|
|
24
|
+
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
25
|
+
NULL_ADDRESS,
|
|
26
|
+
} from '../../constants';
|
|
27
|
+
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
28
|
+
import {
|
|
29
|
+
getBigIntPow,
|
|
30
|
+
getDexKeysWithNetwork,
|
|
31
|
+
isETHAddress,
|
|
32
|
+
isSrcTokenTransferFeeToBeExchanged,
|
|
33
|
+
uuidToBytes16,
|
|
34
|
+
} from '../../utils';
|
|
35
|
+
import { IDex } from '../idex';
|
|
36
|
+
import { IDexHelper } from '../../dex-helper/idex-helper';
|
|
37
|
+
import {
|
|
38
|
+
CurveSwapFunctions,
|
|
39
|
+
CurveV1FactoryData,
|
|
40
|
+
CurveV1FactoryIfaces,
|
|
41
|
+
CurveV1SwapType,
|
|
42
|
+
CustomImplementationNames,
|
|
43
|
+
ImplementationNames,
|
|
44
|
+
PoolConstants,
|
|
45
|
+
DirectCurveV1Param,
|
|
46
|
+
CurveV1FactoryDirectSwap,
|
|
47
|
+
DirectCurveV1FactoryParamV6,
|
|
48
|
+
} from './types';
|
|
49
|
+
import {
|
|
50
|
+
getLocalDeadlineAsFriendlyPlaceholder,
|
|
51
|
+
SimpleExchange,
|
|
52
|
+
} from '../simple-exchange';
|
|
53
|
+
import { CurveV1FactoryConfig, Adapters } from './config';
|
|
54
|
+
import {
|
|
55
|
+
DIRECT_METHOD_NAME,
|
|
56
|
+
FACTORY_MAX_PLAIN_COINS,
|
|
57
|
+
FACTORY_MAX_PLAIN_IMPLEMENTATIONS_FOR_COIN,
|
|
58
|
+
MIN_AMOUNT_TO_RECEIVE,
|
|
59
|
+
POOL_EXCHANGE_GAS_COST,
|
|
60
|
+
} from './constants';
|
|
61
|
+
import { CurveV1FactoryPoolManager } from './curve-v1-pool-manager';
|
|
62
|
+
import CurveABI from '../../abi/Curve.json';
|
|
63
|
+
import DirectSwapABI from '../../abi/DirectSwap.json';
|
|
64
|
+
import FactoryCurveV1ABI from '../../abi/curve-v1-factory/FactoryCurveV1.json';
|
|
65
|
+
import ThreePoolABI from '../../abi/curve-v1-factory/ThreePool.json';
|
|
66
|
+
import ERC20ABI from '../../abi/erc20.json';
|
|
67
|
+
import {
|
|
68
|
+
addressDecode,
|
|
69
|
+
generalDecoder,
|
|
70
|
+
uint256DecodeToNumber,
|
|
71
|
+
uint8ToNumber,
|
|
72
|
+
} from '../../lib/decoders';
|
|
73
|
+
import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper';
|
|
74
|
+
import { BigNumber, BytesLike } from 'ethers';
|
|
75
|
+
import { FactoryStateHandler } from './state-polling-pools/factory-pool-polling';
|
|
76
|
+
import { PoolPollingBase } from './state-polling-pools/pool-polling-base';
|
|
77
|
+
import { CustomBasePoolForFactory } from './state-polling-pools/custom-pool-polling';
|
|
78
|
+
import ImplementationConstants from './price-handlers/functions/constants';
|
|
79
|
+
import { applyTransferFee } from '../../lib/token-transfer-fee';
|
|
80
|
+
import { PriceHandler } from './price-handlers/price-handler';
|
|
81
|
+
import { hexConcat, hexZeroPad, hexlify } from 'ethers/lib/utils';
|
|
82
|
+
import { packCurveData } from '../../lib/curve/encoder';
|
|
83
|
+
import { encodeCurveAssets } from '../curve-v1/packer';
|
|
84
|
+
|
|
85
|
+
import { DIRECT_METHOD_NAME_V6 } from './constants';
|
|
86
|
+
|
|
87
|
+
const DefaultCoinsABI: AbiItem = {
|
|
88
|
+
type: 'function',
|
|
89
|
+
name: 'coins',
|
|
90
|
+
inputs: [
|
|
91
|
+
{
|
|
92
|
+
name: '',
|
|
93
|
+
type: 'uint256',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
outputs: [
|
|
97
|
+
{
|
|
98
|
+
name: '',
|
|
99
|
+
type: 'address',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export class CurveV1Factory
|
|
105
|
+
extends SimpleExchange
|
|
106
|
+
implements
|
|
107
|
+
IDex<CurveV1FactoryData, DirectCurveV1Param | CurveV1FactoryDirectSwap>
|
|
108
|
+
{
|
|
109
|
+
readonly hasConstantPriceLargeAmounts = false;
|
|
110
|
+
readonly needWrapNative = false;
|
|
111
|
+
readonly isFeeOnTransferSupported = true;
|
|
112
|
+
readonly isStatePollingDex = true;
|
|
113
|
+
|
|
114
|
+
private factoryAddresses: string[];
|
|
115
|
+
|
|
116
|
+
readonly poolManager: CurveV1FactoryPoolManager;
|
|
117
|
+
readonly ifaces: CurveV1FactoryIfaces;
|
|
118
|
+
|
|
119
|
+
private areFactoryPoolsFetched: boolean = false;
|
|
120
|
+
private areCustomPoolsFetched: boolean = false;
|
|
121
|
+
|
|
122
|
+
readonly SRC_TOKEN_DEX_TRANSFERS = 1;
|
|
123
|
+
readonly DEST_TOKEN_DEX_TRANSFERS = 1;
|
|
124
|
+
|
|
125
|
+
readonly directSwapIface = new Interface(DirectSwapABI);
|
|
126
|
+
|
|
127
|
+
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
128
|
+
getDexKeysWithNetwork(CurveV1FactoryConfig);
|
|
129
|
+
|
|
130
|
+
logger: Logger;
|
|
131
|
+
|
|
132
|
+
constructor(
|
|
133
|
+
readonly network: Network,
|
|
134
|
+
readonly dexKey: string,
|
|
135
|
+
readonly dexHelper: IDexHelper,
|
|
136
|
+
protected adapters = Adapters[network] || {},
|
|
137
|
+
protected config = CurveV1FactoryConfig[dexKey][network],
|
|
138
|
+
// This type is used to support different encoding for uint128 and uint256 args
|
|
139
|
+
private coinsTypeTemplate: AbiItem = DefaultCoinsABI,
|
|
140
|
+
) {
|
|
141
|
+
super(dexHelper, dexKey);
|
|
142
|
+
this.logger = dexHelper.getLogger(`${this.dexKey}-${this.network}`);
|
|
143
|
+
this.factoryAddresses =
|
|
144
|
+
this.config.factories?.map(e => e.address.toLowerCase()) || [];
|
|
145
|
+
this.ifaces = {
|
|
146
|
+
exchangeRouter: new Interface(CurveABI),
|
|
147
|
+
factory: new Interface(FactoryCurveV1ABI as JsonFragment[]),
|
|
148
|
+
erc20: new Interface(ERC20ABI as JsonFragment[]),
|
|
149
|
+
threePool: new Interface(ThreePoolABI as JsonFragment[]),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// I had to put this initialization into CurveV1 because I have to have access to mapping
|
|
153
|
+
// from address to custom pool
|
|
154
|
+
const allPriceHandlers = Object.values(
|
|
155
|
+
this.config.factoryPoolImplementations,
|
|
156
|
+
).reduce<Record<string, PriceHandler>>((acc, curr) => {
|
|
157
|
+
let baseImplementationName: ImplementationNames | undefined;
|
|
158
|
+
if (curr.basePoolAddress) {
|
|
159
|
+
const customBasePool = this.config.customPools[curr.basePoolAddress];
|
|
160
|
+
if (customBasePool === undefined) {
|
|
161
|
+
this.logger.error(
|
|
162
|
+
`${this.dexKey} on ${this.dexHelper.config.data.network}: can not get customBasePool from basePoolAddress`,
|
|
163
|
+
);
|
|
164
|
+
return acc;
|
|
165
|
+
} else {
|
|
166
|
+
baseImplementationName = customBasePool.name;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
acc[curr.address] = new PriceHandler(
|
|
170
|
+
this.logger,
|
|
171
|
+
curr.name,
|
|
172
|
+
baseImplementationName,
|
|
173
|
+
);
|
|
174
|
+
return acc;
|
|
175
|
+
}, {});
|
|
176
|
+
|
|
177
|
+
Object.values(this.config.customPools).reduce((acc, curr) => {
|
|
178
|
+
if (curr.useForPricing) {
|
|
179
|
+
acc[curr.address] = new PriceHandler(this.logger, curr.name);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return acc;
|
|
183
|
+
}, allPriceHandlers);
|
|
184
|
+
|
|
185
|
+
this.poolManager = new CurveV1FactoryPoolManager(
|
|
186
|
+
this.dexKey,
|
|
187
|
+
dexHelper.getLogger(`${this.dexKey}-state-manager`),
|
|
188
|
+
dexHelper,
|
|
189
|
+
allPriceHandlers,
|
|
190
|
+
this.config.stateUpdatePeriodMs,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async initializePricing(blockNumber: number) {
|
|
195
|
+
// This is only to start timer, each pool is initialized with updated state
|
|
196
|
+
this.poolManager.initializePollingPools();
|
|
197
|
+
await this.fetchFactoryPools(blockNumber);
|
|
198
|
+
await this.poolManager.fetchLiquiditiesFromApi();
|
|
199
|
+
await this.poolManager.updatePollingPoolsInBatch();
|
|
200
|
+
this.logger.info(`${this.dexKey}: successfully initialized`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async initializeCustomPollingPools(
|
|
204
|
+
factoryAddresses: string[],
|
|
205
|
+
blockNumber?: number,
|
|
206
|
+
// We don't want to initialize state for PoolTracker. It doesn't make any sense
|
|
207
|
+
initializeInitialState: boolean = true,
|
|
208
|
+
) {
|
|
209
|
+
if (this.areCustomPoolsFetched) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await Promise.all(
|
|
214
|
+
factoryAddresses.map(async factoryAddress => {
|
|
215
|
+
try {
|
|
216
|
+
await Promise.all(
|
|
217
|
+
Object.values(this.config.customPools).map(async customPool => {
|
|
218
|
+
const poolIdentifier = this.getPoolIdentifier(
|
|
219
|
+
customPool.address,
|
|
220
|
+
false,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const poolContextConstants =
|
|
224
|
+
ImplementationConstants[customPool.name];
|
|
225
|
+
const {
|
|
226
|
+
N_COINS: nCoins,
|
|
227
|
+
USE_LENDING: useLending,
|
|
228
|
+
isLending,
|
|
229
|
+
} = poolContextConstants;
|
|
230
|
+
|
|
231
|
+
const coinsAndImplementations =
|
|
232
|
+
await this.dexHelper.multiWrapper.aggregate(
|
|
233
|
+
_.range(0, nCoins)
|
|
234
|
+
.map(i => ({
|
|
235
|
+
target: customPool.address,
|
|
236
|
+
callData: this.abiCoder.encodeFunctionCall(
|
|
237
|
+
this._getCoinsABI(customPool.coinsInputType),
|
|
238
|
+
[i.toString()],
|
|
239
|
+
),
|
|
240
|
+
decodeFunction: addressDecode,
|
|
241
|
+
}))
|
|
242
|
+
.concat([
|
|
243
|
+
{
|
|
244
|
+
target: factoryAddress,
|
|
245
|
+
callData: this.ifaces.factory.encodeFunctionData(
|
|
246
|
+
'get_implementation_address',
|
|
247
|
+
[customPool.address],
|
|
248
|
+
),
|
|
249
|
+
decodeFunction: addressDecode,
|
|
250
|
+
},
|
|
251
|
+
]),
|
|
252
|
+
);
|
|
253
|
+
const COINS = coinsAndImplementations.slice(0, -1);
|
|
254
|
+
const implementationAddress =
|
|
255
|
+
coinsAndImplementations.slice(-1)[0];
|
|
256
|
+
|
|
257
|
+
const coins_decimals = (
|
|
258
|
+
await this.dexHelper.multiWrapper.tryAggregate(
|
|
259
|
+
true,
|
|
260
|
+
COINS.map(c => ({
|
|
261
|
+
target: c,
|
|
262
|
+
callData: this.ifaces.erc20.encodeFunctionData(
|
|
263
|
+
'decimals',
|
|
264
|
+
[],
|
|
265
|
+
),
|
|
266
|
+
decodeFunction: uint8ToNumber,
|
|
267
|
+
})),
|
|
268
|
+
)
|
|
269
|
+
).map(r => r.returnData);
|
|
270
|
+
|
|
271
|
+
const poolConstants: PoolConstants = {
|
|
272
|
+
COINS,
|
|
273
|
+
coins_decimals,
|
|
274
|
+
rate_multipliers: this._calcRateMultipliers(coins_decimals),
|
|
275
|
+
lpTokenAddress: customPool.lpTokenAddress,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
let newPool: PoolPollingBase;
|
|
279
|
+
if (
|
|
280
|
+
Object.values<ImplementationNames>(
|
|
281
|
+
CustomImplementationNames,
|
|
282
|
+
).includes(customPool.name)
|
|
283
|
+
) {
|
|
284
|
+
// We don't want custom pools to be used for pricing, unless explicitly specified
|
|
285
|
+
newPool = new CustomBasePoolForFactory(
|
|
286
|
+
this.logger,
|
|
287
|
+
this.dexKey,
|
|
288
|
+
this.dexHelper.config.data.network,
|
|
289
|
+
this.cacheStateKey,
|
|
290
|
+
customPool.name,
|
|
291
|
+
implementationAddress,
|
|
292
|
+
customPool.address,
|
|
293
|
+
this.config.stateUpdatePeriodMs,
|
|
294
|
+
poolIdentifier,
|
|
295
|
+
poolConstants,
|
|
296
|
+
poolContextConstants,
|
|
297
|
+
customPool.liquidityApiSlug,
|
|
298
|
+
customPool.lpTokenAddress,
|
|
299
|
+
isLending,
|
|
300
|
+
customPool.balancesInputType,
|
|
301
|
+
useLending,
|
|
302
|
+
customPool.useForPricing,
|
|
303
|
+
);
|
|
304
|
+
} else {
|
|
305
|
+
// Use for pricing pools from factory
|
|
306
|
+
newPool = new CustomBasePoolForFactory(
|
|
307
|
+
this.logger,
|
|
308
|
+
this.dexKey,
|
|
309
|
+
this.dexHelper.config.data.network,
|
|
310
|
+
this.cacheStateKey,
|
|
311
|
+
customPool.name,
|
|
312
|
+
implementationAddress,
|
|
313
|
+
customPool.address,
|
|
314
|
+
this.config.stateUpdatePeriodMs,
|
|
315
|
+
poolIdentifier,
|
|
316
|
+
poolConstants,
|
|
317
|
+
poolContextConstants,
|
|
318
|
+
customPool.liquidityApiSlug,
|
|
319
|
+
customPool.lpTokenAddress,
|
|
320
|
+
isLending,
|
|
321
|
+
customPool.balancesInputType,
|
|
322
|
+
useLending,
|
|
323
|
+
true,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.poolManager.initializeNewPoolForState(
|
|
328
|
+
poolIdentifier,
|
|
329
|
+
newPool,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
if (initializeInitialState) {
|
|
333
|
+
await this.poolManager.initializeIndividualPollingPoolState(
|
|
334
|
+
poolIdentifier,
|
|
335
|
+
CustomBasePoolForFactory.IS_SRC_FEE_ON_TRANSFER_SUPPORTED,
|
|
336
|
+
blockNumber,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}),
|
|
340
|
+
);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
this.logger.error(
|
|
343
|
+
`Error initializing custom polling pools for factory ${factoryAddress}: `,
|
|
344
|
+
e,
|
|
345
|
+
);
|
|
346
|
+
throw e;
|
|
347
|
+
}
|
|
348
|
+
}),
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
this.areCustomPoolsFetched = true;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async fetchFactoryPools(
|
|
355
|
+
blockNumber?: number,
|
|
356
|
+
// Variable initializeInitialState is only for poolTracker. We don't want to keep state updated with scheduler
|
|
357
|
+
// We just want to initialize factory pools and send request to CurveAPI
|
|
358
|
+
// Other values are not used
|
|
359
|
+
initializeInitialState: boolean = false,
|
|
360
|
+
) {
|
|
361
|
+
if (this.areFactoryPoolsFetched) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!this.factoryAddresses || this.factoryAddresses.length == 0) {
|
|
366
|
+
this.logger.warn(`No factory address specified in configs`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// There is no scenario when we need to call initialize custom pools without factory pools
|
|
371
|
+
// So I put it here to not forget call, because custom pools must be initialised before factory pools
|
|
372
|
+
// This function may be called multiple times, but will execute only once
|
|
373
|
+
await this.initializeCustomPollingPools(
|
|
374
|
+
this.factoryAddresses,
|
|
375
|
+
blockNumber,
|
|
376
|
+
initializeInitialState,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
await Promise.all(
|
|
380
|
+
this.factoryAddresses.map(async factoryAddress => {
|
|
381
|
+
try {
|
|
382
|
+
const poolCountResult =
|
|
383
|
+
await this.dexHelper.multiWrapper!.tryAggregate(true, [
|
|
384
|
+
{
|
|
385
|
+
target: factoryAddress,
|
|
386
|
+
callData: this.ifaces.factory.encodeFunctionData('pool_count'),
|
|
387
|
+
decodeFunction: uint256DecodeToNumber,
|
|
388
|
+
},
|
|
389
|
+
// This is used later to request all available implementations. In particular meta implementations
|
|
390
|
+
{
|
|
391
|
+
target: factoryAddress,
|
|
392
|
+
callData:
|
|
393
|
+
this.ifaces.factory.encodeFunctionData('base_pool_count'),
|
|
394
|
+
decodeFunction: uint256DecodeToNumber,
|
|
395
|
+
},
|
|
396
|
+
]);
|
|
397
|
+
|
|
398
|
+
const poolCount = poolCountResult[0].returnData;
|
|
399
|
+
const basePoolCount = poolCountResult[1].returnData;
|
|
400
|
+
|
|
401
|
+
const calldataGetPoolAddresses = _.range(0, poolCount).map(i => ({
|
|
402
|
+
target: factoryAddress,
|
|
403
|
+
callData: this.ifaces.factory.encodeFunctionData('pool_list', [i]),
|
|
404
|
+
decodeFunction: addressDecode,
|
|
405
|
+
}));
|
|
406
|
+
|
|
407
|
+
const calldataGetBasePoolAddresses = _.range(0, basePoolCount).map(
|
|
408
|
+
i => ({
|
|
409
|
+
target: factoryAddress,
|
|
410
|
+
callData: this.ifaces.factory.encodeFunctionData(
|
|
411
|
+
'base_pool_list',
|
|
412
|
+
[i],
|
|
413
|
+
),
|
|
414
|
+
decodeFunction: addressDecode,
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const allPoolAddresses = (
|
|
419
|
+
await this.dexHelper.multiWrapper.tryAggregate(
|
|
420
|
+
true,
|
|
421
|
+
calldataGetPoolAddresses.concat(calldataGetBasePoolAddresses),
|
|
422
|
+
)
|
|
423
|
+
).map(e => e.returnData);
|
|
424
|
+
|
|
425
|
+
const poolAddresses = allPoolAddresses.slice(0, poolCount);
|
|
426
|
+
const basePoolAddresses = allPoolAddresses.slice(poolCount);
|
|
427
|
+
|
|
428
|
+
const customPoolAddresses = Object.values(
|
|
429
|
+
this.config.customPools,
|
|
430
|
+
).map(customPool => customPool.address);
|
|
431
|
+
basePoolAddresses.forEach(basePool => {
|
|
432
|
+
if (
|
|
433
|
+
!customPoolAddresses.includes(basePool) &&
|
|
434
|
+
!this.config.disabledPools.has(basePool)
|
|
435
|
+
) {
|
|
436
|
+
this._reportForUnspecifiedCustomPool(basePool);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const factoryConfig = this.config.factories?.find(
|
|
441
|
+
({ address }) =>
|
|
442
|
+
factoryAddress.toLowerCase() === address.toLowerCase(),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
let callDataFromFactoryPools: MultiCallParams<
|
|
446
|
+
string[] | number[] | string
|
|
447
|
+
>[] = poolAddresses
|
|
448
|
+
.map(p => [
|
|
449
|
+
{
|
|
450
|
+
target: factoryAddress,
|
|
451
|
+
callData: this.ifaces.factory.encodeFunctionData(
|
|
452
|
+
'get_implementation_address',
|
|
453
|
+
[p],
|
|
454
|
+
),
|
|
455
|
+
decodeFunction: addressDecode,
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
target: factoryAddress,
|
|
459
|
+
callData: this.ifaces.factory.encodeFunctionData('get_coins', [
|
|
460
|
+
p,
|
|
461
|
+
]),
|
|
462
|
+
decodeFunction: (
|
|
463
|
+
result: MultiResult<BytesLike> | BytesLike,
|
|
464
|
+
): string[] =>
|
|
465
|
+
generalDecoder<string[]>(
|
|
466
|
+
result,
|
|
467
|
+
[
|
|
468
|
+
`address[${
|
|
469
|
+
factoryConfig?.maxPlainCoins?.toString() || ''
|
|
470
|
+
}]`,
|
|
471
|
+
],
|
|
472
|
+
new Array(4).fill(NULL_ADDRESS),
|
|
473
|
+
parsed => parsed[0].map((p: string) => p.toLowerCase()),
|
|
474
|
+
),
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
target: factoryAddress,
|
|
478
|
+
callData: this.ifaces.factory.encodeFunctionData(
|
|
479
|
+
'get_decimals',
|
|
480
|
+
[p],
|
|
481
|
+
),
|
|
482
|
+
decodeFunction: (
|
|
483
|
+
result: MultiResult<BytesLike> | BytesLike,
|
|
484
|
+
): number[] =>
|
|
485
|
+
generalDecoder<number[]>(
|
|
486
|
+
result,
|
|
487
|
+
[
|
|
488
|
+
`uint256[${
|
|
489
|
+
factoryConfig?.maxPlainCoins?.toString() || ''
|
|
490
|
+
}]`,
|
|
491
|
+
],
|
|
492
|
+
[0, 0, 0, 0],
|
|
493
|
+
parsed =>
|
|
494
|
+
parsed[0].map((p: BigNumber) => Number(p.toString())),
|
|
495
|
+
),
|
|
496
|
+
},
|
|
497
|
+
])
|
|
498
|
+
.flat();
|
|
499
|
+
|
|
500
|
+
// This is divider between pools related results and implementations
|
|
501
|
+
const factoryResultsDivider = callDataFromFactoryPools.length;
|
|
502
|
+
|
|
503
|
+
const metaPoolImplementations = factoryConfig?.isStableNg
|
|
504
|
+
? []
|
|
505
|
+
: basePoolAddresses.map(basePoolAddress => ({
|
|
506
|
+
target: factoryAddress,
|
|
507
|
+
callData: this.ifaces.factory.encodeFunctionData(
|
|
508
|
+
'metapool_implementations',
|
|
509
|
+
[basePoolAddress],
|
|
510
|
+
),
|
|
511
|
+
decodeFunction: (
|
|
512
|
+
result: MultiResult<BytesLike> | BytesLike,
|
|
513
|
+
): string[] =>
|
|
514
|
+
generalDecoder<string[]>(
|
|
515
|
+
result,
|
|
516
|
+
['address[10]'],
|
|
517
|
+
new Array(10).fill(NULL_ADDRESS),
|
|
518
|
+
parsed => parsed[0].map((p: string) => p.toLowerCase()),
|
|
519
|
+
),
|
|
520
|
+
}));
|
|
521
|
+
|
|
522
|
+
const plainImplementations = factoryConfig?.isStableNg
|
|
523
|
+
? []
|
|
524
|
+
: _.flattenDeep(
|
|
525
|
+
_.range(2, FACTORY_MAX_PLAIN_COINS + 1).map(coinNumber =>
|
|
526
|
+
_.range(FACTORY_MAX_PLAIN_IMPLEMENTATIONS_FOR_COIN).map(
|
|
527
|
+
implInd => ({
|
|
528
|
+
target: factoryAddress,
|
|
529
|
+
callData: this.ifaces.factory.encodeFunctionData(
|
|
530
|
+
'plain_implementations',
|
|
531
|
+
[coinNumber, implInd],
|
|
532
|
+
),
|
|
533
|
+
decodeFunction: addressDecode,
|
|
534
|
+
}),
|
|
535
|
+
),
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
// Implementations must be requested from factory, but it accepts as arg basePool address
|
|
540
|
+
// for metaPools
|
|
541
|
+
callDataFromFactoryPools = callDataFromFactoryPools.concat(
|
|
542
|
+
...metaPoolImplementations,
|
|
543
|
+
// To receive plain pool implementation address, you have to call plain_implementations
|
|
544
|
+
// with two variables: N_COINS and implementations_index
|
|
545
|
+
// N_COINS is between 2-4. Currently more than 4 coins is not supported
|
|
546
|
+
// as for implementation index, there are only 0-9 indexes
|
|
547
|
+
...plainImplementations,
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const allResultsFromFactory = (
|
|
551
|
+
await this.dexHelper.multiWrapper.tryAggregate<
|
|
552
|
+
string[] | number[] | string
|
|
553
|
+
>(true, callDataFromFactoryPools)
|
|
554
|
+
).map(r => r.returnData);
|
|
555
|
+
|
|
556
|
+
const resultsFromFactory = allResultsFromFactory.slice(
|
|
557
|
+
0,
|
|
558
|
+
factoryResultsDivider,
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
const allAvailableImplementations = _.flattenDeep(
|
|
562
|
+
allResultsFromFactory.slice(factoryResultsDivider) as string[],
|
|
563
|
+
).filter(
|
|
564
|
+
implementation =>
|
|
565
|
+
implementation !== NULL_ADDRESS &&
|
|
566
|
+
!this.config.disabledImplementations.has(implementation),
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
allAvailableImplementations.forEach(implementation => {
|
|
570
|
+
const currentImplementation =
|
|
571
|
+
this.config.factoryPoolImplementations[implementation];
|
|
572
|
+
if (currentImplementation === undefined) {
|
|
573
|
+
this._reportForUnspecifiedImplementation(implementation);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const stateInitializePromises: Promise<void>[] = [];
|
|
578
|
+
_.chunk(resultsFromFactory, 3).forEach((result, i) => {
|
|
579
|
+
if (this.config.disabledPools.has(poolAddresses[i])) {
|
|
580
|
+
this.logger.trace(`Filtering disabled pool ${poolAddresses[i]}`);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
let [implementationAddress, coins, coins_decimals] = result as [
|
|
585
|
+
string,
|
|
586
|
+
string[],
|
|
587
|
+
number[],
|
|
588
|
+
];
|
|
589
|
+
|
|
590
|
+
implementationAddress = implementationAddress.toLowerCase();
|
|
591
|
+
coins = coins
|
|
592
|
+
.map(c => c.toLowerCase())
|
|
593
|
+
.filter(c => c !== NULL_ADDRESS);
|
|
594
|
+
coins_decimals = coins_decimals.filter(cd => cd !== 0);
|
|
595
|
+
|
|
596
|
+
const factoryImplementationFromConfig =
|
|
597
|
+
this.config.factoryPoolImplementations[implementationAddress];
|
|
598
|
+
|
|
599
|
+
if (
|
|
600
|
+
factoryImplementationFromConfig === undefined &&
|
|
601
|
+
!this.config.disabledImplementations.has(implementationAddress)
|
|
602
|
+
) {
|
|
603
|
+
this._reportForUnspecifiedImplementation(
|
|
604
|
+
implementationAddress,
|
|
605
|
+
poolAddresses[i],
|
|
606
|
+
);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const factoryImplementationConstants =
|
|
611
|
+
ImplementationConstants[factoryImplementationFromConfig.name];
|
|
612
|
+
|
|
613
|
+
let isMeta: boolean = false;
|
|
614
|
+
let basePoolStateFetcher: PoolPollingBase | undefined;
|
|
615
|
+
if (factoryImplementationFromConfig.basePoolAddress !== undefined) {
|
|
616
|
+
isMeta = true;
|
|
617
|
+
const basePoolIdentifier = this.getPoolIdentifier(
|
|
618
|
+
factoryImplementationFromConfig.basePoolAddress,
|
|
619
|
+
false,
|
|
620
|
+
);
|
|
621
|
+
const basePool = this.poolManager.getPool(
|
|
622
|
+
basePoolIdentifier,
|
|
623
|
+
false,
|
|
624
|
+
);
|
|
625
|
+
if (basePool === null) {
|
|
626
|
+
this.logger.error(
|
|
627
|
+
`${this.dexKey}_${this.dexHelper.config.data.network}: custom base pool ${basePoolIdentifier} was not initialized properly. ` +
|
|
628
|
+
`You must call initializeCustomPollingPools before fetching factory`,
|
|
629
|
+
);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
basePoolStateFetcher = basePool;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const poolConstants: PoolConstants = {
|
|
636
|
+
COINS: coins,
|
|
637
|
+
coins_decimals,
|
|
638
|
+
rate_multipliers: this._calcRateMultipliers(coins_decimals),
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const poolIdentifier = this.getPoolIdentifier(
|
|
642
|
+
poolAddresses[i],
|
|
643
|
+
isMeta,
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
const newPool = new FactoryStateHandler(
|
|
647
|
+
this.logger,
|
|
648
|
+
this.dexKey,
|
|
649
|
+
this.dexHelper.config.data.network,
|
|
650
|
+
this.cacheStateKey,
|
|
651
|
+
factoryImplementationFromConfig.name,
|
|
652
|
+
implementationAddress.toLowerCase(),
|
|
653
|
+
poolAddresses[i],
|
|
654
|
+
this.config,
|
|
655
|
+
factoryAddress,
|
|
656
|
+
poolIdentifier,
|
|
657
|
+
poolConstants,
|
|
658
|
+
factoryImplementationConstants,
|
|
659
|
+
factoryImplementationConstants.isFeeOnTransferSupported,
|
|
660
|
+
factoryImplementationFromConfig.liquidityApiSlug ?? '/factory',
|
|
661
|
+
basePoolStateFetcher,
|
|
662
|
+
factoryImplementationFromConfig.customGasCost,
|
|
663
|
+
factoryImplementationFromConfig.isStoreRateSupported,
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
this.poolManager.initializeNewPool(poolIdentifier, newPool);
|
|
667
|
+
|
|
668
|
+
if (initializeInitialState) {
|
|
669
|
+
stateInitializePromises.push(
|
|
670
|
+
this.poolManager.initializeIndividualPollingPoolState(
|
|
671
|
+
poolIdentifier,
|
|
672
|
+
factoryImplementationConstants.isFeeOnTransferSupported,
|
|
673
|
+
),
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
await Promise.all(stateInitializePromises);
|
|
679
|
+
} catch (e) {
|
|
680
|
+
this.logger.error(
|
|
681
|
+
`Error fetching factory pools for ${factoryAddress}: `,
|
|
682
|
+
e,
|
|
683
|
+
);
|
|
684
|
+
throw e;
|
|
685
|
+
}
|
|
686
|
+
}),
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
this.areFactoryPoolsFetched = true;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
693
|
+
return this.adapters[side] ? this.adapters[side] : null;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
getPoolIdentifier(poolAddress: string, isMeta: boolean): string {
|
|
697
|
+
return `${this.dexKey}_${poolAddress}_${isMeta}`;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async getPoolIdentifiers(
|
|
701
|
+
srcToken: Token,
|
|
702
|
+
destToken: Token,
|
|
703
|
+
side: SwapSide,
|
|
704
|
+
blockNumber: number,
|
|
705
|
+
): Promise<string[]> {
|
|
706
|
+
if (side === SwapSide.BUY) {
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const srcTokenAddress = srcToken.address.toLowerCase();
|
|
711
|
+
const destTokenAddress = destToken.address.toLowerCase();
|
|
712
|
+
|
|
713
|
+
if (srcTokenAddress === destTokenAddress) {
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const pools = this.poolManager.getPoolsForPair(
|
|
718
|
+
srcTokenAddress,
|
|
719
|
+
destTokenAddress,
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
return pools.map(pool =>
|
|
723
|
+
this.getPoolIdentifier(pool.address, pool.isMetaPool),
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async getPricesVolume(
|
|
728
|
+
srcToken: Token,
|
|
729
|
+
destToken: Token,
|
|
730
|
+
amounts: bigint[],
|
|
731
|
+
side: SwapSide,
|
|
732
|
+
blockNumber: number,
|
|
733
|
+
limitPools?: string[],
|
|
734
|
+
transferFees: TransferFeeParams = {
|
|
735
|
+
srcFee: 0,
|
|
736
|
+
destFee: 0,
|
|
737
|
+
srcDexFee: 0,
|
|
738
|
+
destDexFee: 0,
|
|
739
|
+
},
|
|
740
|
+
): Promise<null | ExchangePrices<CurveV1FactoryData>> {
|
|
741
|
+
try {
|
|
742
|
+
if (side === SwapSide.BUY) {
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const _isSrcTokenTransferFeeToBeExchanged =
|
|
747
|
+
isSrcTokenTransferFeeToBeExchanged(transferFees);
|
|
748
|
+
|
|
749
|
+
const srcTokenAddress = srcToken.address.toLowerCase();
|
|
750
|
+
const destTokenAddress = destToken.address.toLowerCase();
|
|
751
|
+
|
|
752
|
+
if (srcTokenAddress === destTokenAddress) {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
let pools: PoolPollingBase[] = [];
|
|
757
|
+
if (limitPools !== undefined) {
|
|
758
|
+
pools = limitPools
|
|
759
|
+
.map(poolIdentifier =>
|
|
760
|
+
this.poolManager.getPool(
|
|
761
|
+
poolIdentifier,
|
|
762
|
+
_isSrcTokenTransferFeeToBeExchanged,
|
|
763
|
+
),
|
|
764
|
+
)
|
|
765
|
+
.filter(
|
|
766
|
+
(pool): pool is PoolPollingBase =>
|
|
767
|
+
pool !== null &&
|
|
768
|
+
pool.getPoolData(srcTokenAddress, destTokenAddress) !== null,
|
|
769
|
+
);
|
|
770
|
+
} else {
|
|
771
|
+
pools = this.poolManager.getPoolsForPair(
|
|
772
|
+
srcTokenAddress,
|
|
773
|
+
destTokenAddress,
|
|
774
|
+
_isSrcTokenTransferFeeToBeExchanged,
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (pools.length <= 0) {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (pools.length > 10) {
|
|
783
|
+
// I expect this to not happen
|
|
784
|
+
this.logger.warn(
|
|
785
|
+
`${this.dexKey}: There are more than 10 pools found for pair: ${srcTokenAddress}-${destTokenAddress}`,
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const amountsWithUnit = [
|
|
790
|
+
getBigIntPow(srcToken.decimals),
|
|
791
|
+
...amounts.slice(1),
|
|
792
|
+
];
|
|
793
|
+
const amountsWithUnitAndFee = _isSrcTokenTransferFeeToBeExchanged
|
|
794
|
+
? applyTransferFee(
|
|
795
|
+
applyTransferFee(
|
|
796
|
+
amountsWithUnit,
|
|
797
|
+
side,
|
|
798
|
+
transferFees.srcFee,
|
|
799
|
+
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
800
|
+
),
|
|
801
|
+
side,
|
|
802
|
+
transferFees.srcDexFee,
|
|
803
|
+
this.SRC_TOKEN_DEX_TRANSFERS,
|
|
804
|
+
)
|
|
805
|
+
: amountsWithUnit;
|
|
806
|
+
|
|
807
|
+
const results = await Promise.all(
|
|
808
|
+
pools.map(
|
|
809
|
+
async (pool): Promise<PoolPrices<CurveV1FactoryData> | null> => {
|
|
810
|
+
let state = pool.getState();
|
|
811
|
+
if (!state) {
|
|
812
|
+
await this.poolManager.updateManuallyPollingPools(
|
|
813
|
+
pool.baseStatePoolPolling
|
|
814
|
+
? [pool.baseStatePoolPolling, pool]
|
|
815
|
+
: [pool],
|
|
816
|
+
);
|
|
817
|
+
state = pool.getState();
|
|
818
|
+
if (!state) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (state.balances.every(b => b === 0n)) {
|
|
824
|
+
this.logger.trace(
|
|
825
|
+
`${this.dexKey} on ${this.dexHelper.config.data.network}: State balances equal to 0 in pool ${pool.address}`,
|
|
826
|
+
);
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const poolData = pool.getPoolData(
|
|
831
|
+
srcTokenAddress,
|
|
832
|
+
destTokenAddress,
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
if (poolData === null) {
|
|
836
|
+
this.logger.error(
|
|
837
|
+
`${pool.fullName}: one or both tokens can not be exchanged in pool ${pool.address}: ${srcTokenAddress} -> ${destTokenAddress}`,
|
|
838
|
+
);
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
let outputs: bigint[] = this.poolManager
|
|
843
|
+
.getPriceHandler(pool.implementationAddress)
|
|
844
|
+
.getOutputs(
|
|
845
|
+
state,
|
|
846
|
+
amountsWithUnitAndFee,
|
|
847
|
+
poolData.i,
|
|
848
|
+
poolData.j,
|
|
849
|
+
poolData.underlyingSwap,
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
outputs = applyTransferFee(
|
|
853
|
+
outputs,
|
|
854
|
+
side,
|
|
855
|
+
transferFees.destDexFee,
|
|
856
|
+
this.DEST_TOKEN_DEX_TRANSFERS,
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
prices: [0n, ...outputs.slice(1)],
|
|
861
|
+
unit: outputs[0],
|
|
862
|
+
data: poolData,
|
|
863
|
+
exchange: this.dexKey,
|
|
864
|
+
poolIdentifier: pool.poolIdentifier,
|
|
865
|
+
gasCost: POOL_EXCHANGE_GAS_COST,
|
|
866
|
+
poolAddresses: [pool.address],
|
|
867
|
+
};
|
|
868
|
+
},
|
|
869
|
+
),
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
return results.filter(
|
|
873
|
+
(
|
|
874
|
+
r: PoolPrices<CurveV1FactoryData> | null,
|
|
875
|
+
): r is PoolPrices<CurveV1FactoryData> => r !== null,
|
|
876
|
+
);
|
|
877
|
+
} catch (e) {
|
|
878
|
+
if (blockNumber === 0)
|
|
879
|
+
this.logger.error(
|
|
880
|
+
`Error_${this.dexKey}_${this.dexHelper.config.data.network}_getPrices: Aurelius block manager not yet instantiated`,
|
|
881
|
+
);
|
|
882
|
+
this.logger.error(
|
|
883
|
+
`Error_${this.dexKey}_${this.dexHelper.config.data.network}_getPrices`,
|
|
884
|
+
e,
|
|
885
|
+
);
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
getCalldataGasCost(
|
|
891
|
+
poolPrices: PoolPrices<CurveV1FactoryData>,
|
|
892
|
+
): number | number[] {
|
|
893
|
+
return (
|
|
894
|
+
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
895
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
896
|
+
CALLDATA_GAS_COST.INDEX +
|
|
897
|
+
CALLDATA_GAS_COST.INDEX +
|
|
898
|
+
CALLDATA_GAS_COST.TIMESTAMP +
|
|
899
|
+
CALLDATA_GAS_COST.BOOL
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
getAdapterParam(
|
|
904
|
+
srcToken: string,
|
|
905
|
+
destToken: string,
|
|
906
|
+
srcAmount: string,
|
|
907
|
+
destAmount: string,
|
|
908
|
+
data: CurveV1FactoryData,
|
|
909
|
+
side: SwapSide,
|
|
910
|
+
): AdapterExchangeParam {
|
|
911
|
+
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
912
|
+
|
|
913
|
+
const { i, j, underlyingSwap } = data;
|
|
914
|
+
const payload = this.abiCoder.encodeParameter(
|
|
915
|
+
{
|
|
916
|
+
ParentStruct: {
|
|
917
|
+
i: 'int128',
|
|
918
|
+
j: 'int128',
|
|
919
|
+
deadline: 'uint256',
|
|
920
|
+
underlyingSwap: 'bool',
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
{ i, j, deadline: 0, underlyingSwap },
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
return {
|
|
927
|
+
targetExchange: data.exchange,
|
|
928
|
+
payload,
|
|
929
|
+
networkFee: '0',
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
getTokenFromAddress(address: Address): Token {
|
|
934
|
+
// In this Dex decimals are not used
|
|
935
|
+
return { address, decimals: 0 };
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
async preProcessTransaction(
|
|
939
|
+
optimalSwapExchange: OptimalSwapExchange<CurveV1FactoryData>,
|
|
940
|
+
srcToken: Token,
|
|
941
|
+
_0: Token,
|
|
942
|
+
_1: SwapSide,
|
|
943
|
+
options: PreprocessTransactionOptions,
|
|
944
|
+
): Promise<[OptimalSwapExchange<CurveV1FactoryData>, ExchangeTxInfo]> {
|
|
945
|
+
if (!options.isDirectMethod) {
|
|
946
|
+
return [
|
|
947
|
+
optimalSwapExchange,
|
|
948
|
+
{
|
|
949
|
+
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
950
|
+
},
|
|
951
|
+
];
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
assert(
|
|
955
|
+
optimalSwapExchange.data !== undefined,
|
|
956
|
+
`preProcessTransaction: data field is missing`,
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
let isApproved: boolean | undefined;
|
|
960
|
+
|
|
961
|
+
try {
|
|
962
|
+
isApproved = await this.dexHelper.augustusApprovals.hasApproval(
|
|
963
|
+
options.executionContractAddress,
|
|
964
|
+
this.dexHelper.config.wrapETH(srcToken).address,
|
|
965
|
+
optimalSwapExchange.data.exchange,
|
|
966
|
+
);
|
|
967
|
+
} catch (e) {
|
|
968
|
+
this.logger.error(
|
|
969
|
+
`preProcessTransaction failed to retrieve allowance info: `,
|
|
970
|
+
e,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return [
|
|
975
|
+
{
|
|
976
|
+
...optimalSwapExchange,
|
|
977
|
+
data: {
|
|
978
|
+
...optimalSwapExchange.data,
|
|
979
|
+
isApproved,
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
984
|
+
},
|
|
985
|
+
];
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
getDirectParam(
|
|
989
|
+
srcToken: Address,
|
|
990
|
+
destToken: Address,
|
|
991
|
+
srcAmount: NumberAsString,
|
|
992
|
+
destAmount: NumberAsString,
|
|
993
|
+
expectedAmount: NumberAsString,
|
|
994
|
+
data: CurveV1FactoryData,
|
|
995
|
+
side: SwapSide,
|
|
996
|
+
permit: string,
|
|
997
|
+
uuid: string,
|
|
998
|
+
feePercent: NumberAsString,
|
|
999
|
+
deadline: NumberAsString,
|
|
1000
|
+
partner: string,
|
|
1001
|
+
beneficiary: string,
|
|
1002
|
+
contractMethod?: string,
|
|
1003
|
+
): TxInfo<DirectCurveV1Param> {
|
|
1004
|
+
if (contractMethod !== DIRECT_METHOD_NAME) {
|
|
1005
|
+
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1006
|
+
}
|
|
1007
|
+
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
1008
|
+
|
|
1009
|
+
let isApproved: boolean = !!data.isApproved;
|
|
1010
|
+
if (data.isApproved === undefined) {
|
|
1011
|
+
this.logger.warn(`isApproved is undefined, defaulting to false`);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const swapParams: DirectCurveV1Param = [
|
|
1015
|
+
srcToken,
|
|
1016
|
+
destToken,
|
|
1017
|
+
data.exchange,
|
|
1018
|
+
srcAmount,
|
|
1019
|
+
destAmount,
|
|
1020
|
+
expectedAmount,
|
|
1021
|
+
feePercent,
|
|
1022
|
+
data.i.toString(),
|
|
1023
|
+
data.j.toString(),
|
|
1024
|
+
partner,
|
|
1025
|
+
isApproved,
|
|
1026
|
+
data.underlyingSwap
|
|
1027
|
+
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
1028
|
+
: CurveV1SwapType.EXCHANGE,
|
|
1029
|
+
beneficiary,
|
|
1030
|
+
// For CurveV1 we work as it is, without wrapping and unwrapping
|
|
1031
|
+
false,
|
|
1032
|
+
permit,
|
|
1033
|
+
uuidToBytes16(uuid),
|
|
1034
|
+
];
|
|
1035
|
+
|
|
1036
|
+
const encoder = (...params: DirectCurveV1Param) => {
|
|
1037
|
+
return this.directSwapIface.encodeFunctionData(DIRECT_METHOD_NAME, [
|
|
1038
|
+
params,
|
|
1039
|
+
]);
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
return {
|
|
1043
|
+
params: swapParams,
|
|
1044
|
+
encoder,
|
|
1045
|
+
networkFee: '0',
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
getDirectParamV6(
|
|
1050
|
+
srcToken: Address,
|
|
1051
|
+
destToken: Address,
|
|
1052
|
+
fromAmount: NumberAsString,
|
|
1053
|
+
toAmount: NumberAsString,
|
|
1054
|
+
quotedAmount: NumberAsString,
|
|
1055
|
+
data: CurveV1FactoryData,
|
|
1056
|
+
side: SwapSide,
|
|
1057
|
+
permit: string,
|
|
1058
|
+
uuid: string,
|
|
1059
|
+
partnerAndFee: string,
|
|
1060
|
+
beneficiary: string,
|
|
1061
|
+
blockNumber: number,
|
|
1062
|
+
contractMethod?: string,
|
|
1063
|
+
) {
|
|
1064
|
+
if (contractMethod !== DIRECT_METHOD_NAME_V6) {
|
|
1065
|
+
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1066
|
+
}
|
|
1067
|
+
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
1068
|
+
|
|
1069
|
+
const metadata = hexConcat([
|
|
1070
|
+
hexZeroPad(uuidToBytes16(uuid), 16),
|
|
1071
|
+
hexZeroPad(hexlify(blockNumber), 16),
|
|
1072
|
+
]);
|
|
1073
|
+
|
|
1074
|
+
const swapParams: DirectCurveV1FactoryParamV6 = [
|
|
1075
|
+
packCurveData(
|
|
1076
|
+
data.exchange,
|
|
1077
|
+
!data.isApproved, // approve flag, if not approved then set to true
|
|
1078
|
+
isETHAddress(destToken) ? 0 : isETHAddress(srcToken) ? 3 : 0,
|
|
1079
|
+
data.underlyingSwap
|
|
1080
|
+
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
1081
|
+
: CurveV1SwapType.EXCHANGE,
|
|
1082
|
+
).toString(),
|
|
1083
|
+
encodeCurveAssets(data.i, data.j).toString(),
|
|
1084
|
+
srcToken,
|
|
1085
|
+
destToken,
|
|
1086
|
+
fromAmount,
|
|
1087
|
+
toAmount,
|
|
1088
|
+
quotedAmount,
|
|
1089
|
+
metadata,
|
|
1090
|
+
beneficiary,
|
|
1091
|
+
];
|
|
1092
|
+
|
|
1093
|
+
const encodeParams: CurveV1FactoryDirectSwap = [
|
|
1094
|
+
swapParams,
|
|
1095
|
+
partnerAndFee,
|
|
1096
|
+
permit,
|
|
1097
|
+
];
|
|
1098
|
+
|
|
1099
|
+
const encoder = (...params: CurveV1FactoryDirectSwap) => {
|
|
1100
|
+
return this.augustusV6Interface.encodeFunctionData(
|
|
1101
|
+
DIRECT_METHOD_NAME_V6,
|
|
1102
|
+
[...params],
|
|
1103
|
+
);
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
return {
|
|
1107
|
+
encoder,
|
|
1108
|
+
params: encodeParams,
|
|
1109
|
+
networkFee: '0',
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
static getDirectFunctionName(): string[] {
|
|
1114
|
+
return [DIRECT_METHOD_NAME];
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async getSimpleParam(
|
|
1118
|
+
srcToken: string,
|
|
1119
|
+
destToken: string,
|
|
1120
|
+
srcAmount: string,
|
|
1121
|
+
destAmount: string,
|
|
1122
|
+
data: CurveV1FactoryData,
|
|
1123
|
+
side: SwapSide,
|
|
1124
|
+
): Promise<SimpleExchangeParam> {
|
|
1125
|
+
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1126
|
+
|
|
1127
|
+
const { exchange, i, j, underlyingSwap } = data;
|
|
1128
|
+
const defaultArgs = [i, j, srcAmount, MIN_AMOUNT_TO_RECEIVE];
|
|
1129
|
+
const swapMethod = underlyingSwap
|
|
1130
|
+
? CurveSwapFunctions.exchange_underlying
|
|
1131
|
+
: CurveSwapFunctions.exchange;
|
|
1132
|
+
|
|
1133
|
+
const swapData = this.ifaces.exchangeRouter.encodeFunctionData(
|
|
1134
|
+
swapMethod,
|
|
1135
|
+
defaultArgs,
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
return this.buildSimpleParamWithoutWETHConversion(
|
|
1139
|
+
srcToken,
|
|
1140
|
+
srcAmount,
|
|
1141
|
+
destToken,
|
|
1142
|
+
destAmount,
|
|
1143
|
+
swapData,
|
|
1144
|
+
exchange,
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
static getDirectFunctionNameV6(): string[] {
|
|
1149
|
+
return [DIRECT_METHOD_NAME_V6];
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
getDexParam(
|
|
1153
|
+
srcToken: Address,
|
|
1154
|
+
destToken: Address,
|
|
1155
|
+
srcAmount: NumberAsString,
|
|
1156
|
+
destAmount: NumberAsString,
|
|
1157
|
+
recipient: Address,
|
|
1158
|
+
data: CurveV1FactoryData,
|
|
1159
|
+
side: SwapSide,
|
|
1160
|
+
): DexExchangeParam {
|
|
1161
|
+
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1162
|
+
|
|
1163
|
+
const { exchange, i, j, underlyingSwap } = data;
|
|
1164
|
+
const defaultArgs = [i, j, srcAmount, MIN_AMOUNT_TO_RECEIVE];
|
|
1165
|
+
const swapMethod = underlyingSwap
|
|
1166
|
+
? CurveSwapFunctions.exchange_underlying
|
|
1167
|
+
: CurveSwapFunctions.exchange;
|
|
1168
|
+
const exchangeData = this.ifaces.exchangeRouter.encodeFunctionData(
|
|
1169
|
+
swapMethod,
|
|
1170
|
+
defaultArgs,
|
|
1171
|
+
);
|
|
1172
|
+
|
|
1173
|
+
return {
|
|
1174
|
+
exchangeData,
|
|
1175
|
+
needWrapNative: this.needWrapNative,
|
|
1176
|
+
sendEthButSupportsInsertFromAmount: true,
|
|
1177
|
+
dexFuncHasRecipient: false,
|
|
1178
|
+
targetExchange: exchange,
|
|
1179
|
+
returnAmountPos: undefined,
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
async updatePoolState(): Promise<void> {
|
|
1184
|
+
if (!this.areFactoryPoolsFetched) {
|
|
1185
|
+
await this.fetchFactoryPools(undefined, false);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
await this.poolManager.fetchLiquiditiesFromApi();
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
async getTopPoolsForToken(
|
|
1192
|
+
tokenAddress: Address,
|
|
1193
|
+
limit: number,
|
|
1194
|
+
): Promise<PoolLiquidity[]> {
|
|
1195
|
+
if (!this.areFactoryPoolsFetched) {
|
|
1196
|
+
this.logger.error(
|
|
1197
|
+
`${this.dexKey}: received getTopPools while factory pools are not fetched`,
|
|
1198
|
+
);
|
|
1199
|
+
return [];
|
|
1200
|
+
}
|
|
1201
|
+
const _tokenAddress = tokenAddress.toLowerCase();
|
|
1202
|
+
const poolsWithToken = this.poolManager.getPoolsWithToken(_tokenAddress);
|
|
1203
|
+
|
|
1204
|
+
if (poolsWithToken.length === 0) {
|
|
1205
|
+
return [];
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
return poolsWithToken
|
|
1209
|
+
.reduce<PoolLiquidity[]>((acc, pool) => {
|
|
1210
|
+
let inCoins = false;
|
|
1211
|
+
let inUnderlying = false;
|
|
1212
|
+
const inCoinConnectors: Token[] = [];
|
|
1213
|
+
for (const [i, coin] of pool.poolConstants.COINS.entries()) {
|
|
1214
|
+
if (coin === _tokenAddress) {
|
|
1215
|
+
inCoins = true;
|
|
1216
|
+
} else {
|
|
1217
|
+
inCoinConnectors.push({
|
|
1218
|
+
address: coin,
|
|
1219
|
+
decimals: pool.poolConstants.coins_decimals[i],
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const underlyingConnectors: Token[] = [];
|
|
1225
|
+
for (const [underlying, i] of Object.entries(
|
|
1226
|
+
pool.underlyingCoinsToIndices,
|
|
1227
|
+
)) {
|
|
1228
|
+
if (underlying === _tokenAddress) {
|
|
1229
|
+
inUnderlying = true;
|
|
1230
|
+
} else {
|
|
1231
|
+
underlyingConnectors.push({
|
|
1232
|
+
address: underlying,
|
|
1233
|
+
decimals: pool.underlyingDecimals[i],
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
let connectorTokens: Token[] = [];
|
|
1238
|
+
if (inCoins) {
|
|
1239
|
+
connectorTokens = connectorTokens.concat(inCoinConnectors);
|
|
1240
|
+
}
|
|
1241
|
+
if (inUnderlying) {
|
|
1242
|
+
connectorTokens = connectorTokens.concat(underlyingConnectors);
|
|
1243
|
+
}
|
|
1244
|
+
if (connectorTokens.length) {
|
|
1245
|
+
acc.push({
|
|
1246
|
+
exchange: this.dexKey,
|
|
1247
|
+
address: pool.address,
|
|
1248
|
+
liquidityUSD: pool.liquidityUSD,
|
|
1249
|
+
connectorTokens: _.uniqBy(connectorTokens, 'address'),
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
return acc;
|
|
1253
|
+
}, [])
|
|
1254
|
+
.sort((a, b) => b.liquidityUSD - a.liquidityUSD)
|
|
1255
|
+
.slice(0, limit);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
releaseResources(): AsyncOrSync<void> {
|
|
1259
|
+
this.poolManager.releaseResources();
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
private _calcRateMultipliers(coins_decimals: number[]): bigint[] {
|
|
1263
|
+
return coins_decimals.map(coinDecimal => getBigIntPow(36 - coinDecimal));
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
private _reportForUnspecifiedImplementation(
|
|
1267
|
+
implementation: Address,
|
|
1268
|
+
pool?: Address,
|
|
1269
|
+
) {
|
|
1270
|
+
this.logger.warn(
|
|
1271
|
+
`${this.dexKey}: on network ${this.dexHelper.config.data.network} ` +
|
|
1272
|
+
`found unspecified implementation: ${implementation} for ${pool} pool. Skipping pool`,
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
private _reportForUnspecifiedCustomPool(pool: Address) {
|
|
1277
|
+
this.logger.error(
|
|
1278
|
+
`${this.dexKey}: on network ${this.dexHelper.config.data.network} ` +
|
|
1279
|
+
`found unspecified custom pool: ${pool}`,
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
private _getCoinsABI(type: string): AbiItem {
|
|
1284
|
+
const newCoinsType = _.cloneDeep(this.coinsTypeTemplate);
|
|
1285
|
+
newCoinsType.inputs![0].type = type;
|
|
1286
|
+
return newCoinsType;
|
|
1287
|
+
}
|
|
1288
|
+
}
|