@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,1701 +1,1669 @@
|
|
|
1
|
-
import { Interface } from '@ethersproject/abi';
|
|
2
|
-
import { assert, DeepReadonly } from 'ts-essentials';
|
|
3
|
-
import _, { keyBy } from 'lodash';
|
|
4
|
-
import {
|
|
5
|
-
AdapterExchangeParam,
|
|
6
|
-
Address,
|
|
7
|
-
DexExchangeParam,
|
|
8
|
-
ExchangePrices,
|
|
9
|
-
ExchangeTxInfo,
|
|
10
|
-
Log,
|
|
11
|
-
Logger,
|
|
12
|
-
PoolLiquidity,
|
|
13
|
-
PoolPrices,
|
|
14
|
-
PreprocessTransactionOptions,
|
|
15
|
-
SimpleExchangeParam,
|
|
16
|
-
Token,
|
|
17
|
-
TxInfo,
|
|
18
|
-
} from '../../types';
|
|
19
|
-
import {
|
|
20
|
-
ETHER_ADDRESS,
|
|
21
|
-
MAX_INT,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import
|
|
35
|
-
import
|
|
36
|
-
import
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
import {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
import {
|
|
82
|
-
import
|
|
83
|
-
import
|
|
84
|
-
import {
|
|
85
|
-
import {
|
|
86
|
-
import {
|
|
87
|
-
|
|
88
|
-
// If you disable some pool, don't forget to clear the cache, otherwise changes won't be applied immediately
|
|
89
|
-
const enabledPoolTypes = [
|
|
90
|
-
// BalancerPoolTypes.MetaStable, // BOOSTED POOLS Disabled since vulnerability https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md
|
|
91
|
-
BalancerPoolTypes.Stable,
|
|
92
|
-
BalancerPoolTypes.Weighted,
|
|
93
|
-
BalancerPoolTypes.LiquidityBootstrapping,
|
|
94
|
-
BalancerPoolTypes.Investment,
|
|
95
|
-
BalancerPoolTypes.StablePhantom,
|
|
96
|
-
BalancerPoolTypes.AaveLinear,
|
|
97
|
-
BalancerPoolTypes.ERC4626Linear,
|
|
98
|
-
BalancerPoolTypes.Linear,
|
|
99
|
-
BalancerPoolTypes.ComposableStable,
|
|
100
|
-
BalancerPoolTypes.BeefyLinear,
|
|
101
|
-
BalancerPoolTypes.GearboxLinear,
|
|
102
|
-
BalancerPoolTypes.MidasLinear,
|
|
103
|
-
BalancerPoolTypes.ReaperLinear,
|
|
104
|
-
BalancerPoolTypes.SiloLinear,
|
|
105
|
-
BalancerPoolTypes.TetuLinear,
|
|
106
|
-
BalancerPoolTypes.YearnLinear,
|
|
107
|
-
BalancerPoolTypes.GyroE,
|
|
108
|
-
BalancerPoolTypes.Gyro3,
|
|
109
|
-
];
|
|
110
|
-
|
|
111
|
-
const disabledPoolIds = [
|
|
112
|
-
// broken ?
|
|
113
|
-
'0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6',
|
|
114
|
-
|
|
115
|
-
/* DISABLED POOLS SINCE VULNERABILITY https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md*/
|
|
116
|
-
/* START:2023-08-mitigation */
|
|
117
|
-
//mainnet
|
|
118
|
-
'0xbf2ef8bdc2fc0f3203b3a01778e3ec5009aeef3300000000000000000000058d',
|
|
119
|
-
'0x99c88ad7dc566616548adde8ed3effa730eb6c3400000000000000000000049a',
|
|
120
|
-
'0x60683b05e9a39e3509d8fdb9c959f23170f8a0fa000000000000000000000489',
|
|
121
|
-
'0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d',
|
|
122
|
-
'0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe',
|
|
123
|
-
'0x25accb7943fd73dda5e23ba6329085a3c24bfb6a000200000000000000000387',
|
|
124
|
-
'0x50cf90b954958480b8df7958a9e965752f62712400000000000000000000046f',
|
|
125
|
-
'0x133d241f225750d2c92948e464a5a80111920331000000000000000000000476',
|
|
126
|
-
'0x8a6b25e33b12d1bb6929a8793961076bd1f9d3eb0002000000000000000003e8',
|
|
127
|
-
'0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472',
|
|
128
|
-
'0x9b692f571b256140a39a34676bffa30634c586e100000000000000000000059d',
|
|
129
|
-
'0xe7b1d394f3b40abeaa0b64a545dbcf89da1ecb3f00010000000000000000009a',
|
|
130
|
-
|
|
131
|
-
// polygon
|
|
132
|
-
'0xb3d658d5b95bf04e2932370dd1ff976fe18dd66a000000000000000000000ace',
|
|
133
|
-
'0x48e6b98ef6329f8f0a30ebb8c7c960330d64808500000000000000000000075b',
|
|
134
|
-
'0xb54b2125b711cd183edd3dd09433439d5396165200000000000000000000075e',
|
|
135
|
-
|
|
136
|
-
// arbitrum
|
|
137
|
-
'0xa8af146d79ac0bb981e4e0d8b788ec5711b1d5d000000000000000000000047b',
|
|
138
|
-
'0x077794c30afeccdf5ad2abc0588e8cee7197b71a000000000000000000000352',
|
|
139
|
-
'0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382',
|
|
140
|
-
|
|
141
|
-
// optimism
|
|
142
|
-
'0x23ca0306b21ea71552b148cf3c4db4fc85ae19290000000000000000000000ac',
|
|
143
|
-
'0x62cf35db540152e94936de63efc90d880d4e241b0000000000000000000000ef',
|
|
144
|
-
'0x098f32d98d0d64dba199fc1923d3bf4192e787190001000000000000000000d2',
|
|
145
|
-
'0x43da214fab3315aa6c02e0b8f2bfb7ef2e3c60a50000000000000000000000ae',
|
|
146
|
-
'0xb1c9ac57594e9b1ec0f3787d9f6744ef4cb0a02400000000000000000000006e',
|
|
147
|
-
'0xde45f101250f2ca1c0f8adfc172576d10c12072d00000000000000000000003f',
|
|
148
|
-
'0x05e7732bf9ae5592e6aa05afe8cd80f7ab0a7bea00020000000000000000005a',
|
|
149
|
-
'0x981fb05b738e981ac532a99e77170ecb4bc27aef00010000000000000000004b',
|
|
150
|
-
'0x6222ae1d2a9f6894da50aa25cb7b303497f9bebd000000000000000000000046',
|
|
151
|
-
'0x3c74c4ed512050eb843d89fb9dcd5ebb4668eb6d0002000000000000000000cc',
|
|
152
|
-
|
|
153
|
-
// fantom
|
|
154
|
-
'0xc0064b291bd3d4ba0e44ccfc81bf8e7f7a579cd200000000000000000000042c',
|
|
155
|
-
'0x6e6dc948ce85c62125ff7a1e543d761a88f0a4cb000000000000000000000743',
|
|
156
|
-
'0x78ab08bf98f90f29a09c9b1d85b3b549369b03a3000100000000000000000354',
|
|
157
|
-
'0x302b8b64795b064cadc32f74993a6372498608070001000000000000000003e0',
|
|
158
|
-
'0x5ddb92a5340fd0ead3987d3661afcd6104c3b757000000000000000000000187',
|
|
159
|
-
'0xdfc65c1f15ad3507754ef0fd4ba67060c108db7e000000000000000000000406',
|
|
160
|
-
'0x6da14f5acd58dd5c8e486cfa1dc1c550f5c61c1c0000000000000000000003cf',
|
|
161
|
-
'0x592fa9f9d58065096f2b7838709c116957d7b5cf00020000000000000000043c',
|
|
162
|
-
'0xf47f4d59c863c02cbfa3eefe6771b9c9fbe7b97800000000000000000000072b',
|
|
163
|
-
'0xff2753aaba51c9f84689b9bd0a21b3cf380a1cff00000000000000000000072e',
|
|
164
|
-
'0x10441785a928040b456a179691141c48356eb3a50001000000000000000002fa',
|
|
165
|
-
'0x64b301e21d640f9bef90458b0987d81fb4cf1b9e00020000000000000000022e',
|
|
166
|
-
'0xba0e9aea8a7fa1daab4edf244191f2387a4e472b000100000000000000000737',
|
|
167
|
-
'0x1e2576344d49779bdbb71b1b76193d27e6f996b700020000000000000000032d',
|
|
168
|
-
'0xa10285f445bcb521f1d623300dc4998b02f11c8f00000000000000000000043b',
|
|
169
|
-
|
|
170
|
-
// zkevm
|
|
171
|
-
'0x6f34a44fce1506352a171232163e7716dd073ade000200000000000000000015',
|
|
172
|
-
'0xe274c9deb6ed34cfe4130f8d0a8a948dea5bb28600000000000000000000000d',
|
|
173
|
-
/* END:2023-08-mitigation */
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
const fetchAllPools = `query ($count: Int) {
|
|
177
|
-
pools: pools(
|
|
178
|
-
first: $count
|
|
179
|
-
orderBy: totalLiquidity
|
|
180
|
-
orderDirection: desc
|
|
181
|
-
where: {
|
|
182
|
-
totalLiquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()},
|
|
183
|
-
totalShares_not_in: ["0", "0.000000000001"],
|
|
184
|
-
id_not_in: [
|
|
185
|
-
${disabledPoolIds.map(p => `"${p}"`).join(', ')}
|
|
186
|
-
],
|
|
187
|
-
address_not_in: [
|
|
188
|
-
"0x0afbd58beca09545e4fb67772faf3858e610bcd0",
|
|
189
|
-
"0x2ff1a9dbdacd55297452cfd8a4d94724bc22a5f7",
|
|
190
|
-
"0xbc0f2372008005471874e426e86ccfae7b4de79d",
|
|
191
|
-
"0xdba274b4d04097b90a72b62467d828cefd708037",
|
|
192
|
-
"0xf22ff21e17157340575158ad7394e068048dd98b",
|
|
193
|
-
"0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa",
|
|
194
|
-
],
|
|
195
|
-
swapEnabled: true,
|
|
196
|
-
poolType_in: [
|
|
197
|
-
${enabledPoolTypes.map(p => `"${p}"`).join(', ')}
|
|
198
|
-
]
|
|
199
|
-
}
|
|
200
|
-
) {
|
|
201
|
-
id
|
|
202
|
-
address
|
|
203
|
-
poolType
|
|
204
|
-
poolTypeVersion
|
|
205
|
-
tokens (orderBy: index) {
|
|
206
|
-
address
|
|
207
|
-
decimals
|
|
208
|
-
}
|
|
209
|
-
mainIndex
|
|
210
|
-
wrappedIndex
|
|
211
|
-
|
|
212
|
-
root3Alpha
|
|
213
|
-
}
|
|
214
|
-
}`;
|
|
215
|
-
// skipping low liquidity composableStablePool (0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6) with oracle issues. Experimental.
|
|
216
|
-
|
|
217
|
-
const fetchWeightUpdating = `query ($count: Int, $timestampPast: Int, $timestampFuture: Int) {
|
|
218
|
-
gradualWeightUpdates(
|
|
219
|
-
first: $count,
|
|
220
|
-
where: {startTimestamp_lt: $timestampFuture, endTimestamp_gt: $timestampPast }
|
|
221
|
-
) {
|
|
222
|
-
poolId {
|
|
223
|
-
address
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}`;
|
|
227
|
-
|
|
228
|
-
const MAX_POOL_CNT = 1000; // Taken from SOR
|
|
229
|
-
const POOL_CACHE_TTL = 60 * 60; // 1 hr
|
|
230
|
-
const POOL_EVENT_DISABLED_TTL = 5 * 60; // 5 min
|
|
231
|
-
const POOL_EVENT_REENABLE_DELAY = 7 * 24 * 60 * 60; // 1 week
|
|
232
|
-
|
|
233
|
-
function typecastReadOnlyPoolState(pool: DeepReadonly<PoolState>): PoolState {
|
|
234
|
-
return _.cloneDeep(pool) as PoolState;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export class BalancerV2EventPool extends StatefulEventSubscriber<PoolStateMap> {
|
|
238
|
-
public vaultInterface: Interface;
|
|
239
|
-
|
|
240
|
-
handlers: {
|
|
241
|
-
[event: string]: (event: any, pool: PoolState, log: Log) => PoolState;
|
|
242
|
-
} = {};
|
|
243
|
-
|
|
244
|
-
pools: {
|
|
245
|
-
[type: string]:
|
|
246
|
-
| WeightedPool
|
|
247
|
-
| StablePool
|
|
248
|
-
| LinearPool
|
|
249
|
-
| PhantomStablePool
|
|
250
|
-
| Gyro3Pool
|
|
251
|
-
| GyroEPool;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
public allPools: SubgraphPoolBase[] = [];
|
|
255
|
-
vaultDecoder: (log: Log) => any;
|
|
256
|
-
|
|
257
|
-
buySupportedPoolTypes: Set<BalancerPoolTypes> = new Set([
|
|
258
|
-
BalancerPoolTypes.Weighted,
|
|
259
|
-
BalancerPoolTypes.GyroE,
|
|
260
|
-
]);
|
|
261
|
-
|
|
262
|
-
eventSupportedPoolTypes: BalancerPoolTypes[] = [
|
|
263
|
-
BalancerPoolTypes.Stable,
|
|
264
|
-
BalancerPoolTypes.Weighted,
|
|
265
|
-
BalancerPoolTypes.LiquidityBootstrapping,
|
|
266
|
-
BalancerPoolTypes.Investment,
|
|
267
|
-
|
|
268
|
-
// Need to check if we can support these pools with event base
|
|
269
|
-
// BalancerPoolTypes.ComposableStable,
|
|
270
|
-
// BalancerPoolTypes.Linear,
|
|
271
|
-
// BalancerPoolTypes.MetaStable,
|
|
272
|
-
// BalancerPoolTypes.AaveLinear,
|
|
273
|
-
// BalancerPoolTypes.ERC4626Linear,
|
|
274
|
-
|
|
275
|
-
// If this pool is enabled as event supported, it is failing BeetsFi: can not decode getRate()
|
|
276
|
-
// BalancerPoolTypes.StablePhantom,
|
|
277
|
-
];
|
|
278
|
-
|
|
279
|
-
eventRemovedPools = (
|
|
280
|
-
[
|
|
281
|
-
// Gradual weight changes are not currently handled in event system
|
|
282
|
-
// This pool keeps changing weights and is causing pricing issue
|
|
283
|
-
// But should now be handled by eventDisabledPools so don't need here!
|
|
284
|
-
//'0x34809aEDF93066b49F638562c42A9751eDb36DF5',
|
|
285
|
-
] as Address[]
|
|
286
|
-
).map(s => s.toLowerCase());
|
|
287
|
-
|
|
288
|
-
constructor(
|
|
289
|
-
parentName: string,
|
|
290
|
-
protected network: number,
|
|
291
|
-
public vaultAddress: Address,
|
|
292
|
-
protected subgraphURL: string,
|
|
293
|
-
protected dexHelper: IDexHelper,
|
|
294
|
-
logger: Logger,
|
|
295
|
-
) {
|
|
296
|
-
super(parentName, vaultAddress, dexHelper, logger);
|
|
297
|
-
this.vaultInterface = new Interface(VaultABI);
|
|
298
|
-
const weightedPool = new WeightedPool(
|
|
299
|
-
this.vaultAddress,
|
|
300
|
-
this.vaultInterface,
|
|
301
|
-
);
|
|
302
|
-
const stablePool = new StablePool(this.vaultAddress, this.vaultInterface);
|
|
303
|
-
const stablePhantomPool = new PhantomStablePool(
|
|
304
|
-
this.vaultAddress,
|
|
305
|
-
this.vaultInterface,
|
|
306
|
-
);
|
|
307
|
-
/*
|
|
308
|
-
ComposableStable has same maths as StablePhantom.
|
|
309
|
-
The main difference is that ComposableStables have join/exit functions when StablePhantom did not.
|
|
310
|
-
The difference of note for swaps is ComposableStable must use 'actualSupply' instead of VirtualSupply.
|
|
311
|
-
VirtualSupply could be calculated easily whereas actualSupply cannot hence the use of onchain call.
|
|
312
|
-
*/
|
|
313
|
-
const composableStable = new PhantomStablePool(
|
|
314
|
-
this.vaultAddress,
|
|
315
|
-
this.vaultInterface,
|
|
316
|
-
true,
|
|
317
|
-
);
|
|
318
|
-
const linearPool = new LinearPool(this.vaultAddress, this.vaultInterface);
|
|
319
|
-
const gyro3Pool = new Gyro3Pool(this.vaultAddress, this.vaultInterface);
|
|
320
|
-
const gyroEPool = new GyroEPool(this.vaultAddress, this.vaultInterface);
|
|
321
|
-
|
|
322
|
-
this.pools = {};
|
|
323
|
-
this.pools[BalancerPoolTypes.Weighted] = weightedPool;
|
|
324
|
-
this.pools[BalancerPoolTypes.Stable] = stablePool;
|
|
325
|
-
this.pools[BalancerPoolTypes.MetaStable] = stablePool;
|
|
326
|
-
this.pools[BalancerPoolTypes.LiquidityBootstrapping] = weightedPool;
|
|
327
|
-
this.pools[BalancerPoolTypes.Investment] = weightedPool;
|
|
328
|
-
this.pools[BalancerPoolTypes.StablePhantom] = stablePhantomPool;
|
|
329
|
-
this.pools[BalancerPoolTypes.ComposableStable] = composableStable;
|
|
330
|
-
// All these Linear pool have same maths and ABI as AaveLinear but have different factories
|
|
331
|
-
this.pools[BalancerPoolTypes.AaveLinear] = linearPool;
|
|
332
|
-
this.pools[BalancerPoolTypes.ERC4626Linear] = linearPool;
|
|
333
|
-
this.pools[BalancerPoolTypes.GearboxLinear] = linearPool;
|
|
334
|
-
this.pools[BalancerPoolTypes.MidasLinear] = linearPool;
|
|
335
|
-
this.pools[BalancerPoolTypes.ReaperLinear] = linearPool;
|
|
336
|
-
this.pools[BalancerPoolTypes.SiloLinear] = linearPool;
|
|
337
|
-
this.pools[BalancerPoolTypes.TetuLinear] = linearPool;
|
|
338
|
-
this.pools[BalancerPoolTypes.YearnLinear] = linearPool;
|
|
339
|
-
this.pools[BalancerPoolTypes.BeefyLinear] = linearPool;
|
|
340
|
-
// Beets uses "Linear" generically for all linear pool types
|
|
341
|
-
this.pools[BalancerPoolTypes.Linear] = linearPool;
|
|
342
|
-
|
|
343
|
-
this.pools[BalancerPoolTypes.Gyro3] = gyro3Pool;
|
|
344
|
-
this.pools[BalancerPoolTypes.GyroE] = gyroEPool;
|
|
345
|
-
|
|
346
|
-
this.vaultDecoder = (log: Log) => this.vaultInterface.parseLog(log);
|
|
347
|
-
this.addressesSubscribed = [vaultAddress];
|
|
348
|
-
|
|
349
|
-
// Add default handlers
|
|
350
|
-
this.handlers['Swap'] = this.handleSwap.bind(this);
|
|
351
|
-
this.handlers['PoolBalanceChanged'] =
|
|
352
|
-
this.handlePoolBalanceChanged.bind(this);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
protected processLog(
|
|
356
|
-
state: DeepReadonly<PoolStateMap>,
|
|
357
|
-
log: Readonly<Log>,
|
|
358
|
-
): DeepReadonly<PoolStateMap> | null {
|
|
359
|
-
const _state: PoolStateMap = {};
|
|
360
|
-
for (const [address, pool] of Object.entries(state))
|
|
361
|
-
_state[address] = typecastReadOnlyPoolState(pool);
|
|
362
|
-
|
|
363
|
-
try {
|
|
364
|
-
const event = this.vaultDecoder(log);
|
|
365
|
-
if (event.name in this.handlers) {
|
|
366
|
-
const poolAddress = event.args.poolId.slice(0, 42).toLowerCase();
|
|
367
|
-
// Only update the _state if we are tracking the pool
|
|
368
|
-
if (poolAddress in _state) {
|
|
369
|
-
_state[poolAddress] = this.handlers[event.name](
|
|
370
|
-
event,
|
|
371
|
-
_state[poolAddress],
|
|
372
|
-
log,
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return _state;
|
|
377
|
-
} catch (e) {
|
|
378
|
-
this.logger.error(
|
|
379
|
-
`Error_${this.parentName}_processLog could not parse the log with topic ${log.topics}:`,
|
|
380
|
-
e,
|
|
381
|
-
);
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async fetchAllSubgraphPools(): Promise<SubgraphPoolBase[]> {
|
|
387
|
-
const cacheKey = 'BalancerV2SubgraphPools2';
|
|
388
|
-
const cachedPools = await this.dexHelper.cache.get(
|
|
389
|
-
this.parentName,
|
|
390
|
-
this.network,
|
|
391
|
-
cacheKey,
|
|
392
|
-
);
|
|
393
|
-
if (cachedPools) {
|
|
394
|
-
const allPools = JSON.parse(cachedPools);
|
|
395
|
-
this.logger.info(
|
|
396
|
-
`Got ${allPools.length} ${this.parentName}_${this.network} pools from cache`,
|
|
397
|
-
);
|
|
398
|
-
return allPools;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
this.logger.info(
|
|
402
|
-
`Fetching ${this.parentName}_${this.network} Pools from subgraph`,
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
const variables = {
|
|
406
|
-
count: MAX_POOL_CNT,
|
|
407
|
-
};
|
|
408
|
-
const { data } = await this.dexHelper.httpRequest.post(
|
|
409
|
-
this.subgraphURL,
|
|
410
|
-
{ query: fetchAllPools, variables },
|
|
411
|
-
SUBGRAPH_TIMEOUT,
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
if (!(data && data.pools))
|
|
415
|
-
throw new Error('Unable to fetch pools from the subgraph');
|
|
416
|
-
|
|
417
|
-
const poolsMap = keyBy(data.pools, 'address');
|
|
418
|
-
const allPools: SubgraphPoolBase[] = data.pools.map(
|
|
419
|
-
(pool: Omit<SubgraphPoolBase, 'mainTokens'>) => ({
|
|
420
|
-
...pool,
|
|
421
|
-
mainTokens: poolGetMainTokens(pool, poolsMap),
|
|
422
|
-
tokensMap: pool.tokens.reduce(
|
|
423
|
-
(acc, token) => ({ ...acc, [token.address.toLowerCase()]: token }),
|
|
424
|
-
{},
|
|
425
|
-
),
|
|
426
|
-
}),
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
this.dexHelper.cache.setex(
|
|
430
|
-
this.parentName,
|
|
431
|
-
this.network,
|
|
432
|
-
cacheKey,
|
|
433
|
-
POOL_CACHE_TTL,
|
|
434
|
-
JSON.stringify(allPools),
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
this.logger.info(
|
|
438
|
-
`Got ${allPools.length} ${this.parentName}_${this.network} pools from subgraph`,
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
return allPools;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async generateState(blockNumber: number): Promise<Readonly<PoolStateMap>> {
|
|
445
|
-
const allPools = await this.fetchAllSubgraphPools();
|
|
446
|
-
this.allPools = allPools;
|
|
447
|
-
|
|
448
|
-
const eventSupportedPools = allPools.filter(
|
|
449
|
-
pool =>
|
|
450
|
-
this.eventSupportedPoolTypes.includes(pool.poolType) &&
|
|
451
|
-
!this.eventRemovedPools.includes(pool.address.toLowerCase()),
|
|
452
|
-
);
|
|
453
|
-
const allPoolsLatestState = await this.getOnChainState(
|
|
454
|
-
eventSupportedPools,
|
|
455
|
-
blockNumber,
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
return allPoolsLatestState;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
handleSwap(event: any, pool: PoolState, log: Log): PoolState {
|
|
462
|
-
const tokenIn = event.args.tokenIn.toLowerCase();
|
|
463
|
-
const amountIn = BigInt(event.args.amountIn.toString());
|
|
464
|
-
const tokenOut = event.args.tokenOut.toLowerCase();
|
|
465
|
-
const amountOut = BigInt(event.args.amountOut.toString());
|
|
466
|
-
pool.tokens[tokenIn].balance += amountIn;
|
|
467
|
-
pool.tokens[tokenOut].balance -= amountOut;
|
|
468
|
-
return pool;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
handlePoolBalanceChanged(event: any, pool: PoolState, log: Log): PoolState {
|
|
472
|
-
const tokens = event.args.tokens.map((t: string) => t.toLowerCase());
|
|
473
|
-
const deltas = event.args.deltas.map((d: any) => BigInt(d.toString()));
|
|
474
|
-
const fees = event.args.protocolFeeAmounts.map((d: any) =>
|
|
475
|
-
BigInt(d.toString()),
|
|
476
|
-
) as bigint[];
|
|
477
|
-
tokens.forEach((t: string, i: number) => {
|
|
478
|
-
const diff = deltas[i] - fees[i];
|
|
479
|
-
pool.tokens[t].balance += diff;
|
|
480
|
-
});
|
|
481
|
-
return pool;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
isSupportedPool(poolType: string): boolean {
|
|
485
|
-
const supportedPoolTypes: string[] = Object.values(BalancerPoolTypes);
|
|
486
|
-
return supportedPoolTypes.includes(poolType);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
getPricesPool(
|
|
490
|
-
from: Token,
|
|
491
|
-
to: Token,
|
|
492
|
-
subgraphPool: SubgraphPoolBase,
|
|
493
|
-
poolState: PoolState,
|
|
494
|
-
amounts: bigint[],
|
|
495
|
-
unitVolume: bigint,
|
|
496
|
-
side: SwapSide,
|
|
497
|
-
): { unit: bigint; prices: bigint[] } | null {
|
|
498
|
-
if (!this.isSupportedPool(subgraphPool.poolType)) {
|
|
499
|
-
this.logger.error(`Unsupported Pool Type: ${subgraphPool.poolType}`);
|
|
500
|
-
return null;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (
|
|
504
|
-
side === SwapSide.BUY &&
|
|
505
|
-
!this.buySupportedPoolTypes.has(subgraphPool.poolType)
|
|
506
|
-
) {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const amountWithoutZero = amounts.slice(1);
|
|
511
|
-
const pool = this.pools[subgraphPool.poolType];
|
|
512
|
-
|
|
513
|
-
const poolPairData = pool.parsePoolPairData(
|
|
514
|
-
subgraphPool,
|
|
515
|
-
poolState,
|
|
516
|
-
from.address,
|
|
517
|
-
to.address,
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
const swapMaxAmount = pool.getSwapMaxAmount(
|
|
521
|
-
// Don't like this but don't have time to refactor it properly
|
|
522
|
-
poolPairData as any,
|
|
523
|
-
side,
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
const checkedAmounts: bigint[] = new Array(amountWithoutZero.length).fill(
|
|
527
|
-
0n,
|
|
528
|
-
);
|
|
529
|
-
const checkedUnitVolume = pool._nullifyIfMaxAmountExceeded(
|
|
530
|
-
unitVolume,
|
|
531
|
-
swapMaxAmount,
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
let nonZeroAmountIndex = 0;
|
|
535
|
-
for (const [i, amountIn] of amountWithoutZero.entries()) {
|
|
536
|
-
const checkedOutput = pool._nullifyIfMaxAmountExceeded(
|
|
537
|
-
amountIn,
|
|
538
|
-
swapMaxAmount,
|
|
539
|
-
);
|
|
540
|
-
if (checkedOutput === 0n) {
|
|
541
|
-
// Stop earlier because other values are bigger and for sure wont' be tradable
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
nonZeroAmountIndex = i + 1;
|
|
545
|
-
checkedAmounts[i] = checkedOutput;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (nonZeroAmountIndex === 0) {
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const unitResult =
|
|
553
|
-
checkedUnitVolume === 0n
|
|
554
|
-
? 0n
|
|
555
|
-
: side === SwapSide.SELL
|
|
556
|
-
? pool.onSell([checkedUnitVolume], poolPairData as any)[0]
|
|
557
|
-
: pool.onBuy([checkedUnitVolume], poolPairData as any)[0];
|
|
558
|
-
|
|
559
|
-
const prices: bigint[] = new Array(amounts.length).fill(0n);
|
|
560
|
-
|
|
561
|
-
const outputs =
|
|
562
|
-
side === SwapSide.SELL
|
|
563
|
-
? pool.onSell(
|
|
564
|
-
amountWithoutZero.slice(0, nonZeroAmountIndex),
|
|
565
|
-
poolPairData as any,
|
|
566
|
-
)
|
|
567
|
-
: pool.onBuy(
|
|
568
|
-
amountWithoutZero.slice(0, nonZeroAmountIndex),
|
|
569
|
-
poolPairData as any,
|
|
570
|
-
);
|
|
571
|
-
|
|
572
|
-
assert(
|
|
573
|
-
outputs.length <= prices.length,
|
|
574
|
-
`Wrong length logic: outputs.length (${outputs.length}) <= prices.length (${prices.length})`,
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
for (const [i, output] of outputs.entries()) {
|
|
578
|
-
// Outputs shifted right to one to keep first entry as 0
|
|
579
|
-
prices[i + 1] = output;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return { unit: unitResult, prices };
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
async getOnChainState(
|
|
586
|
-
subgraphPoolBase: SubgraphPoolBase[],
|
|
587
|
-
blockNumber: number,
|
|
588
|
-
): Promise<PoolStateMap> {
|
|
589
|
-
const multiCallData = subgraphPoolBase
|
|
590
|
-
.map(pool => {
|
|
591
|
-
if (!this.isSupportedPool(pool.poolType)) return [];
|
|
592
|
-
|
|
593
|
-
return this.pools[pool.poolType].getOnChainCalls(pool);
|
|
594
|
-
})
|
|
595
|
-
.flat();
|
|
596
|
-
|
|
597
|
-
// 500 is an arbitrary number chosen based on the blockGasLimit
|
|
598
|
-
const slicedMultiCallData = _.chunk(multiCallData, 500);
|
|
599
|
-
|
|
600
|
-
const returnData = (
|
|
601
|
-
await Promise.all(
|
|
602
|
-
slicedMultiCallData.map(async _multiCallData =>
|
|
603
|
-
this.dexHelper.multiContract.methods
|
|
604
|
-
.tryAggregate(false, _multiCallData)
|
|
605
|
-
.call({}, blockNumber),
|
|
606
|
-
),
|
|
607
|
-
)
|
|
608
|
-
).flat();
|
|
609
|
-
|
|
610
|
-
let i = 0;
|
|
611
|
-
const onChainStateMap = subgraphPoolBase.reduce(
|
|
612
|
-
(acc: { [address: string]: PoolState }, pool) => {
|
|
613
|
-
if (!this.isSupportedPool(pool.poolType)) return acc;
|
|
614
|
-
|
|
615
|
-
const [decoded, newIndex] = this.pools[
|
|
616
|
-
pool.poolType
|
|
617
|
-
].decodeOnChainCalls(pool, returnData, i);
|
|
618
|
-
i = newIndex;
|
|
619
|
-
acc = { ...acc, ...decoded };
|
|
620
|
-
return acc;
|
|
621
|
-
},
|
|
622
|
-
{},
|
|
623
|
-
);
|
|
624
|
-
|
|
625
|
-
return onChainStateMap;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
export class BalancerV2
|
|
630
|
-
extends SimpleExchange
|
|
631
|
-
implements
|
|
632
|
-
IDex<
|
|
633
|
-
BalancerV2Data,
|
|
634
|
-
BalancerV2DirectParam | BalancerV2DirectParamV6Swap,
|
|
635
|
-
OptimizedBalancerV2Data
|
|
636
|
-
>
|
|
637
|
-
{
|
|
638
|
-
public eventPools: BalancerV2EventPool;
|
|
639
|
-
|
|
640
|
-
readonly hasConstantPriceLargeAmounts = false;
|
|
641
|
-
readonly isFeeOnTransferSupported = false;
|
|
642
|
-
readonly needWrapNative = true; // temporary
|
|
643
|
-
|
|
644
|
-
readonly directSwapIface = new Interface(DirectSwapABI);
|
|
645
|
-
readonly balancerVaultInterface = new Interface(BalancerVaultABI);
|
|
646
|
-
|
|
647
|
-
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
648
|
-
getDexKeysWithNetwork(BalancerConfig);
|
|
649
|
-
|
|
650
|
-
logger: Logger;
|
|
651
|
-
|
|
652
|
-
// In memory pool state for non-event pools
|
|
653
|
-
nonEventPoolStateCache: PoolStateCache;
|
|
654
|
-
|
|
655
|
-
eventDisabledPoolsTimer?: NodeJS.Timer;
|
|
656
|
-
eventDisabledPools: Address[] = [];
|
|
657
|
-
|
|
658
|
-
constructor(
|
|
659
|
-
protected network: Network,
|
|
660
|
-
dexKey: string,
|
|
661
|
-
public dexHelper: IDexHelper,
|
|
662
|
-
public vaultAddress: Address = BalancerConfig[dexKey][network].vaultAddress,
|
|
663
|
-
protected subgraphURL: string = BalancerConfig[dexKey][network].subgraphURL,
|
|
664
|
-
protected adapters = Adapters[network],
|
|
665
|
-
) {
|
|
666
|
-
super(dexHelper, dexKey);
|
|
667
|
-
// Initialise cache - this will hold pool state of non-event pools in memory to be reused if block hasn't expired
|
|
668
|
-
this.nonEventPoolStateCache = { blockNumber: 0, poolState: {} };
|
|
669
|
-
this.logger = dexHelper.getLogger(dexKey);
|
|
670
|
-
this.eventPools = new BalancerV2EventPool(
|
|
671
|
-
dexKey,
|
|
672
|
-
network,
|
|
673
|
-
vaultAddress,
|
|
674
|
-
subgraphURL,
|
|
675
|
-
dexHelper,
|
|
676
|
-
this.logger,
|
|
677
|
-
);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
async setupEventPools(blockNumber: number) {
|
|
681
|
-
await this.eventPools.initialize(blockNumber);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
async fetchEventDisabledPools() {
|
|
685
|
-
const cacheKey = 'eventDisabledPools';
|
|
686
|
-
const poolAddressListFromCache = await this.dexHelper.cache.get(
|
|
687
|
-
this.dexKey,
|
|
688
|
-
this.network,
|
|
689
|
-
cacheKey,
|
|
690
|
-
);
|
|
691
|
-
if (poolAddressListFromCache) {
|
|
692
|
-
this.eventDisabledPools = JSON.parse(poolAddressListFromCache);
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
this.logger.info(
|
|
696
|
-
`Fetching ${this.dexKey}_${this.network} Weight Updates from subgraph`,
|
|
697
|
-
);
|
|
698
|
-
const timeNow = Math.floor(Date.now() / 1000);
|
|
699
|
-
const variables = {
|
|
700
|
-
count: MAX_POOL_CNT,
|
|
701
|
-
timestampPast: timeNow - POOL_EVENT_REENABLE_DELAY,
|
|
702
|
-
timestampFuture: timeNow + POOL_EVENT_DISABLED_TTL,
|
|
703
|
-
};
|
|
704
|
-
const { data } = await this.dexHelper.httpRequest.post(
|
|
705
|
-
this.subgraphURL,
|
|
706
|
-
{ query: fetchWeightUpdating, variables },
|
|
707
|
-
SUBGRAPH_TIMEOUT,
|
|
708
|
-
);
|
|
709
|
-
|
|
710
|
-
if (!(data && data.gradualWeightUpdates)) {
|
|
711
|
-
throw new Error(
|
|
712
|
-
`${this.dexKey}_${this.network} failed to fetch weight updates from subgraph`,
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
this.eventDisabledPools = _.uniq(
|
|
717
|
-
data.gradualWeightUpdates.map(
|
|
718
|
-
(wu: { poolId: { address: Address } }) => wu.poolId.address,
|
|
719
|
-
),
|
|
720
|
-
);
|
|
721
|
-
const poolAddressList = JSON.stringify(this.eventDisabledPools);
|
|
722
|
-
this.logger.info(
|
|
723
|
-
`Pools blocked from event based on ${this.dexKey}_${this.network}: ${poolAddressList}`,
|
|
724
|
-
);
|
|
725
|
-
this.dexHelper.cache.setex(
|
|
726
|
-
this.dexKey,
|
|
727
|
-
this.network,
|
|
728
|
-
cacheKey,
|
|
729
|
-
POOL_EVENT_DISABLED_TTL,
|
|
730
|
-
poolAddressList,
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
async initializePricing(blockNumber: number) {
|
|
735
|
-
if (!this.eventDisabledPoolsTimer) {
|
|
736
|
-
await this.fetchEventDisabledPools();
|
|
737
|
-
this.eventDisabledPoolsTimer = setInterval(async () => {
|
|
738
|
-
try {
|
|
739
|
-
await this.fetchEventDisabledPools();
|
|
740
|
-
} catch (e) {
|
|
741
|
-
this.logger.error(
|
|
742
|
-
`${this.dexKey}: Failed to update event disabled pools:`,
|
|
743
|
-
e,
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
}, POOL_EVENT_DISABLED_TTL * 1000);
|
|
747
|
-
}
|
|
748
|
-
await this.setupEventPools(blockNumber);
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
releaseResources(): void {
|
|
752
|
-
if (this.eventDisabledPoolsTimer) {
|
|
753
|
-
clearInterval(this.eventDisabledPoolsTimer);
|
|
754
|
-
this.eventDisabledPoolsTimer = undefined;
|
|
755
|
-
this.logger.info(
|
|
756
|
-
`${this.dexKey}: cleared eventDisabledPoolsTimer before shutting down`,
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
getPoolsWithTokenPair(from: Token, to: Token): SubgraphPoolBase[] {
|
|
762
|
-
const pools = this.eventPools.allPools.filter(p => {
|
|
763
|
-
const fromMain = p.mainTokens.find(
|
|
764
|
-
token => token.address.toLowerCase() === from.address.toLowerCase(),
|
|
765
|
-
);
|
|
766
|
-
const toMain = p.mainTokens.find(
|
|
767
|
-
token => token.address.toLowerCase() === to.address.toLowerCase(),
|
|
768
|
-
);
|
|
769
|
-
|
|
770
|
-
return (
|
|
771
|
-
fromMain &&
|
|
772
|
-
toMain &&
|
|
773
|
-
// filter instances similar to the following:
|
|
774
|
-
// USDC -> DAI in a pool where bbaUSD is nested (ie: MAI / bbaUSD)
|
|
775
|
-
!(fromMain.isDeeplyNested && toMain.isDeeplyNested)
|
|
776
|
-
);
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
return pools.slice(0, 10);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
783
|
-
return this.adapters[side] ? this.adapters[side] : null;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
async getPoolIdentifiers(
|
|
787
|
-
from: Token,
|
|
788
|
-
to: Token,
|
|
789
|
-
side: SwapSide,
|
|
790
|
-
blockNumber: number,
|
|
791
|
-
): Promise<string[]> {
|
|
792
|
-
const _from = this.dexHelper.config.wrapETH(from);
|
|
793
|
-
const _to = this.dexHelper.config.wrapETH(to);
|
|
794
|
-
|
|
795
|
-
const pools = this.getPoolsWithTokenPair(_from, _to);
|
|
796
|
-
|
|
797
|
-
return pools.map(
|
|
798
|
-
({ address }) => `${this.dexKey}_${address.toLowerCase()}`,
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* Returns cached poolState if blockNumber matches cached value. Resets if not.
|
|
804
|
-
*/
|
|
805
|
-
private getNonEventPoolStateCache(blockNumber: number): PoolStateMap {
|
|
806
|
-
if (this.nonEventPoolStateCache.blockNumber !== blockNumber)
|
|
807
|
-
this.nonEventPoolStateCache.poolState = {};
|
|
808
|
-
return this.nonEventPoolStateCache.poolState;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
/**
|
|
812
|
-
* Update poolState cache.
|
|
813
|
-
* If same blockNumber as current cache then update with new pool state.
|
|
814
|
-
* If different blockNumber overwrite cache with latest.
|
|
815
|
-
*/
|
|
816
|
-
private updateNonEventPoolStateCache(
|
|
817
|
-
poolState: PoolStateMap,
|
|
818
|
-
blockNumber: number,
|
|
819
|
-
): PoolStateMap {
|
|
820
|
-
if (this.nonEventPoolStateCache.blockNumber !== blockNumber) {
|
|
821
|
-
this.nonEventPoolStateCache.blockNumber = blockNumber;
|
|
822
|
-
this.nonEventPoolStateCache.poolState = poolState;
|
|
823
|
-
} else
|
|
824
|
-
this.nonEventPoolStateCache.poolState = {
|
|
825
|
-
...this.nonEventPoolStateCache.poolState,
|
|
826
|
-
...poolState,
|
|
827
|
-
};
|
|
828
|
-
return this.nonEventPoolStateCache.poolState;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
async getPricesVolume(
|
|
832
|
-
from: Token,
|
|
833
|
-
to: Token,
|
|
834
|
-
amounts: bigint[],
|
|
835
|
-
side: SwapSide,
|
|
836
|
-
blockNumber: number,
|
|
837
|
-
limitPools?: string[],
|
|
838
|
-
): Promise<null | ExchangePrices<BalancerV2Data>> {
|
|
839
|
-
try {
|
|
840
|
-
const _from = this.dexHelper.config.wrapETH(from);
|
|
841
|
-
const _to = this.dexHelper.config.wrapETH(to);
|
|
842
|
-
|
|
843
|
-
if (_from.address === _to.address) {
|
|
844
|
-
return null;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
const allPools = this.getPoolsWithTokenPair(_from, _to);
|
|
848
|
-
|
|
849
|
-
const allowedPools = limitPools
|
|
850
|
-
? allPools.filter(({ address }) =>
|
|
851
|
-
limitPools.includes(`${this.dexKey}_${address.toLowerCase()}`),
|
|
852
|
-
)
|
|
853
|
-
: allPools;
|
|
854
|
-
|
|
855
|
-
if (!allowedPools.length) return null;
|
|
856
|
-
|
|
857
|
-
const eventPoolStatesRO = await this.eventPools.getState(blockNumber);
|
|
858
|
-
if (!eventPoolStatesRO) {
|
|
859
|
-
this.logger.error(`getState returned null`);
|
|
860
|
-
}
|
|
861
|
-
const eventPoolStates = { ...(eventPoolStatesRO || {}) };
|
|
862
|
-
for (const addr of this.eventDisabledPools) delete eventPoolStates[addr];
|
|
863
|
-
|
|
864
|
-
// Fetch previously cached non-event pool states
|
|
865
|
-
let nonEventPoolStates = this.getNonEventPoolStateCache(blockNumber);
|
|
866
|
-
|
|
867
|
-
//get all pools that would be used in the paths, nested pools included
|
|
868
|
-
const poolsFlattened = getAllPoolsUsedInPaths(
|
|
869
|
-
_from.address,
|
|
870
|
-
_to.address,
|
|
871
|
-
allowedPools,
|
|
872
|
-
this.poolAddressMap,
|
|
873
|
-
side,
|
|
874
|
-
);
|
|
875
|
-
|
|
876
|
-
// Missing pools are pools that don't already exist in event or non-event
|
|
877
|
-
const missingPools = poolsFlattened.filter(
|
|
878
|
-
pool =>
|
|
879
|
-
!(
|
|
880
|
-
pool.address.toLowerCase() in eventPoolStates ||
|
|
881
|
-
pool.address.toLowerCase() in nonEventPoolStates
|
|
882
|
-
),
|
|
883
|
-
);
|
|
884
|
-
|
|
885
|
-
// Retrieve onchain state for any missing pools
|
|
886
|
-
if (missingPools.length > 0) {
|
|
887
|
-
const missingPoolsStateMap = await this.eventPools.getOnChainState(
|
|
888
|
-
missingPools,
|
|
889
|
-
blockNumber,
|
|
890
|
-
);
|
|
891
|
-
// Update non-event pool state cache with newly retrieved data so it can be reused in future
|
|
892
|
-
nonEventPoolStates = this.updateNonEventPoolStateCache(
|
|
893
|
-
missingPoolsStateMap,
|
|
894
|
-
blockNumber,
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
const poolPrices = allowedPools
|
|
899
|
-
.map((pool: SubgraphPoolBase) => {
|
|
900
|
-
try {
|
|
901
|
-
const poolAddress = pool.address.toLowerCase();
|
|
902
|
-
|
|
903
|
-
const path = poolGetPathForTokenInOut(
|
|
904
|
-
_from.address,
|
|
905
|
-
_to.address,
|
|
906
|
-
pool,
|
|
907
|
-
this.poolAddressMap,
|
|
908
|
-
side,
|
|
909
|
-
);
|
|
910
|
-
|
|
911
|
-
let pathAmounts = amounts;
|
|
912
|
-
let resOut: { unit: bigint; prices: bigint[] } | null = null;
|
|
913
|
-
|
|
914
|
-
for (let i = 0; i < path.length; i++) {
|
|
915
|
-
const poolAddress = path[i].pool.address.toLowerCase();
|
|
916
|
-
const poolState = (eventPoolStates[poolAddress] ||
|
|
917
|
-
nonEventPoolStates[poolAddress]) as PoolState | undefined;
|
|
918
|
-
if (!poolState) {
|
|
919
|
-
this.logger.error(
|
|
920
|
-
`Unable to find the poolState ${poolAddress}`,
|
|
921
|
-
);
|
|
922
|
-
return null;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
const unitVolume = getBigIntPow(
|
|
926
|
-
(side === SwapSide.SELL ? path[i].tokenIn : path[i].tokenOut)
|
|
927
|
-
.decimals,
|
|
928
|
-
);
|
|
929
|
-
|
|
930
|
-
const res = this.eventPools.getPricesPool(
|
|
931
|
-
path[i].tokenIn,
|
|
932
|
-
path[i].tokenOut,
|
|
933
|
-
path[i].pool,
|
|
934
|
-
poolState,
|
|
935
|
-
pathAmounts,
|
|
936
|
-
unitVolume,
|
|
937
|
-
side,
|
|
938
|
-
);
|
|
939
|
-
|
|
940
|
-
if (!res) {
|
|
941
|
-
return null;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
pathAmounts = res.prices;
|
|
945
|
-
|
|
946
|
-
if (i === path.length - 1) {
|
|
947
|
-
resOut = res;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
if (!resOut) {
|
|
952
|
-
return null;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
return {
|
|
956
|
-
unit: resOut.unit,
|
|
957
|
-
prices: resOut.prices,
|
|
958
|
-
data: {
|
|
959
|
-
poolId: pool.id,
|
|
960
|
-
},
|
|
961
|
-
poolAddresses: [poolAddress],
|
|
962
|
-
exchange: this.dexKey,
|
|
963
|
-
gasCost:
|
|
964
|
-
STABLE_GAS_COST + VARIABLE_GAS_COST_PER_CYCLE * path.length,
|
|
965
|
-
poolIdentifier: `${this.dexKey}_${poolAddress}`,
|
|
966
|
-
};
|
|
967
|
-
|
|
968
|
-
// TODO: re-check what should be the current block time stamp
|
|
969
|
-
} catch (e) {
|
|
970
|
-
this.logger.warn(
|
|
971
|
-
`Error_getPrices ${from.symbol || from.address}, ${
|
|
972
|
-
to.symbol || to.address
|
|
973
|
-
}, ${side}, ${pool.address}:`,
|
|
974
|
-
e,
|
|
975
|
-
);
|
|
976
|
-
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
})
|
|
980
|
-
.filter(p => !!p);
|
|
981
|
-
return poolPrices as ExchangePrices<BalancerV2Data>;
|
|
982
|
-
} catch (e) {
|
|
983
|
-
this.logger.error(
|
|
984
|
-
`Error_getPrices ${from.symbol || from.address}, ${
|
|
985
|
-
to.symbol || to.address
|
|
986
|
-
}, ${side}:`,
|
|
987
|
-
e,
|
|
988
|
-
);
|
|
989
|
-
return null;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// Returns estimated gas cost of calldata for this DEX in multiSwap
|
|
994
|
-
getCalldataGasCost(
|
|
995
|
-
poolPrices: PoolPrices<BalancerV2Data>,
|
|
996
|
-
): number | number[] {
|
|
997
|
-
return (
|
|
998
|
-
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
999
|
-
CALLDATA_GAS_COST.LENGTH_LARGE +
|
|
1000
|
-
// ParentStruct header
|
|
1001
|
-
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
1002
|
-
// ParentStruct -> swaps[] header
|
|
1003
|
-
CALLDATA_GAS_COST.OFFSET_LARGE +
|
|
1004
|
-
// ParentStruct -> assets[] header
|
|
1005
|
-
CALLDATA_GAS_COST.OFFSET_LARGE +
|
|
1006
|
-
// ParentStruct -> funds
|
|
1007
|
-
CALLDATA_GAS_COST.ADDRESS +
|
|
1008
|
-
CALLDATA_GAS_COST.BOOL +
|
|
1009
|
-
CALLDATA_GAS_COST.ADDRESS +
|
|
1010
|
-
CALLDATA_GAS_COST.BOOL +
|
|
1011
|
-
// ParentStruct -> limits[] header
|
|
1012
|
-
CALLDATA_GAS_COST.OFFSET_LARGE +
|
|
1013
|
-
// ParentStruct -> deadline
|
|
1014
|
-
CALLDATA_GAS_COST.TIMESTAMP +
|
|
1015
|
-
// ParentStruct -> swaps[]
|
|
1016
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
1017
|
-
// ParentStruct -> swaps[0] header
|
|
1018
|
-
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
1019
|
-
// ParentStruct -> swaps[0] -> poolId
|
|
1020
|
-
CALLDATA_GAS_COST.FULL_WORD +
|
|
1021
|
-
// ParentStruct -> swaps[0] -> assetInIndex
|
|
1022
|
-
CALLDATA_GAS_COST.INDEX +
|
|
1023
|
-
// ParentStruct -> swaps[0] -> assetOutIndex
|
|
1024
|
-
CALLDATA_GAS_COST.INDEX +
|
|
1025
|
-
// ParentStruct -> swaps[0] -> amount
|
|
1026
|
-
CALLDATA_GAS_COST.AMOUNT +
|
|
1027
|
-
// ParentStruct -> swaps[0] -> userData header
|
|
1028
|
-
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
1029
|
-
// ParentStruct -> swaps[0] -> userData
|
|
1030
|
-
CALLDATA_GAS_COST.ZERO +
|
|
1031
|
-
// ParentStruct -> assets[]
|
|
1032
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
1033
|
-
// ParentStruct -> assets[0:2]
|
|
1034
|
-
CALLDATA_GAS_COST.ADDRESS * 2 +
|
|
1035
|
-
// ParentStruct -> limits[]
|
|
1036
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
1037
|
-
// ParentStruct -> limits[0:2]
|
|
1038
|
-
CALLDATA_GAS_COST.FULL_WORD * 2
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
getAdapterParam(
|
|
1043
|
-
srcToken: string,
|
|
1044
|
-
destToken: string,
|
|
1045
|
-
srcAmount: string,
|
|
1046
|
-
destAmount: string,
|
|
1047
|
-
data: OptimizedBalancerV2Data,
|
|
1048
|
-
side: SwapSide,
|
|
1049
|
-
): AdapterExchangeParam {
|
|
1050
|
-
const params = this.getBalancerV2BatchSwapParam(
|
|
1051
|
-
srcToken,
|
|
1052
|
-
destToken,
|
|
1053
|
-
data,
|
|
1054
|
-
side,
|
|
1055
|
-
this.dexHelper.config.data.augustusAddress!,
|
|
1056
|
-
this.dexHelper.config.data.augustusAddress!,
|
|
1057
|
-
);
|
|
1058
|
-
|
|
1059
|
-
const payload = this.abiCoder.encodeParameter(
|
|
1060
|
-
{
|
|
1061
|
-
ParentStruct: {
|
|
1062
|
-
'swaps[]': {
|
|
1063
|
-
poolId: 'bytes32',
|
|
1064
|
-
assetInIndex: 'uint256',
|
|
1065
|
-
assetOutIndex: 'uint256',
|
|
1066
|
-
amount: 'uint256',
|
|
1067
|
-
userData: 'bytes',
|
|
1068
|
-
},
|
|
1069
|
-
assets: 'address[]',
|
|
1070
|
-
funds: {
|
|
1071
|
-
sender: 'address',
|
|
1072
|
-
fromInternalBalance: 'bool',
|
|
1073
|
-
recipient: 'address',
|
|
1074
|
-
toInternalBalance: 'bool',
|
|
1075
|
-
},
|
|
1076
|
-
limits: 'int256[]',
|
|
1077
|
-
deadline: 'uint256',
|
|
1078
|
-
},
|
|
1079
|
-
},
|
|
1080
|
-
{
|
|
1081
|
-
swaps: params[1],
|
|
1082
|
-
assets: params[2],
|
|
1083
|
-
funds: params[3],
|
|
1084
|
-
limits: params[4],
|
|
1085
|
-
deadline: params[5],
|
|
1086
|
-
},
|
|
1087
|
-
);
|
|
1088
|
-
|
|
1089
|
-
return {
|
|
1090
|
-
targetExchange: this.vaultAddress,
|
|
1091
|
-
payload,
|
|
1092
|
-
networkFee: '0',
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
public getBalancerV2SwapParam(
|
|
1097
|
-
srcToken: string,
|
|
1098
|
-
destToken: string,
|
|
1099
|
-
data: OptimizedBalancerV2Data,
|
|
1100
|
-
side: SwapSide,
|
|
1101
|
-
recipient: string,
|
|
1102
|
-
sender: string,
|
|
1103
|
-
): BalancerV2SwapParam {
|
|
1104
|
-
assert(data.swaps.length === 1, 'should have exactly one pool');
|
|
1105
|
-
|
|
1106
|
-
const singleSwap: BalancerV2SingleSwap = {
|
|
1107
|
-
poolId: data.swaps[0].poolId,
|
|
1108
|
-
kind:
|
|
1109
|
-
side === SwapSide.SELL ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut,
|
|
1110
|
-
assetIn:
|
|
1111
|
-
srcToken.toLowerCase() === ETHER_ADDRESS ? NULL_ADDRESS : srcToken,
|
|
1112
|
-
assetOut:
|
|
1113
|
-
destToken.toLowerCase() === ETHER_ADDRESS ? NULL_ADDRESS : destToken,
|
|
1114
|
-
amount: data.swaps[0].amount,
|
|
1115
|
-
userData: '0x',
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
const funds = {
|
|
1119
|
-
sender,
|
|
1120
|
-
recipient,
|
|
1121
|
-
fromInternalBalance: false,
|
|
1122
|
-
toInternalBalance: false,
|
|
1123
|
-
};
|
|
1124
|
-
|
|
1125
|
-
const params: BalancerV2SwapParam = [
|
|
1126
|
-
singleSwap,
|
|
1127
|
-
funds,
|
|
1128
|
-
side === SwapSide.SELL ? '1' : MAX_INT,
|
|
1129
|
-
getLocalDeadlineAsFriendlyPlaceholder(),
|
|
1130
|
-
];
|
|
1131
|
-
|
|
1132
|
-
return params;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
/*
|
|
1136
|
-
Algorithm to determine balancer (sender, recipient) params:
|
|
1137
|
-
|
|
1138
|
-
if version = 5
|
|
1139
|
-
sender = recipient = augustusV5
|
|
1140
|
-
else (so V6)
|
|
1141
|
-
if direct swap
|
|
1142
|
-
sender = recipient = augustusV6
|
|
1143
|
-
else (so generic swaps)
|
|
1144
|
-
if sell
|
|
1145
|
-
if swap.destToken = priceRoute.destToken <> ETH (need withdraw for eth currently, need fix in future)
|
|
1146
|
-
sender = executor and recipient = augustusV6 (skip 1 extra transfer)
|
|
1147
|
-
else
|
|
1148
|
-
sender = recipient = executor
|
|
1149
|
-
# note: we pass sender=null then the address of the executor is inferred contract side
|
|
1150
|
-
else (so buy)
|
|
1151
|
-
sender = recipient = executor
|
|
1152
|
-
*/
|
|
1153
|
-
public getBalancerV2BatchSwapParam(
|
|
1154
|
-
srcToken: string,
|
|
1155
|
-
destToken: string,
|
|
1156
|
-
data: OptimizedBalancerV2Data,
|
|
1157
|
-
side: SwapSide,
|
|
1158
|
-
recipient: string,
|
|
1159
|
-
sender: string,
|
|
1160
|
-
shouldWalkAssetsBackward?: boolean, // should do for all buy but prefer keep it under control
|
|
1161
|
-
): BalancerV2BatchSwapParam {
|
|
1162
|
-
let swapOffset = 0;
|
|
1163
|
-
let swaps: BalancerSwap[] = [];
|
|
1164
|
-
let assets: string[] = [];
|
|
1165
|
-
let limits: string[] = [];
|
|
1166
|
-
|
|
1167
|
-
for (const swapData of data.swaps) {
|
|
1168
|
-
const pool = this.poolIdMap[swapData.poolId];
|
|
1169
|
-
const hasEth = [srcToken.toLowerCase(), destToken.toLowerCase()].includes(
|
|
1170
|
-
ETHER_ADDRESS.toLowerCase(),
|
|
1171
|
-
);
|
|
1172
|
-
const _srcToken = this.dexHelper.config.wrapETH({
|
|
1173
|
-
address: srcToken,
|
|
1174
|
-
decimals: 18,
|
|
1175
|
-
}).address;
|
|
1176
|
-
const _destToken = this.dexHelper.config.wrapETH({
|
|
1177
|
-
address: destToken,
|
|
1178
|
-
decimals: 18,
|
|
1179
|
-
}).address;
|
|
1180
|
-
|
|
1181
|
-
let path = poolGetPathForTokenInOut(
|
|
1182
|
-
_srcToken,
|
|
1183
|
-
_destToken,
|
|
1184
|
-
pool,
|
|
1185
|
-
this.poolAddressMap,
|
|
1186
|
-
side,
|
|
1187
|
-
);
|
|
1188
|
-
|
|
1189
|
-
if (side === SwapSide.BUY) {
|
|
1190
|
-
path = path.reverse();
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
const _swaps = path.map((hop, index) => {
|
|
1194
|
-
const assetInIndex = shouldWalkAssetsBackward
|
|
1195
|
-
? swapOffset + path.length - index
|
|
1196
|
-
: swapOffset + index;
|
|
1197
|
-
|
|
1198
|
-
const assetOutIndex = shouldWalkAssetsBackward
|
|
1199
|
-
? swapOffset + path.length - index - 1
|
|
1200
|
-
: swapOffset + index + 1;
|
|
1201
|
-
|
|
1202
|
-
const amount =
|
|
1203
|
-
(side === SwapSide.SELL && index === 0) ||
|
|
1204
|
-
(side === SwapSide.BUY && index === path.length - 1)
|
|
1205
|
-
? swapData.amount
|
|
1206
|
-
: '0';
|
|
1207
|
-
|
|
1208
|
-
if (assetInIndex < 0 || assetOutIndex < 0) {
|
|
1209
|
-
const error = new Error(`Invalid indices in balancer`);
|
|
1210
|
-
this.logger.error(error.message, error);
|
|
1211
|
-
throw error;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
return {
|
|
1215
|
-
poolId: hop.pool.id,
|
|
1216
|
-
assetInIndex,
|
|
1217
|
-
assetOutIndex,
|
|
1218
|
-
amount,
|
|
1219
|
-
userData: '0x',
|
|
1220
|
-
};
|
|
1221
|
-
});
|
|
1222
|
-
|
|
1223
|
-
swapOffset += path.length + 1;
|
|
1224
|
-
|
|
1225
|
-
// BalancerV2 Uses Address(0) as ETH
|
|
1226
|
-
const _assets = [_srcToken, ...path.map(hop => hop.tokenOut.address)].map(
|
|
1227
|
-
t => (hasEth && this.dexHelper.config.isWETH(t) ? NULL_ADDRESS : t),
|
|
1228
|
-
);
|
|
1229
|
-
|
|
1230
|
-
const _limits = _assets.map(_ => MAX_INT);
|
|
1231
|
-
|
|
1232
|
-
swaps = swaps.concat(_swaps);
|
|
1233
|
-
assets = assets.concat(_assets);
|
|
1234
|
-
limits = limits.concat(_limits);
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const funds = {
|
|
1238
|
-
sender,
|
|
1239
|
-
recipient,
|
|
1240
|
-
fromInternalBalance: false,
|
|
1241
|
-
toInternalBalance: false,
|
|
1242
|
-
};
|
|
1243
|
-
|
|
1244
|
-
const params: BalancerV2BatchSwapParam = [
|
|
1245
|
-
side === SwapSide.SELL ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut,
|
|
1246
|
-
side === SwapSide.SELL ? swaps : swaps.reverse(),
|
|
1247
|
-
shouldWalkAssetsBackward ? assets.reverse() : assets,
|
|
1248
|
-
funds,
|
|
1249
|
-
limits,
|
|
1250
|
-
getLocalDeadlineAsFriendlyPlaceholder(),
|
|
1251
|
-
];
|
|
1252
|
-
|
|
1253
|
-
return params;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
static getDirectFunctionName(): string[] {
|
|
1257
|
-
return [DirectMethods.directSell, DirectMethods.directBuy];
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
getTokenFromAddress(address: Address): Token {
|
|
1261
|
-
// In this Dex decimals are not used
|
|
1262
|
-
return { address, decimals: 0 };
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
async preProcessTransaction(
|
|
1266
|
-
optimalSwapExchange: OptimalSwapExchange<OptimizedBalancerV2Data>,
|
|
1267
|
-
srcToken: Token,
|
|
1268
|
-
_0: Token,
|
|
1269
|
-
_1: SwapSide,
|
|
1270
|
-
options: PreprocessTransactionOptions,
|
|
1271
|
-
): Promise<[OptimalSwapExchange<OptimizedBalancerV2Data>, ExchangeTxInfo]> {
|
|
1272
|
-
if (!options.isDirectMethod) {
|
|
1273
|
-
return [
|
|
1274
|
-
optimalSwapExchange,
|
|
1275
|
-
{
|
|
1276
|
-
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
1277
|
-
},
|
|
1278
|
-
];
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
assert(
|
|
1282
|
-
optimalSwapExchange.data !== undefined,
|
|
1283
|
-
`preProcessTransaction: data field is missing`,
|
|
1284
|
-
);
|
|
1285
|
-
|
|
1286
|
-
let isApproved: boolean | undefined;
|
|
1287
|
-
|
|
1288
|
-
try {
|
|
1289
|
-
isApproved = await this.dexHelper.augustusApprovals.hasApproval(
|
|
1290
|
-
options.executionContractAddress,
|
|
1291
|
-
this.dexHelper.config.wrapETH(srcToken).address,
|
|
1292
|
-
this.vaultAddress,
|
|
1293
|
-
);
|
|
1294
|
-
} catch (e) {
|
|
1295
|
-
this.logger.error(
|
|
1296
|
-
`preProcessTransaction failed to retrieve allowance info: `,
|
|
1297
|
-
e,
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
return [
|
|
1302
|
-
{
|
|
1303
|
-
...optimalSwapExchange,
|
|
1304
|
-
data: {
|
|
1305
|
-
...optimalSwapExchange.data,
|
|
1306
|
-
isApproved,
|
|
1307
|
-
},
|
|
1308
|
-
},
|
|
1309
|
-
{
|
|
1310
|
-
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
1311
|
-
},
|
|
1312
|
-
];
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
getDirectParam(
|
|
1316
|
-
srcToken: Address,
|
|
1317
|
-
destToken: Address,
|
|
1318
|
-
srcAmount: NumberAsString,
|
|
1319
|
-
destAmount: NumberAsString,
|
|
1320
|
-
expectedAmount: NumberAsString,
|
|
1321
|
-
data: OptimizedBalancerV2Data,
|
|
1322
|
-
side: SwapSide,
|
|
1323
|
-
permit: string,
|
|
1324
|
-
uuid: string,
|
|
1325
|
-
feePercent: NumberAsString,
|
|
1326
|
-
deadline: NumberAsString,
|
|
1327
|
-
partner: string,
|
|
1328
|
-
beneficiary: string,
|
|
1329
|
-
contractMethod?: string,
|
|
1330
|
-
): TxInfo<BalancerV2DirectParam> {
|
|
1331
|
-
if (
|
|
1332
|
-
contractMethod !== DirectMethods.directSell &&
|
|
1333
|
-
contractMethod !== DirectMethods.directBuy
|
|
1334
|
-
) {
|
|
1335
|
-
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
let isApproved: boolean = !!data.isApproved;
|
|
1339
|
-
if (data.isApproved === undefined) {
|
|
1340
|
-
this.logger.warn(`isApproved is undefined, defaulting to false`);
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
const [, swaps, assets, funds, limits, _deadline] =
|
|
1344
|
-
this.getBalancerV2BatchSwapParam(
|
|
1345
|
-
srcToken,
|
|
1346
|
-
destToken,
|
|
1347
|
-
data,
|
|
1348
|
-
side,
|
|
1349
|
-
this.dexHelper.config.data.augustusAddress!,
|
|
1350
|
-
this.dexHelper.config.data.augustusAddress!,
|
|
1351
|
-
);
|
|
1352
|
-
|
|
1353
|
-
const swapParams: BalancerV2DirectParam = [
|
|
1354
|
-
swaps,
|
|
1355
|
-
assets,
|
|
1356
|
-
funds,
|
|
1357
|
-
limits,
|
|
1358
|
-
srcAmount,
|
|
1359
|
-
destAmount,
|
|
1360
|
-
expectedAmount,
|
|
1361
|
-
_deadline,
|
|
1362
|
-
feePercent,
|
|
1363
|
-
this.vaultAddress,
|
|
1364
|
-
partner,
|
|
1365
|
-
isApproved,
|
|
1366
|
-
beneficiary,
|
|
1367
|
-
permit,
|
|
1368
|
-
uuidToBytes16(uuid),
|
|
1369
|
-
];
|
|
1370
|
-
|
|
1371
|
-
const encoder = (...params: BalancerV2DirectParam) => {
|
|
1372
|
-
return this.directSwapIface.encodeFunctionData(
|
|
1373
|
-
side === SwapSide.SELL
|
|
1374
|
-
? DirectMethods.directSell
|
|
1375
|
-
: DirectMethods.directBuy,
|
|
1376
|
-
[params],
|
|
1377
|
-
);
|
|
1378
|
-
};
|
|
1379
|
-
|
|
1380
|
-
return {
|
|
1381
|
-
params: swapParams,
|
|
1382
|
-
encoder,
|
|
1383
|
-
networkFee: '0',
|
|
1384
|
-
};
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
getDirectParamV6(
|
|
1388
|
-
srcToken: Address,
|
|
1389
|
-
destToken: Address,
|
|
1390
|
-
fromAmount: NumberAsString,
|
|
1391
|
-
toAmount: NumberAsString,
|
|
1392
|
-
quotedAmount: NumberAsString,
|
|
1393
|
-
data: OptimizedBalancerV2Data,
|
|
1394
|
-
side: SwapSide,
|
|
1395
|
-
permit: string,
|
|
1396
|
-
uuid: string,
|
|
1397
|
-
partnerAndFee: string,
|
|
1398
|
-
beneficiary: string,
|
|
1399
|
-
blockNumber: number,
|
|
1400
|
-
contractMethod?: string,
|
|
1401
|
-
) {
|
|
1402
|
-
if (!contractMethod) throw new Error(`contractMethod need to be passed`);
|
|
1403
|
-
|
|
1404
|
-
if (!BalancerV2.getDirectFunctionNameV6().includes(contractMethod!)) {
|
|
1405
|
-
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
const metadata = hexConcat([
|
|
1409
|
-
hexZeroPad(uuidToBytes16(uuid), 16),
|
|
1410
|
-
hexZeroPad(hexlify(blockNumber), 16),
|
|
1411
|
-
]);
|
|
1412
|
-
|
|
1413
|
-
const balancerBatchSwapParam = this.getBalancerV2BatchSwapParam(
|
|
1414
|
-
srcToken,
|
|
1415
|
-
destToken,
|
|
1416
|
-
data,
|
|
1417
|
-
side,
|
|
1418
|
-
this.dexHelper.config.data.augustusV6Address!,
|
|
1419
|
-
this.dexHelper.config.data.augustusV6Address!,
|
|
1420
|
-
side === SwapSide.BUY,
|
|
1421
|
-
);
|
|
1422
|
-
|
|
1423
|
-
// after getBalancerV2BatchSwapParam runs we may get more swaps so we can't decide on single vs batch before resolving it
|
|
1424
|
-
const [, swaps] = balancerBatchSwapParam;
|
|
1425
|
-
const isSingleSwap = swaps.length === 1;
|
|
1426
|
-
|
|
1427
|
-
const balancerParams = isSingleSwap
|
|
1428
|
-
? this.getBalancerV2SwapParam(
|
|
1429
|
-
srcToken,
|
|
1430
|
-
destToken,
|
|
1431
|
-
data,
|
|
1432
|
-
side,
|
|
1433
|
-
this.dexHelper.config.data.augustusV6Address!,
|
|
1434
|
-
this.dexHelper.config.data.augustusV6Address!,
|
|
1435
|
-
)
|
|
1436
|
-
: balancerBatchSwapParam;
|
|
1437
|
-
|
|
1438
|
-
const swapParams: BalancerV2DirectParamV6 = [
|
|
1439
|
-
fromAmount,
|
|
1440
|
-
toAmount,
|
|
1441
|
-
quotedAmount,
|
|
1442
|
-
metadata,
|
|
1443
|
-
this.encodeBeneficiaryAndApproveFlag(beneficiary, !data.isApproved),
|
|
1444
|
-
];
|
|
1445
|
-
|
|
1446
|
-
const encodeParams: BalancerV2DirectParamV6Swap = [
|
|
1447
|
-
swapParams,
|
|
1448
|
-
partnerAndFee,
|
|
1449
|
-
permit,
|
|
1450
|
-
balancerParams.length === 4 // TODO: upgrade ts to use isSingleSwap
|
|
1451
|
-
? this.encodeBalancerV2SwapParam(balancerParams)
|
|
1452
|
-
: this.encodeBalancerV2BatchSwapParam(balancerParams),
|
|
1453
|
-
];
|
|
1454
|
-
|
|
1455
|
-
const encoder = (...params: BalancerV2DirectParamV6Swap) => {
|
|
1456
|
-
return this.augustusV6Interface.encodeFunctionData(
|
|
1457
|
-
side === SwapSide.SELL
|
|
1458
|
-
? DirectMethodsV6.directSell
|
|
1459
|
-
: DirectMethodsV6.directBuy,
|
|
1460
|
-
[...params],
|
|
1461
|
-
);
|
|
1462
|
-
};
|
|
1463
|
-
|
|
1464
|
-
return {
|
|
1465
|
-
encoder,
|
|
1466
|
-
params: encodeParams,
|
|
1467
|
-
networkFee: '0',
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
private encodeBeneficiaryAndApproveFlag(
|
|
1472
|
-
beneficiary: Address,
|
|
1473
|
-
approveFlag: boolean,
|
|
1474
|
-
) {
|
|
1475
|
-
const addressBN = BigNumber.from(beneficiary);
|
|
1476
|
-
const flagBN = approveFlag ? BigNumber.from(1).shl(255) : BigNumber.from(0);
|
|
1477
|
-
|
|
1478
|
-
return addressBN.or(flagBN).toString();
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
private encodeBalancerV2SwapParam(param: BalancerV2SwapParam): string {
|
|
1482
|
-
const [singleSwap, funds, limit, deadline] = param;
|
|
1483
|
-
|
|
1484
|
-
const encoded = this.balancerVaultInterface.encodeFunctionData('swap', [
|
|
1485
|
-
singleSwap,
|
|
1486
|
-
funds,
|
|
1487
|
-
limit,
|
|
1488
|
-
deadline,
|
|
1489
|
-
]);
|
|
1490
|
-
|
|
1491
|
-
return encoded;
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
private encodeBalancerV2BatchSwapParam(
|
|
1495
|
-
param: BalancerV2BatchSwapParam,
|
|
1496
|
-
): string {
|
|
1497
|
-
const [kind, swaps, assets, funds, limits, deadline] = param;
|
|
1498
|
-
|
|
1499
|
-
const encoded = this.balancerVaultInterface.encodeFunctionData(
|
|
1500
|
-
'batchSwap',
|
|
1501
|
-
[kind, swaps, assets, funds, limits, deadline],
|
|
1502
|
-
);
|
|
1503
|
-
return encoded;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
static getDirectFunctionNameV6(): string[] {
|
|
1507
|
-
return [DirectMethodsV6.directSell, DirectMethodsV6.directBuy];
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
async getSimpleParam(
|
|
1511
|
-
srcToken: string,
|
|
1512
|
-
destToken: string,
|
|
1513
|
-
srcAmount: string,
|
|
1514
|
-
destAmount: string,
|
|
1515
|
-
data: OptimizedBalancerV2Data,
|
|
1516
|
-
side: SwapSide,
|
|
1517
|
-
): Promise<SimpleExchangeParam> {
|
|
1518
|
-
const params = this.getBalancerV2BatchSwapParam(
|
|
1519
|
-
srcToken,
|
|
1520
|
-
destToken,
|
|
1521
|
-
data,
|
|
1522
|
-
side,
|
|
1523
|
-
this.dexHelper.config.data.augustusAddress!,
|
|
1524
|
-
this.dexHelper.config.data.augustusAddress!,
|
|
1525
|
-
);
|
|
1526
|
-
|
|
1527
|
-
const swapData = this.eventPools.vaultInterface.encodeFunctionData(
|
|
1528
|
-
'batchSwap',
|
|
1529
|
-
params,
|
|
1530
|
-
);
|
|
1531
|
-
|
|
1532
|
-
return this.buildSimpleParamWithoutWETHConversion(
|
|
1533
|
-
srcToken,
|
|
1534
|
-
srcAmount,
|
|
1535
|
-
destToken,
|
|
1536
|
-
destAmount,
|
|
1537
|
-
swapData,
|
|
1538
|
-
this.vaultAddress,
|
|
1539
|
-
);
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
getDexParam(
|
|
1543
|
-
srcToken: Address,
|
|
1544
|
-
destToken: Address,
|
|
1545
|
-
srcAmount: NumberAsString,
|
|
1546
|
-
destAmount: NumberAsString,
|
|
1547
|
-
recipient: Address,
|
|
1548
|
-
data: OptimizedBalancerV2Data,
|
|
1549
|
-
side: SwapSide,
|
|
1550
|
-
context: Context,
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
);
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
'
|
|
1577
|
-
|
|
1578
|
-
);
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
address
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
SUBGRAPH_TIMEOUT,
|
|
1671
|
-
);
|
|
1672
|
-
|
|
1673
|
-
if (!(data && data.pools))
|
|
1674
|
-
throw new Error(
|
|
1675
|
-
`Error_${this.dexKey}_Subgraph: couldn't fetch the pools from the subgraph`,
|
|
1676
|
-
);
|
|
1677
|
-
|
|
1678
|
-
return _.map(data.pools, pool => {
|
|
1679
|
-
const subgraphPool = poolsWithToken.find(poolWithToken =>
|
|
1680
|
-
isSameAddress(poolWithToken.address, pool.address),
|
|
1681
|
-
)!;
|
|
1682
|
-
|
|
1683
|
-
return {
|
|
1684
|
-
exchange: this.dexKey,
|
|
1685
|
-
address: pool.address.toLowerCase(),
|
|
1686
|
-
connectorTokens: subgraphPool.mainTokens.filter(
|
|
1687
|
-
token => !isSameAddress(tokenAddress, token.address),
|
|
1688
|
-
),
|
|
1689
|
-
liquidityUSD: parseFloat(pool.totalLiquidity),
|
|
1690
|
-
};
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
private get poolAddressMap(): SubgraphPoolAddressDictionary {
|
|
1695
|
-
return keyBy(this.eventPools.allPools, 'address');
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
private get poolIdMap(): { [poolId: string]: SubgraphPoolBase } {
|
|
1699
|
-
return keyBy(this.eventPools.allPools, 'id');
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1
|
+
import { Interface } from '@ethersproject/abi';
|
|
2
|
+
import { assert, DeepReadonly } from 'ts-essentials';
|
|
3
|
+
import _, { keyBy } from 'lodash';
|
|
4
|
+
import {
|
|
5
|
+
AdapterExchangeParam,
|
|
6
|
+
Address,
|
|
7
|
+
DexExchangeParam,
|
|
8
|
+
ExchangePrices,
|
|
9
|
+
ExchangeTxInfo,
|
|
10
|
+
Log,
|
|
11
|
+
Logger,
|
|
12
|
+
PoolLiquidity,
|
|
13
|
+
PoolPrices,
|
|
14
|
+
PreprocessTransactionOptions,
|
|
15
|
+
SimpleExchangeParam,
|
|
16
|
+
Token,
|
|
17
|
+
TxInfo,
|
|
18
|
+
} from '../../types';
|
|
19
|
+
import {
|
|
20
|
+
ETHER_ADDRESS,
|
|
21
|
+
MAX_INT,
|
|
22
|
+
MAX_UINT,
|
|
23
|
+
Network,
|
|
24
|
+
NULL_ADDRESS,
|
|
25
|
+
SUBGRAPH_TIMEOUT,
|
|
26
|
+
SwapSide,
|
|
27
|
+
} from '../../constants';
|
|
28
|
+
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
29
|
+
import { StablePool } from './pools/stable/StablePool';
|
|
30
|
+
import { WeightedPool } from './pools/weighted/WeightedPool';
|
|
31
|
+
import { PhantomStablePool } from './pools/phantom-stable/PhantomStablePool';
|
|
32
|
+
import { LinearPool } from './pools/linear/LinearPool';
|
|
33
|
+
import { Gyro3Pool } from './pools/gyro/Gyro3Pool';
|
|
34
|
+
import { GyroEPool } from './pools/gyro/GyroEPool';
|
|
35
|
+
import VaultABI from '../../abi/balancer-v2/vault.json';
|
|
36
|
+
import DirectSwapABI from '../../abi/DirectSwap.json';
|
|
37
|
+
import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
|
|
38
|
+
import {
|
|
39
|
+
getBigIntPow,
|
|
40
|
+
getDexKeysWithNetwork,
|
|
41
|
+
uuidToBytes16,
|
|
42
|
+
} from '../../utils';
|
|
43
|
+
import { Context, IDex } from '../../dex/idex';
|
|
44
|
+
import { IDexHelper } from '../../dex-helper';
|
|
45
|
+
import {
|
|
46
|
+
BalancerV2BatchSwapParam,
|
|
47
|
+
BalancerPoolTypes,
|
|
48
|
+
BalancerSwap,
|
|
49
|
+
BalancerV2Data,
|
|
50
|
+
BalancerV2DirectParam,
|
|
51
|
+
OptimizedBalancerV2Data,
|
|
52
|
+
PoolState,
|
|
53
|
+
PoolStateCache,
|
|
54
|
+
PoolStateMap,
|
|
55
|
+
SubgraphPoolAddressDictionary,
|
|
56
|
+
SubgraphPoolBase,
|
|
57
|
+
SwapTypes,
|
|
58
|
+
BalancerV2DirectParamV6,
|
|
59
|
+
BalancerV2DirectParamV6Swap,
|
|
60
|
+
BalancerV2SwapParam,
|
|
61
|
+
BalancerV2SingleSwap,
|
|
62
|
+
} from './types';
|
|
63
|
+
import {
|
|
64
|
+
getLocalDeadlineAsFriendlyPlaceholder,
|
|
65
|
+
SimpleExchange,
|
|
66
|
+
} from '../simple-exchange';
|
|
67
|
+
import { Adapters, BalancerConfig } from './config';
|
|
68
|
+
import {
|
|
69
|
+
getAllPoolsUsedInPaths,
|
|
70
|
+
isSameAddress,
|
|
71
|
+
poolGetMainTokens,
|
|
72
|
+
poolGetPathForTokenInOut,
|
|
73
|
+
} from './utils';
|
|
74
|
+
import {
|
|
75
|
+
DirectMethods,
|
|
76
|
+
DirectMethodsV6,
|
|
77
|
+
MIN_USD_LIQUIDITY_TO_FETCH,
|
|
78
|
+
STABLE_GAS_COST,
|
|
79
|
+
VARIABLE_GAS_COST_PER_CYCLE,
|
|
80
|
+
} from './constants';
|
|
81
|
+
import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
|
|
82
|
+
import { hexConcat, hexlify, hexZeroPad, solidityPack } from 'ethers/lib/utils';
|
|
83
|
+
import BalancerVaultABI from '../../abi/balancer-v2/vault.json';
|
|
84
|
+
import { BigNumber, utils } from 'ethers';
|
|
85
|
+
import { Executors, SpecialDex } from '../../executor/types';
|
|
86
|
+
import { S } from '@bgd-labs/aave-address-book/dist/AaveV2Ethereum-timF4kft';
|
|
87
|
+
|
|
88
|
+
// If you disable some pool, don't forget to clear the cache, otherwise changes won't be applied immediately
|
|
89
|
+
const enabledPoolTypes = [
|
|
90
|
+
// BalancerPoolTypes.MetaStable, // BOOSTED POOLS Disabled since vulnerability https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md
|
|
91
|
+
BalancerPoolTypes.Stable,
|
|
92
|
+
BalancerPoolTypes.Weighted,
|
|
93
|
+
BalancerPoolTypes.LiquidityBootstrapping,
|
|
94
|
+
BalancerPoolTypes.Investment,
|
|
95
|
+
BalancerPoolTypes.StablePhantom,
|
|
96
|
+
BalancerPoolTypes.AaveLinear,
|
|
97
|
+
BalancerPoolTypes.ERC4626Linear,
|
|
98
|
+
BalancerPoolTypes.Linear,
|
|
99
|
+
BalancerPoolTypes.ComposableStable,
|
|
100
|
+
BalancerPoolTypes.BeefyLinear,
|
|
101
|
+
BalancerPoolTypes.GearboxLinear,
|
|
102
|
+
BalancerPoolTypes.MidasLinear,
|
|
103
|
+
BalancerPoolTypes.ReaperLinear,
|
|
104
|
+
BalancerPoolTypes.SiloLinear,
|
|
105
|
+
BalancerPoolTypes.TetuLinear,
|
|
106
|
+
BalancerPoolTypes.YearnLinear,
|
|
107
|
+
BalancerPoolTypes.GyroE,
|
|
108
|
+
BalancerPoolTypes.Gyro3,
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const disabledPoolIds = [
|
|
112
|
+
// broken ?
|
|
113
|
+
'0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6',
|
|
114
|
+
|
|
115
|
+
/* DISABLED POOLS SINCE VULNERABILITY https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md*/
|
|
116
|
+
/* START:2023-08-mitigation */
|
|
117
|
+
//mainnet
|
|
118
|
+
'0xbf2ef8bdc2fc0f3203b3a01778e3ec5009aeef3300000000000000000000058d',
|
|
119
|
+
'0x99c88ad7dc566616548adde8ed3effa730eb6c3400000000000000000000049a',
|
|
120
|
+
'0x60683b05e9a39e3509d8fdb9c959f23170f8a0fa000000000000000000000489',
|
|
121
|
+
'0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d',
|
|
122
|
+
'0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe',
|
|
123
|
+
'0x25accb7943fd73dda5e23ba6329085a3c24bfb6a000200000000000000000387',
|
|
124
|
+
'0x50cf90b954958480b8df7958a9e965752f62712400000000000000000000046f',
|
|
125
|
+
'0x133d241f225750d2c92948e464a5a80111920331000000000000000000000476',
|
|
126
|
+
'0x8a6b25e33b12d1bb6929a8793961076bd1f9d3eb0002000000000000000003e8',
|
|
127
|
+
'0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472',
|
|
128
|
+
'0x9b692f571b256140a39a34676bffa30634c586e100000000000000000000059d',
|
|
129
|
+
'0xe7b1d394f3b40abeaa0b64a545dbcf89da1ecb3f00010000000000000000009a',
|
|
130
|
+
|
|
131
|
+
// polygon
|
|
132
|
+
'0xb3d658d5b95bf04e2932370dd1ff976fe18dd66a000000000000000000000ace',
|
|
133
|
+
'0x48e6b98ef6329f8f0a30ebb8c7c960330d64808500000000000000000000075b',
|
|
134
|
+
'0xb54b2125b711cd183edd3dd09433439d5396165200000000000000000000075e',
|
|
135
|
+
|
|
136
|
+
// arbitrum
|
|
137
|
+
'0xa8af146d79ac0bb981e4e0d8b788ec5711b1d5d000000000000000000000047b',
|
|
138
|
+
'0x077794c30afeccdf5ad2abc0588e8cee7197b71a000000000000000000000352',
|
|
139
|
+
'0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382',
|
|
140
|
+
|
|
141
|
+
// optimism
|
|
142
|
+
'0x23ca0306b21ea71552b148cf3c4db4fc85ae19290000000000000000000000ac',
|
|
143
|
+
'0x62cf35db540152e94936de63efc90d880d4e241b0000000000000000000000ef',
|
|
144
|
+
'0x098f32d98d0d64dba199fc1923d3bf4192e787190001000000000000000000d2',
|
|
145
|
+
'0x43da214fab3315aa6c02e0b8f2bfb7ef2e3c60a50000000000000000000000ae',
|
|
146
|
+
'0xb1c9ac57594e9b1ec0f3787d9f6744ef4cb0a02400000000000000000000006e',
|
|
147
|
+
'0xde45f101250f2ca1c0f8adfc172576d10c12072d00000000000000000000003f',
|
|
148
|
+
'0x05e7732bf9ae5592e6aa05afe8cd80f7ab0a7bea00020000000000000000005a',
|
|
149
|
+
'0x981fb05b738e981ac532a99e77170ecb4bc27aef00010000000000000000004b',
|
|
150
|
+
'0x6222ae1d2a9f6894da50aa25cb7b303497f9bebd000000000000000000000046',
|
|
151
|
+
'0x3c74c4ed512050eb843d89fb9dcd5ebb4668eb6d0002000000000000000000cc',
|
|
152
|
+
|
|
153
|
+
// fantom
|
|
154
|
+
'0xc0064b291bd3d4ba0e44ccfc81bf8e7f7a579cd200000000000000000000042c',
|
|
155
|
+
'0x6e6dc948ce85c62125ff7a1e543d761a88f0a4cb000000000000000000000743',
|
|
156
|
+
'0x78ab08bf98f90f29a09c9b1d85b3b549369b03a3000100000000000000000354',
|
|
157
|
+
'0x302b8b64795b064cadc32f74993a6372498608070001000000000000000003e0',
|
|
158
|
+
'0x5ddb92a5340fd0ead3987d3661afcd6104c3b757000000000000000000000187',
|
|
159
|
+
'0xdfc65c1f15ad3507754ef0fd4ba67060c108db7e000000000000000000000406',
|
|
160
|
+
'0x6da14f5acd58dd5c8e486cfa1dc1c550f5c61c1c0000000000000000000003cf',
|
|
161
|
+
'0x592fa9f9d58065096f2b7838709c116957d7b5cf00020000000000000000043c',
|
|
162
|
+
'0xf47f4d59c863c02cbfa3eefe6771b9c9fbe7b97800000000000000000000072b',
|
|
163
|
+
'0xff2753aaba51c9f84689b9bd0a21b3cf380a1cff00000000000000000000072e',
|
|
164
|
+
'0x10441785a928040b456a179691141c48356eb3a50001000000000000000002fa',
|
|
165
|
+
'0x64b301e21d640f9bef90458b0987d81fb4cf1b9e00020000000000000000022e',
|
|
166
|
+
'0xba0e9aea8a7fa1daab4edf244191f2387a4e472b000100000000000000000737',
|
|
167
|
+
'0x1e2576344d49779bdbb71b1b76193d27e6f996b700020000000000000000032d',
|
|
168
|
+
'0xa10285f445bcb521f1d623300dc4998b02f11c8f00000000000000000000043b',
|
|
169
|
+
|
|
170
|
+
// zkevm
|
|
171
|
+
'0x6f34a44fce1506352a171232163e7716dd073ade000200000000000000000015',
|
|
172
|
+
'0xe274c9deb6ed34cfe4130f8d0a8a948dea5bb28600000000000000000000000d',
|
|
173
|
+
/* END:2023-08-mitigation */
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const fetchAllPools = `query ($count: Int) {
|
|
177
|
+
pools: pools(
|
|
178
|
+
first: $count
|
|
179
|
+
orderBy: totalLiquidity
|
|
180
|
+
orderDirection: desc
|
|
181
|
+
where: {
|
|
182
|
+
totalLiquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()},
|
|
183
|
+
totalShares_not_in: ["0", "0.000000000001"],
|
|
184
|
+
id_not_in: [
|
|
185
|
+
${disabledPoolIds.map(p => `"${p}"`).join(', ')}
|
|
186
|
+
],
|
|
187
|
+
address_not_in: [
|
|
188
|
+
"0x0afbd58beca09545e4fb67772faf3858e610bcd0",
|
|
189
|
+
"0x2ff1a9dbdacd55297452cfd8a4d94724bc22a5f7",
|
|
190
|
+
"0xbc0f2372008005471874e426e86ccfae7b4de79d",
|
|
191
|
+
"0xdba274b4d04097b90a72b62467d828cefd708037",
|
|
192
|
+
"0xf22ff21e17157340575158ad7394e068048dd98b",
|
|
193
|
+
"0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa",
|
|
194
|
+
],
|
|
195
|
+
swapEnabled: true,
|
|
196
|
+
poolType_in: [
|
|
197
|
+
${enabledPoolTypes.map(p => `"${p}"`).join(', ')}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
) {
|
|
201
|
+
id
|
|
202
|
+
address
|
|
203
|
+
poolType
|
|
204
|
+
poolTypeVersion
|
|
205
|
+
tokens (orderBy: index) {
|
|
206
|
+
address
|
|
207
|
+
decimals
|
|
208
|
+
}
|
|
209
|
+
mainIndex
|
|
210
|
+
wrappedIndex
|
|
211
|
+
|
|
212
|
+
root3Alpha
|
|
213
|
+
}
|
|
214
|
+
}`;
|
|
215
|
+
// skipping low liquidity composableStablePool (0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6) with oracle issues. Experimental.
|
|
216
|
+
|
|
217
|
+
const fetchWeightUpdating = `query ($count: Int, $timestampPast: Int, $timestampFuture: Int) {
|
|
218
|
+
gradualWeightUpdates(
|
|
219
|
+
first: $count,
|
|
220
|
+
where: {startTimestamp_lt: $timestampFuture, endTimestamp_gt: $timestampPast }
|
|
221
|
+
) {
|
|
222
|
+
poolId {
|
|
223
|
+
address
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}`;
|
|
227
|
+
|
|
228
|
+
const MAX_POOL_CNT = 1000; // Taken from SOR
|
|
229
|
+
const POOL_CACHE_TTL = 60 * 60; // 1 hr
|
|
230
|
+
const POOL_EVENT_DISABLED_TTL = 5 * 60; // 5 min
|
|
231
|
+
const POOL_EVENT_REENABLE_DELAY = 7 * 24 * 60 * 60; // 1 week
|
|
232
|
+
|
|
233
|
+
function typecastReadOnlyPoolState(pool: DeepReadonly<PoolState>): PoolState {
|
|
234
|
+
return _.cloneDeep(pool) as PoolState;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export class BalancerV2EventPool extends StatefulEventSubscriber<PoolStateMap> {
|
|
238
|
+
public vaultInterface: Interface;
|
|
239
|
+
|
|
240
|
+
handlers: {
|
|
241
|
+
[event: string]: (event: any, pool: PoolState, log: Log) => PoolState;
|
|
242
|
+
} = {};
|
|
243
|
+
|
|
244
|
+
pools: {
|
|
245
|
+
[type: string]:
|
|
246
|
+
| WeightedPool
|
|
247
|
+
| StablePool
|
|
248
|
+
| LinearPool
|
|
249
|
+
| PhantomStablePool
|
|
250
|
+
| Gyro3Pool
|
|
251
|
+
| GyroEPool;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
public allPools: SubgraphPoolBase[] = [];
|
|
255
|
+
vaultDecoder: (log: Log) => any;
|
|
256
|
+
|
|
257
|
+
buySupportedPoolTypes: Set<BalancerPoolTypes> = new Set([
|
|
258
|
+
BalancerPoolTypes.Weighted,
|
|
259
|
+
BalancerPoolTypes.GyroE,
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
eventSupportedPoolTypes: BalancerPoolTypes[] = [
|
|
263
|
+
BalancerPoolTypes.Stable,
|
|
264
|
+
BalancerPoolTypes.Weighted,
|
|
265
|
+
BalancerPoolTypes.LiquidityBootstrapping,
|
|
266
|
+
BalancerPoolTypes.Investment,
|
|
267
|
+
|
|
268
|
+
// Need to check if we can support these pools with event base
|
|
269
|
+
// BalancerPoolTypes.ComposableStable,
|
|
270
|
+
// BalancerPoolTypes.Linear,
|
|
271
|
+
// BalancerPoolTypes.MetaStable,
|
|
272
|
+
// BalancerPoolTypes.AaveLinear,
|
|
273
|
+
// BalancerPoolTypes.ERC4626Linear,
|
|
274
|
+
|
|
275
|
+
// If this pool is enabled as event supported, it is failing BeetsFi: can not decode getRate()
|
|
276
|
+
// BalancerPoolTypes.StablePhantom,
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
eventRemovedPools = (
|
|
280
|
+
[
|
|
281
|
+
// Gradual weight changes are not currently handled in event system
|
|
282
|
+
// This pool keeps changing weights and is causing pricing issue
|
|
283
|
+
// But should now be handled by eventDisabledPools so don't need here!
|
|
284
|
+
//'0x34809aEDF93066b49F638562c42A9751eDb36DF5',
|
|
285
|
+
] as Address[]
|
|
286
|
+
).map(s => s.toLowerCase());
|
|
287
|
+
|
|
288
|
+
constructor(
|
|
289
|
+
parentName: string,
|
|
290
|
+
protected network: number,
|
|
291
|
+
public vaultAddress: Address,
|
|
292
|
+
protected subgraphURL: string,
|
|
293
|
+
protected dexHelper: IDexHelper,
|
|
294
|
+
logger: Logger,
|
|
295
|
+
) {
|
|
296
|
+
super(parentName, vaultAddress, dexHelper, logger);
|
|
297
|
+
this.vaultInterface = new Interface(VaultABI);
|
|
298
|
+
const weightedPool = new WeightedPool(
|
|
299
|
+
this.vaultAddress,
|
|
300
|
+
this.vaultInterface,
|
|
301
|
+
);
|
|
302
|
+
const stablePool = new StablePool(this.vaultAddress, this.vaultInterface);
|
|
303
|
+
const stablePhantomPool = new PhantomStablePool(
|
|
304
|
+
this.vaultAddress,
|
|
305
|
+
this.vaultInterface,
|
|
306
|
+
);
|
|
307
|
+
/*
|
|
308
|
+
ComposableStable has same maths as StablePhantom.
|
|
309
|
+
The main difference is that ComposableStables have join/exit functions when StablePhantom did not.
|
|
310
|
+
The difference of note for swaps is ComposableStable must use 'actualSupply' instead of VirtualSupply.
|
|
311
|
+
VirtualSupply could be calculated easily whereas actualSupply cannot hence the use of onchain call.
|
|
312
|
+
*/
|
|
313
|
+
const composableStable = new PhantomStablePool(
|
|
314
|
+
this.vaultAddress,
|
|
315
|
+
this.vaultInterface,
|
|
316
|
+
true,
|
|
317
|
+
);
|
|
318
|
+
const linearPool = new LinearPool(this.vaultAddress, this.vaultInterface);
|
|
319
|
+
const gyro3Pool = new Gyro3Pool(this.vaultAddress, this.vaultInterface);
|
|
320
|
+
const gyroEPool = new GyroEPool(this.vaultAddress, this.vaultInterface);
|
|
321
|
+
|
|
322
|
+
this.pools = {};
|
|
323
|
+
this.pools[BalancerPoolTypes.Weighted] = weightedPool;
|
|
324
|
+
this.pools[BalancerPoolTypes.Stable] = stablePool;
|
|
325
|
+
this.pools[BalancerPoolTypes.MetaStable] = stablePool;
|
|
326
|
+
this.pools[BalancerPoolTypes.LiquidityBootstrapping] = weightedPool;
|
|
327
|
+
this.pools[BalancerPoolTypes.Investment] = weightedPool;
|
|
328
|
+
this.pools[BalancerPoolTypes.StablePhantom] = stablePhantomPool;
|
|
329
|
+
this.pools[BalancerPoolTypes.ComposableStable] = composableStable;
|
|
330
|
+
// All these Linear pool have same maths and ABI as AaveLinear but have different factories
|
|
331
|
+
this.pools[BalancerPoolTypes.AaveLinear] = linearPool;
|
|
332
|
+
this.pools[BalancerPoolTypes.ERC4626Linear] = linearPool;
|
|
333
|
+
this.pools[BalancerPoolTypes.GearboxLinear] = linearPool;
|
|
334
|
+
this.pools[BalancerPoolTypes.MidasLinear] = linearPool;
|
|
335
|
+
this.pools[BalancerPoolTypes.ReaperLinear] = linearPool;
|
|
336
|
+
this.pools[BalancerPoolTypes.SiloLinear] = linearPool;
|
|
337
|
+
this.pools[BalancerPoolTypes.TetuLinear] = linearPool;
|
|
338
|
+
this.pools[BalancerPoolTypes.YearnLinear] = linearPool;
|
|
339
|
+
this.pools[BalancerPoolTypes.BeefyLinear] = linearPool;
|
|
340
|
+
// Beets uses "Linear" generically for all linear pool types
|
|
341
|
+
this.pools[BalancerPoolTypes.Linear] = linearPool;
|
|
342
|
+
|
|
343
|
+
this.pools[BalancerPoolTypes.Gyro3] = gyro3Pool;
|
|
344
|
+
this.pools[BalancerPoolTypes.GyroE] = gyroEPool;
|
|
345
|
+
|
|
346
|
+
this.vaultDecoder = (log: Log) => this.vaultInterface.parseLog(log);
|
|
347
|
+
this.addressesSubscribed = [vaultAddress];
|
|
348
|
+
|
|
349
|
+
// Add default handlers
|
|
350
|
+
this.handlers['Swap'] = this.handleSwap.bind(this);
|
|
351
|
+
this.handlers['PoolBalanceChanged'] =
|
|
352
|
+
this.handlePoolBalanceChanged.bind(this);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
protected processLog(
|
|
356
|
+
state: DeepReadonly<PoolStateMap>,
|
|
357
|
+
log: Readonly<Log>,
|
|
358
|
+
): DeepReadonly<PoolStateMap> | null {
|
|
359
|
+
const _state: PoolStateMap = {};
|
|
360
|
+
for (const [address, pool] of Object.entries(state))
|
|
361
|
+
_state[address] = typecastReadOnlyPoolState(pool);
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const event = this.vaultDecoder(log);
|
|
365
|
+
if (event.name in this.handlers) {
|
|
366
|
+
const poolAddress = event.args.poolId.slice(0, 42).toLowerCase();
|
|
367
|
+
// Only update the _state if we are tracking the pool
|
|
368
|
+
if (poolAddress in _state) {
|
|
369
|
+
_state[poolAddress] = this.handlers[event.name](
|
|
370
|
+
event,
|
|
371
|
+
_state[poolAddress],
|
|
372
|
+
log,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return _state;
|
|
377
|
+
} catch (e) {
|
|
378
|
+
this.logger.error(
|
|
379
|
+
`Error_${this.parentName}_processLog could not parse the log with topic ${log.topics}:`,
|
|
380
|
+
e,
|
|
381
|
+
);
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async fetchAllSubgraphPools(): Promise<SubgraphPoolBase[]> {
|
|
387
|
+
const cacheKey = 'BalancerV2SubgraphPools2';
|
|
388
|
+
const cachedPools = await this.dexHelper.cache.get(
|
|
389
|
+
this.parentName,
|
|
390
|
+
this.network,
|
|
391
|
+
cacheKey,
|
|
392
|
+
);
|
|
393
|
+
if (cachedPools) {
|
|
394
|
+
const allPools = JSON.parse(cachedPools);
|
|
395
|
+
this.logger.info(
|
|
396
|
+
`Got ${allPools.length} ${this.parentName}_${this.network} pools from cache`,
|
|
397
|
+
);
|
|
398
|
+
return allPools;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
this.logger.info(
|
|
402
|
+
`Fetching ${this.parentName}_${this.network} Pools from subgraph`,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const variables = {
|
|
406
|
+
count: MAX_POOL_CNT,
|
|
407
|
+
};
|
|
408
|
+
const { data } = await this.dexHelper.httpRequest.post(
|
|
409
|
+
this.subgraphURL,
|
|
410
|
+
{ query: fetchAllPools, variables },
|
|
411
|
+
SUBGRAPH_TIMEOUT,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
if (!(data && data.pools))
|
|
415
|
+
throw new Error('Unable to fetch pools from the subgraph');
|
|
416
|
+
|
|
417
|
+
const poolsMap = keyBy(data.pools, 'address');
|
|
418
|
+
const allPools: SubgraphPoolBase[] = data.pools.map(
|
|
419
|
+
(pool: Omit<SubgraphPoolBase, 'mainTokens'>) => ({
|
|
420
|
+
...pool,
|
|
421
|
+
mainTokens: poolGetMainTokens(pool, poolsMap),
|
|
422
|
+
tokensMap: pool.tokens.reduce(
|
|
423
|
+
(acc, token) => ({ ...acc, [token.address.toLowerCase()]: token }),
|
|
424
|
+
{},
|
|
425
|
+
),
|
|
426
|
+
}),
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
this.dexHelper.cache.setex(
|
|
430
|
+
this.parentName,
|
|
431
|
+
this.network,
|
|
432
|
+
cacheKey,
|
|
433
|
+
POOL_CACHE_TTL,
|
|
434
|
+
JSON.stringify(allPools),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
this.logger.info(
|
|
438
|
+
`Got ${allPools.length} ${this.parentName}_${this.network} pools from subgraph`,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
return allPools;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async generateState(blockNumber: number): Promise<Readonly<PoolStateMap>> {
|
|
445
|
+
const allPools = await this.fetchAllSubgraphPools();
|
|
446
|
+
this.allPools = allPools;
|
|
447
|
+
|
|
448
|
+
const eventSupportedPools = allPools.filter(
|
|
449
|
+
pool =>
|
|
450
|
+
this.eventSupportedPoolTypes.includes(pool.poolType) &&
|
|
451
|
+
!this.eventRemovedPools.includes(pool.address.toLowerCase()),
|
|
452
|
+
);
|
|
453
|
+
const allPoolsLatestState = await this.getOnChainState(
|
|
454
|
+
eventSupportedPools,
|
|
455
|
+
blockNumber,
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
return allPoolsLatestState;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
handleSwap(event: any, pool: PoolState, log: Log): PoolState {
|
|
462
|
+
const tokenIn = event.args.tokenIn.toLowerCase();
|
|
463
|
+
const amountIn = BigInt(event.args.amountIn.toString());
|
|
464
|
+
const tokenOut = event.args.tokenOut.toLowerCase();
|
|
465
|
+
const amountOut = BigInt(event.args.amountOut.toString());
|
|
466
|
+
pool.tokens[tokenIn].balance += amountIn;
|
|
467
|
+
pool.tokens[tokenOut].balance -= amountOut;
|
|
468
|
+
return pool;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
handlePoolBalanceChanged(event: any, pool: PoolState, log: Log): PoolState {
|
|
472
|
+
const tokens = event.args.tokens.map((t: string) => t.toLowerCase());
|
|
473
|
+
const deltas = event.args.deltas.map((d: any) => BigInt(d.toString()));
|
|
474
|
+
const fees = event.args.protocolFeeAmounts.map((d: any) =>
|
|
475
|
+
BigInt(d.toString()),
|
|
476
|
+
) as bigint[];
|
|
477
|
+
tokens.forEach((t: string, i: number) => {
|
|
478
|
+
const diff = deltas[i] - fees[i];
|
|
479
|
+
pool.tokens[t].balance += diff;
|
|
480
|
+
});
|
|
481
|
+
return pool;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
isSupportedPool(poolType: string): boolean {
|
|
485
|
+
const supportedPoolTypes: string[] = Object.values(BalancerPoolTypes);
|
|
486
|
+
return supportedPoolTypes.includes(poolType);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
getPricesPool(
|
|
490
|
+
from: Token,
|
|
491
|
+
to: Token,
|
|
492
|
+
subgraphPool: SubgraphPoolBase,
|
|
493
|
+
poolState: PoolState,
|
|
494
|
+
amounts: bigint[],
|
|
495
|
+
unitVolume: bigint,
|
|
496
|
+
side: SwapSide,
|
|
497
|
+
): { unit: bigint; prices: bigint[] } | null {
|
|
498
|
+
if (!this.isSupportedPool(subgraphPool.poolType)) {
|
|
499
|
+
this.logger.error(`Unsupported Pool Type: ${subgraphPool.poolType}`);
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (
|
|
504
|
+
side === SwapSide.BUY &&
|
|
505
|
+
!this.buySupportedPoolTypes.has(subgraphPool.poolType)
|
|
506
|
+
) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const amountWithoutZero = amounts.slice(1);
|
|
511
|
+
const pool = this.pools[subgraphPool.poolType];
|
|
512
|
+
|
|
513
|
+
const poolPairData = pool.parsePoolPairData(
|
|
514
|
+
subgraphPool,
|
|
515
|
+
poolState,
|
|
516
|
+
from.address,
|
|
517
|
+
to.address,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const swapMaxAmount = pool.getSwapMaxAmount(
|
|
521
|
+
// Don't like this but don't have time to refactor it properly
|
|
522
|
+
poolPairData as any,
|
|
523
|
+
side,
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const checkedAmounts: bigint[] = new Array(amountWithoutZero.length).fill(
|
|
527
|
+
0n,
|
|
528
|
+
);
|
|
529
|
+
const checkedUnitVolume = pool._nullifyIfMaxAmountExceeded(
|
|
530
|
+
unitVolume,
|
|
531
|
+
swapMaxAmount,
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
let nonZeroAmountIndex = 0;
|
|
535
|
+
for (const [i, amountIn] of amountWithoutZero.entries()) {
|
|
536
|
+
const checkedOutput = pool._nullifyIfMaxAmountExceeded(
|
|
537
|
+
amountIn,
|
|
538
|
+
swapMaxAmount,
|
|
539
|
+
);
|
|
540
|
+
if (checkedOutput === 0n) {
|
|
541
|
+
// Stop earlier because other values are bigger and for sure wont' be tradable
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
nonZeroAmountIndex = i + 1;
|
|
545
|
+
checkedAmounts[i] = checkedOutput;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (nonZeroAmountIndex === 0) {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const unitResult =
|
|
553
|
+
checkedUnitVolume === 0n
|
|
554
|
+
? 0n
|
|
555
|
+
: side === SwapSide.SELL
|
|
556
|
+
? pool.onSell([checkedUnitVolume], poolPairData as any)[0]
|
|
557
|
+
: pool.onBuy([checkedUnitVolume], poolPairData as any)[0];
|
|
558
|
+
|
|
559
|
+
const prices: bigint[] = new Array(amounts.length).fill(0n);
|
|
560
|
+
|
|
561
|
+
const outputs =
|
|
562
|
+
side === SwapSide.SELL
|
|
563
|
+
? pool.onSell(
|
|
564
|
+
amountWithoutZero.slice(0, nonZeroAmountIndex),
|
|
565
|
+
poolPairData as any,
|
|
566
|
+
)
|
|
567
|
+
: pool.onBuy(
|
|
568
|
+
amountWithoutZero.slice(0, nonZeroAmountIndex),
|
|
569
|
+
poolPairData as any,
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
assert(
|
|
573
|
+
outputs.length <= prices.length,
|
|
574
|
+
`Wrong length logic: outputs.length (${outputs.length}) <= prices.length (${prices.length})`,
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
for (const [i, output] of outputs.entries()) {
|
|
578
|
+
// Outputs shifted right to one to keep first entry as 0
|
|
579
|
+
prices[i + 1] = output;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { unit: unitResult, prices };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async getOnChainState(
|
|
586
|
+
subgraphPoolBase: SubgraphPoolBase[],
|
|
587
|
+
blockNumber: number,
|
|
588
|
+
): Promise<PoolStateMap> {
|
|
589
|
+
const multiCallData = subgraphPoolBase
|
|
590
|
+
.map(pool => {
|
|
591
|
+
if (!this.isSupportedPool(pool.poolType)) return [];
|
|
592
|
+
|
|
593
|
+
return this.pools[pool.poolType].getOnChainCalls(pool);
|
|
594
|
+
})
|
|
595
|
+
.flat();
|
|
596
|
+
|
|
597
|
+
// 500 is an arbitrary number chosen based on the blockGasLimit
|
|
598
|
+
const slicedMultiCallData = _.chunk(multiCallData, 500);
|
|
599
|
+
|
|
600
|
+
const returnData = (
|
|
601
|
+
await Promise.all(
|
|
602
|
+
slicedMultiCallData.map(async _multiCallData =>
|
|
603
|
+
this.dexHelper.multiContract.methods
|
|
604
|
+
.tryAggregate(false, _multiCallData)
|
|
605
|
+
.call({}, blockNumber),
|
|
606
|
+
),
|
|
607
|
+
)
|
|
608
|
+
).flat();
|
|
609
|
+
|
|
610
|
+
let i = 0;
|
|
611
|
+
const onChainStateMap = subgraphPoolBase.reduce(
|
|
612
|
+
(acc: { [address: string]: PoolState }, pool) => {
|
|
613
|
+
if (!this.isSupportedPool(pool.poolType)) return acc;
|
|
614
|
+
|
|
615
|
+
const [decoded, newIndex] = this.pools[
|
|
616
|
+
pool.poolType
|
|
617
|
+
].decodeOnChainCalls(pool, returnData, i);
|
|
618
|
+
i = newIndex;
|
|
619
|
+
acc = { ...acc, ...decoded };
|
|
620
|
+
return acc;
|
|
621
|
+
},
|
|
622
|
+
{},
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
return onChainStateMap;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export class BalancerV2
|
|
630
|
+
extends SimpleExchange
|
|
631
|
+
implements
|
|
632
|
+
IDex<
|
|
633
|
+
BalancerV2Data,
|
|
634
|
+
BalancerV2DirectParam | BalancerV2DirectParamV6Swap,
|
|
635
|
+
OptimizedBalancerV2Data
|
|
636
|
+
>
|
|
637
|
+
{
|
|
638
|
+
public eventPools: BalancerV2EventPool;
|
|
639
|
+
|
|
640
|
+
readonly hasConstantPriceLargeAmounts = false;
|
|
641
|
+
readonly isFeeOnTransferSupported = false;
|
|
642
|
+
readonly needWrapNative = true; // temporary
|
|
643
|
+
|
|
644
|
+
readonly directSwapIface = new Interface(DirectSwapABI);
|
|
645
|
+
readonly balancerVaultInterface = new Interface(BalancerVaultABI);
|
|
646
|
+
|
|
647
|
+
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
648
|
+
getDexKeysWithNetwork(BalancerConfig);
|
|
649
|
+
|
|
650
|
+
logger: Logger;
|
|
651
|
+
|
|
652
|
+
// In memory pool state for non-event pools
|
|
653
|
+
nonEventPoolStateCache: PoolStateCache;
|
|
654
|
+
|
|
655
|
+
eventDisabledPoolsTimer?: NodeJS.Timer;
|
|
656
|
+
eventDisabledPools: Address[] = [];
|
|
657
|
+
|
|
658
|
+
constructor(
|
|
659
|
+
protected network: Network,
|
|
660
|
+
dexKey: string,
|
|
661
|
+
public dexHelper: IDexHelper,
|
|
662
|
+
public vaultAddress: Address = BalancerConfig[dexKey][network].vaultAddress,
|
|
663
|
+
protected subgraphURL: string = BalancerConfig[dexKey][network].subgraphURL,
|
|
664
|
+
protected adapters = Adapters[network],
|
|
665
|
+
) {
|
|
666
|
+
super(dexHelper, dexKey);
|
|
667
|
+
// Initialise cache - this will hold pool state of non-event pools in memory to be reused if block hasn't expired
|
|
668
|
+
this.nonEventPoolStateCache = { blockNumber: 0, poolState: {} };
|
|
669
|
+
this.logger = dexHelper.getLogger(dexKey);
|
|
670
|
+
this.eventPools = new BalancerV2EventPool(
|
|
671
|
+
dexKey,
|
|
672
|
+
network,
|
|
673
|
+
vaultAddress,
|
|
674
|
+
subgraphURL,
|
|
675
|
+
dexHelper,
|
|
676
|
+
this.logger,
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async setupEventPools(blockNumber: number) {
|
|
681
|
+
await this.eventPools.initialize(blockNumber);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
async fetchEventDisabledPools() {
|
|
685
|
+
const cacheKey = 'eventDisabledPools';
|
|
686
|
+
const poolAddressListFromCache = await this.dexHelper.cache.get(
|
|
687
|
+
this.dexKey,
|
|
688
|
+
this.network,
|
|
689
|
+
cacheKey,
|
|
690
|
+
);
|
|
691
|
+
if (poolAddressListFromCache) {
|
|
692
|
+
this.eventDisabledPools = JSON.parse(poolAddressListFromCache);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
this.logger.info(
|
|
696
|
+
`Fetching ${this.dexKey}_${this.network} Weight Updates from subgraph`,
|
|
697
|
+
);
|
|
698
|
+
const timeNow = Math.floor(Date.now() / 1000);
|
|
699
|
+
const variables = {
|
|
700
|
+
count: MAX_POOL_CNT,
|
|
701
|
+
timestampPast: timeNow - POOL_EVENT_REENABLE_DELAY,
|
|
702
|
+
timestampFuture: timeNow + POOL_EVENT_DISABLED_TTL,
|
|
703
|
+
};
|
|
704
|
+
const { data } = await this.dexHelper.httpRequest.post(
|
|
705
|
+
this.subgraphURL,
|
|
706
|
+
{ query: fetchWeightUpdating, variables },
|
|
707
|
+
SUBGRAPH_TIMEOUT,
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
if (!(data && data.gradualWeightUpdates)) {
|
|
711
|
+
throw new Error(
|
|
712
|
+
`${this.dexKey}_${this.network} failed to fetch weight updates from subgraph`,
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
this.eventDisabledPools = _.uniq(
|
|
717
|
+
data.gradualWeightUpdates.map(
|
|
718
|
+
(wu: { poolId: { address: Address } }) => wu.poolId.address,
|
|
719
|
+
),
|
|
720
|
+
);
|
|
721
|
+
const poolAddressList = JSON.stringify(this.eventDisabledPools);
|
|
722
|
+
this.logger.info(
|
|
723
|
+
`Pools blocked from event based on ${this.dexKey}_${this.network}: ${poolAddressList}`,
|
|
724
|
+
);
|
|
725
|
+
this.dexHelper.cache.setex(
|
|
726
|
+
this.dexKey,
|
|
727
|
+
this.network,
|
|
728
|
+
cacheKey,
|
|
729
|
+
POOL_EVENT_DISABLED_TTL,
|
|
730
|
+
poolAddressList,
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async initializePricing(blockNumber: number) {
|
|
735
|
+
if (!this.eventDisabledPoolsTimer) {
|
|
736
|
+
await this.fetchEventDisabledPools();
|
|
737
|
+
this.eventDisabledPoolsTimer = setInterval(async () => {
|
|
738
|
+
try {
|
|
739
|
+
await this.fetchEventDisabledPools();
|
|
740
|
+
} catch (e) {
|
|
741
|
+
this.logger.error(
|
|
742
|
+
`${this.dexKey}: Failed to update event disabled pools:`,
|
|
743
|
+
e,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
}, POOL_EVENT_DISABLED_TTL * 1000);
|
|
747
|
+
}
|
|
748
|
+
await this.setupEventPools(blockNumber);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
releaseResources(): void {
|
|
752
|
+
if (this.eventDisabledPoolsTimer) {
|
|
753
|
+
clearInterval(this.eventDisabledPoolsTimer);
|
|
754
|
+
this.eventDisabledPoolsTimer = undefined;
|
|
755
|
+
this.logger.info(
|
|
756
|
+
`${this.dexKey}: cleared eventDisabledPoolsTimer before shutting down`,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
getPoolsWithTokenPair(from: Token, to: Token): SubgraphPoolBase[] {
|
|
762
|
+
const pools = this.eventPools.allPools.filter(p => {
|
|
763
|
+
const fromMain = p.mainTokens.find(
|
|
764
|
+
token => token.address.toLowerCase() === from.address.toLowerCase(),
|
|
765
|
+
);
|
|
766
|
+
const toMain = p.mainTokens.find(
|
|
767
|
+
token => token.address.toLowerCase() === to.address.toLowerCase(),
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
return (
|
|
771
|
+
fromMain &&
|
|
772
|
+
toMain &&
|
|
773
|
+
// filter instances similar to the following:
|
|
774
|
+
// USDC -> DAI in a pool where bbaUSD is nested (ie: MAI / bbaUSD)
|
|
775
|
+
!(fromMain.isDeeplyNested && toMain.isDeeplyNested)
|
|
776
|
+
);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
return pools.slice(0, 10);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
783
|
+
return this.adapters[side] ? this.adapters[side] : null;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async getPoolIdentifiers(
|
|
787
|
+
from: Token,
|
|
788
|
+
to: Token,
|
|
789
|
+
side: SwapSide,
|
|
790
|
+
blockNumber: number,
|
|
791
|
+
): Promise<string[]> {
|
|
792
|
+
const _from = this.dexHelper.config.wrapETH(from);
|
|
793
|
+
const _to = this.dexHelper.config.wrapETH(to);
|
|
794
|
+
|
|
795
|
+
const pools = this.getPoolsWithTokenPair(_from, _to);
|
|
796
|
+
|
|
797
|
+
return pools.map(
|
|
798
|
+
({ address }) => `${this.dexKey}_${address.toLowerCase()}`,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Returns cached poolState if blockNumber matches cached value. Resets if not.
|
|
804
|
+
*/
|
|
805
|
+
private getNonEventPoolStateCache(blockNumber: number): PoolStateMap {
|
|
806
|
+
if (this.nonEventPoolStateCache.blockNumber !== blockNumber)
|
|
807
|
+
this.nonEventPoolStateCache.poolState = {};
|
|
808
|
+
return this.nonEventPoolStateCache.poolState;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Update poolState cache.
|
|
813
|
+
* If same blockNumber as current cache then update with new pool state.
|
|
814
|
+
* If different blockNumber overwrite cache with latest.
|
|
815
|
+
*/
|
|
816
|
+
private updateNonEventPoolStateCache(
|
|
817
|
+
poolState: PoolStateMap,
|
|
818
|
+
blockNumber: number,
|
|
819
|
+
): PoolStateMap {
|
|
820
|
+
if (this.nonEventPoolStateCache.blockNumber !== blockNumber) {
|
|
821
|
+
this.nonEventPoolStateCache.blockNumber = blockNumber;
|
|
822
|
+
this.nonEventPoolStateCache.poolState = poolState;
|
|
823
|
+
} else
|
|
824
|
+
this.nonEventPoolStateCache.poolState = {
|
|
825
|
+
...this.nonEventPoolStateCache.poolState,
|
|
826
|
+
...poolState,
|
|
827
|
+
};
|
|
828
|
+
return this.nonEventPoolStateCache.poolState;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async getPricesVolume(
|
|
832
|
+
from: Token,
|
|
833
|
+
to: Token,
|
|
834
|
+
amounts: bigint[],
|
|
835
|
+
side: SwapSide,
|
|
836
|
+
blockNumber: number,
|
|
837
|
+
limitPools?: string[],
|
|
838
|
+
): Promise<null | ExchangePrices<BalancerV2Data>> {
|
|
839
|
+
try {
|
|
840
|
+
const _from = this.dexHelper.config.wrapETH(from);
|
|
841
|
+
const _to = this.dexHelper.config.wrapETH(to);
|
|
842
|
+
|
|
843
|
+
if (_from.address === _to.address) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const allPools = this.getPoolsWithTokenPair(_from, _to);
|
|
848
|
+
|
|
849
|
+
const allowedPools = limitPools
|
|
850
|
+
? allPools.filter(({ address }) =>
|
|
851
|
+
limitPools.includes(`${this.dexKey}_${address.toLowerCase()}`),
|
|
852
|
+
)
|
|
853
|
+
: allPools;
|
|
854
|
+
|
|
855
|
+
if (!allowedPools.length) return null;
|
|
856
|
+
|
|
857
|
+
const eventPoolStatesRO = await this.eventPools.getState(blockNumber);
|
|
858
|
+
if (!eventPoolStatesRO) {
|
|
859
|
+
this.logger.error(`getState returned null`);
|
|
860
|
+
}
|
|
861
|
+
const eventPoolStates = { ...(eventPoolStatesRO || {}) };
|
|
862
|
+
for (const addr of this.eventDisabledPools) delete eventPoolStates[addr];
|
|
863
|
+
|
|
864
|
+
// Fetch previously cached non-event pool states
|
|
865
|
+
let nonEventPoolStates = this.getNonEventPoolStateCache(blockNumber);
|
|
866
|
+
|
|
867
|
+
//get all pools that would be used in the paths, nested pools included
|
|
868
|
+
const poolsFlattened = getAllPoolsUsedInPaths(
|
|
869
|
+
_from.address,
|
|
870
|
+
_to.address,
|
|
871
|
+
allowedPools,
|
|
872
|
+
this.poolAddressMap,
|
|
873
|
+
side,
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
// Missing pools are pools that don't already exist in event or non-event
|
|
877
|
+
const missingPools = poolsFlattened.filter(
|
|
878
|
+
pool =>
|
|
879
|
+
!(
|
|
880
|
+
pool.address.toLowerCase() in eventPoolStates ||
|
|
881
|
+
pool.address.toLowerCase() in nonEventPoolStates
|
|
882
|
+
),
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
// Retrieve onchain state for any missing pools
|
|
886
|
+
if (missingPools.length > 0) {
|
|
887
|
+
const missingPoolsStateMap = await this.eventPools.getOnChainState(
|
|
888
|
+
missingPools,
|
|
889
|
+
blockNumber,
|
|
890
|
+
);
|
|
891
|
+
// Update non-event pool state cache with newly retrieved data so it can be reused in future
|
|
892
|
+
nonEventPoolStates = this.updateNonEventPoolStateCache(
|
|
893
|
+
missingPoolsStateMap,
|
|
894
|
+
blockNumber,
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const poolPrices = allowedPools
|
|
899
|
+
.map((pool: SubgraphPoolBase) => {
|
|
900
|
+
try {
|
|
901
|
+
const poolAddress = pool.address.toLowerCase();
|
|
902
|
+
|
|
903
|
+
const path = poolGetPathForTokenInOut(
|
|
904
|
+
_from.address,
|
|
905
|
+
_to.address,
|
|
906
|
+
pool,
|
|
907
|
+
this.poolAddressMap,
|
|
908
|
+
side,
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
let pathAmounts = amounts;
|
|
912
|
+
let resOut: { unit: bigint; prices: bigint[] } | null = null;
|
|
913
|
+
|
|
914
|
+
for (let i = 0; i < path.length; i++) {
|
|
915
|
+
const poolAddress = path[i].pool.address.toLowerCase();
|
|
916
|
+
const poolState = (eventPoolStates[poolAddress] ||
|
|
917
|
+
nonEventPoolStates[poolAddress]) as PoolState | undefined;
|
|
918
|
+
if (!poolState) {
|
|
919
|
+
this.logger.error(
|
|
920
|
+
`Unable to find the poolState ${poolAddress}`,
|
|
921
|
+
);
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const unitVolume = getBigIntPow(
|
|
926
|
+
(side === SwapSide.SELL ? path[i].tokenIn : path[i].tokenOut)
|
|
927
|
+
.decimals,
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
const res = this.eventPools.getPricesPool(
|
|
931
|
+
path[i].tokenIn,
|
|
932
|
+
path[i].tokenOut,
|
|
933
|
+
path[i].pool,
|
|
934
|
+
poolState,
|
|
935
|
+
pathAmounts,
|
|
936
|
+
unitVolume,
|
|
937
|
+
side,
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
if (!res) {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
pathAmounts = res.prices;
|
|
945
|
+
|
|
946
|
+
if (i === path.length - 1) {
|
|
947
|
+
resOut = res;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (!resOut) {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return {
|
|
956
|
+
unit: resOut.unit,
|
|
957
|
+
prices: resOut.prices,
|
|
958
|
+
data: {
|
|
959
|
+
poolId: pool.id,
|
|
960
|
+
},
|
|
961
|
+
poolAddresses: [poolAddress],
|
|
962
|
+
exchange: this.dexKey,
|
|
963
|
+
gasCost:
|
|
964
|
+
STABLE_GAS_COST + VARIABLE_GAS_COST_PER_CYCLE * path.length,
|
|
965
|
+
poolIdentifier: `${this.dexKey}_${poolAddress}`,
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// TODO: re-check what should be the current block time stamp
|
|
969
|
+
} catch (e) {
|
|
970
|
+
this.logger.warn(
|
|
971
|
+
`Error_getPrices ${from.symbol || from.address}, ${
|
|
972
|
+
to.symbol || to.address
|
|
973
|
+
}, ${side}, ${pool.address}:`,
|
|
974
|
+
e,
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
})
|
|
980
|
+
.filter(p => !!p);
|
|
981
|
+
return poolPrices as ExchangePrices<BalancerV2Data>;
|
|
982
|
+
} catch (e) {
|
|
983
|
+
this.logger.error(
|
|
984
|
+
`Error_getPrices ${from.symbol || from.address}, ${
|
|
985
|
+
to.symbol || to.address
|
|
986
|
+
}, ${side}:`,
|
|
987
|
+
e,
|
|
988
|
+
);
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Returns estimated gas cost of calldata for this DEX in multiSwap
|
|
994
|
+
getCalldataGasCost(
|
|
995
|
+
poolPrices: PoolPrices<BalancerV2Data>,
|
|
996
|
+
): number | number[] {
|
|
997
|
+
return (
|
|
998
|
+
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
999
|
+
CALLDATA_GAS_COST.LENGTH_LARGE +
|
|
1000
|
+
// ParentStruct header
|
|
1001
|
+
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
1002
|
+
// ParentStruct -> swaps[] header
|
|
1003
|
+
CALLDATA_GAS_COST.OFFSET_LARGE +
|
|
1004
|
+
// ParentStruct -> assets[] header
|
|
1005
|
+
CALLDATA_GAS_COST.OFFSET_LARGE +
|
|
1006
|
+
// ParentStruct -> funds
|
|
1007
|
+
CALLDATA_GAS_COST.ADDRESS +
|
|
1008
|
+
CALLDATA_GAS_COST.BOOL +
|
|
1009
|
+
CALLDATA_GAS_COST.ADDRESS +
|
|
1010
|
+
CALLDATA_GAS_COST.BOOL +
|
|
1011
|
+
// ParentStruct -> limits[] header
|
|
1012
|
+
CALLDATA_GAS_COST.OFFSET_LARGE +
|
|
1013
|
+
// ParentStruct -> deadline
|
|
1014
|
+
CALLDATA_GAS_COST.TIMESTAMP +
|
|
1015
|
+
// ParentStruct -> swaps[]
|
|
1016
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
1017
|
+
// ParentStruct -> swaps[0] header
|
|
1018
|
+
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
1019
|
+
// ParentStruct -> swaps[0] -> poolId
|
|
1020
|
+
CALLDATA_GAS_COST.FULL_WORD +
|
|
1021
|
+
// ParentStruct -> swaps[0] -> assetInIndex
|
|
1022
|
+
CALLDATA_GAS_COST.INDEX +
|
|
1023
|
+
// ParentStruct -> swaps[0] -> assetOutIndex
|
|
1024
|
+
CALLDATA_GAS_COST.INDEX +
|
|
1025
|
+
// ParentStruct -> swaps[0] -> amount
|
|
1026
|
+
CALLDATA_GAS_COST.AMOUNT +
|
|
1027
|
+
// ParentStruct -> swaps[0] -> userData header
|
|
1028
|
+
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
1029
|
+
// ParentStruct -> swaps[0] -> userData
|
|
1030
|
+
CALLDATA_GAS_COST.ZERO +
|
|
1031
|
+
// ParentStruct -> assets[]
|
|
1032
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
1033
|
+
// ParentStruct -> assets[0:2]
|
|
1034
|
+
CALLDATA_GAS_COST.ADDRESS * 2 +
|
|
1035
|
+
// ParentStruct -> limits[]
|
|
1036
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
1037
|
+
// ParentStruct -> limits[0:2]
|
|
1038
|
+
CALLDATA_GAS_COST.FULL_WORD * 2
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
getAdapterParam(
|
|
1043
|
+
srcToken: string,
|
|
1044
|
+
destToken: string,
|
|
1045
|
+
srcAmount: string,
|
|
1046
|
+
destAmount: string,
|
|
1047
|
+
data: OptimizedBalancerV2Data,
|
|
1048
|
+
side: SwapSide,
|
|
1049
|
+
): AdapterExchangeParam {
|
|
1050
|
+
const params = this.getBalancerV2BatchSwapParam(
|
|
1051
|
+
srcToken,
|
|
1052
|
+
destToken,
|
|
1053
|
+
data,
|
|
1054
|
+
side,
|
|
1055
|
+
this.dexHelper.config.data.augustusAddress!,
|
|
1056
|
+
this.dexHelper.config.data.augustusAddress!,
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
const payload = this.abiCoder.encodeParameter(
|
|
1060
|
+
{
|
|
1061
|
+
ParentStruct: {
|
|
1062
|
+
'swaps[]': {
|
|
1063
|
+
poolId: 'bytes32',
|
|
1064
|
+
assetInIndex: 'uint256',
|
|
1065
|
+
assetOutIndex: 'uint256',
|
|
1066
|
+
amount: 'uint256',
|
|
1067
|
+
userData: 'bytes',
|
|
1068
|
+
},
|
|
1069
|
+
assets: 'address[]',
|
|
1070
|
+
funds: {
|
|
1071
|
+
sender: 'address',
|
|
1072
|
+
fromInternalBalance: 'bool',
|
|
1073
|
+
recipient: 'address',
|
|
1074
|
+
toInternalBalance: 'bool',
|
|
1075
|
+
},
|
|
1076
|
+
limits: 'int256[]',
|
|
1077
|
+
deadline: 'uint256',
|
|
1078
|
+
},
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
swaps: params[1],
|
|
1082
|
+
assets: params[2],
|
|
1083
|
+
funds: params[3],
|
|
1084
|
+
limits: params[4],
|
|
1085
|
+
deadline: params[5],
|
|
1086
|
+
},
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
return {
|
|
1090
|
+
targetExchange: this.vaultAddress,
|
|
1091
|
+
payload,
|
|
1092
|
+
networkFee: '0',
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
public getBalancerV2SwapParam(
|
|
1097
|
+
srcToken: string,
|
|
1098
|
+
destToken: string,
|
|
1099
|
+
data: OptimizedBalancerV2Data,
|
|
1100
|
+
side: SwapSide,
|
|
1101
|
+
recipient: string,
|
|
1102
|
+
sender: string,
|
|
1103
|
+
): BalancerV2SwapParam {
|
|
1104
|
+
assert(data.swaps.length === 1, 'should have exactly one pool');
|
|
1105
|
+
|
|
1106
|
+
const singleSwap: BalancerV2SingleSwap = {
|
|
1107
|
+
poolId: data.swaps[0].poolId,
|
|
1108
|
+
kind:
|
|
1109
|
+
side === SwapSide.SELL ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut,
|
|
1110
|
+
assetIn:
|
|
1111
|
+
srcToken.toLowerCase() === ETHER_ADDRESS ? NULL_ADDRESS : srcToken,
|
|
1112
|
+
assetOut:
|
|
1113
|
+
destToken.toLowerCase() === ETHER_ADDRESS ? NULL_ADDRESS : destToken,
|
|
1114
|
+
amount: data.swaps[0].amount,
|
|
1115
|
+
userData: '0x',
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
const funds = {
|
|
1119
|
+
sender,
|
|
1120
|
+
recipient,
|
|
1121
|
+
fromInternalBalance: false,
|
|
1122
|
+
toInternalBalance: false,
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
const params: BalancerV2SwapParam = [
|
|
1126
|
+
singleSwap,
|
|
1127
|
+
funds,
|
|
1128
|
+
side === SwapSide.SELL ? '1' : MAX_INT,
|
|
1129
|
+
getLocalDeadlineAsFriendlyPlaceholder(),
|
|
1130
|
+
];
|
|
1131
|
+
|
|
1132
|
+
return params;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/*
|
|
1136
|
+
Algorithm to determine balancer (sender, recipient) params:
|
|
1137
|
+
|
|
1138
|
+
if version = 5
|
|
1139
|
+
sender = recipient = augustusV5
|
|
1140
|
+
else (so V6)
|
|
1141
|
+
if direct swap
|
|
1142
|
+
sender = recipient = augustusV6
|
|
1143
|
+
else (so generic swaps)
|
|
1144
|
+
if sell
|
|
1145
|
+
if swap.destToken = priceRoute.destToken <> ETH (need withdraw for eth currently, need fix in future)
|
|
1146
|
+
sender = executor and recipient = augustusV6 (skip 1 extra transfer)
|
|
1147
|
+
else
|
|
1148
|
+
sender = recipient = executor
|
|
1149
|
+
# note: we pass sender=null then the address of the executor is inferred contract side
|
|
1150
|
+
else (so buy)
|
|
1151
|
+
sender = recipient = executor
|
|
1152
|
+
*/
|
|
1153
|
+
public getBalancerV2BatchSwapParam(
|
|
1154
|
+
srcToken: string,
|
|
1155
|
+
destToken: string,
|
|
1156
|
+
data: OptimizedBalancerV2Data,
|
|
1157
|
+
side: SwapSide,
|
|
1158
|
+
recipient: string,
|
|
1159
|
+
sender: string,
|
|
1160
|
+
shouldWalkAssetsBackward?: boolean, // should do for all buy but prefer keep it under control
|
|
1161
|
+
): BalancerV2BatchSwapParam {
|
|
1162
|
+
let swapOffset = 0;
|
|
1163
|
+
let swaps: BalancerSwap[] = [];
|
|
1164
|
+
let assets: string[] = [];
|
|
1165
|
+
let limits: string[] = [];
|
|
1166
|
+
|
|
1167
|
+
for (const swapData of data.swaps) {
|
|
1168
|
+
const pool = this.poolIdMap[swapData.poolId];
|
|
1169
|
+
const hasEth = [srcToken.toLowerCase(), destToken.toLowerCase()].includes(
|
|
1170
|
+
ETHER_ADDRESS.toLowerCase(),
|
|
1171
|
+
);
|
|
1172
|
+
const _srcToken = this.dexHelper.config.wrapETH({
|
|
1173
|
+
address: srcToken,
|
|
1174
|
+
decimals: 18,
|
|
1175
|
+
}).address;
|
|
1176
|
+
const _destToken = this.dexHelper.config.wrapETH({
|
|
1177
|
+
address: destToken,
|
|
1178
|
+
decimals: 18,
|
|
1179
|
+
}).address;
|
|
1180
|
+
|
|
1181
|
+
let path = poolGetPathForTokenInOut(
|
|
1182
|
+
_srcToken,
|
|
1183
|
+
_destToken,
|
|
1184
|
+
pool,
|
|
1185
|
+
this.poolAddressMap,
|
|
1186
|
+
side,
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
if (side === SwapSide.BUY) {
|
|
1190
|
+
path = path.reverse();
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const _swaps = path.map((hop, index) => {
|
|
1194
|
+
const assetInIndex = shouldWalkAssetsBackward
|
|
1195
|
+
? swapOffset + path.length - index
|
|
1196
|
+
: swapOffset + index;
|
|
1197
|
+
|
|
1198
|
+
const assetOutIndex = shouldWalkAssetsBackward
|
|
1199
|
+
? swapOffset + path.length - index - 1
|
|
1200
|
+
: swapOffset + index + 1;
|
|
1201
|
+
|
|
1202
|
+
const amount =
|
|
1203
|
+
(side === SwapSide.SELL && index === 0) ||
|
|
1204
|
+
(side === SwapSide.BUY && index === path.length - 1)
|
|
1205
|
+
? swapData.amount
|
|
1206
|
+
: '0';
|
|
1207
|
+
|
|
1208
|
+
if (assetInIndex < 0 || assetOutIndex < 0) {
|
|
1209
|
+
const error = new Error(`Invalid indices in balancer`);
|
|
1210
|
+
this.logger.error(error.message, error);
|
|
1211
|
+
throw error;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
return {
|
|
1215
|
+
poolId: hop.pool.id,
|
|
1216
|
+
assetInIndex,
|
|
1217
|
+
assetOutIndex,
|
|
1218
|
+
amount,
|
|
1219
|
+
userData: '0x',
|
|
1220
|
+
};
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
swapOffset += path.length + 1;
|
|
1224
|
+
|
|
1225
|
+
// BalancerV2 Uses Address(0) as ETH
|
|
1226
|
+
const _assets = [_srcToken, ...path.map(hop => hop.tokenOut.address)].map(
|
|
1227
|
+
t => (hasEth && this.dexHelper.config.isWETH(t) ? NULL_ADDRESS : t),
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
const _limits = _assets.map(_ => MAX_INT);
|
|
1231
|
+
|
|
1232
|
+
swaps = swaps.concat(_swaps);
|
|
1233
|
+
assets = assets.concat(_assets);
|
|
1234
|
+
limits = limits.concat(_limits);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const funds = {
|
|
1238
|
+
sender,
|
|
1239
|
+
recipient,
|
|
1240
|
+
fromInternalBalance: false,
|
|
1241
|
+
toInternalBalance: false,
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
const params: BalancerV2BatchSwapParam = [
|
|
1245
|
+
side === SwapSide.SELL ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut,
|
|
1246
|
+
side === SwapSide.SELL ? swaps : swaps.reverse(),
|
|
1247
|
+
shouldWalkAssetsBackward ? assets.reverse() : assets,
|
|
1248
|
+
funds,
|
|
1249
|
+
limits,
|
|
1250
|
+
getLocalDeadlineAsFriendlyPlaceholder(),
|
|
1251
|
+
];
|
|
1252
|
+
|
|
1253
|
+
return params;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
static getDirectFunctionName(): string[] {
|
|
1257
|
+
return [DirectMethods.directSell, DirectMethods.directBuy];
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
getTokenFromAddress(address: Address): Token {
|
|
1261
|
+
// In this Dex decimals are not used
|
|
1262
|
+
return { address, decimals: 0 };
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
async preProcessTransaction(
|
|
1266
|
+
optimalSwapExchange: OptimalSwapExchange<OptimizedBalancerV2Data>,
|
|
1267
|
+
srcToken: Token,
|
|
1268
|
+
_0: Token,
|
|
1269
|
+
_1: SwapSide,
|
|
1270
|
+
options: PreprocessTransactionOptions,
|
|
1271
|
+
): Promise<[OptimalSwapExchange<OptimizedBalancerV2Data>, ExchangeTxInfo]> {
|
|
1272
|
+
if (!options.isDirectMethod) {
|
|
1273
|
+
return [
|
|
1274
|
+
optimalSwapExchange,
|
|
1275
|
+
{
|
|
1276
|
+
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
1277
|
+
},
|
|
1278
|
+
];
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
assert(
|
|
1282
|
+
optimalSwapExchange.data !== undefined,
|
|
1283
|
+
`preProcessTransaction: data field is missing`,
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
let isApproved: boolean | undefined;
|
|
1287
|
+
|
|
1288
|
+
try {
|
|
1289
|
+
isApproved = await this.dexHelper.augustusApprovals.hasApproval(
|
|
1290
|
+
options.executionContractAddress,
|
|
1291
|
+
this.dexHelper.config.wrapETH(srcToken).address,
|
|
1292
|
+
this.vaultAddress,
|
|
1293
|
+
);
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
this.logger.error(
|
|
1296
|
+
`preProcessTransaction failed to retrieve allowance info: `,
|
|
1297
|
+
e,
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
return [
|
|
1302
|
+
{
|
|
1303
|
+
...optimalSwapExchange,
|
|
1304
|
+
data: {
|
|
1305
|
+
...optimalSwapExchange.data,
|
|
1306
|
+
isApproved,
|
|
1307
|
+
},
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
1311
|
+
},
|
|
1312
|
+
];
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
getDirectParam(
|
|
1316
|
+
srcToken: Address,
|
|
1317
|
+
destToken: Address,
|
|
1318
|
+
srcAmount: NumberAsString,
|
|
1319
|
+
destAmount: NumberAsString,
|
|
1320
|
+
expectedAmount: NumberAsString,
|
|
1321
|
+
data: OptimizedBalancerV2Data,
|
|
1322
|
+
side: SwapSide,
|
|
1323
|
+
permit: string,
|
|
1324
|
+
uuid: string,
|
|
1325
|
+
feePercent: NumberAsString,
|
|
1326
|
+
deadline: NumberAsString,
|
|
1327
|
+
partner: string,
|
|
1328
|
+
beneficiary: string,
|
|
1329
|
+
contractMethod?: string,
|
|
1330
|
+
): TxInfo<BalancerV2DirectParam> {
|
|
1331
|
+
if (
|
|
1332
|
+
contractMethod !== DirectMethods.directSell &&
|
|
1333
|
+
contractMethod !== DirectMethods.directBuy
|
|
1334
|
+
) {
|
|
1335
|
+
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
let isApproved: boolean = !!data.isApproved;
|
|
1339
|
+
if (data.isApproved === undefined) {
|
|
1340
|
+
this.logger.warn(`isApproved is undefined, defaulting to false`);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
const [, swaps, assets, funds, limits, _deadline] =
|
|
1344
|
+
this.getBalancerV2BatchSwapParam(
|
|
1345
|
+
srcToken,
|
|
1346
|
+
destToken,
|
|
1347
|
+
data,
|
|
1348
|
+
side,
|
|
1349
|
+
this.dexHelper.config.data.augustusAddress!,
|
|
1350
|
+
this.dexHelper.config.data.augustusAddress!,
|
|
1351
|
+
);
|
|
1352
|
+
|
|
1353
|
+
const swapParams: BalancerV2DirectParam = [
|
|
1354
|
+
swaps,
|
|
1355
|
+
assets,
|
|
1356
|
+
funds,
|
|
1357
|
+
limits,
|
|
1358
|
+
srcAmount,
|
|
1359
|
+
destAmount,
|
|
1360
|
+
expectedAmount,
|
|
1361
|
+
_deadline,
|
|
1362
|
+
feePercent,
|
|
1363
|
+
this.vaultAddress,
|
|
1364
|
+
partner,
|
|
1365
|
+
isApproved,
|
|
1366
|
+
beneficiary,
|
|
1367
|
+
permit,
|
|
1368
|
+
uuidToBytes16(uuid),
|
|
1369
|
+
];
|
|
1370
|
+
|
|
1371
|
+
const encoder = (...params: BalancerV2DirectParam) => {
|
|
1372
|
+
return this.directSwapIface.encodeFunctionData(
|
|
1373
|
+
side === SwapSide.SELL
|
|
1374
|
+
? DirectMethods.directSell
|
|
1375
|
+
: DirectMethods.directBuy,
|
|
1376
|
+
[params],
|
|
1377
|
+
);
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
return {
|
|
1381
|
+
params: swapParams,
|
|
1382
|
+
encoder,
|
|
1383
|
+
networkFee: '0',
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
getDirectParamV6(
|
|
1388
|
+
srcToken: Address,
|
|
1389
|
+
destToken: Address,
|
|
1390
|
+
fromAmount: NumberAsString,
|
|
1391
|
+
toAmount: NumberAsString,
|
|
1392
|
+
quotedAmount: NumberAsString,
|
|
1393
|
+
data: OptimizedBalancerV2Data,
|
|
1394
|
+
side: SwapSide,
|
|
1395
|
+
permit: string,
|
|
1396
|
+
uuid: string,
|
|
1397
|
+
partnerAndFee: string,
|
|
1398
|
+
beneficiary: string,
|
|
1399
|
+
blockNumber: number,
|
|
1400
|
+
contractMethod?: string,
|
|
1401
|
+
) {
|
|
1402
|
+
if (!contractMethod) throw new Error(`contractMethod need to be passed`);
|
|
1403
|
+
|
|
1404
|
+
if (!BalancerV2.getDirectFunctionNameV6().includes(contractMethod!)) {
|
|
1405
|
+
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
const metadata = hexConcat([
|
|
1409
|
+
hexZeroPad(uuidToBytes16(uuid), 16),
|
|
1410
|
+
hexZeroPad(hexlify(blockNumber), 16),
|
|
1411
|
+
]);
|
|
1412
|
+
|
|
1413
|
+
const balancerBatchSwapParam = this.getBalancerV2BatchSwapParam(
|
|
1414
|
+
srcToken,
|
|
1415
|
+
destToken,
|
|
1416
|
+
data,
|
|
1417
|
+
side,
|
|
1418
|
+
this.dexHelper.config.data.augustusV6Address!,
|
|
1419
|
+
this.dexHelper.config.data.augustusV6Address!,
|
|
1420
|
+
side === SwapSide.BUY,
|
|
1421
|
+
);
|
|
1422
|
+
|
|
1423
|
+
// after getBalancerV2BatchSwapParam runs we may get more swaps so we can't decide on single vs batch before resolving it
|
|
1424
|
+
const [, swaps] = balancerBatchSwapParam;
|
|
1425
|
+
const isSingleSwap = swaps.length === 1;
|
|
1426
|
+
|
|
1427
|
+
const balancerParams = isSingleSwap
|
|
1428
|
+
? this.getBalancerV2SwapParam(
|
|
1429
|
+
srcToken,
|
|
1430
|
+
destToken,
|
|
1431
|
+
data,
|
|
1432
|
+
side,
|
|
1433
|
+
this.dexHelper.config.data.augustusV6Address!,
|
|
1434
|
+
this.dexHelper.config.data.augustusV6Address!,
|
|
1435
|
+
)
|
|
1436
|
+
: balancerBatchSwapParam;
|
|
1437
|
+
|
|
1438
|
+
const swapParams: BalancerV2DirectParamV6 = [
|
|
1439
|
+
fromAmount,
|
|
1440
|
+
toAmount,
|
|
1441
|
+
quotedAmount,
|
|
1442
|
+
metadata,
|
|
1443
|
+
this.encodeBeneficiaryAndApproveFlag(beneficiary, !data.isApproved),
|
|
1444
|
+
];
|
|
1445
|
+
|
|
1446
|
+
const encodeParams: BalancerV2DirectParamV6Swap = [
|
|
1447
|
+
swapParams,
|
|
1448
|
+
partnerAndFee,
|
|
1449
|
+
permit,
|
|
1450
|
+
balancerParams.length === 4 // TODO: upgrade ts to use isSingleSwap
|
|
1451
|
+
? this.encodeBalancerV2SwapParam(balancerParams)
|
|
1452
|
+
: this.encodeBalancerV2BatchSwapParam(balancerParams),
|
|
1453
|
+
];
|
|
1454
|
+
|
|
1455
|
+
const encoder = (...params: BalancerV2DirectParamV6Swap) => {
|
|
1456
|
+
return this.augustusV6Interface.encodeFunctionData(
|
|
1457
|
+
side === SwapSide.SELL
|
|
1458
|
+
? DirectMethodsV6.directSell
|
|
1459
|
+
: DirectMethodsV6.directBuy,
|
|
1460
|
+
[...params],
|
|
1461
|
+
);
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
return {
|
|
1465
|
+
encoder,
|
|
1466
|
+
params: encodeParams,
|
|
1467
|
+
networkFee: '0',
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
private encodeBeneficiaryAndApproveFlag(
|
|
1472
|
+
beneficiary: Address,
|
|
1473
|
+
approveFlag: boolean,
|
|
1474
|
+
) {
|
|
1475
|
+
const addressBN = BigNumber.from(beneficiary);
|
|
1476
|
+
const flagBN = approveFlag ? BigNumber.from(1).shl(255) : BigNumber.from(0);
|
|
1477
|
+
|
|
1478
|
+
return addressBN.or(flagBN).toString();
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
private encodeBalancerV2SwapParam(param: BalancerV2SwapParam): string {
|
|
1482
|
+
const [singleSwap, funds, limit, deadline] = param;
|
|
1483
|
+
|
|
1484
|
+
const encoded = this.balancerVaultInterface.encodeFunctionData('swap', [
|
|
1485
|
+
singleSwap,
|
|
1486
|
+
funds,
|
|
1487
|
+
limit,
|
|
1488
|
+
deadline,
|
|
1489
|
+
]);
|
|
1490
|
+
|
|
1491
|
+
return encoded;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
private encodeBalancerV2BatchSwapParam(
|
|
1495
|
+
param: BalancerV2BatchSwapParam,
|
|
1496
|
+
): string {
|
|
1497
|
+
const [kind, swaps, assets, funds, limits, deadline] = param;
|
|
1498
|
+
|
|
1499
|
+
const encoded = this.balancerVaultInterface.encodeFunctionData(
|
|
1500
|
+
'batchSwap',
|
|
1501
|
+
[kind, swaps, assets, funds, limits, deadline],
|
|
1502
|
+
);
|
|
1503
|
+
return encoded;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
static getDirectFunctionNameV6(): string[] {
|
|
1507
|
+
return [DirectMethodsV6.directSell, DirectMethodsV6.directBuy];
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
async getSimpleParam(
|
|
1511
|
+
srcToken: string,
|
|
1512
|
+
destToken: string,
|
|
1513
|
+
srcAmount: string,
|
|
1514
|
+
destAmount: string,
|
|
1515
|
+
data: OptimizedBalancerV2Data,
|
|
1516
|
+
side: SwapSide,
|
|
1517
|
+
): Promise<SimpleExchangeParam> {
|
|
1518
|
+
const params = this.getBalancerV2BatchSwapParam(
|
|
1519
|
+
srcToken,
|
|
1520
|
+
destToken,
|
|
1521
|
+
data,
|
|
1522
|
+
side,
|
|
1523
|
+
this.dexHelper.config.data.augustusAddress!,
|
|
1524
|
+
this.dexHelper.config.data.augustusAddress!,
|
|
1525
|
+
);
|
|
1526
|
+
|
|
1527
|
+
const swapData = this.eventPools.vaultInterface.encodeFunctionData(
|
|
1528
|
+
'batchSwap',
|
|
1529
|
+
params,
|
|
1530
|
+
);
|
|
1531
|
+
|
|
1532
|
+
return this.buildSimpleParamWithoutWETHConversion(
|
|
1533
|
+
srcToken,
|
|
1534
|
+
srcAmount,
|
|
1535
|
+
destToken,
|
|
1536
|
+
destAmount,
|
|
1537
|
+
swapData,
|
|
1538
|
+
this.vaultAddress,
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
getDexParam(
|
|
1543
|
+
srcToken: Address,
|
|
1544
|
+
destToken: Address,
|
|
1545
|
+
srcAmount: NumberAsString,
|
|
1546
|
+
destAmount: NumberAsString,
|
|
1547
|
+
recipient: Address,
|
|
1548
|
+
data: OptimizedBalancerV2Data,
|
|
1549
|
+
side: SwapSide,
|
|
1550
|
+
context: Context,
|
|
1551
|
+
): DexExchangeParam {
|
|
1552
|
+
const params = this.getBalancerV2BatchSwapParam(
|
|
1553
|
+
srcToken,
|
|
1554
|
+
destToken,
|
|
1555
|
+
data,
|
|
1556
|
+
side,
|
|
1557
|
+
recipient,
|
|
1558
|
+
side === SwapSide.BUY
|
|
1559
|
+
? this.dexHelper.config.data.executorsAddresses![Executors.THREE]
|
|
1560
|
+
: NULL_ADDRESS,
|
|
1561
|
+
);
|
|
1562
|
+
|
|
1563
|
+
let exchangeData = this.eventPools.vaultInterface.encodeFunctionData(
|
|
1564
|
+
'batchSwap',
|
|
1565
|
+
params,
|
|
1566
|
+
);
|
|
1567
|
+
let specialDexFlag = SpecialDex.DEFAULT;
|
|
1568
|
+
|
|
1569
|
+
if (side === SwapSide.SELL) {
|
|
1570
|
+
const swaps = params[1];
|
|
1571
|
+
const totalAmount = swaps.reduce<BigNumber>((acc, swap) => {
|
|
1572
|
+
return acc.add(swap.amount);
|
|
1573
|
+
}, BigNumber.from(0));
|
|
1574
|
+
|
|
1575
|
+
exchangeData = solidityPack(
|
|
1576
|
+
['bytes32', 'bytes'],
|
|
1577
|
+
[hexZeroPad(hexlify(totalAmount), 32), exchangeData],
|
|
1578
|
+
);
|
|
1579
|
+
specialDexFlag = SpecialDex.SWAP_ON_BALANCER_V2;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
return {
|
|
1583
|
+
needWrapNative: this.needWrapNative,
|
|
1584
|
+
dexFuncHasRecipient: true,
|
|
1585
|
+
exchangeData,
|
|
1586
|
+
specialDexFlag,
|
|
1587
|
+
targetExchange: this.vaultAddress,
|
|
1588
|
+
returnAmountPos: undefined,
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
async updatePoolState(): Promise<void> {
|
|
1593
|
+
this.eventPools.allPools = await this.eventPools.fetchAllSubgraphPools();
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
async getTopPoolsForToken(
|
|
1597
|
+
tokenAddress: Address,
|
|
1598
|
+
count: number,
|
|
1599
|
+
): Promise<PoolLiquidity[]> {
|
|
1600
|
+
const poolsWithToken = this.eventPools.allPools.filter(pool =>
|
|
1601
|
+
pool.mainTokens.some(mainToken =>
|
|
1602
|
+
isSameAddress(mainToken.address, tokenAddress),
|
|
1603
|
+
),
|
|
1604
|
+
);
|
|
1605
|
+
|
|
1606
|
+
const variables = {
|
|
1607
|
+
poolIds: poolsWithToken.map(pool => pool.id),
|
|
1608
|
+
count,
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
const query = `query ($poolIds: [String!]!, $count: Int) {
|
|
1612
|
+
pools (first: $count, orderBy: totalLiquidity, orderDirection: desc,
|
|
1613
|
+
where: {id_in: $poolIds,
|
|
1614
|
+
swapEnabled: true,
|
|
1615
|
+
totalLiquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()}}) {
|
|
1616
|
+
address
|
|
1617
|
+
totalLiquidity
|
|
1618
|
+
tokens {
|
|
1619
|
+
address
|
|
1620
|
+
decimals
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}`;
|
|
1624
|
+
const { data } = await this.dexHelper.httpRequest.post<{
|
|
1625
|
+
data: {
|
|
1626
|
+
pools: {
|
|
1627
|
+
address: string;
|
|
1628
|
+
totalLiquidity: string;
|
|
1629
|
+
tokens: { address: string; decimals: number }[];
|
|
1630
|
+
}[];
|
|
1631
|
+
};
|
|
1632
|
+
}>(
|
|
1633
|
+
this.subgraphURL,
|
|
1634
|
+
{
|
|
1635
|
+
query,
|
|
1636
|
+
variables,
|
|
1637
|
+
},
|
|
1638
|
+
SUBGRAPH_TIMEOUT,
|
|
1639
|
+
);
|
|
1640
|
+
|
|
1641
|
+
if (!(data && data.pools))
|
|
1642
|
+
throw new Error(
|
|
1643
|
+
`Error_${this.dexKey}_Subgraph: couldn't fetch the pools from the subgraph`,
|
|
1644
|
+
);
|
|
1645
|
+
|
|
1646
|
+
return _.map(data.pools, pool => {
|
|
1647
|
+
const subgraphPool = poolsWithToken.find(poolWithToken =>
|
|
1648
|
+
isSameAddress(poolWithToken.address, pool.address),
|
|
1649
|
+
)!;
|
|
1650
|
+
|
|
1651
|
+
return {
|
|
1652
|
+
exchange: this.dexKey,
|
|
1653
|
+
address: pool.address.toLowerCase(),
|
|
1654
|
+
connectorTokens: subgraphPool.mainTokens.filter(
|
|
1655
|
+
token => !isSameAddress(tokenAddress, token.address),
|
|
1656
|
+
),
|
|
1657
|
+
liquidityUSD: parseFloat(pool.totalLiquidity),
|
|
1658
|
+
};
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
private get poolAddressMap(): SubgraphPoolAddressDictionary {
|
|
1663
|
+
return keyBy(this.eventPools.allPools, 'address');
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
private get poolIdMap(): { [poolId: string]: SubgraphPoolBase } {
|
|
1667
|
+
return keyBy(this.eventPools.allPools, 'id');
|
|
1668
|
+
}
|
|
1669
|
+
}
|