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