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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +377 -377
  2. package/build/config.js +9 -5
  3. package/build/config.js.map +1 -1
  4. package/build/dex/algebra/algebra.js +25 -25
  5. package/build/dex/balancer-v1/balancer-v1.js +34 -34
  6. package/build/dex/balancer-v2/balancer-v2.d.ts +1 -1
  7. package/build/dex/balancer-v2/balancer-v2.js +65 -78
  8. package/build/dex/balancer-v2/balancer-v2.js.map +1 -1
  9. package/build/dex/camelot/camelot.js +27 -27
  10. package/build/dex/kyberdmm/kyberdmm.js +27 -27
  11. package/build/dex/pancakeswap-v3/pancakeswap-v3.js +25 -25
  12. package/build/dex/solidly/solidly.js +27 -27
  13. package/build/dex/solidly-v3/solidly-v3.js +25 -25
  14. package/build/dex/swaap-v2/config.js +15 -0
  15. package/build/dex/swaap-v2/config.js.map +1 -1
  16. package/build/dex/uniswap-v2/uniswap-v2.js +26 -26
  17. package/build/dex/uniswap-v3/uniswap-v3.js +25 -25
  18. package/funding.json +5 -5
  19. package/package.json +1 -1
  20. package/src/config.ts +500 -496
  21. package/src/constants.ts +67 -67
  22. package/src/dex/aave-v2/aave-v2.ts +409 -409
  23. package/src/dex/aave-v3/aave-v3.ts +320 -320
  24. package/src/dex/algebra/algebra.ts +1180 -1180
  25. package/src/dex/angle-staked-stable/angle-staked-stable.ts +347 -347
  26. package/src/dex/augustus-rfq.ts +180 -180
  27. package/src/dex/bProtocol/bProtocol.ts +108 -108
  28. package/src/dex/balancer-v1/balancer-v1.ts +572 -572
  29. package/src/dex/balancer-v2/balancer-v2-e2e.test.ts +1699 -1699
  30. package/src/dex/balancer-v2/balancer-v2.ts +1669 -1701
  31. package/src/dex/bancor/bancor.ts +128 -128
  32. package/src/dex/camelot/camelot.ts +873 -873
  33. package/src/dex/compound/compound.ts +107 -107
  34. package/src/dex/curve-v1/config.ts +1179 -1179
  35. package/src/dex/curve-v1/curve-v1-e2e.test.ts +507 -507
  36. package/src/dex/curve-v1/curve-v1.ts +1315 -1315
  37. package/src/dex/curve-v1-factory/config.ts +1018 -1018
  38. package/src/dex/curve-v1-factory/curve-v1-factory.ts +1288 -1288
  39. package/src/dex/curve-v2/curve-v2.ts +440 -440
  40. package/src/dex/dexalot/dexalot.ts +1100 -1100
  41. package/src/dex/dodo-v1/dodo-v1.ts +132 -132
  42. package/src/dex/dodo-v2.ts +266 -266
  43. package/src/dex/etherfi.ts +239 -239
  44. package/src/dex/generic-rfq/generic-rfq.ts +431 -431
  45. package/src/dex/gmx/gmx.ts +321 -321
  46. package/src/dex/hashflow/hashflow.ts +1135 -1135
  47. package/src/dex/idex.ts +254 -254
  48. package/src/dex/index.ts +357 -357
  49. package/src/dex/jarvis-v6/jarvis-v6.ts +509 -509
  50. package/src/dex/jarvis.ts +262 -262
  51. package/src/dex/kyberdmm/kyberdmm.ts +649 -649
  52. package/src/dex/lido/lido.ts +110 -110
  53. package/src/dex/maker-psm/maker-psm.ts +592 -592
  54. package/src/dex/maverick-v1/maverick-v1.ts +537 -537
  55. package/src/dex/nerve/config.ts +635 -635
  56. package/src/dex/nerve/nerve-e2e.test.ts +731 -731
  57. package/src/dex/nerve/nerve-events.test.ts +911 -911
  58. package/src/dex/nerve/nerve.ts +467 -467
  59. package/src/dex/onebit/onebit.ts +113 -113
  60. package/src/dex/pancakeswap-v3/pancakeswap-v3.ts +1150 -1150
  61. package/src/dex/paraswap-limit-orders/config.ts +60 -60
  62. package/src/dex/paraswap-limit-orders/paraswap-limit-orders-e2e.test.ts +137 -137
  63. package/src/dex/paraswap-limit-orders/paraswap-limit-orders-integration.test.ts +328 -328
  64. package/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts +631 -631
  65. package/src/dex/polygon-migrator/polygon-migrator.ts +194 -194
  66. package/src/dex/quick-perps/quick-perps.ts +337 -337
  67. package/src/dex/quickswap/config.ts +16 -16
  68. package/src/dex/quickswap/quickswap-e2e.test.ts +384 -384
  69. package/src/dex/smoothy/smoothy.ts +110 -110
  70. package/src/dex/solidly/config.ts +225 -225
  71. package/src/dex/solidly/solidly-e2e.test.ts +1421 -1421
  72. package/src/dex/solidly/solidly-integration.test.ts +1753 -1753
  73. package/src/dex/solidly/solidly.ts +687 -687
  74. package/src/dex/solidly-v3/solidly-v3.ts +968 -968
  75. package/src/dex/spark/spark.ts +284 -284
  76. package/src/dex/stable-pool/stable-pool.ts +129 -129
  77. package/src/dex/swaap-v2/config.ts +15 -0
  78. package/src/dex/swaap-v2/swaap-v2-e2e.test.ts +233 -17
  79. package/src/dex/swaap-v2/swaap-v2-integration.test.ts +1 -1
  80. package/src/dex/swaap-v2/swaap-v2.ts +1056 -1056
  81. package/src/dex/swell/swell.ts +312 -312
  82. package/src/dex/synthetix/synthetix.ts +420 -420
  83. package/src/dex/trader-joe-v2.1/trader-joe-v2.1.ts +180 -180
  84. package/src/dex/uniswap-v2/config.ts +637 -637
  85. package/src/dex/uniswap-v2/constants.ts +58 -58
  86. package/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts +1257 -1257
  87. package/src/dex/uniswap-v2/uniswap-v2-e2e-base.test.ts +42 -42
  88. package/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +1295 -1295
  89. package/src/dex/uniswap-v2/uniswap-v2-e2e-mainnet.test.ts +1834 -1834
  90. package/src/dex/uniswap-v2/uniswap-v2-e2e-polygon.test.ts +871 -871
  91. package/src/dex/uniswap-v2/uniswap-v2.ts +1117 -1117
  92. package/src/dex/uniswap-v3/uniswap-v3.ts +1501 -1501
  93. package/src/dex/uniswap-v3.ts +242 -242
  94. package/src/dex/weth/config.ts +73 -73
  95. package/src/dex/weth/weth.ts +230 -230
  96. package/src/dex/wombat/wombat.ts +467 -467
  97. package/src/dex/woo-fi-v2/woo-fi-v2.ts +529 -529
  98. package/src/dex/wsteth/wsteth.ts +304 -304
  99. package/src/dex/zerox/index.ts +328 -328
  100. package/src/executor/Executor01BytecodeBuilder.ts +474 -474
  101. package/src/executor/Executor02BytecodeBuilder.ts +1392 -1392
  102. package/src/executor/ExecutorBytecodeBuilder.ts +390 -390
  103. package/src/executor/ExecutorDetector.ts +131 -131
  104. package/src/executor/constants.ts +66 -66
  105. package/src/executor/executor01-bytecode-builder-e2e.test.ts +1478 -1478
  106. package/src/executor/utils.ts +62 -62
  107. package/src/router/simpleswap.ts +366 -366
  108. package/src/types.ts +342 -342
  109. package/tests/constants-e2e.ts +1632 -1622
  110. package/tests/generate-new-limit-order.ts +107 -107
@@ -1,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
- Network,
23
- NULL_ADDRESS,
24
- SUBGRAPH_TIMEOUT,
25
- SwapSide,
26
- } from '../../constants';
27
- import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
28
- import { StablePool } from './pools/stable/StablePool';
29
- import { WeightedPool } from './pools/weighted/WeightedPool';
30
- import { PhantomStablePool } from './pools/phantom-stable/PhantomStablePool';
31
- import { LinearPool } from './pools/linear/LinearPool';
32
- import { Gyro3Pool } from './pools/gyro/Gyro3Pool';
33
- import { GyroEPool } from './pools/gyro/GyroEPool';
34
- import VaultABI from '../../abi/balancer-v2/vault.json';
35
- import DirectSwapABI from '../../abi/DirectSwap.json';
36
- import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
37
- import {
38
- getBigIntPow,
39
- getDexKeysWithNetwork,
40
- uuidToBytes16,
41
- } from '../../utils';
42
- import { Context, IDex } from '../../dex/idex';
43
- import { IDexHelper } from '../../dex-helper';
44
- import {
45
- BalancerV2BatchSwapParam,
46
- BalancerPoolTypes,
47
- BalancerSwap,
48
- BalancerV2Data,
49
- BalancerV2DirectParam,
50
- OptimizedBalancerV2Data,
51
- PoolState,
52
- PoolStateCache,
53
- PoolStateMap,
54
- SubgraphPoolAddressDictionary,
55
- SubgraphPoolBase,
56
- SwapTypes,
57
- BalancerV2DirectParamV6,
58
- BalancerV2DirectParamV6Swap,
59
- BalancerV2SwapParam,
60
- BalancerV2SingleSwap,
61
- } from './types';
62
- import {
63
- getLocalDeadlineAsFriendlyPlaceholder,
64
- SimpleExchange,
65
- } from '../simple-exchange';
66
- import { Adapters, BalancerConfig } from './config';
67
- import {
68
- getAllPoolsUsedInPaths,
69
- isSameAddress,
70
- poolGetMainTokens,
71
- poolGetPathForTokenInOut,
72
- } from './utils';
73
- import {
74
- DirectMethods,
75
- DirectMethodsV6,
76
- MIN_USD_LIQUIDITY_TO_FETCH,
77
- STABLE_GAS_COST,
78
- VARIABLE_GAS_COST_PER_CYCLE,
79
- } from './constants';
80
- import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
81
- import { hexConcat, hexlify, hexZeroPad, solidityPack } from 'ethers/lib/utils';
82
- import BalancerVaultABI from '../../abi/balancer-v2/vault.json';
83
- import { BigNumber } from 'ethers';
84
- import { SpecialDex } from '../../executor/types';
85
- import { S } from '@bgd-labs/aave-address-book/dist/AaveV2Ethereum-timF4kft';
86
- import { extractReturnAmountPosition } from '../../executor/utils';
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
- executor: Address,
1552
- ): DexExchangeParam {
1553
- const balancerBatchSwapParam = this.getBalancerV2BatchSwapParam(
1554
- srcToken,
1555
- destToken,
1556
- data,
1557
- side,
1558
- recipient,
1559
- side === SwapSide.SELL ? NULL_ADDRESS : executor,
1560
- );
1561
-
1562
- const [, swaps] = balancerBatchSwapParam;
1563
- const isSingleSwap = swaps.length === 1;
1564
-
1565
- if (isSingleSwap) {
1566
- const balancerSwapParam = this.getBalancerV2SwapParam(
1567
- srcToken,
1568
- destToken,
1569
- data,
1570
- side,
1571
- recipient,
1572
- executor,
1573
- );
1574
-
1575
- const exchangeData = this.eventPools.vaultInterface.encodeFunctionData(
1576
- 'swap',
1577
- balancerSwapParam,
1578
- );
1579
-
1580
- return {
1581
- needWrapNative: this.needWrapNative,
1582
- dexFuncHasRecipient: true,
1583
- exchangeData,
1584
- targetExchange: this.vaultAddress,
1585
- returnAmountPos:
1586
- side === SwapSide.SELL
1587
- ? extractReturnAmountPosition(
1588
- this.balancerVaultInterface,
1589
- 'swap',
1590
- 'amountCalculated',
1591
- )
1592
- : undefined,
1593
- };
1594
- }
1595
-
1596
- let exchangeData = this.eventPools.vaultInterface.encodeFunctionData(
1597
- 'batchSwap',
1598
- balancerBatchSwapParam,
1599
- );
1600
- let specialDexFlag = SpecialDex.DEFAULT;
1601
-
1602
- if (side === SwapSide.SELL) {
1603
- const totalAmount = swaps.reduce<BigNumber>((acc, swap) => {
1604
- return acc.add(swap.amount);
1605
- }, BigNumber.from(0));
1606
-
1607
- exchangeData = solidityPack(
1608
- ['bytes32', 'bytes'],
1609
- [hexZeroPad(hexlify(totalAmount), 32), exchangeData],
1610
- );
1611
- specialDexFlag = SpecialDex.SWAP_ON_BALANCER_V2;
1612
- }
1613
-
1614
- return {
1615
- needWrapNative: this.needWrapNative,
1616
- dexFuncHasRecipient: true,
1617
- exchangeData,
1618
- specialDexFlag,
1619
- targetExchange: this.vaultAddress,
1620
- returnAmountPos: undefined,
1621
- };
1622
- }
1623
-
1624
- async updatePoolState(): Promise<void> {
1625
- this.eventPools.allPools = await this.eventPools.fetchAllSubgraphPools();
1626
- }
1627
-
1628
- async getTopPoolsForToken(
1629
- tokenAddress: Address,
1630
- count: number,
1631
- ): Promise<PoolLiquidity[]> {
1632
- const poolsWithToken = this.eventPools.allPools.filter(pool =>
1633
- pool.mainTokens.some(mainToken =>
1634
- isSameAddress(mainToken.address, tokenAddress),
1635
- ),
1636
- );
1637
-
1638
- const variables = {
1639
- poolIds: poolsWithToken.map(pool => pool.id),
1640
- count,
1641
- };
1642
-
1643
- const query = `query ($poolIds: [String!]!, $count: Int) {
1644
- pools (first: $count, orderBy: totalLiquidity, orderDirection: desc,
1645
- where: {id_in: $poolIds,
1646
- swapEnabled: true,
1647
- totalLiquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()}}) {
1648
- address
1649
- totalLiquidity
1650
- tokens {
1651
- address
1652
- decimals
1653
- }
1654
- }
1655
- }`;
1656
- const { data } = await this.dexHelper.httpRequest.post<{
1657
- data: {
1658
- pools: {
1659
- address: string;
1660
- totalLiquidity: string;
1661
- tokens: { address: string; decimals: number }[];
1662
- }[];
1663
- };
1664
- }>(
1665
- this.subgraphURL,
1666
- {
1667
- query,
1668
- variables,
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
+ }