Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

exact_out.gno

7.55 Kb · 266 lines
  1package router
  2
  3import (
  4	"chain"
  5	"errors"
  6
  7	ufmt "gno.land/p/nt/ufmt/v0"
  8
  9	u256 "gno.land/p/gnoswap/uint256"
 10	"gno.land/p/gnoswap/utils"
 11
 12	"gno.land/r/gnoswap/common"
 13	"gno.land/r/gnoswap/emission"
 14	"gno.land/r/gnoswap/halt"
 15	"gno.land/r/gnoswap/referral"
 16)
 17
 18// ExactOutSwapRoute swaps tokens for an exact output amount.
 19//
 20// Executes swap to receive exact output tokens.
 21// Calculates required input working backwards through route.
 22// Useful for buying specific amounts regardless of price.
 23//
 24// Parameters:
 25//   - inputToken, outputToken: Token contract paths
 26//   - amountOut: Exact output amount desired
 27//   - routeArr: Swap route "TOKEN0:TOKEN1:FEE,TOKEN1:TOKEN2:FEE" (max 7 hops)
 28//   - quoteArr: Split percentages "70,30" (must sum to 100)
 29//   - amountInMax: Maximum input to spend (slippage protection)
 30//   - deadline: Unix timestamp for expiration
 31//   - referrer: Optional referral address
 32//
 33// Route calculation:
 34//   - Works backwards from output to input
 35//   - Each hop increases required input
 36//   - Multi-path aggregates total input
 37//
 38// Returns:
 39//   - amountIn: Actual input consumed
 40//   - amountOut: Exact output received
 41//
 42// Reverts if input > amountInMax or deadline passed.
 43func (r *routerV1) ExactOutSwapRoute(
 44	_ int,
 45	rlm realm,
 46	inputToken string,
 47	outputToken string,
 48	amountOut string,
 49	routeArr string,
 50	quoteArr string,
 51	amountInMax string,
 52	deadline int64,
 53	referrer string,
 54) (string, string) {
 55	if !rlm.IsCurrent() {
 56		panic(errors.New(errSpoofedRealm))
 57	}
 58
 59	halt.AssertIsNotHaltedRouter()
 60
 61	common.AssertIsNotHandleNativeCoin()
 62
 63	assertIsValidRoutePaths(routeArr, inputToken, outputToken)
 64	assertIsNotExpired(deadline)
 65	assertIsExistsPools(routeArr)
 66
 67	emission.MintAndDistributeGns(cross(rlm))
 68
 69	params := SwapRouteParams{
 70		inputToken:        inputToken,
 71		outputToken:       outputToken,
 72		routeArr:          routeArr,
 73		quoteArr:          quoteArr,
 74		deadline:          deadline,
 75		typ:               ExactOut,
 76		exactAmount:       utils.SafeParseInt64(amountOut),
 77		limitAmount:       utils.SafeParseInt64(amountInMax),
 78		sqrtPriceLimitX96: u256.Zero(), // multi-hop swap is not allowed to set sqrtPriceLimitX96
 79	}
 80
 81	inputAmount, outputAmount := r.exactOutSwapRoute(0, rlm, params, referrer)
 82
 83	return utils.FormatInt(inputAmount), utils.FormatInt(outputAmount)
 84}
 85
 86// ExactOutSingleSwapRoute swaps tokens for an exact output amount through a single route.
 87//
 88// Executes single-hop swaps through a single specified route.
 89// Allows price limit control via sqrtPriceLimitX96 parameter.
 90// Applies slippage protection via maximum input amount.
 91//
 92// Parameters:
 93//   - inputToken, outputToken: Token contract paths
 94//   - amountOut: Exact output amount desired
 95//   - routeArr: Single swap route (max 3 hops)
 96//   - amountInMax: Maximum input to spend (slippage protection)
 97//   - sqrtPriceLimitX96: Price limit for swap execution (0 for no limit)
 98//   - deadline: Unix timestamp for expiration
 99//   - referrer: Optional referral address
100//
101// Route format:
102//   - Single-hop: "INPUT_TOKEN:OUTPUT_TOKEN:FEE"
103//
104// Returns:
105//   - amountIn: Actual input consumed
106//   - amountOut: Exact output received
107//
108// Reverts the transaction if the input amount is greater than `amountInMax`,
109// the deadline has passed, or the price limit is exceeded.
110func (r *routerV1) ExactOutSingleSwapRoute(
111	_ int,
112	rlm realm,
113	inputToken string,
114	outputToken string,
115	amountOut string,
116	routeArr string,
117	amountInMax string,
118	sqrtPriceLimitX96 string,
119	deadline int64,
120	referrer string,
121) (string, string) {
122	if !rlm.IsCurrent() {
123		panic(errors.New(errSpoofedRealm))
124	}
125
126	halt.AssertIsNotHaltedRouter()
127
128	common.AssertIsNotHandleNativeCoin()
129
130	assertIsValidSingleSwapRouteArrPath(routeArr, inputToken, outputToken)
131	assertIsValidSqrtPriceLimitX96(sqrtPriceLimitX96)
132	assertIsNotExpired(deadline)
133	assertIsExistsPools(routeArr)
134
135	emission.MintAndDistributeGns(cross(rlm))
136
137	params := SwapRouteParams{
138		inputToken:        inputToken,
139		outputToken:       outputToken,
140		routeArr:          routeArr,
141		quoteArr:          "100",
142		deadline:          deadline,
143		typ:               ExactOut,
144		exactAmount:       utils.SafeParseInt64(amountOut),
145		limitAmount:       utils.SafeParseInt64(amountInMax),
146		sqrtPriceLimitX96: u256.MustFromDecimal(sqrtPriceLimitX96), // single swap is allowed to set sqrtPriceLimitX96
147	}
148
149	inputAmount, outputAmount := r.exactOutSwapRoute(0, rlm, params, referrer)
150
151	return utils.FormatInt(inputAmount), utils.FormatInt(outputAmount)
152}
153
154// exactOutSwapRoute executes the swap operation and handles token transfers and referral registration.
155//
156// Performs the actual swap operation using commonSwapRoute and handles:
157// - Safe token transfers to the caller
158// - Referral registration and tracking
159// - Event emission for swap completion
160//
161// Parameters:
162//   - params: SwapRouteParams containing all swap configuration
163//   - referrer: Referral address for registration
164//
165// Returns:
166//   - inputAmount: Actual input amount consumed as string
167//   - outputAmount: Actual output amount received as string (negative value)
168//
169// Panics if swap execution fails or token transfer fails.
170func (r *routerV1) exactOutSwapRoute(
171	_ int,
172	rlm realm,
173	params SwapRouteParams,
174	referrer string,
175) (int64, int64) {
176	inputAmount, outputAmount, err := r.commonSwapRoute(0, rlm, params)
177	if err != nil {
178		panic(err)
179	}
180
181	previousRealm := rlm.Previous()
182	caller := previousRealm.Address()
183
184	common.SafeGRC20Transfer(cross(rlm), params.outputToken, caller, outputAmount)
185
186	// handle referral registration
187	actualReferrer := referral.TryRegister(cross(rlm), caller, referrer)
188
189	resultInputAmount := inputAmount
190	resultOutputAmount := -outputAmount
191
192	eventAttrs := append([]string{
193		"prevAddr", caller.String(),
194		"prevRealm", previousRealm.PkgPath(),
195		"input", params.inputToken,
196		"output", params.outputToken,
197		"exactAmount", utils.FormatInt(params.exactAmount),
198		"quote", params.quoteArr,
199		"resultInputAmount", utils.FormatInt(resultInputAmount),
200		"resultOutputAmount", utils.FormatInt(resultOutputAmount),
201		"referrer", actualReferrer,
202	}, buildRouteEventAttrs(params.routeArr)...)
203
204	chain.Emit(
205		"ExactOutSwap",
206		eventAttrs...,
207	)
208
209	return resultInputAmount, resultOutputAmount
210}
211
212// ExactOutSwapOperation handles swaps where the output amount is specified.
213type ExactOutSwapOperation struct {
214	router *routerV1
215	baseSwapOperation
216	params ExactOutParams
217}
218
219// NewExactOutSwapOperation creates a new exact-out swap operation.
220func NewExactOutSwapOperation(r *routerV1, pp ExactOutParams) *ExactOutSwapOperation {
221	return &ExactOutSwapOperation{
222		router: r,
223		params: pp,
224		baseSwapOperation: baseSwapOperation{
225			sqrtPriceLimitX96: pp.SqrtPriceLimitX96,
226		},
227	}
228}
229
230// Validate ensures the exact-out swap parameters are valid.
231func (op *ExactOutSwapOperation) Validate() error {
232	amountOut := op.params.AmountOut
233	if amountOut <= 0 {
234		return ufmt.Errorf("invalid amountOut(%d), must be positive", amountOut)
235	}
236
237	// assign a signed reversed `amountOut` to `amountSpecified`
238	// when it's an ExactOut
239	op.amountSpecified = -amountOut
240
241	routes, quotes, err := validateRoutesAndQuotes(op.params.RouteArr, op.params.QuoteArr)
242	if err != nil {
243		return err
244	}
245
246	op.routes = routes
247	op.quotes = quotes
248
249	return nil
250}
251
252// Process executes the exact-out swap operation.
253func (op *ExactOutSwapOperation) Process(_ int, rlm realm) (*SwapResult, error) {
254	resultAmountIn, resultAmountOut, err := op.processRoutes(0, rlm, op.router, ExactOut)
255	if err != nil {
256		return nil, err
257	}
258
259	return &SwapResult{
260		AmountIn:        resultAmountIn,
261		AmountOut:       resultAmountOut,
262		Routes:          op.routes,
263		Quotes:          op.quotes,
264		AmountSpecified: op.amountSpecified,
265	}, nil
266}