package v1 import ( "chain" "chain/runtime" "gno.land/p/nt/ufmt" u256 "gno.land/p/gnoswap/uint256" "gno.land/r/gnoswap/common" "gno.land/r/gnoswap/emission" "gno.land/r/gnoswap/halt" "gno.land/r/gnoswap/referral" ) // ExactOutSwapRoute swaps tokens for an exact output amount. // // Executes swap to receive exact output tokens. // Calculates required input working backwards through route. // Useful for buying specific amounts regardless of price. // // Parameters: // - inputToken, outputToken: Token contract paths // - amountOut: Exact output amount desired // - routeArr: Swap route "TOKEN0:TOKEN1:FEE,TOKEN1:TOKEN2:FEE" (max 7 hops) // - quoteArr: Split percentages "70,30" (must sum to 100) // - amountInMax: Maximum input to spend (slippage protection) // - deadline: Unix timestamp for expiration // - referrer: Optional referral address // // Route calculation: // - Works backwards from output to input // - Each hop increases required input // - Multi-path aggregates total input // // Returns: // - amountIn: Actual input consumed // - amountOut: Exact output received // // Reverts if input > amountInMax or deadline passed. func (r *routerV1) ExactOutSwapRoute( inputToken string, outputToken string, amountOut string, routeArr string, quoteArr string, amountInMax string, deadline int64, referrer string, ) (string, string) { halt.AssertIsNotHaltedRouter() assertIsValidUserCoinSend(inputToken, amountInMax) assertIsValidRoutePaths(routeArr, inputToken, outputToken) assertIsNotExpired(deadline) assertIsExistsPools(routeArr) emission.MintAndDistributeGns(cross) params := SwapRouteParams{ inputToken: inputToken, outputToken: outputToken, routeArr: routeArr, quoteArr: quoteArr, deadline: deadline, typ: ExactOut, exactAmount: safeParseInt64(amountOut), limitAmount: safeParseInt64(amountInMax), sqrtPriceLimitX96: u256.Zero(), // multi-hop swap is not allowed to set sqrtPriceLimitX96 } inputAmount, outputAmount := r.exactOutSwapRoute(params, referrer) return formatInt64(inputAmount), formatInt64(outputAmount) } // ExactOutSingleSwapRoute swaps tokens for an exact output amount through a single route. // // Executes single-hop swaps through a single specified route. // Allows price limit control via sqrtPriceLimitX96 parameter. // Applies slippage protection via maximum input amount. // // Parameters: // - inputToken, outputToken: Token contract paths // - amountOut: Exact output amount desired // - routeArr: Single swap route (max 3 hops) // - amountInMax: Maximum input to spend (slippage protection) // - sqrtPriceLimitX96: Price limit for swap execution (0 for no limit) // - deadline: Unix timestamp for expiration // - referrer: Optional referral address // // Route format: // - Single-hop: "INPUT_TOKEN:OUTPUT_TOKEN:FEE" // // Returns: // - amountIn: Actual input consumed // - amountOut: Exact output received // // Reverts the transaction if the input amount is greater than `amountInMax`, // the deadline has passed, or the price limit is exceeded. func (r *routerV1) ExactOutSingleSwapRoute( inputToken string, outputToken string, amountOut string, routeArr string, amountInMax string, sqrtPriceLimitX96 string, deadline int64, referrer string, ) (string, string) { halt.AssertIsNotHaltedRouter() assertIsValidUserCoinSend(inputToken, amountInMax) assertIsValidSingleSwapRouteArrPath(routeArr, inputToken, outputToken) assertIsValidSqrtPriceLimitX96(sqrtPriceLimitX96) assertIsNotExpired(deadline) assertIsExistsPools(routeArr) emission.MintAndDistributeGns(cross) params := SwapRouteParams{ inputToken: inputToken, outputToken: outputToken, routeArr: routeArr, quoteArr: "100", deadline: deadline, typ: ExactOut, exactAmount: safeParseInt64(amountOut), limitAmount: safeParseInt64(amountInMax), sqrtPriceLimitX96: u256.MustFromDecimal(sqrtPriceLimitX96), // single swap is allowed to set sqrtPriceLimitX96 } inputAmount, outputAmount := r.exactOutSwapRoute(params, referrer) return formatInt64(inputAmount), formatInt64(outputAmount) } // exactOutSwapRoute executes the swap operation and handles token transfers and referral registration. // // Performs the actual swap operation using commonSwapRoute and handles: // - Token unwrapping for native tokens (WUGNOT -> UGNOT) // - Safe token transfers to the caller // - Referral registration and tracking // - Event emission for swap completion // // Parameters: // - params: SwapRouteParams containing all swap configuration // - referrer: Referral address for registration // // Returns: // - inputAmount: Actual input amount consumed as string // - outputAmount: Actual output amount received as string (negative value) // // Panics if swap execution fails or token transfer fails. func (r *routerV1) exactOutSwapRoute( params SwapRouteParams, referrer string, ) (int64, int64) { inputAmount, outputAmount, err := r.commonSwapRoute(params) if err != nil { panic(err) } if params.IsUnwrap() { err = unwrapWithTransfer(runtime.PreviousRealm().Address(), outputAmount) if err != nil { panic(err) } } else { common.SafeGRC20Transfer(cross, params.outputToken, runtime.PreviousRealm().Address(), outputAmount) } // handle referral registration previousRealm := runtime.PreviousRealm() caller := previousRealm.Address() success := referral.TryRegister(cross, caller, referrer) actualReferrer := referrer if !success { actualReferrer = referral.GetReferral(caller.String()) } resultInputAmount := inputAmount resultOutputAmount := -outputAmount chain.Emit( "ExactOutSwap", "prevAddr", previousRealm.Address().String(), "prevRealm", previousRealm.PkgPath(), "input", params.inputToken, "output", params.outputToken, "exactAmount", formatInt64(params.exactAmount), "route", params.routeArr, "quote", params.quoteArr, "resultInputAmount", formatInt64(resultInputAmount), "resultOutputAmount", formatInt64(resultOutputAmount), "referrer", actualReferrer, ) return resultInputAmount, resultOutputAmount } // ExactOutSwapOperation handles swaps where the output amount is specified. type ExactOutSwapOperation struct { router *routerV1 baseSwapOperation params ExactOutParams } // NewExactOutSwapOperation creates a new exact-out swap operation. func NewExactOutSwapOperation(r *routerV1, pp ExactOutParams) *ExactOutSwapOperation { return &ExactOutSwapOperation{ router: r, params: pp, baseSwapOperation: baseSwapOperation{ userWrappedWugnot: INITIAL_WUGNOT_BALANCE, sqrtPriceLimitX96: pp.SqrtPriceLimitX96, }, } } // Validate ensures the exact-out swap parameters are valid. func (op *ExactOutSwapOperation) Validate() error { amountOut := op.params.AmountOut if amountOut <= 0 { return ufmt.Errorf("invalid amountOut(%d), must be positive", amountOut) } // assign a signed reversed `amountOut` to `amountSpecified` // when it's an ExactOut op.amountSpecified = -amountOut routes, quotes, err := validateRoutesAndQuotes(op.params.RouteArr, op.params.QuoteArr) if err != nil { return err } op.routes = routes op.quotes = quotes return nil } // Process executes the exact-out swap operation. func (op *ExactOutSwapOperation) Process() (*SwapResult, error) { if err := op.handleNativeTokenWrapping(); err != nil { return nil, err } resultAmountIn, resultAmountOut, err := op.processRoutes(op.router, ExactOut) if err != nil { return nil, err } return &SwapResult{ AmountIn: resultAmountIn, AmountOut: resultAmountOut, Routes: op.routes, Quotes: op.quotes, AmountSpecified: op.amountSpecified, WithUnwrap: op.withUnwrap, }, nil } // handleNativeTokenWrapping manages native token wrapping for exact-out swaps. func (op *ExactOutSwapOperation) handleNativeTokenWrapping() error { return op.baseSwapOperation.handleNativeTokenWrapping( op.params.InputToken, op.params.OutputToken, op.params.AmountInMax, ) }