package v1 import ( "errors" "strings" prbac "gno.land/p/gnoswap/rbac" u256 "gno.land/p/gnoswap/uint256" ufmt "gno.land/p/nt/ufmt/v0" "gno.land/r/gnoswap/access" ) const ( SINGLE_HOP_ROUTE int = 1 ) // swap can be done by multiple pools // to separate each pool, we use POOL_SEPARATOR const ( POOL_SEPARATOR = "*POOL*" ) type RouterOperation interface { Validate() error Process(_ int, rlm realm) (*SwapResult, error) } // executeSwapOperation validates and processes a swap operation. func executeSwapOperation(_ int, rlm realm, op RouterOperation) (*SwapResult, error) { if err := op.Validate(); err != nil { return nil, err } result, err := op.Process(0, rlm) if err != nil { return nil, err } return result, nil } type BaseSwapParams struct { InputToken string OutputToken string RouteArr string QuoteArr string SqrtPriceLimitX96 *u256.Uint Deadline int64 } // common swap operation type baseSwapOperation struct { sqrtPriceLimitX96 *u256.Uint routes []string quotes []string amountSpecified int64 } // processRoutes processes all swap routes and returns total amounts. func (op *baseSwapOperation) processRoutes(_ int, rlm realm, r *routerV1, swapType SwapType) (int64, int64, error) { resultAmountIn, resultAmountOut := int64(0), int64(0) remainRequestAmount := op.amountSpecified for i, route := range op.routes { toSwapAmount := int64(0) // if it's the last route, use the remaining amount isLastRoute := i == len(op.routes)-1 if !isLastRoute { // calculate the amount to swap for this route swapAmount, err := calculateSwapAmountByQuote(op.amountSpecified, op.quotes[i]) if err != nil { return 0, 0, err } // update the remaining amount remainRequestAmount = safeSubInt64(remainRequestAmount, swapAmount) toSwapAmount = swapAmount } else { toSwapAmount = remainRequestAmount } amountIn, amountOut, err := op.processRoute(0, rlm, r, route, toSwapAmount, swapType) if err != nil { return 0, 0, err } resultAmountIn = safeAddInt64(resultAmountIn, amountIn) resultAmountOut = safeAddInt64(resultAmountOut, amountOut) } return resultAmountIn, resultAmountOut, nil } // processRoute processes a single route with specified swap amount. func (op *baseSwapOperation) processRoute( _ int, rlm realm, r *routerV1, route string, toSwap int64, swapType SwapType, ) (amountIn, amountOut int64, err error) { numHops := strings.Count(route, POOL_SEPARATOR) + 1 assertHopsInRange(numHops) switch numHops { case SINGLE_HOP_ROUTE: amountIn, amountOut = r.handleSingleSwap(0, rlm, route, toSwap, op.sqrtPriceLimitX96) default: amountIn, amountOut = r.handleMultiSwap(0, rlm, swapType, route, numHops, toSwap) } return amountIn, amountOut, nil } // handleSingleSwap executes a single-hop swap with the specified amount. func (r *routerV1) handleSingleSwap(_ int, rlm realm, route string, amountSpecified int64, sqrtPriceLimitX96 *u256.Uint) (int64, int64) { input, output, fee := getDataForSinglePath(route) singleParams := SingleSwapParams{ tokenIn: input, tokenOut: output, fee: fee, amountSpecified: amountSpecified, sqrtPriceLimitX96: sqrtPriceLimitX96, } return r.singleSwap(0, rlm, &singleParams) } // handleMultiSwap processes multi-hop swaps across multiple pools. func (r *routerV1) handleMultiSwap( _ int, rlm realm, swapType SwapType, route string, numHops int, amountSpecified int64, ) (int64, int64) { recipient := access.MustGetAddress(prbac.ROLE_ROUTER.String()) switch swapType { case ExactIn: input, output, fee := getDataForMultiPath(route, 0) // first data sp := newSwapParams(input, output, fee, recipient, amountSpecified) return r.multiSwap(0, rlm, *sp, numHops, route) case ExactOut: input, output, fee := getDataForMultiPath(route, numHops-1) // last data sp := newSwapParams(input, output, fee, recipient, amountSpecified) return r.multiSwapNegative(0, rlm, *sp, numHops, route) default: panic(errInvalidSwapType) } } // SwapRouteParams contains all parameters needed for swap route execution type SwapRouteParams struct { inputToken string outputToken string routeArr string quoteArr string deadline int64 typ SwapType exactAmount int64 // amountIn for ExactIn, amountOut for ExactOut limitAmount int64 // amountOutMin for ExactIn, amountInMax for ExactOut sqrtPriceLimitX96 *u256.Uint // if sqrtPriceLimitX96 is zero string, it will be set to MIN_PRICE or MAX_PRICE } func (p *SwapRouteParams) ExactAmount() int64 { return p.exactAmount } // when exact out, calculate amount to fetch from pool including router fee func (p *SwapRouteParams) ExpectedExactAmountByFee(feeBps uint64) int64 { if p.typ == ExactIn { return p.exactAmount } return calculateExactOutWithRouterFee(p.exactAmount, feeBps) } func (p *SwapRouteParams) SwapCount() int64 { swapCount := int64(0) for _, route := range strings.Split(p.routeArr, ",") { swapCount += int64(strings.Count(route, POOL_SEPARATOR) + 1) } return swapCount } func buildRouteEventAttrs(routeArr string) []string { routes := strings.Split(routeArr, ",") routeEventAttrs := make([]string, 0, len(routes)*2) for index, route := range routes { routeEventAttrs = append(routeEventAttrs, ufmt.Sprintf("routes[%d]", index)) routeEventAttrs = append(routeEventAttrs, route) } return routeEventAttrs } func (p *SwapRouteParams) IsSetSqrtPriceLimitX96() bool { return p.sqrtPriceLimitX96 != nil && !p.sqrtPriceLimitX96.IsZero() } // createSwapOperation creates the appropriate swap operation based on swap type. func createSwapOperation(r *routerV1, params SwapRouteParams) (RouterOperation, error) { baseParams := BaseSwapParams{ InputToken: params.inputToken, OutputToken: params.outputToken, RouteArr: params.routeArr, QuoteArr: params.quoteArr, SqrtPriceLimitX96: params.sqrtPriceLimitX96, Deadline: params.deadline, } switch params.typ { case ExactIn: pp := NewExactInParams(baseParams, params.ExactAmount(), params.limitAmount) return NewExactInSwapOperation(r, pp), nil case ExactOut: routerFee := r.store.GetSwapFee() pp := NewExactOutParams(baseParams, params.ExpectedExactAmountByFee(routerFee), params.limitAmount) return NewExactOutSwapOperation(r, pp), nil default: msg := addDetailToError(errInvalidSwapType, "unknown swap type") return nil, errors.New(msg) } } // commonSwapRoute handles the common logic for both ExactIn and ExactOut swaps. func (r *routerV1) commonSwapRoute(_ int, rlm realm, params SwapRouteParams) (int64, int64, error) { op, err := createSwapOperation(r, params) if err != nil { return 0, 0, err } result, err := executeSwapOperation(0, rlm, op) if err != nil { msg := addDetailToError( errInvalidInput, ufmt.Sprintf("invalid %s SwapOperation: %s", params.typ.String(), err.Error()), ) return 0, 0, errors.New(msg) } inputAmount, outputAmount := r.finalizeSwap( 0, rlm, params.inputToken, params.outputToken, result.AmountIn, result.AmountOut, params.typ, params.limitAmount, params.ExactAmount(), params.SwapCount(), params.IsSetSqrtPriceLimitX96(), ) return inputAmount, outputAmount, nil }