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

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