@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.
- package/build/dex/balancer-v3/balancer-v3-pool.d.ts +8 -2
- package/build/dex/balancer-v3/balancer-v3-pool.js +118 -72
- package/build/dex/balancer-v3/balancer-v3-pool.js.map +1 -1
- package/build/dex/balancer-v3/balancer-v3.js +12 -2
- package/build/dex/balancer-v3/balancer-v3.js.map +1 -1
- package/build/dex/balancer-v3/getGasCost.js +15 -11
- package/build/dex/balancer-v3/getGasCost.js.map +1 -1
- package/build/dex/balancer-v3/getOnChainState.js +10 -1
- package/build/dex/balancer-v3/getOnChainState.js.map +1 -1
- package/build/dex/balancer-v3/getPoolsApi.js +2 -0
- package/build/dex/balancer-v3/getPoolsApi.js.map +1 -1
- package/build/dex/balancer-v3/getTopPoolsApi.d.ts +1 -0
- package/build/dex/balancer-v3/getTopPoolsApi.js +1 -0
- package/build/dex/balancer-v3/getTopPoolsApi.js.map +1 -1
- package/build/dex/balancer-v3/types.d.ts +8 -4
- package/build/dex/balancer-v3/types.js +7 -0
- package/build/dex/balancer-v3/types.js.map +1 -1
- package/package.json +1 -1
- package/src/dex/balancer-v3/balancer-v3-integration.test.ts +135 -2
- package/src/dex/balancer-v3/balancer-v3-pool.ts +161 -80
- package/src/dex/balancer-v3/balancer-v3.ts +13 -2
- package/src/dex/balancer-v3/getGasCost.ts +14 -9
- package/src/dex/balancer-v3/getOnChainState.ts +16 -3
- package/src/dex/balancer-v3/getPoolsApi.ts +5 -0
- package/src/dex/balancer-v3/getTopPoolsApi.ts +2 -0
- package/src/dex/balancer-v3/types.ts +9 -4
- package/tests/constants-e2e.ts +15 -0
|
@@ -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
|
-
|
|
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
|
@@ -205,10 +205,29 @@ async function checkOnChainPricingNonMulti(
|
|
|
205
205
|
price,
|
|
206
206
|
amounts,
|
|
207
207
|
);
|
|
208
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (
|
|
607
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
) {
|
|
623
|
-
|
|
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
|
-
|
|
642
|
-
if (!
|
|
653
|
+
private validateUnderlyingToken(pool: PoolState, tokenIndex: number): void {
|
|
654
|
+
if (!pool.tokensUnderlying[tokenIndex] || !pool.erc4626Rates[tokenIndex]) {
|
|
643
655
|
throw new Error(
|
|
644
|
-
`
|
|
656
|
+
`Underlying Token Error: token at index ${tokenIndex}. ${pool.tokensUnderlying[tokenIndex]} ${pool.erc4626Rates[tokenIndex]}`,
|
|
645
657
|
);
|
|
646
|
-
|
|
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:
|
|
685
|
+
pool: underlyingToken,
|
|
649
686
|
isBuffer: true,
|
|
650
687
|
swapInput: {
|
|
651
|
-
tokenIn:
|
|
652
|
-
tokenOut:
|
|
688
|
+
tokenIn: nestedUnderlyingToken,
|
|
689
|
+
tokenOut: underlyingToken,
|
|
653
690
|
},
|
|
654
691
|
poolState: {
|
|
655
692
|
poolType: 'Buffer',
|
|
656
|
-
rate: token.
|
|
657
|
-
poolAddress:
|
|
658
|
-
tokens: [
|
|
693
|
+
rate: pool.erc4626NestedRates[token.index] as bigint,
|
|
694
|
+
poolAddress: underlyingToken,
|
|
695
|
+
tokens: [nestedUnderlyingToken, underlyingToken],
|
|
659
696
|
},
|
|
660
697
|
};
|
|
661
698
|
}
|
|
662
699
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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:
|
|
753
|
+
pool: wrappedToken,
|
|
671
754
|
isBuffer: true,
|
|
672
755
|
swapInput: {
|
|
673
|
-
tokenIn:
|
|
674
|
-
tokenOut:
|
|
756
|
+
tokenIn: wrappedToken,
|
|
757
|
+
tokenOut: underlyingToken,
|
|
675
758
|
},
|
|
676
759
|
poolState: {
|
|
677
760
|
poolType: 'Buffer',
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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.
|
|
693
|
-
tokenOut: tokenOut.
|
|
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) ||
|
|
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
|
|
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 ===
|
|
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(
|
|
271
|
-
pool
|
|
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 {
|