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}