diff --git a/models/src/replicating/libraries/SwapLib.ts b/models/src/replicating/libraries/SwapLib.ts index cb43a68..863f49c 100644 --- a/models/src/replicating/libraries/SwapLib.ts +++ b/models/src/replicating/libraries/SwapLib.ts @@ -51,7 +51,12 @@ export const getSwapAmounts = ({ ? Decimal.max(thresholdSqrtPrice, scaledLowerSqrtPrice) : scaledLowerSqrtPrice; - const { amountIn, amountOut, fees, nextSqrtPrice } = computeSwapAmount({ + const { + amountIn: netAmountIn, + amountOut, + fees, + nextSqrtPrice, + } = computeSwapAmount({ currSqrtPrice: startSqrtPrice, targetSqrtPrice, liquidity, @@ -60,19 +65,24 @@ export const getSwapAmounts = ({ exactInput, }); - const grossAmountIn = new Decimal(amountIn).add(fees); + const amountIn = + nextSqrtPrice !== targetSqrtPrice && + new Decimal(liquidity).gt(0) && + exactInput + ? amount + : new Decimal(netAmountIn).add(fees); if (thresholdAmount) { if (exactInput) { if (amountOut < thresholdAmount) throw new Error("Threshold amount not met"); } else { - if (grossAmountIn > thresholdAmount) + if (amountIn > thresholdAmount) throw new Error("Threshold amount exceeded"); } } - return { amountIn: grossAmountIn, amountOut, fees }; + return { amountIn, amountOut, fees }; }; export const computeSwapAmount = ({ diff --git a/models/src/replicating/tests/SwapLib.test.ts b/models/src/replicating/tests/SwapLib.test.ts new file mode 100644 index 0000000..92e0beb --- /dev/null +++ b/models/src/replicating/tests/SwapLib.test.ts @@ -0,0 +1,120 @@ +import Decimal from "decimal.js"; +import { getSwapAmounts } from "../libraries/SwapLib"; + +type SwapParams = { + isBuy: boolean; + exactInput: boolean; + amount: Decimal.Value; + swapFeeRate: Decimal.Value; + thresholdSqrtPrice: Decimal.Value | null; + thresholdAmount: Decimal.Value | null; + lowerSqrtPrice: Decimal.Value; + upperSqrtPrice: Decimal.Value; + liquidity: Decimal.Value; + baseDecimals: number; + quoteDecimals: number; +}; +const testGetSwapAmounts = () => { + const cases: SwapParams[] = [ + { + isBuy: true, + exactInput: true, + amount: 1, + swapFeeRate: 0, + thresholdSqrtPrice: null, + thresholdAmount: null, + lowerSqrtPrice: 0.8 ** 0.5, + upperSqrtPrice: 1, + liquidity: 10000, + baseDecimals: 18, + quoteDecimals: 18, + }, + { + isBuy: true, + exactInput: true, + amount: 1, + swapFeeRate: 0.005, + thresholdSqrtPrice: null, + thresholdAmount: null, + lowerSqrtPrice: 1, + upperSqrtPrice: 1.2 ** 0.5, + liquidity: 0, + baseDecimals: 18, + quoteDecimals: 18, + }, + { + isBuy: false, + exactInput: true, + amount: 10, + swapFeeRate: 0.005, + thresholdSqrtPrice: 0.95 ** 0.5, + thresholdAmount: null, + lowerSqrtPrice: 0.8 ** 0.5, + upperSqrtPrice: 1, + liquidity: 200, + baseDecimals: 18, + quoteDecimals: 18, + }, + { + isBuy: true, + exactInput: true, + amount: 10, + swapFeeRate: 0.005, + thresholdSqrtPrice: 1.05 ** 0.5, + thresholdAmount: null, + lowerSqrtPrice: 1, + upperSqrtPrice: 1.2 ** 0.5, + liquidity: 200, + baseDecimals: 18, + quoteDecimals: 18, + }, + { + isBuy: true, + exactInput: true, + amount: 9.26891, + swapFeeRate: 0.005, + thresholdSqrtPrice: null, + thresholdAmount: null, + lowerSqrtPrice: 0.375 ** 0.5, + upperSqrtPrice: 0.4 ** 0.5, + liquidity: 1, + baseDecimals: 18, + quoteDecimals: 6, + }, + ]; + + for (let i = 0; i < cases.length; i++) { + const params = cases[i]; + const { amountIn, amountOut, fees } = getSwapAmounts({ + isBuy: params.isBuy, + exactInput: params.exactInput, + amount: params.amount, + swapFeeRate: params.swapFeeRate, + thresholdSqrtPrice: params.thresholdSqrtPrice, + thresholdAmount: params.thresholdAmount, + lowerSqrtPrice: params.lowerSqrtPrice, + upperSqrtPrice: params.upperSqrtPrice, + liquidity: params.liquidity, + baseDecimals: params.baseDecimals, + quoteDecimals: params.quoteDecimals, + }); + console.log(`Case ${i + 1}`); + const inDecimals = params.isBuy + ? params.quoteDecimals + : params.baseDecimals; + const outDecimals = params.isBuy + ? params.baseDecimals + : params.quoteDecimals; + console.log({ + amountIn: new Decimal(amountIn) + .mul(new Decimal(10).pow(inDecimals)) + .toFixed(0), + amountOut: new Decimal(amountOut) + .mul(new Decimal(10).pow(outDecimals)) + .toFixed(0), + fees: new Decimal(fees).mul(new Decimal(10).pow(inDecimals)).toFixed(0), + }); + } +}; + +testGetSwapAmounts(); diff --git a/models/src/reversion/libraries/SwapLib.ts b/models/src/reversion/libraries/SwapLib.ts index cb43a68..863f49c 100644 --- a/models/src/reversion/libraries/SwapLib.ts +++ b/models/src/reversion/libraries/SwapLib.ts @@ -51,7 +51,12 @@ export const getSwapAmounts = ({ ? Decimal.max(thresholdSqrtPrice, scaledLowerSqrtPrice) : scaledLowerSqrtPrice; - const { amountIn, amountOut, fees, nextSqrtPrice } = computeSwapAmount({ + const { + amountIn: netAmountIn, + amountOut, + fees, + nextSqrtPrice, + } = computeSwapAmount({ currSqrtPrice: startSqrtPrice, targetSqrtPrice, liquidity, @@ -60,19 +65,24 @@ export const getSwapAmounts = ({ exactInput, }); - const grossAmountIn = new Decimal(amountIn).add(fees); + const amountIn = + nextSqrtPrice !== targetSqrtPrice && + new Decimal(liquidity).gt(0) && + exactInput + ? amount + : new Decimal(netAmountIn).add(fees); if (thresholdAmount) { if (exactInput) { if (amountOut < thresholdAmount) throw new Error("Threshold amount not met"); } else { - if (grossAmountIn > thresholdAmount) + if (amountIn > thresholdAmount) throw new Error("Threshold amount exceeded"); } } - return { amountIn: grossAmountIn, amountOut, fees }; + return { amountIn, amountOut, fees }; }; export const computeSwapAmount = ({ diff --git a/models/src/reversion/tests/SwapLib.test.ts b/models/src/reversion/tests/SwapLib.test.ts index 3cd05aa..92e0beb 100644 --- a/models/src/reversion/tests/SwapLib.test.ts +++ b/models/src/reversion/tests/SwapLib.test.ts @@ -11,6 +11,8 @@ type SwapParams = { lowerSqrtPrice: Decimal.Value; upperSqrtPrice: Decimal.Value; liquidity: Decimal.Value; + baseDecimals: number; + quoteDecimals: number; }; const testGetSwapAmounts = () => { const cases: SwapParams[] = [ @@ -24,6 +26,8 @@ const testGetSwapAmounts = () => { lowerSqrtPrice: 0.8 ** 0.5, upperSqrtPrice: 1, liquidity: 10000, + baseDecimals: 18, + quoteDecimals: 18, }, { isBuy: true, @@ -35,6 +39,8 @@ const testGetSwapAmounts = () => { lowerSqrtPrice: 1, upperSqrtPrice: 1.2 ** 0.5, liquidity: 0, + baseDecimals: 18, + quoteDecimals: 18, }, { isBuy: false, @@ -46,6 +52,8 @@ const testGetSwapAmounts = () => { lowerSqrtPrice: 0.8 ** 0.5, upperSqrtPrice: 1, liquidity: 200, + baseDecimals: 18, + quoteDecimals: 18, }, { isBuy: true, @@ -57,32 +65,54 @@ const testGetSwapAmounts = () => { lowerSqrtPrice: 1, upperSqrtPrice: 1.2 ** 0.5, liquidity: 200, + baseDecimals: 18, + quoteDecimals: 18, + }, + { + isBuy: true, + exactInput: true, + amount: 9.26891, + swapFeeRate: 0.005, + thresholdSqrtPrice: null, + thresholdAmount: null, + lowerSqrtPrice: 0.375 ** 0.5, + upperSqrtPrice: 0.4 ** 0.5, + liquidity: 1, + baseDecimals: 18, + quoteDecimals: 6, }, ]; - const baseDecimals = 18; - const quoteDecimals = 18; - for (let i = 0; i < cases.length; i++) { const params = cases[i]; - const { amountIn, amountOut, fees } = getSwapAmounts( - params.isBuy, - params.exactInput, - params.amount, - params.swapFeeRate, - params.thresholdSqrtPrice, - params.thresholdAmount, - params.lowerSqrtPrice, - params.upperSqrtPrice, - params.liquidity, - baseDecimals, - quoteDecimals - ); + const { amountIn, amountOut, fees } = getSwapAmounts({ + isBuy: params.isBuy, + exactInput: params.exactInput, + amount: params.amount, + swapFeeRate: params.swapFeeRate, + thresholdSqrtPrice: params.thresholdSqrtPrice, + thresholdAmount: params.thresholdAmount, + lowerSqrtPrice: params.lowerSqrtPrice, + upperSqrtPrice: params.upperSqrtPrice, + liquidity: params.liquidity, + baseDecimals: params.baseDecimals, + quoteDecimals: params.quoteDecimals, + }); console.log(`Case ${i + 1}`); + const inDecimals = params.isBuy + ? params.quoteDecimals + : params.baseDecimals; + const outDecimals = params.isBuy + ? params.baseDecimals + : params.quoteDecimals; console.log({ - amountIn: new Decimal(amountIn).mul(1e18).toFixed(0), - amountOut: new Decimal(amountOut).mul(1e18).toFixed(0), - fees: new Decimal(fees).mul(1e18).toFixed(0), + amountIn: new Decimal(amountIn) + .mul(new Decimal(10).pow(inDecimals)) + .toFixed(0), + amountOut: new Decimal(amountOut) + .mul(new Decimal(10).pow(outDecimals)) + .toFixed(0), + fees: new Decimal(fees).mul(new Decimal(10).pow(inDecimals)).toFixed(0), }); } }; diff --git a/packages/replicating/src/libraries/swap_lib.cairo b/packages/replicating/src/libraries/swap_lib.cairo index 1ad7c0a..9a603d6 100644 --- a/packages/replicating/src/libraries/swap_lib.cairo +++ b/packages/replicating/src/libraries/swap_lib.cairo @@ -49,7 +49,7 @@ pub fn get_swap_amounts( }; // Compute swap amounts. - let (amount_in, amount_out, fees, _) = compute_swap_amounts( + let (net_amount_in, amount_out, fees, next_sqrt_price) = compute_swap_amounts( start_sqrt_price, target_sqrt_price, position.liquidity, @@ -58,7 +58,17 @@ pub fn get_swap_amounts( swap_params.exact_input, ); - (amount_in + fees, amount_out, fees) + // If liquidity is sufficient to fill entire swap amount, we want to make sure the + // requested amount is fully consumed for exact input case. + let amount_in = if next_sqrt_price != target_sqrt_price + && position.liquidity != 0 + && swap_params.exact_input { + swap_params.amount + } else { + net_amount_in + fees + }; + + (amount_in, amount_out, fees) } // Compute amounts swapped and new price after swapping between two prices. @@ -72,7 +82,7 @@ pub fn get_swap_amounts( // * `exact_input` - whether swap amount is exact input or output // // # Returns -// * `amount_in` - amount of tokens swapped in +// * `net_amount_in` - net amount of tokens swapped in // * `amount_out` - amount of tokens swapped out // * `fee_amount` - amount of fees // * `next_sqrt_price` - next sqrt price diff --git a/packages/replicating/src/tests/libraries/test_swap_lib.cairo b/packages/replicating/src/tests/libraries/test_swap_lib.cairo index 8e1c8e9..87fa278 100644 --- a/packages/replicating/src/tests/libraries/test_swap_lib.cairo +++ b/packages/replicating/src/tests/libraries/test_swap_lib.cairo @@ -106,6 +106,24 @@ fn test_get_swap_amounts_ask_threshold_sqrt_price() { ); } +#[test] +fn test_swap_amount_always_consumes_full_input_if_sufficient_liquidity() { + let swap_params = SwapParams { + is_buy: true, + amount: 9268910, + exact_input: true, + threshold_sqrt_price: Option::None(()), + threshold_amount: Option::None(()), + deadline: Option::None(()), + }; + let position = PositionInfo { + lower_sqrt_price: to_e18(6100), upper_sqrt_price: to_e18(6500), liquidity: to_e18_u128(1), + }; + let swap_fee_rate = 50; + let (amount_in, _, _) = get_swap_amounts(swap_params, swap_fee_rate, position); + assert(amount_in == swap_params.amount, 'Swap amount'); +} + //////////////////////////////// // TESTS - compute_swap_amounts //////////////////////////////// diff --git a/packages/reversion/src/libraries/swap_lib.cairo b/packages/reversion/src/libraries/swap_lib.cairo index 1ad7c0a..b09a764 100644 --- a/packages/reversion/src/libraries/swap_lib.cairo +++ b/packages/reversion/src/libraries/swap_lib.cairo @@ -49,7 +49,7 @@ pub fn get_swap_amounts( }; // Compute swap amounts. - let (amount_in, amount_out, fees, _) = compute_swap_amounts( + let (net_amount_in, amount_out, fees, next_sqrt_price) = compute_swap_amounts( start_sqrt_price, target_sqrt_price, position.liquidity, @@ -58,7 +58,17 @@ pub fn get_swap_amounts( swap_params.exact_input, ); - (amount_in + fees, amount_out, fees) + // If liquidity is sufficient to fill entire swap amount, we want to make sure the + // requested amount is fully consumed for exact input case. + let amount_in = if next_sqrt_price != target_sqrt_price + && position.liquidity != 0 + && swap_params.exact_input { + swap_params.amount + } else { + net_amount_in + fees + }; + + (amount_in, amount_out, fees) } // Compute amounts swapped and new price after swapping between two prices. diff --git a/packages/reversion/src/tests/libraries/test_swap_lib.cairo b/packages/reversion/src/tests/libraries/test_swap_lib.cairo index 29c4d9f..855b594 100644 --- a/packages/reversion/src/tests/libraries/test_swap_lib.cairo +++ b/packages/reversion/src/tests/libraries/test_swap_lib.cairo @@ -104,6 +104,24 @@ fn test_get_swap_amounts_ask_threshold_sqrt_price() { ); } +#[test] +fn test_swap_amount_always_consumes_full_input_if_sufficient_liquidity() { + let swap_params = SwapParams { + is_buy: true, + amount: 9268910, + exact_input: true, + threshold_sqrt_price: Option::None(()), + threshold_amount: Option::None(()), + deadline: Option::None(()), + }; + let position = PositionInfo { + lower_sqrt_price: to_e18(6100), upper_sqrt_price: to_e18(6500), liquidity: to_e18_u128(1), + }; + let swap_fee_rate = 50; + let (amount_in, _, _) = get_swap_amounts(swap_params, swap_fee_rate, position); + assert(amount_in == swap_params.amount, 'Swap amount'); +} + //////////////////////////////// // TESTS - compute_swap_amounts //////////////////////////////// diff --git a/scripts/mainnet.sh b/scripts/mainnet.sh index d305062..f552600 100644 --- a/scripts/mainnet.sh +++ b/scripts/mainnet.sh @@ -30,6 +30,9 @@ starkli deploy --rpc $STARKNET_RPC $REPLICATING_SOLVER_CLASS $OWNER $ORACLE $VAU # Deployments ############################# +# 4 October 2024 +export REPLICATING_SOLVER_CLASS=0x025a7d7c63acb6458b3d53c7ccf05fa8d5d3374993a30121db32e13623899860 + # 13 September 2024 export REPLICATING_SOLVER=0x073cc79b07a02fe5dcd714903d62f9f3081e15aeb34e3725f44e495ecd88a5a1 export REPLICATING_SOLVER_CLASS=0x030ba3f1857330dfe032ae7e43f51d4b206756ec4757fa3ac14e79ff79a98a44 diff --git a/scripts/sepolia.sh b/scripts/sepolia.sh index 3041bae..8c50ad0 100644 --- a/scripts/sepolia.sh +++ b/scripts/sepolia.sh @@ -36,6 +36,10 @@ starkli deploy --rpc $STARKNET_RPC --account $STARKNET_ACCOUNT $REVERSION_SOLVER # Deployments ############################# +# 4 October 2024 +export REVERSION_SOLVER_CLASS=0x01026b3b80531374d6310a7d095ba7fa568d1422313e71f1e2d8a2407b03c2fb +export REPLICATING_SOLVER_CLASS=0x025a7d7c63acb6458b3d53c7ccf05fa8d5d3374993a30121db32e13623899860 + # 29 September 2024 export REVERSION_SOLVER=0x02dadd1655400572bbbec530ce4f7c4a691566614cf3d3a3ca602387bc852f33 export REVERSION_SOLVER_CLASS=0x05d2e696c1205022d858d44ca15fda56e374acd63cd90c601648079f8c3c2fc3