From 2c3cfad78d944dcc8b6113909ba866ca4edbce2a Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 4 Jan 2024 20:34:07 +0900 Subject: [PATCH] =?UTF-8?q?GSW-782=20fix:=20minimize=20gap=20between=20dry?= =?UTF-8?q?=20=E2=89=88=20actual=20route=20swap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ....gnoa => _TEST_router_api_getter_test.gno} | 0 ...swap_route_1route_1hop_out_range_test.gnoa | 8 +-- ..._route_1route_2hop_native_in_out_test.gnoa | 4 +- ..._route_1route_3hop_native_middle_test.gnoa | 8 +-- ...T_router_swap_route_2route_2hop_test.gnoa} | 2 +- router/consts.gno | 5 +- router/router.gno | 50 ++++++++++---- router/swap_inner.gno | 68 ++++++------------- router/swap_multi.gno | 61 +++++++++++------ router/swap_single.gno | 12 ++-- 10 files changed, 116 insertions(+), 102 deletions(-) rename router/{_TEST_router_api_getter_test.gnoa => _TEST_router_api_getter_test.gno} (100%) rename router/{_TEST_router_swap_route_2route_2hop_test.gno => _TEST_router_swap_route_2route_2hop_test.gnoa} (99%) diff --git a/router/_TEST_router_api_getter_test.gnoa b/router/_TEST_router_api_getter_test.gno similarity index 100% rename from router/_TEST_router_api_getter_test.gnoa rename to router/_TEST_router_api_getter_test.gno diff --git a/router/_TEST_router_swap_route_1route_1hop_out_range_test.gnoa b/router/_TEST_router_swap_route_1route_1hop_out_range_test.gnoa index 9883c69d9..285f87b80 100644 --- a/router/_TEST_router_swap_route_1route_1hop_out_range_test.gnoa +++ b/router/_TEST_router_swap_route_1route_1hop_out_range_test.gnoa @@ -33,9 +33,9 @@ func TestPositionMint(t *testing.T) { // Mint tokenId, liquidity, amount0, amount1 := pos.Mint(barPath, bazPath, fee500, int32(12000), int32(15000), bigint(100_000), bigint(100_000), 0, 0, max_timeout) shouldEQ(t, tokenId, uint64(1)) - shouldEQ(t, liquidity, bigint(1308149)) - shouldEQ(t, amount0, bigint(99999)) // ONLY BAR - shouldEQ(t, amount1, bigint(0)) // NO BAZ + shouldEQ(t, liquidity, bigint(1308150)) + shouldEQ(t, amount0, bigint(100000)) // ONLY BAR + shouldEQ(t, amount1, bigint(0)) // NO BAZ } func TestDrySwapRouteBarBazExactIn(t *testing.T) { @@ -50,7 +50,7 @@ func TestDrySwapRouteBarBazExactIn(t *testing.T) { "100", // quoteArr ) - shouldEQ(t, dryResult, bigint(0)) + shouldEQ(t, dryResult, bigint(-1)) } func TestSwapRouteBarBazExactIn(t *testing.T) { diff --git a/router/_TEST_router_swap_route_1route_2hop_native_in_out_test.gnoa b/router/_TEST_router_swap_route_1route_2hop_native_in_out_test.gnoa index 2f5b328ae..0657f6140 100644 --- a/router/_TEST_router_swap_route_1route_2hop_native_in_out_test.gnoa +++ b/router/_TEST_router_swap_route_1route_2hop_native_in_out_test.gnoa @@ -53,9 +53,9 @@ func TestPositionMintQuxGnot(t *testing.T) { std.TestSetOrigCaller(test1) // send - std.TestSetOrigSend(std.Coins{{"ugnot", 99999}}, nil) + std.TestSetOrigSend(std.Coins{{"ugnot", 999999}}, nil) testBanker := std.GetBanker(std.BankerTypeRealmIssue) - testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", 99999) + testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", 999999) // Deposit(wrap) std.TestSetPrevAddr(test1) diff --git a/router/_TEST_router_swap_route_1route_3hop_native_middle_test.gnoa b/router/_TEST_router_swap_route_1route_3hop_native_middle_test.gnoa index f9a0cd0a6..1362b71d0 100644 --- a/router/_TEST_router_swap_route_1route_3hop_native_middle_test.gnoa +++ b/router/_TEST_router_swap_route_1route_3hop_native_middle_test.gnoa @@ -34,9 +34,9 @@ func TestPositionMintGnsGnot(t *testing.T) { std.TestSetOrigCaller(test1) // send - std.TestSetOrigSend(std.Coins{{"ugnot", 99999}}, nil) + std.TestSetOrigSend(std.Coins{{"ugnot", 999999}}, nil) testBanker := std.GetBanker(std.BankerTypeRealmIssue) - testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", 99999) + testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", 999999) // Deposit(wrap) std.TestSetPrevAddr(test1) @@ -52,9 +52,9 @@ func TestPositionMintGnotBar(t *testing.T) { std.TestSetOrigCaller(test1) // send - std.TestSetOrigSend(std.Coins{{"ugnot", 99999}}, nil) + std.TestSetOrigSend(std.Coins{{"ugnot", 999999}}, nil) testBanker := std.GetBanker(std.BankerTypeRealmIssue) - testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", 99999) + testBanker.RemoveCoin(std.GetOrigCaller(), "ugnot", 999999) // Deposit(wrap) std.TestSetPrevAddr(test1) diff --git a/router/_TEST_router_swap_route_2route_2hop_test.gno b/router/_TEST_router_swap_route_2route_2hop_test.gnoa similarity index 99% rename from router/_TEST_router_swap_route_2route_2hop_test.gno rename to router/_TEST_router_swap_route_2route_2hop_test.gnoa index 96f371f8d..e14d48720 100644 --- a/router/_TEST_router_swap_route_2route_2hop_test.gno +++ b/router/_TEST_router_swap_route_2route_2hop_test.gnoa @@ -157,5 +157,5 @@ func TestwapRouteQuxBarExactOut(t *testing.T) { 99999, // tokenAmountLimit ) - shouldEQ(t, swapResult, bigint(7356)) + shouldEQ(t, swapResult, bigint(7350)) } diff --git a/router/consts.gno b/router/consts.gno index 71056afd0..1d6382211 100644 --- a/router/consts.gno +++ b/router/consts.gno @@ -6,7 +6,10 @@ const ( MIN_PRICE bigint = 4295128740 // MIN_SQRT_RATIO + 1 MAX_PRICE bigint = 1461446703485210103287273052203988822378723970341 // MAX_SQRT_RATIO - 1 - Q96 bigint = 79228162514264337593543950336 // 2 ** 96 + Q96 bigint = 79228162514264337593543950336 // 2 ** 96 + MAX_UINT256 bigint = 115792089237316195423570985008687907853269984665640564039457584007913129639935 + + MAX_UINT64 uint64 = 18446744073709551615 ADDR_POOL std.Address = std.DerivePkgAddr("gno.land/r/demo/pool") ADDR_ROUTER std.Address = std.DerivePkgAddr("gno.land/r/demo/router") diff --git a/router/router.gno b/router/router.gno index 765c0a716..f541451e8 100644 --- a/router/router.gno +++ b/router/router.gno @@ -29,11 +29,11 @@ func DrySwapRoute( // check route length ( should be 1 ~ 7 ) routes := strings.Split(strRouteArr, ",") - require(1 <= len(routes) && len(routes) <= 7, ufmt.Sprintf("[ROUTER] router.gno__DrySwapRoute() || len(routes) should be 1 ~ 7 (len(routes):%d)", len(routes))) + require(1 <= len(routes) && len(routes) <= 7, ufmt.Sprintf("[ROUTER] router.gno__DrySwapRoute() || len(routes) should be 1 ~ 7 (len(routes)[%d])", len(routes))) // check if routes length and quotes length are same quotes := strings.Split(quoteArr, ",") - require(len(routes) == len(quotes), "[ROUTER] router.gno__DrySwapRoute() || len(routes) != len(quotes)") + require(len(routes) == len(quotes), ufmt.Sprintf("[ROUTER] router.gno__DrySwapRoute() || len(routes[%d]) != len(quotes[%d])", len(routes), len(quotes))) // check if quotes are up to 100% quotesSum := 0 @@ -43,7 +43,7 @@ func DrySwapRoute( } require(quotesSum == 100, "[ROUTER] router.gno__DrySwapRoute() || quotesSum != 100") - resultAmount := bigint(0) + var resultAmountIn, resultAmountOut bigint for i, route := range routes { numHops := strings.Count(route, "*POOL*") + 1 quote, _ := strconv.Atoi(quotes[i]) @@ -54,14 +54,31 @@ func DrySwapRoute( toSwap := amountSpecified * bigint(quote) / bigint(100) if numHops == 1 { // SINGLE - resultAmount += handleSingleSwap(route, toSwap, true) + amountIn, amountOut := handleSingleSwap(route, toSwap, true) + resultAmountIn += amountIn + resultAmountOut += amountOut } else if 2 <= numHops && numHops <= 3 { // MULTI - resultAmount += handleMultiSwap(swapType, route, numHops, toSwap, true) + amountIn, amountOut := handleMultiSwap(swapType, route, numHops, toSwap, true) + resultAmountIn += amountIn + resultAmountOut += amountOut } else { panic("[ROUTER] router.gno__DrySwapRoute() || numHops should be 1 ~ 3") } } - return resultAmount + + if swapType == ExactIn { + if resultAmountIn != amountSpecified { + return -1 // if pool doesn't have enough output token amount to swap against input token amount + } + return resultAmountOut + } + + if swapType == ExactOut { + if resultAmountOut < amountSpecified { // if pool doesn't user wanted amount of output token + return -1 + } + return resultAmountIn + } } func SwapRoute( @@ -100,7 +117,7 @@ func SwapRoute( } require(quotesSum == 100, "[ROUTER] router.gno__SwapRoute() || quotesSum != 100") - resultAmount := bigint(0) + var resultAmountIn, resultAmountOut bigint for i, route := range routes { numHops := strings.Count(route, "*POOL*") + 1 quote, _ := strconv.Atoi(quotes[i]) @@ -111,23 +128,28 @@ func SwapRoute( toSwap := amountSpecified * bigint(quote) / bigint(100) if numHops == 1 { // SINGLE - resultAmount += handleSingleSwap(route, toSwap, false) + amountIn, amountOut := handleSingleSwap(route, toSwap, false) + resultAmountIn += amountIn + resultAmountOut += amountOut } else if 2 <= numHops && numHops <= 3 { // MULTI - resultAmount += handleMultiSwap(swapType, route, numHops, toSwap, false) + amountIn, amountOut := handleMultiSwap(swapType, route, numHops, toSwap, false) + resultAmountIn += amountIn + resultAmountOut += amountOut } else { panic("[ROUTER] router.gno__SwapRoute() || numHops should be 1 ~ 3") } } if swapType == ExactIn { - require(tokenAmountLimit <= resultAmount, ufmt.Sprintf("[ROUTER] router.gno__SwapRoute() || too few received (expected minimum output:%d, actual output:%d)", tokenAmountLimit, resultAmount)) + require(tokenAmountLimit <= resultAmountOut, ufmt.Sprintf("[ROUTER] router.gno__SwapRoute() || too few received (expected minimum output:%d, actual output:%d)", tokenAmountLimit, resultAmountOut)) + return resultAmountOut } else { // EXACT_OUT - require(resultAmount <= tokenAmountLimit, ufmt.Sprintf("[ROUTER] router.gno__SwapRoute() || too much spend (expected maximum input:%d, actual input:%d)", tokenAmountLimit, resultAmount)) + require(resultAmountIn <= tokenAmountLimit, ufmt.Sprintf("[ROUTER] router.gno__SwapRoute() || too much spend (expected maximum input:%d, actual input:%d)", tokenAmountLimit, resultAmountIn)) + return resultAmountIn } - return resultAmount } -func handleSingleSwap(route string, amountSpecified bigint, isDry bool) bigint { +func handleSingleSwap(route string, amountSpecified bigint, isDry bool) (amountIn, amountOut bigint) { input, output, fee := getDataForSinglePath(route) singleParams := SingleSwapParams{ tokenIn: input, @@ -142,7 +164,7 @@ func handleSingleSwap(route string, amountSpecified bigint, isDry bool) bigint { return singleSwap(singleParams) } -func handleMultiSwap(swapType SwapType, route string, numHops int, amountSpecified bigint, isDry bool) bigint { +func handleMultiSwap(swapType SwapType, route string, numHops int, amountSpecified bigint, isDry bool) (amountIn, amountOut bigint) { switch swapType { case ExactIn: input, output, fee := getDataForMultiPath(route, 0) // first data diff --git a/router/swap_inner.gno b/router/swap_inner.gno index 5f2cec640..15cc054e6 100644 --- a/router/swap_inner.gno +++ b/router/swap_inner.gno @@ -11,7 +11,7 @@ func _swap( recipient std.Address, sqrtPriceLimitX96 bigint, data SwapCallbackData, -) (amountResult bigint) { +) (amountPoolRecv, amountPoolOut bigint) { // prepare zeroForOne := data.tokenIn < data.tokenOut @@ -23,20 +23,8 @@ func _swap( } } - // dry swap -> esteimate amount -> approve exact amount - approveAmount0, approveAmount1, _ := p.DrySwap( - data.tokenIn, - data.tokenOut, - data.fee, - - recipient, - zeroForOne, - amountSpecified, - sqrtPriceLimitX96, - ) - toApproveAmount := max(abs(approveAmount0), abs(approveAmount1)) - // ROUTER approves POOL as spender + toApproveAmount := MAX_UINT64 approveByRegisterCall(data.tokenIn, ADDR_POOL, toApproveAmount) approveByRegisterCall(data.tokenOut, ADDR_POOL, toApproveAmount) @@ -53,21 +41,7 @@ func _swap( data.payer, ) - if amountSpecified > 0 { - if zeroForOne { // return amount, user recvs ~= pool sends - amountResult = -amount1 - } else { - amountResult = -amount0 - } - } else { - if zeroForOne { // return amount, user sends ~= pool recvs - amountResult = amount0 - } else { - amountResult = amount1 - } - } - - return amountResult + return absBigint(maxBigint(amount0, amount1)), absBigint(minBigint(amount0, amount1)) } func _swapDry( @@ -75,7 +49,7 @@ func _swapDry( recipient std.Address, sqrtPriceLimitX96 bigint, data SwapCallbackData, -) (amountResult bigint) { +) (amountPoolRecv, amountPoolOut bigint) { zeroForOne := data.tokenIn < data.tokenOut if sqrtPriceLimitX96 == 0 { @@ -98,31 +72,27 @@ func _swapDry( sqrtPriceLimitX96, ) if !ok { - return 0 + return 0, 0 } - if amountSpecified > 0 { // EXACT_IN, - // input from user: tokenIn, tokenInAmount, tokenOut - // return: return tokenOutAmount(in positive) ≈ pool sends ≈ user recvs - if zeroForOne { - return absBigint(amount1) - } else { - return absBigint(amount0) - } - } else { // EXACT_OUT - // input from user: tokenIn, tokenOut, tokenOutAmount - // return: return tokenInAmount(in positive) ≈ user sends ≈ pool recvs - if zeroForOne { - return absBigint(amount0) - } else { - return absBigint(amount1) - } + return absBigint(maxBigint(amount0, amount1)), absBigint(minBigint(amount0, amount1)) +} + +func max(a, b uint64) uint64 { + if a > b { + return a } + return b +} - return amountResult +func minBigint(a, b bigint) bigint { + if a < b { + return a + } + return b } -func max(a, b uint64) uint64 { +func maxBigint(a, b bigint) bigint { if a > b { return a } diff --git a/router/swap_multi.gno b/router/swap_multi.gno index 2ab14a605..620619a77 100644 --- a/router/swap_multi.gno +++ b/router/swap_multi.gno @@ -4,7 +4,7 @@ import ( "std" ) -func multiSwap(params SwapParams, currentPoolIndex, numPools int, swapPath string) (amountOut bigint) { +func multiSwap(params SwapParams, currentPoolIndex, numPools int, swapPath string) (firstAmountIn, lastAmountOut bigint) { payer := std.GetOrigCaller() // user for { @@ -17,7 +17,7 @@ func multiSwap(params SwapParams, currentPoolIndex, numPools int, swapPath strin recipient = params.recipient // user ~= std.GetOrigCaller() } - params.amountSpecified = _swap( + amountIn, amountOut := _swap( params.amountSpecified, recipient, 0, @@ -29,6 +29,10 @@ func multiSwap(params SwapParams, currentPoolIndex, numPools int, swapPath strin }, ) + if currentPoolIndex == 1 { + firstAmountIn = amountIn + } + if currentPoolIndex < numPools { payer = std.DerivePkgAddr("gno.land/r/demo/router") @@ -36,20 +40,22 @@ func multiSwap(params SwapParams, currentPoolIndex, numPools int, swapPath strin params.tokenIn = nextInput params.tokenOut = nextOutput params.fee = nextFee + + params.amountSpecified = amountOut + } else { - amountOut = params.amountSpecified - return amountOut + return firstAmountIn, amountOut } } } -func multiSwapNegative(params SwapParams, numPools int, swapPath string) (amountOut bigint) { +func multiSwapNegative(params SwapParams, numPools int, swapPath string) (fisrtAmountIn, lastAmountOut bigint) { swapInfo := []SingleSwapParams{} currentPoolIndex := numPools // CALCULATE BACKWARD INFO for { - wantedSingle := singleSwapDry( + amountIn, amountOut := singleSwapDry( SingleSwapParams{ tokenIn: params.tokenIn, tokenOut: params.tokenOut, @@ -74,7 +80,7 @@ func multiSwapNegative(params SwapParams, numPools int, swapPath string) (amount params.tokenIn = nextInput params.tokenOut = nextOutput params.fee = nextFee - params.amountSpecified = -wantedSingle - 1 + params.amountSpecified = -amountIn } } @@ -90,7 +96,7 @@ func multiSwapNegative(params SwapParams, numPools int, swapPath string) (amount recipient = std.DerivePkgAddr("gno.land/r/demo/router") } - resultAmount := _swap( + amountIn, amountOut := _swap( swapInfo[currentPoolIndex].amountSpecified, recipient, 0, @@ -102,16 +108,21 @@ func multiSwapNegative(params SwapParams, numPools int, swapPath string) (amount }, ) + // save route's first hop's amountIn to check whether crossed limit or not + if currentPoolIndex == len(swapInfo)-1 { + fisrtAmountIn = amountIn + } + if currentPoolIndex == 0 { - return amountOut + return fisrtAmountIn, amountOut } else { payer = std.DerivePkgAddr("gno.land/r/demo/router") - amountOut = resultAmount // return first swap input amount + swapInfo[currentPoolIndex-1].amountSpecified = amountOut } } } -func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath string) (amountOut bigint) { +func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath string) (firstAmountIn, lastAmountOut bigint) { payer := std.GetOrigCaller() // user for { @@ -124,7 +135,7 @@ func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath str recipient = params.recipient // user ~= std.GetOrigCaller() } - params.amountSpecified = _swapDry( + amountIn, amountOut := _swapDry( params.amountSpecified, recipient, 0, @@ -136,6 +147,10 @@ func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath str }, ) + if currentPoolIndex == 1 { + firstAmountIn = amountIn + } + if currentPoolIndex < numPool { payer = std.DerivePkgAddr("gno.land/r/demo/router") @@ -143,15 +158,16 @@ func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath str params.tokenIn = nextInput params.tokenOut = nextOutput params.fee = nextFee + + params.amountSpecified = amountOut } else { - amountOut = params.amountSpecified - return amountOut + return firstAmountIn, amountOut } } } -func multiSwapNegativeDry(params SwapParams, currentPoolIndex int, swapPath string) (amountOut bigint) { +func multiSwapNegativeDry(params SwapParams, currentPoolIndex int, swapPath string) (firstAmountIn, lastAmountOut bigint) { payer := std.DerivePkgAddr("gno.land/r/demo/router") numPools := currentPoolIndex @@ -164,7 +180,7 @@ func multiSwapNegativeDry(params SwapParams, currentPoolIndex int, swapPath stri recipient = std.DerivePkgAddr("gno.land/r/demo/router") } - params.amountSpecified = _swapDry( + amountIn, amountOut := _swapDry( params.amountSpecified, recipient, 0, @@ -175,6 +191,12 @@ func multiSwapNegativeDry(params SwapParams, currentPoolIndex int, swapPath stri payer, }, ) + + if currentPoolIndex == 0 { + // save for return + firstAmountIn = amountIn + } + currentPoolIndex-- if currentPoolIndex != -1 { @@ -183,12 +205,9 @@ func multiSwapNegativeDry(params SwapParams, currentPoolIndex int, swapPath stri params.tokenOut = nextOutput params.fee = nextFee - // input amounts derived in the last stage must be used again as the n-1th output amounts - // >> must be negative - params.amountSpecified = -params.amountSpecified + params.amountSpecified = -amountIn } else { - amountOut = params.amountSpecified - return amountOut + return firstAmountIn, amountOut } } } diff --git a/router/swap_single.gno b/router/swap_single.gno index ad1ba67d6..ec2ddb778 100644 --- a/router/swap_single.gno +++ b/router/swap_single.gno @@ -4,8 +4,8 @@ import ( "std" ) -func singleSwap(params SingleSwapParams) (amountOut bigint) { - amountOut = _swap( +func singleSwap(params SingleSwapParams) (amountIn, amountOut bigint) { + amountIn, amountOut = _swap( params.amountSpecified, std.GetOrigCaller(), // if single swap => user will recieve 0, // sqrtPriceLimitX96 @@ -17,11 +17,11 @@ func singleSwap(params SingleSwapParams) (amountOut bigint) { }, ) - return amountOut + return amountIn, amountOut } -func singleSwapDry(params SingleSwapParams) (amountOut bigint) { - amountOut = _swapDry( +func singleSwapDry(params SingleSwapParams) (amountIn, amountOut bigint) { + amountIn, amountOut = _swapDry( params.amountSpecified, std.GetOrigCaller(), // if single swap => user will recieve 0, // sqrtPriceLimitX96 @@ -33,5 +33,5 @@ func singleSwapDry(params SingleSwapParams) (amountOut bigint) { }, ) - return amountOut + return amountIn, amountOut }