Search Apps Documentation Source Content File Folder Download Copy Actions Download

exact_out.gno

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