@paraswap/dex-lib 4.0.23 → 4.0.24-bal-v3-nested-erc4626.0

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.
@@ -5,12 +5,14 @@ export type CommonImmutablePoolState = {
5
5
  poolType: string;
6
6
  tokens: string[];
7
7
  tokensUnderlying: (string | null)[];
8
+ tokensNestedERC4626Underlying: (string | null)[];
8
9
  weights: bigint[];
9
10
  hookType: string | undefined;
10
11
  };
11
12
  export interface CommonMutableState {
12
13
  tokenRates: bigint[];
13
14
  erc4626Rates: (bigint | null)[];
15
+ erc4626NestedRates: (bigint | null)[];
14
16
  balancesLiveScaled18: bigint[];
15
17
  swapFee: bigint;
16
18
  aggregateSwapFee: bigint;
@@ -53,11 +55,13 @@ export type DexParams = {
53
55
  balancerRouterAddress: string;
54
56
  balancerBatchRouterAddress: string;
55
57
  };
58
+ export declare enum TokenType {
59
+ MainToken = 1,
60
+ ERC4626 = 2,
61
+ ERC4626Nested = 3
62
+ }
56
63
  export type TokenInfo = {
57
- isBoosted: boolean;
58
- underlyingToken: string | null;
59
- mainToken: string;
60
64
  index: number;
61
- rate: bigint;
65
+ type: TokenType;
62
66
  };
63
67
  export {};
@@ -1,3 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenType = void 0;
4
+ var TokenType;
5
+ (function (TokenType) {
6
+ TokenType[TokenType["MainToken"] = 1] = "MainToken";
7
+ TokenType[TokenType["ERC4626"] = 2] = "ERC4626";
8
+ TokenType[TokenType["ERC4626Nested"] = 3] = "ERC4626Nested";
9
+ })(TokenType || (exports.TokenType = TokenType = {}));
3
10
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/dex/balancer-v3/types.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/dex/balancer-v3/types.ts"],"names":[],"mappings":";;;AAkFA,IAAY,SAIX;AAJD,WAAY,SAAS;IACnB,mDAAa,CAAA;IACb,+CAAO,CAAA;IACP,2DAAa,CAAA;AACf,CAAC,EAJW,SAAS,yBAAT,SAAS,QAIpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paraswap/dex-lib",
3
- "version": "4.0.23",
3
+ "version": "4.0.24-bal-v3-nested-erc4626.0",
4
4
  "main": "build/index.js",
5
5
  "types": "build/index.d.ts",
6
6
  "repository": "https://github.com/paraswap/paraswap-dex-lib",
@@ -205,10 +205,29 @@ async function checkOnChainPricingNonMulti(
205
205
  price,
206
206
  amounts,
207
207
  );
208
- expect(price.prices).toEqual(expectedPrices);
208
+ price.prices.forEach((p, i) => {
209
+ expect(areBigIntsWithinPercent(p, expectedPrices[i], 0.001)).toEqual(
210
+ true,
211
+ );
212
+ });
209
213
  }
210
214
  }
211
215
 
216
+ // Helper function to check if two BigInts are within a given percentage
217
+ export function areBigIntsWithinPercent(
218
+ value1: bigint,
219
+ value2: bigint,
220
+ percent: number,
221
+ ): boolean {
222
+ if (percent < 0) {
223
+ throw new Error('Percent must be non-negative');
224
+ }
225
+ const difference = value1 > value2 ? value1 - value2 : value2 - value1;
226
+ const percentFactor = BigInt(Math.floor(percent * 1e8));
227
+ const tolerance = (value2 * percentFactor) / BigInt(1e10);
228
+ return difference <= tolerance;
229
+ }
230
+
212
231
  async function testPricingOnNetwork(
213
232
  balancerV3: BalancerV3,
214
233
  network: Network,
@@ -901,7 +920,7 @@ describe('BalancerV3', function () {
901
920
  Note for maths: Instead of manually adding support for each ERC4626 implementation (e.g. stata with Ray maths) we always use an
902
921
  18 decimal scaled rate and do 18 decimal maths to convert. We may end up loosing 100% accuracy but thats deemed acceptable.
903
922
  */
904
- describe.only('Buffer wrap 6decimal>18decimal', () => {
923
+ describe('Buffer wrap 6decimal>18decimal', () => {
905
924
  const dexHelper = new DummyDexHelper(network);
906
925
 
907
926
  const tokens = Tokens[network];
@@ -1059,6 +1078,120 @@ describe('BalancerV3', function () {
1059
1078
  });
1060
1079
  });
1061
1080
  });
1081
+
1082
+ describe('Nested ERC4626', () => {
1083
+ /*
1084
+ Pool has csUSDL which has nested ERC4626. USDL swaps can be enabled by simply adding extra buffer step.
1085
+ https://balancer.fi/pools/ethereum/v3/0x10a04efba5b880e169920fd4348527c64fb29d4d
1086
+ csUSDC: 0x7204b7dbf9412567835633b6f00c3edc3a8d6330
1087
+ - USDC: 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
1088
+ csUSDL: 0xbeefc011e94f43b8b7b455ebab290c7ab4e216f1
1089
+ - wUSDL: 0x7751e2f4b8ae93ef6b79d86419d42fe3295a4559
1090
+ - USDL: 0xbdc7c08592ee4aa51d06c27ee23d5087d65adbcd
1091
+ */
1092
+ describe('Nested underlying as token in', () => {
1093
+ const dexHelper = new DummyDexHelper(network);
1094
+
1095
+ const tokens = Tokens[network];
1096
+ const srcTokenSymbol = 'USDL';
1097
+ const destTokenSymbol = 'USDC';
1098
+
1099
+ const amountsForSell = [
1100
+ 0n,
1101
+ 1n * BI_POWS[tokens[srcTokenSymbol].decimals],
1102
+ ];
1103
+
1104
+ const amountsForBuy = [
1105
+ 0n,
1106
+ 1n * BI_POWS[tokens[destTokenSymbol].decimals],
1107
+ ];
1108
+
1109
+ beforeAll(async () => {
1110
+ blockNumber = await dexHelper.web3Provider.eth.getBlockNumber();
1111
+ balancerV3 = new BalancerV3(network, dexKey, dexHelper);
1112
+ if (balancerV3.initializePricing) {
1113
+ await balancerV3.initializePricing(blockNumber);
1114
+ }
1115
+ });
1116
+
1117
+ it('getPoolIdentifiers and getPricesVolume SELL', async function () {
1118
+ await testPricingOnNetwork(
1119
+ balancerV3,
1120
+ network,
1121
+ dexKey,
1122
+ blockNumber,
1123
+ srcTokenSymbol,
1124
+ destTokenSymbol,
1125
+ SwapSide.SELL,
1126
+ amountsForSell,
1127
+ );
1128
+ });
1129
+
1130
+ it('getPoolIdentifiers and getPricesVolume BUY', async function () {
1131
+ await testPricingOnNetwork(
1132
+ balancerV3,
1133
+ network,
1134
+ dexKey,
1135
+ blockNumber,
1136
+ srcTokenSymbol,
1137
+ destTokenSymbol,
1138
+ SwapSide.BUY,
1139
+ amountsForBuy,
1140
+ );
1141
+ });
1142
+ });
1143
+ describe('Nested underlying as token out', () => {
1144
+ const dexHelper = new DummyDexHelper(network);
1145
+
1146
+ const tokens = Tokens[network];
1147
+ const srcTokenSymbol = 'USDC';
1148
+ const destTokenSymbol = 'USDL';
1149
+
1150
+ const amountsForSell = [
1151
+ 0n,
1152
+ 1n * BI_POWS[tokens[srcTokenSymbol].decimals],
1153
+ ];
1154
+
1155
+ const amountsForBuy = [
1156
+ 0n,
1157
+ 1n * BI_POWS[tokens[destTokenSymbol].decimals],
1158
+ ];
1159
+
1160
+ beforeAll(async () => {
1161
+ blockNumber = await dexHelper.web3Provider.eth.getBlockNumber();
1162
+ balancerV3 = new BalancerV3(network, dexKey, dexHelper);
1163
+ if (balancerV3.initializePricing) {
1164
+ await balancerV3.initializePricing(blockNumber);
1165
+ }
1166
+ });
1167
+
1168
+ it('getPoolIdentifiers and getPricesVolume SELL', async function () {
1169
+ await testPricingOnNetwork(
1170
+ balancerV3,
1171
+ network,
1172
+ dexKey,
1173
+ blockNumber,
1174
+ srcTokenSymbol,
1175
+ destTokenSymbol,
1176
+ SwapSide.SELL,
1177
+ amountsForSell,
1178
+ );
1179
+ });
1180
+
1181
+ it('getPoolIdentifiers and getPricesVolume BUY', async function () {
1182
+ await testPricingOnNetwork(
1183
+ balancerV3,
1184
+ network,
1185
+ dexKey,
1186
+ blockNumber,
1187
+ srcTokenSymbol,
1188
+ destTokenSymbol,
1189
+ SwapSide.BUY,
1190
+ amountsForBuy,
1191
+ );
1192
+ });
1193
+ });
1194
+ });
1062
1195
  });
1063
1196
  });
1064
1197
 
@@ -11,6 +11,7 @@ import {
11
11
  StableMutableState,
12
12
  Step,
13
13
  TokenInfo,
14
+ TokenType,
14
15
  } from './types';
15
16
  import { getPoolsApi } from './getPoolsApi';
16
17
  import vaultExtensionAbi_V3 from '../../abi/balancer-v3/vault-extension.json';
@@ -543,11 +544,8 @@ export class BalancerV3EventPool extends StatefulEventSubscriber<PoolStateMap> {
543
544
  );
544
545
  if (tokenIndex !== -1) {
545
546
  return {
546
- isBoosted: false,
547
- mainToken: tokenAddress,
548
- underlyingToken: null,
549
547
  index: tokenIndex,
550
- rate: poolState.tokenRates[tokenIndex],
548
+ type: TokenType.MainToken,
551
549
  };
552
550
  }
553
551
 
@@ -558,18 +556,23 @@ export class BalancerV3EventPool extends StatefulEventSubscriber<PoolStateMap> {
558
556
  address && address.toLowerCase() === tokenAddress.toLowerCase(),
559
557
  );
560
558
  if (tokenIndex !== -1) {
561
- if (poolState.erc4626Rates[tokenIndex] === null) {
562
- this.logger.error(
563
- `missing erc4626 token rate ${poolState.tokens[tokenIndex]}`,
564
- );
565
- return null;
566
- }
567
559
  return {
568
- isBoosted: true,
569
- mainToken: poolState.tokens[tokenIndex],
570
- underlyingToken: tokenAddress,
571
560
  index: tokenIndex,
572
- rate: poolState.erc4626Rates[tokenIndex]!,
561
+ type: TokenType.ERC4626,
562
+ };
563
+ }
564
+ }
565
+
566
+ // Check in nested underlying tokens if available
567
+ if (poolState.tokensNestedERC4626Underlying) {
568
+ tokenIndex = poolState.tokensNestedERC4626Underlying.findIndex(
569
+ address =>
570
+ address && address.toLowerCase() === tokenAddress.toLowerCase(),
571
+ );
572
+ if (tokenIndex !== -1) {
573
+ return {
574
+ index: tokenIndex,
575
+ type: TokenType.ERC4626Nested,
573
576
  };
574
577
  }
575
578
  }
@@ -593,104 +596,182 @@ export class BalancerV3EventPool extends StatefulEventSubscriber<PoolStateMap> {
593
596
  * https://docs-v3.balancer.fi/concepts/vault/buffer.html
594
597
  */
595
598
  getSteps(pool: PoolState, tokenIn: TokenInfo, tokenOut: TokenInfo): Step[] {
596
- if (tokenIn.isBoosted && tokenOut.isBoosted) {
597
- return [
598
- // Wrap tokenIn underlying to main token
599
- this.getWrapStep(tokenIn),
600
- // Swap main > main
601
- this.getSwapStep(pool, tokenIn, tokenOut),
602
- // Unwrap tokenOut main to underlying token
603
- this.getUnwrapStep(tokenOut),
604
- ];
605
- } else if (tokenIn.isBoosted) {
606
- if (
607
- tokenIn.mainToken.toLowerCase() === tokenOut.mainToken.toLowerCase()
608
- ) {
609
- // wrap, token > erc4626
610
- // tokenIn is boosted, e.g. isn't pool token and must be wrapped
611
- return [this.getWrapStep(tokenIn)];
599
+ // This is a single buffer wrap/unwrap
600
+ if (tokenIn.index === tokenOut.index) {
601
+ return this.singleBufferStep(pool, tokenIn, tokenOut);
602
+ }
603
+
604
+ // Create steps based on token types
605
+ const steps: Step[] = [];
606
+
607
+ // Handle input token wrapping if needed
608
+ if (this.needsBuffer(tokenIn)) {
609
+ if (tokenIn.type === TokenType.ERC4626Nested) {
610
+ steps.push(this.getWrapStepNested(pool, tokenIn));
612
611
  }
613
- return [
614
- // Wrap tokenIn underlying to main token
615
- this.getWrapStep(tokenIn),
616
- // Swap main > main
617
- this.getSwapStep(pool, tokenIn, tokenOut),
618
- ];
619
- } else if (tokenOut.isBoosted) {
620
- if (
621
- tokenIn.mainToken.toLowerCase() === tokenOut.mainToken.toLowerCase()
622
- ) {
623
- // unwrap, stata > token
624
- // token out is boosted, e.g. isn't pool token
625
- return [this.getUnwrapStep(tokenOut)];
612
+ steps.push(this.getWrapStep(pool, tokenIn));
613
+ }
614
+
615
+ // Add main swap step
616
+ steps.push(this.getSwapStep(pool, tokenIn, tokenOut));
617
+
618
+ // Handle output token unwrapping if needed
619
+ if (this.needsBuffer(tokenOut)) {
620
+ steps.push(this.getUnwrapStep(pool, tokenOut));
621
+ if (tokenOut.type === TokenType.ERC4626Nested) {
622
+ steps.push(this.getUnwrapStepNested(pool, tokenOut));
626
623
  }
627
- return [
628
- // Swap main > main
629
- this.getSwapStep(pool, tokenIn, tokenOut),
630
- // Unwrap tokenOut main to underlying token
631
- this.getUnwrapStep(tokenOut),
632
- ];
633
- } else {
634
- return [
635
- // Swap main > main
636
- this.getSwapStep(pool, tokenIn, tokenOut),
637
- ];
638
624
  }
625
+
626
+ return steps;
627
+ }
628
+
629
+ private singleBufferStep(
630
+ pool: PoolState,
631
+ tokenIn: TokenInfo,
632
+ tokenOut: TokenInfo,
633
+ ): Step[] {
634
+ if (tokenIn.type === TokenType.ERC4626) {
635
+ // wrap, token > erc4626
636
+ // tokenIn is boosted, e.g. isn't pool token and must be wrapped
637
+ return [this.getWrapStep(pool, tokenIn)];
638
+ }
639
+ if (tokenOut.type === TokenType.ERC4626) {
640
+ // unwrap, erc4626 > token
641
+ // tokenOut is boosted, e.g. isn't pool token and must be unwrapped
642
+ return [this.getUnwrapStep(pool, tokenOut)];
643
+ }
644
+ throw new Error(`Error get step with same token index`);
645
+ }
646
+
647
+ private needsBuffer(token: TokenInfo): boolean {
648
+ return (
649
+ token.type === TokenType.ERC4626 || token.type === TokenType.ERC4626Nested
650
+ );
639
651
  }
640
652
 
641
- getWrapStep(token: TokenInfo): Step {
642
- if (!token.underlyingToken)
653
+ private validateUnderlyingToken(pool: PoolState, tokenIndex: number): void {
654
+ if (!pool.tokensUnderlying[tokenIndex] || !pool.erc4626Rates[tokenIndex]) {
643
655
  throw new Error(
644
- `Buffer wrap: token has no underlying. ${token.mainToken}`,
656
+ `Underlying Token Error: token at index ${tokenIndex}. ${pool.tokensUnderlying[tokenIndex]} ${pool.erc4626Rates[tokenIndex]}`,
645
657
  );
646
- // Vault expects pool to be the ERC4626 wrapped token, e.g. aUSDC
658
+ }
659
+ }
660
+
661
+ private validateNestedUnderlyingToken(
662
+ pool: PoolState,
663
+ tokenIndex: number,
664
+ ): void {
665
+ if (
666
+ !pool.tokensUnderlying[tokenIndex] ||
667
+ !pool.tokensNestedERC4626Underlying[tokenIndex] ||
668
+ !pool.erc4626NestedRates[tokenIndex]
669
+ ) {
670
+ throw new Error(
671
+ `NestedUnderlying Token Error: token at index ${tokenIndex}. ${pool.tokensUnderlying[tokenIndex]} ${pool.tokensNestedERC4626Underlying[tokenIndex]} ${pool.erc4626NestedRates[tokenIndex]}`,
672
+ );
673
+ }
674
+ }
675
+
676
+ getWrapStepNested(pool: PoolState, token: TokenInfo): Step {
677
+ this.validateNestedUnderlyingToken(pool, token.index);
678
+
679
+ const underlyingToken = pool.tokensUnderlying[token.index] as string;
680
+ const nestedUnderlyingToken = pool.tokensNestedERC4626Underlying[
681
+ token.index
682
+ ] as string;
683
+
647
684
  return {
648
- pool: token.mainToken,
685
+ pool: underlyingToken,
649
686
  isBuffer: true,
650
687
  swapInput: {
651
- tokenIn: token.underlyingToken,
652
- tokenOut: token.mainToken,
688
+ tokenIn: nestedUnderlyingToken,
689
+ tokenOut: underlyingToken,
653
690
  },
654
691
  poolState: {
655
692
  poolType: 'Buffer',
656
- rate: token.rate,
657
- poolAddress: token.mainToken,
658
- tokens: [token.mainToken, token.underlyingToken], // staticToken & underlying
693
+ rate: pool.erc4626NestedRates[token.index] as bigint,
694
+ poolAddress: underlyingToken,
695
+ tokens: [nestedUnderlyingToken, underlyingToken],
659
696
  },
660
697
  };
661
698
  }
662
699
 
663
- getUnwrapStep(token: TokenInfo): Step {
664
- if (!token.underlyingToken)
665
- throw new Error(
666
- `Buffer unwrap: token has no underlying. ${token.mainToken}`,
667
- );
668
- // Vault expects pool to be the ERC4626 wrapped token, e.g. aUSDC
700
+ getWrapStep(pool: PoolState, token: TokenInfo): Step {
701
+ this.validateUnderlyingToken(pool, token.index);
702
+
703
+ const wrappedToken = pool.tokens[token.index];
704
+ const underlyingToken = pool.tokensUnderlying[token.index] as string;
705
+
706
+ return {
707
+ pool: wrappedToken,
708
+ isBuffer: true,
709
+ swapInput: {
710
+ tokenIn: underlyingToken,
711
+ tokenOut: wrappedToken,
712
+ },
713
+ poolState: {
714
+ poolType: 'Buffer',
715
+ rate: pool.erc4626Rates[token.index] as bigint,
716
+ poolAddress: wrappedToken,
717
+ tokens: [wrappedToken, underlyingToken],
718
+ },
719
+ };
720
+ }
721
+
722
+ getUnwrapStepNested(pool: PoolState, token: TokenInfo): Step {
723
+ this.validateNestedUnderlyingToken(pool, token.index);
724
+
725
+ const underlyingToken = pool.tokensUnderlying[token.index] as string;
726
+ const nestedUnderlyingToken = pool.tokensNestedERC4626Underlying[
727
+ token.index
728
+ ] as string;
729
+
730
+ return {
731
+ pool: underlyingToken,
732
+ isBuffer: true,
733
+ swapInput: {
734
+ tokenIn: underlyingToken,
735
+ tokenOut: nestedUnderlyingToken,
736
+ },
737
+ poolState: {
738
+ poolType: 'Buffer',
739
+ rate: pool.erc4626NestedRates[token.index] as bigint,
740
+ poolAddress: underlyingToken,
741
+ tokens: [nestedUnderlyingToken, underlyingToken],
742
+ },
743
+ };
744
+ }
745
+
746
+ getUnwrapStep(pool: PoolState, token: TokenInfo): Step {
747
+ this.validateUnderlyingToken(pool, token.index);
748
+
749
+ const wrappedToken = pool.tokens[token.index];
750
+ const underlyingToken = pool.tokensUnderlying[token.index] as string;
751
+
669
752
  return {
670
- pool: token.mainToken,
753
+ pool: wrappedToken,
671
754
  isBuffer: true,
672
755
  swapInput: {
673
- tokenIn: token.mainToken,
674
- tokenOut: token.underlyingToken,
756
+ tokenIn: wrappedToken,
757
+ tokenOut: underlyingToken,
675
758
  },
676
759
  poolState: {
677
760
  poolType: 'Buffer',
678
- // TODO: for ERC4626 fetch the wrap/unwrap rate
679
- rate: token.rate,
680
- poolAddress: token.mainToken,
681
- tokens: [token.mainToken, token.underlyingToken], // staticToken & underlying
761
+ rate: pool.erc4626Rates[token.index] as bigint,
762
+ poolAddress: wrappedToken,
763
+ tokens: [wrappedToken, underlyingToken],
682
764
  },
683
765
  };
684
766
  }
685
767
 
686
768
  getSwapStep(pool: PoolState, tokenIn: TokenInfo, tokenOut: TokenInfo): Step {
687
- // A normal swap between two tokens in a pool
688
769
  return {
689
770
  pool: pool.poolAddress,
690
771
  isBuffer: false,
691
772
  swapInput: {
692
- tokenIn: tokenIn.mainToken,
693
- tokenOut: tokenOut.mainToken,
773
+ tokenIn: pool.tokens[tokenIn.index],
774
+ tokenOut: pool.tokens[tokenOut.index],
694
775
  },
695
776
  poolState: pool,
696
777
  };
@@ -177,7 +177,9 @@ export class BalancerV3 extends SimpleExchange implements IDex<BalancerV3Data> {
177
177
  hasTokens(pool: DeepReadonly<PoolState>, tokens: string[]): boolean {
178
178
  return tokens.every(
179
179
  token =>
180
- pool.tokens.includes(token) || pool.tokensUnderlying.includes(token),
180
+ pool.tokens.includes(token) ||
181
+ pool.tokensUnderlying.includes(token) ||
182
+ pool.tokensNestedERC4626Underlying.includes(token),
181
183
  );
182
184
  }
183
185
 
@@ -586,11 +588,20 @@ export class BalancerV3 extends SimpleExchange implements IDex<BalancerV3Data> {
586
588
  .filter(t => !!t)
587
589
  .filter(t => t?.address !== tokenAddress) as Token[];
588
590
 
591
+ const nestedUnderlyingTokens = pool.poolTokens
592
+ .map(t => ({
593
+ address: t.underlyingToken?.underlyingTokenAddress,
594
+ decimals: t.underlyingToken?.decimals,
595
+ }))
596
+ .filter(item => !!item.address) as Token[];
597
+
589
598
  return {
590
599
  exchange: this.dexKey,
591
600
  address: pool.address,
592
601
  liquidityUSD: parseFloat(pool.dynamicData.totalLiquidity),
593
- connectorTokens: tokens.concat(underlyingTokens),
602
+ connectorTokens: tokens
603
+ .concat(underlyingTokens)
604
+ .concat(nestedUnderlyingTokens),
594
605
  };
595
606
  });
596
607
  }
@@ -14,15 +14,7 @@ const PARTIAL_BOOSTED_SWAP_GAS_COST = 259815;
14
14
  const BUFFER_WRAP_UNWRAP_GAS_COST = 155921;
15
15
 
16
16
  export function getGasCost(steps: Step[]): number {
17
- if (steps.length === 2) {
18
- // Partial boosted/buffer swap:
19
- // token[wrap]wrappedToken[swap]wrappedToken or
20
- // wrappedToken[swap]wrappedToken[unwrap]token
21
- return PARTIAL_BOOSTED_SWAP_GAS_COST;
22
- } else if (steps.length === 3) {
23
- // Full boosted/buffer swap: token[wrap]wrappedToken[swap]wrappedToken[unwrap]token
24
- return FULL_BOOSTED_SWAP_GAS_COST;
25
- } else {
17
+ if (steps.length === 1) {
26
18
  switch (steps[0].poolState.poolType) {
27
19
  case 'WEIGHTED':
28
20
  return WEIGHTED_GAS_COST;
@@ -33,5 +25,18 @@ export function getGasCost(steps: Step[]): number {
33
25
  default:
34
26
  return WEIGHTED_GAS_COST;
35
27
  }
28
+ } else if (steps.length === 2) {
29
+ // Partial boosted/buffer swap:
30
+ // token[wrap]wrappedToken[swap]wrappedToken or
31
+ // wrappedToken[swap]wrappedToken[unwrap]token
32
+ return PARTIAL_BOOSTED_SWAP_GAS_COST;
33
+ } else if (steps.length === 3) {
34
+ // Full boosted/buffer swap: token[wrap]wrappedToken[swap]wrappedToken[unwrap]token
35
+ return FULL_BOOSTED_SWAP_GAS_COST;
36
+ } else {
37
+ return (
38
+ FULL_BOOSTED_SWAP_GAS_COST +
39
+ (steps.length - 3) * BUFFER_WRAP_UNWRAP_GAS_COST
40
+ );
36
41
  }
37
42
  }
@@ -86,7 +86,7 @@ const poolOnChain: Record<
86
86
  poolAddress: string,
87
87
  data: any,
88
88
  startIndex: number,
89
- ): Omit<CommonMutableState, 'erc4626Rates'> => {
89
+ ): Omit<CommonMutableState, 'erc4626Rates' | 'erc4626NestedRates'> => {
90
90
  const resultTokenRates = decodeThrowError(
91
91
  contractInterface,
92
92
  'getPoolTokenRates',
@@ -267,8 +267,17 @@ export function getErc4626MultiCallData(
267
267
  // We want to query rate for each unique ERC4626 token
268
268
  const uniqueErc4626Tokens = Array.from(
269
269
  new Set(
270
- Object.values(immutablePoolStateMap).flatMap(pool =>
271
- pool.tokens.filter((_, index) => pool.tokensUnderlying[index] !== null),
270
+ Object.values(immutablePoolStateMap).flatMap(
271
+ pool =>
272
+ [
273
+ // Get tokens with corresponding non-null tokensUnderlying
274
+ ...pool.tokens.filter(
275
+ (_, index) => pool.tokensUnderlying[index] !== null,
276
+ ),
277
+ ...pool.tokensUnderlying.filter(
278
+ (_, index) => pool.tokensNestedERC4626Underlying[index] !== null,
279
+ ),
280
+ ] as string[],
272
281
  ),
273
282
  ),
274
283
  );
@@ -392,6 +401,10 @@ export async function getOnChainState(
392
401
  if (!tokensWithRates[t]) return null;
393
402
  return tokensWithRates[t];
394
403
  }),
404
+ erc4626NestedRates: pool.tokensUnderlying.map(t => {
405
+ if (!t || !tokensWithRates[t]) return null;
406
+ return tokensWithRates[t];
407
+ }),
395
408
  },
396
409
  ];
397
410
  }),
@@ -14,6 +14,7 @@ interface PoolToken {
14
14
  isErc4626: boolean;
15
15
  underlyingToken: {
16
16
  address: string;
17
+ underlyingTokenAddress: string | null;
17
18
  } | null;
18
19
  }
19
20
 
@@ -67,6 +68,7 @@ function createQuery(
67
68
  isErc4626
68
69
  underlyingToken {
69
70
  address
71
+ underlyingTokenAddress
70
72
  }
71
73
  }
72
74
  }
@@ -82,6 +84,9 @@ function toImmutablePoolStateMap(pools: Pool[]): ImmutablePoolStateMap {
82
84
  tokensUnderlying: pool.poolTokens.map(t =>
83
85
  t.underlyingToken ? t.underlyingToken.address : null,
84
86
  ),
87
+ tokensNestedERC4626Underlying: pool.poolTokens.map(t =>
88
+ t.underlyingToken ? t.underlyingToken.underlyingTokenAddress : null,
89
+ ),
85
90
  weights: pool.poolTokens.map(t =>
86
91
  t.weight ? parseUnits(t.weight, 18).toBigInt() : 0n,
87
92
  ),
@@ -7,6 +7,7 @@ interface PoolToken {
7
7
  underlyingToken?: {
8
8
  address: string;
9
9
  decimals: number;
10
+ underlyingTokenAddress: string;
10
11
  };
11
12
  }
12
13
 
@@ -63,6 +64,7 @@ function createQuery(
63
64
  underlyingToken {
64
65
  address
65
66
  decimals
67
+ underlyingTokenAddress
66
68
  }
67
69
  }
68
70
  dynamicData {