From 432af540ab16977e999528bd394eb2a20427c136 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Sat, 21 Dec 2024 15:16:11 +0900 Subject: [PATCH] doc: `getMaxTick`, `getMinTick` --- router/router.gno | 100 ++++++++++++++---------------------------- router/swap_inner.gno | 46 +++++++++++++++++-- 2 files changed, 77 insertions(+), 69 deletions(-) diff --git a/router/router.gno b/router/router.gno index d32fba27..187fb7b6 100644 --- a/router/router.gno +++ b/router/router.gno @@ -27,11 +27,6 @@ const ( SINGLE_HOP_ROUTE int = 1 ) -// type Router interface { -// ExactInSwapRoute(ExactInParams) (string, string) -// ExactOutSwapRoute(ExactOutParams) (string, string) -// } - // SwapRoute swaps the input token to the output token and returns the result amount // If swapType is EXACT_IN, it returns the amount of output token ≈ amount of user to receive // If swapType is EXACT_OUT, it returns the amount of input token ≈ amount of user to pay @@ -510,33 +505,6 @@ func validateRoutesAndQuotes(routes, quotes []string) error { return nil } -func processRoutes(routes, quotes []string, amountSpecified *i256.Int, swapType string) (*u256.Uint, *u256.Uint) { - resultAmountIn := u256.Zero() - resultAmountOut := u256.Zero() - - for i, route := range routes { - numHops := strings.Count(route, POOL_SEPARATOR) + 1 - quote, _ := strconv.Atoi(quotes[i]) - - assertHopsInRange(numHops) - - toSwap := i256.Zero().Mul(amountSpecified, i256.NewInt(int64(quote))) - toSwap = toSwap.Div(toSwap, i256.NewInt(100)) - - var amountIn, amountOut *u256.Uint - if numHops == 1 { - amountIn, amountOut = handleSingleSwap(route, toSwap) - } else { - amountIn, amountOut = handleMultiSwap(swapType, route, numHops, toSwap) - } - - resultAmountIn = new(u256.Uint).Add(resultAmountIn, amountIn) - resultAmountOut = new(u256.Uint).Add(resultAmountOut, amountOut) - } - - return resultAmountIn, resultAmountOut -} - func handleSingleSwap(route string, amountSpecified *i256.Int) (*u256.Uint, *u256.Uint) { input, output, fee := getDataForSinglePath(route) singleParams := SingleSwapParams{ @@ -549,6 +517,40 @@ func handleSingleSwap(route string, amountSpecified *i256.Int) (*u256.Uint, *u25 return singleSwap(singleParams) } +func handleMultiSwap(swapType string, route string, numHops int, amountSpecified *i256.Int) (*u256.Uint, *u256.Uint) { + switch swapType { + case ExactIn: + input, output, fee := getDataForMultiPath(route, 0) // first data + swapParams := SwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + recipient: std.PrevRealm().Addr(), + amountSpecified: amountSpecified, + } + + return multiSwap(swapParams, 0, numHops, route) // iterate here + + case ExactOut: + input, output, fee := getDataForMultiPath(route, numHops-1) // last data + swapParams := SwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + recipient: std.PrevRealm().Addr(), + amountSpecified: amountSpecified, + } + + return multiSwapNegative(swapParams, numHops-1, route) // iterate here + + default: + panic(addDetailToError( + errInvalidSwapType, + ufmt.Sprintf("unknown swapType(%s)", swapType), + )) + } +} + func finalizeSwap(inputToken, outputToken string, resultAmountIn, resultAmountOut *u256.Uint, swapType string, tokenAmountLimit *u256.Uint, userBeforeWugnotBalance, userWrappedWugnot uint64, amountSpecified *u256.Uint) (string, string) { if swapType == ExactOut && resultAmountOut.Lt(amountSpecified) { panic(addDetailToError( @@ -602,37 +604,3 @@ func finalizeSwap(inputToken, outputToken string, resultAmountIn, resultAmountOu intAmountOut := i256.FromUint256(afterFee) return resultAmountIn.ToString(), i256.Zero().Neg(intAmountOut).ToString() } - -func handleMultiSwap(swapType string, route string, numHops int, amountSpecified *i256.Int) (*u256.Uint, *u256.Uint) { - switch swapType { - case ExactIn: - input, output, fee := getDataForMultiPath(route, 0) // first data - swapParams := SwapParams{ - tokenIn: input, - tokenOut: output, - fee: fee, - recipient: std.PrevRealm().Addr(), - amountSpecified: amountSpecified, - } - - return multiSwap(swapParams, 0, numHops, route) // iterate here - - case ExactOut: - input, output, fee := getDataForMultiPath(route, numHops-1) // last data - swapParams := SwapParams{ - tokenIn: input, - tokenOut: output, - fee: fee, - recipient: std.PrevRealm().Addr(), - amountSpecified: amountSpecified, - } - - return multiSwapNegative(swapParams, numHops-1, route) // iterate here - - default: - panic(addDetailToError( - errInvalidSwapType, - ufmt.Sprintf("unknown swapType(%s)", swapType), - )) - } -} diff --git a/router/swap_inner.gno b/router/swap_inner.gno index 777f1ada..ac7c7a3f 100644 --- a/router/swap_inner.gno +++ b/router/swap_inner.gno @@ -157,7 +157,26 @@ func calculateSqrtPriceLimitForSwap(zeroForOne bool, fee uint32, sqrtPriceLimitX // // Fee tier to min tick mapping demonstrates varying levels of price granularity: // -// Tick Range Visualization: +// ## How these values are calculated? +// +// The Tick bounds in Uniswap V3 are derived from the desired price range and precisions: +// 1. Price Range: Uniswap V3 uses the formula price = 1.0001^tick +// 2. The minimum tick is calculated to represent a very small but non-zero price: +// - Let min_tick = log(minimum_price) / log(1.0001) +// - The minimum price is chosen to be 2^-128 ≈ 2.9387e-39 +// - Therefor, min_tick = log(2^-128) / log(1.0001) ≈ -887272 +// +// ### Tick Spacing Adjustment +// +// - Each fee tier has different tick spacing for efficiency +// - The actual minimum tick is rounded to the nearest tick spacing: +// * 0.01% fee -> spacing of 1 -> -887272 +// * 0.05% fee -> spacing of 10 -> -887270 +// * 0.30% fee -> spacing of 60 -> -887220 +// * 1.00% fee -> spacing of 200 -> -887200 +// +// ## Tick Range Visualization: +// // ``` // 0 // Fee Tier Min Tick | Max Tick Tick Spacing @@ -174,6 +193,7 @@ func calculateSqrtPriceLimitForSwap(zeroForOne bool, fee uint32, sqrtPriceLimitX // ``` // // Tick spacing determines the granularity of price points: +// // - Smaller tick spacing (1) = More precise price points // Example for 0.01% fee tier: // ``` @@ -198,6 +218,9 @@ func calculateSqrtPriceLimitForSwap(zeroForOne bool, fee uint32, sqrtPriceLimitX // // Panic: // - If the fee tier is not supported +// +// Reference: +// - https://blog.uniswap.org/uniswap-v3-math-primer func getMinTick(fee uint32) int32 { switch fee { case 100: @@ -211,13 +234,27 @@ func getMinTick(fee uint32) int32 { default: panic(addDetailToError( errInvalidPoolFeeTier, - ufmt.Sprintf("swapInner.gno__getMaxTick() || unknown fee(%d)", fee), + ufmt.Sprintf("unknown fee(%d)", fee), )) } } // getMaxTick returns the maximum tick value for a given fee tier. // +// ## How these values are calculated? +// +// The max tick values are the exact negatives of min tick values because: +// 1. Price symmetry: If min_price = 2^-128, then max_price = 2^128 +// 2. Using the same formula: max_tick = log(2^128) / log(1.0001) ≈ 887272 +// +// ### Tick Spacing Relationship: +// +// The max ticks follow the same spacing rules as min ticks: +// * 0.01% fee -> +887272 (finest granularity) +// * 0.05% fee -> +887270 (10-tick spacing) +// * 0.30% fee -> +887220 (60-tick spacing) +// * 1.00% fee -> +887200 (coarsest granularity) +// // Parameters: // - fee: Fee tier in basis points // @@ -226,6 +263,9 @@ func getMinTick(fee uint32) int32 { // // Panic: // - If the fee tier is not supported +// +// Reference: +// - https://blog.uniswap.org/uniswap-v3-math-primer func getMaxTick(fee uint32) int32 { switch fee { case 100: @@ -239,7 +279,7 @@ func getMaxTick(fee uint32) int32 { default: panic(addDetailToError( errInvalidPoolFeeTier, - ufmt.Sprintf("swapInner.gno__getMaxTick() || unknown fee(%d)", fee), + ufmt.Sprintf("unknown fee(%d)", fee), )) } }