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}