diff --git a/python/src/pools/buffer/buffer_data.py b/python/src/pools/buffer/buffer_data.py index c155aa6..46c1821 100644 --- a/python/src/pools/buffer/buffer_data.py +++ b/python/src/pools/buffer/buffer_data.py @@ -7,6 +7,7 @@ class BufferState: # Immutable fields pool_address: str tokens: List[str] + scaling_factor: int # Mutable fields rate: int max_deposit: Optional[int] = None @@ -19,5 +20,6 @@ def map_buffer_state(pool_state: dict) -> BufferState: return BufferState( pool_address=pool_state["poolAddress"], tokens=pool_state["tokens"], + scaling_factor=pool_state["scalingFactor"], rate=pool_state["rate"], ) diff --git a/python/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.py b/python/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.py index 636eff3..2ffadc6 100644 --- a/python/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.py +++ b/python/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.py @@ -1,4 +1,4 @@ -from src.common.types import SwapInput +from src.common.types import SwapInput, SwapKind from src.common.utils import is_same_address from src.pools.buffer.buffer_data import BufferState from src.pools.buffer.buffer_math import calculate_buffer_amounts @@ -20,9 +20,34 @@ def erc4626_buffer_wrap_or_unwrap(swap_input: SwapInput, pool_state: BufferState else WrappingDirection.WRAP ) - return calculate_buffer_amounts( + scaling_factor = pool_state.scaling_factor + + # Scale underlying amounts up before 18-decimal math + amount_for_calc = swap_input.amount_raw + if ( + wrapping_direction == WrappingDirection.WRAP + and swap_input.swap_kind == SwapKind.GIVENIN + ) or ( + wrapping_direction == WrappingDirection.UNWRAP + and swap_input.swap_kind == SwapKind.GIVENOUT + ): + amount_for_calc = swap_input.amount_raw * scaling_factor + + result = calculate_buffer_amounts( wrapping_direction, swap_input.swap_kind, - swap_input.amount_raw, + amount_for_calc, pool_state.rate, ) + + # Scale results back down to underlying decimals + if ( + wrapping_direction == WrappingDirection.WRAP + and swap_input.swap_kind == SwapKind.GIVENOUT + ) or ( + wrapping_direction == WrappingDirection.UNWRAP + and swap_input.swap_kind == SwapKind.GIVENIN + ): + return result // scaling_factor + + return result diff --git a/rust/src/pools/buffer/buffer_data.rs b/rust/src/pools/buffer/buffer_data.rs index fc437a8..462aaf6 100644 --- a/rust/src/pools/buffer/buffer_data.rs +++ b/rust/src/pools/buffer/buffer_data.rs @@ -15,6 +15,7 @@ pub struct BufferMutable { pub struct BufferImmutable { pub pool_address: String, pub tokens: Vec, + pub scaling_factor: U256, } /// Buffer pool state diff --git a/rust/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.rs b/rust/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.rs index adb3078..32e94b1 100644 --- a/rust/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.rs +++ b/rust/src/pools/buffer/erc4626_buffer_wrap_or_unwrap.rs @@ -1,6 +1,6 @@ //! ERC4626 Buffer wrap or unwrap function -use crate::common::types::SwapInput; +use crate::common::types::{SwapInput, SwapKind}; use crate::pools::buffer::buffer_data::BufferState; use crate::pools::buffer::buffer_math::calculate_buffer_amounts; use crate::pools::buffer::enums::WrappingDirection; @@ -35,14 +35,37 @@ pub fn erc4626_buffer_wrap_or_unwrap( WrappingDirection::Wrap }; - calculate_buffer_amounts( + let scaling_factor = &pool_state.immutable.scaling_factor; + + // Scale underlying amounts up before 18-decimal math + let amount_for_calc = if (wrapping_direction == WrappingDirection::Wrap + && swap_input.swap_kind == SwapKind::GivenIn) + || (wrapping_direction == WrappingDirection::Unwrap + && swap_input.swap_kind == SwapKind::GivenOut) + { + swap_input.amount_raw * *scaling_factor + } else { + swap_input.amount_raw + }; + + let result = calculate_buffer_amounts( wrapping_direction, swap_input.swap_kind.clone(), - &swap_input.amount_raw, + &amount_for_calc, &pool_state.mutable.rate, pool_state.mutable.max_deposit.as_ref(), pool_state.mutable.max_mint.as_ref(), - ) + )?; + + // Scale results back down to underlying decimals + if (wrapping_direction == WrappingDirection::Wrap && swap_input.swap_kind == SwapKind::GivenOut) + || (wrapping_direction == WrappingDirection::Unwrap + && swap_input.swap_kind == SwapKind::GivenIn) + { + Ok(result / *scaling_factor) + } else { + Ok(result) + } } /// Check if two addresses are the same (case-insensitive) diff --git a/rust/tests/utils/read_test_data.rs b/rust/tests/utils/read_test_data.rs index c7ddd52..0374fe3 100644 --- a/rust/tests/utils/read_test_data.rs +++ b/rust/tests/utils/read_test_data.rs @@ -211,6 +211,8 @@ pub struct BufferImmutable { #[serde(rename = "poolAddress")] pub pool_address: String, pub tokens: Vec, + #[serde(rename = "scalingFactor")] + pub scaling_factor: U256, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -483,6 +485,8 @@ struct RawPool { pub max_deposit: Option, #[serde(rename = "maxMint")] pub max_mint: Option, + #[serde(rename = "scalingFactor")] + pub scaling_factor_buffer: Option, // ReClamm specific fields #[serde(rename = "lastVirtualBalances")] pub last_virtual_balances: Option>, @@ -1381,6 +1385,12 @@ fn map_pool(raw_pool: RawPool) -> Result()) .transpose()?; + let scaling_factor = raw_pool + .scaling_factor_buffer + .as_ref() + .ok_or("Buffer pool missing scalingFactor")? + .parse::()?; + // Buffer pools have minimal required fields, use defaults for missing ones let buffer_state = BufferState { base: BasePoolState { @@ -1407,6 +1417,7 @@ fn map_pool(raw_pool: RawPool) -> Result PoolStateOrBuffer { immutable: BufferImmutable { pool_address: buffer_pool.state.immutable.pool_address.clone(), tokens: buffer_pool.state.immutable.tokens.clone(), + scaling_factor: buffer_pool.state.immutable.scaling_factor, }, }; PoolStateOrBuffer::Buffer(Box::new(buffer_state)) diff --git a/testData/src/buffer.ts b/testData/src/buffer.ts index 16cbf2b..f8391ed 100644 --- a/testData/src/buffer.ts +++ b/testData/src/buffer.ts @@ -5,12 +5,14 @@ import { type Address, type Chain, erc4626Abi, + erc20Abi, } from 'viem'; import { CHAINS, VAULT_V3 } from '@balancer/sdk'; import { TransformBigintToString } from './types'; export type BufferImmutable = { tokens: Address[]; + scalingFactor: bigint; }; type BufferMutable = { @@ -43,20 +45,40 @@ export class BufferPool { blockNumber, }); + const [mainDecimals, underlyingDecimals] = await Promise.all([ + this.client.readContract({ + address, + abi: erc20Abi, + functionName: 'decimals', + blockNumber, + }), + this.client.readContract({ + address: asset, + abi: erc20Abi, + functionName: 'decimals', + blockNumber, + }), + ]); + + const scalingFactor = 10n ** BigInt(mainDecimals - underlyingDecimals); + return { tokens: [address, asset], + scalingFactor: scalingFactor.toString(), }; } async fetchMutableData( address: Address, blockNumber: bigint, + scalingFactor: bigint, ): Promise> { + const convertToAssetsInput = 1000000000000000000n * scalingFactor; const rate = await this.client.readContract({ address, abi: erc4626Abi, functionName: 'convertToAssets', - args: [1000000000000000000n], + args: [convertToAssetsInput], blockNumber, }); return { diff --git a/testData/src/getPool.ts b/testData/src/getPool.ts index 9b3b1e9..19eea37 100644 --- a/testData/src/getPool.ts +++ b/testData/src/getPool.ts @@ -47,10 +47,17 @@ export async function getPool( poolAddress, blockNumber, ); - const mutable = await poolData[poolType].fetchMutableData( - poolAddress, - blockNumber, - ); + const mutable = + poolType === 'Buffer' + ? await (poolData[poolType] as BufferPool).fetchMutableData( + poolAddress, + blockNumber, + BigInt(immutable.scalingFactor), + ) + : await poolData[poolType].fetchMutableData( + poolAddress, + blockNumber, + ); console.log('Done'); // Fetch hook data for all pools except Buffer pools (which don't support hooks) diff --git a/testData/testData/1-21671845-Buffer-csUSDC.json b/testData/testData/1-21671845-Buffer-csUSDC.json index 5ee2749..a8d3f25 100644 --- a/testData/testData/1-21671845-Buffer-csUSDC.json +++ b/testData/testData/1-21671845-Buffer-csUSDC.json @@ -24,6 +24,7 @@ "0x7204B7Dbf9412567835633B6F00C3Edc3a8D6330", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" ], - "rate": "1005205" + "scalingFactor": "1000000000000", + "rate": "1005205772403674748" } } \ No newline at end of file diff --git a/testData/testData/1-21671845-Buffer-waEthLidoWETH.json b/testData/testData/1-21671845-Buffer-waEthLidoWETH.json index 6304ca2..97def03 100644 --- a/testData/testData/1-21671845-Buffer-waEthLidoWETH.json +++ b/testData/testData/1-21671845-Buffer-waEthLidoWETH.json @@ -24,6 +24,7 @@ "0x0FE906e030a44eF24CA8c7dC7B7c53A6C4F00ce9", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" ], + "scalingFactor": "1", "rate": "1010169874970004409" } } \ No newline at end of file diff --git a/testData/testData/1-21671845-Buffer-waEthUSDC.json b/testData/testData/1-21671845-Buffer-waEthUSDC.json index 01c8ddd..a2c57fe 100644 --- a/testData/testData/1-21671845-Buffer-waEthUSDC.json +++ b/testData/testData/1-21671845-Buffer-waEthUSDC.json @@ -24,6 +24,7 @@ "0xD4fa2D31b7968E448877f69A96DE69f5de8cD23E", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" ], + "scalingFactor": "1", "rate": "1115987637915440452" } } \ No newline at end of file diff --git a/testData/testData/buffer-manual.json b/testData/testData/buffer-manual.json index 6096990..80ee140 100644 --- a/testData/testData/buffer-manual.json +++ b/testData/testData/buffer-manual.json @@ -38,6 +38,7 @@ "0x8a88124522dbbf1e56352ba3de1d9f78c143751e", "0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8" ], + "scalingFactor": "1", "rate": "1026429645525566689" } } \ No newline at end of file diff --git a/typescript/src/buffer/data.ts b/typescript/src/buffer/data.ts index 89bf3d9..4eb4aaf 100644 --- a/typescript/src/buffer/data.ts +++ b/typescript/src/buffer/data.ts @@ -9,6 +9,7 @@ export type BufferMutable = { export type BufferImmutable = { poolAddress: string; tokens: string[]; + scalingFactor: bigint; }; /** diff --git a/typescript/src/buffer/erc4626BufferWrapOrUnwrap.ts b/typescript/src/buffer/erc4626BufferWrapOrUnwrap.ts index aa88b15..3f16235 100644 --- a/typescript/src/buffer/erc4626BufferWrapOrUnwrap.ts +++ b/typescript/src/buffer/erc4626BufferWrapOrUnwrap.ts @@ -1,6 +1,6 @@ import { BufferState } from '../buffer/data'; import { isSameAddress } from '../vault/utils'; -import { SwapInput } from '../vault/types'; +import { SwapInput, SwapKind } from '../vault/types'; import { calculateBufferAmounts } from './bufferMath'; import { WrappingDirection } from './types'; @@ -23,12 +23,37 @@ export function erc4626BufferWrapOrUnwrap( ? WrappingDirection.UNWRAP : WrappingDirection.WRAP; - return calculateBufferAmounts( + const { scalingFactor } = poolState; + + // Scale underlying amounts up before 18-decimal math + let amountForCalc = input.amountRaw; + if ( + (wrappingDirection === WrappingDirection.WRAP && + input.swapKind === SwapKind.GivenIn) || + (wrappingDirection === WrappingDirection.UNWRAP && + input.swapKind === SwapKind.GivenOut) + ) { + amountForCalc = input.amountRaw * scalingFactor; + } + + const result = calculateBufferAmounts( wrappingDirection, input.swapKind, - input.amountRaw, + amountForCalc, poolState.rate, poolState.maxDeposit, poolState.maxMint, ); + + // Scale results back down to underlying decimals + if ( + (wrappingDirection === WrappingDirection.WRAP && + input.swapKind === SwapKind.GivenOut) || + (wrappingDirection === WrappingDirection.UNWRAP && + input.swapKind === SwapKind.GivenIn) + ) { + return result / scalingFactor; + } + + return result; } diff --git a/typescript/test/bufferPool.test.ts b/typescript/test/bufferPool.test.ts index 6d6da94..de4cd93 100644 --- a/typescript/test/bufferPool.test.ts +++ b/typescript/test/bufferPool.test.ts @@ -14,6 +14,7 @@ describe('buffer pool', () => { '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', ], + scalingFactor: 1n, maxDeposit: 1900471418535512n, maxMint: 1692675790387594n, }; @@ -40,6 +41,7 @@ describe('buffer pool', () => { '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', ], + scalingFactor: 1n, maxDeposit: 1900471418535512n, maxMint: 1692675790387594n, }; @@ -68,6 +70,7 @@ describe('buffer pool', () => { '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', ], + scalingFactor: 1n, maxDeposit: 1900471418535512n, maxMint: 1692675790387594n, }; @@ -94,6 +97,7 @@ describe('buffer pool', () => { '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', ], + scalingFactor: 1n, maxDeposit: 1900471418535512n, maxMint: 1692675790387594n, }; diff --git a/typescript/test/utils/readTestData.ts b/typescript/test/utils/readTestData.ts index 8bdff36..dae2f49 100644 --- a/typescript/test/utils/readTestData.ts +++ b/typescript/test/utils/readTestData.ts @@ -252,6 +252,7 @@ function mapPool( return { ...pool, rate: BigInt(pool.rate), + scalingFactor: BigInt(pool.scalingFactor), }; } if (pool.poolType === 'GYROE') {