exact_in.gno
7.91 Kb ยท 271 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// ExactInSwapRoute swaps an exact amount of input tokens for output tokens.
18//
19// Executes multi-hop swaps through specified route.
20// Supports splitting across multiple paths for price optimization.
21// Applies slippage protection via minimum output amount.
22//
23// Parameters:
24// - inputToken, outputToken: Token contract paths
25// - amountIn: Exact input amount to swap
26// - routeArr: Swap route (max 3 hops per path, multiple paths separated by comma)
27// - quoteArr: Split percentages "70,30" (must sum to 100)
28// - amountOutMin: Minimum acceptable output (slippage protection)
29// - deadline: Unix timestamp for expiration
30// - referrer: Optional referral address
31//
32// Route format:
33// - Single-hop: "TOKEN0:TOKEN1:FEE"
34// - Multi-hop: "TOKEN0:TOKEN1:FEE*POOL*TOKEN1:TOKEN2:FEE" (use *POOL* separator)
35// - Multi-path: "route1,route2" (use comma separator between different routes)
36//
37// Returns:
38// - amountIn: Actual input consumed
39// - amountOut: Actual output received
40//
41// Reverts if output < amountOutMin or deadline passed.
42func (r *routerV1) ExactInSwapRoute(
43 inputToken string,
44 outputToken string,
45 amountIn string,
46 routeArr string,
47 quoteArr string,
48 amountOutMin string,
49 deadline int64,
50 referrer string,
51) (string, string) {
52 halt.AssertIsNotHaltedRouter()
53
54 assertIsValidUserCoinSend(inputToken, amountIn)
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: ExactIn,
68 exactAmount: safeParseInt64(amountIn),
69 limitAmount: safeParseInt64(amountOutMin),
70 sqrtPriceLimitX96: u256.Zero(), // multi-hop swap is not allowed to set sqrtPriceLimitX96
71 }
72
73 inputAmount, outputAmount := r.exactInSwapRoute(params, referrer)
74
75 return formatInt64(inputAmount), formatInt64(outputAmount)
76}
77
78// ExactInSingleSwapRoute swaps an exact amount of input tokens for output tokens 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 minimum output amount.
83//
84// Parameters:
85// - inputToken, outputToken: Token contract paths
86// - amountIn: Exact input amount to swap
87// - routeArr: Single swap route (just 1 hop)
88// - amountOutMin: Minimum acceptable output (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: Actual output received
99//
100// Reverts the transaction if the output amount is less than amountOutMin,
101// the deadline has passed, or the price limit is exceeded.
102func (r *routerV1) ExactInSingleSwapRoute(
103 inputToken string,
104 outputToken string,
105 amountIn string,
106 routeArr string,
107 amountOutMin string,
108 sqrtPriceLimitX96 string,
109 deadline int64,
110 referrer string,
111) (string, string) {
112 halt.AssertIsNotHaltedRouter()
113
114 assertIsValidUserCoinSend(inputToken, amountIn)
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: ExactIn,
129 exactAmount: safeParseInt64(amountIn),
130 limitAmount: safeParseInt64(amountOutMin),
131 sqrtPriceLimitX96: u256.MustFromDecimal(sqrtPriceLimitX96), // single swap is allowed to set sqrtPriceLimitX96
132 }
133
134 inputAmount, outputAmount := r.exactInSwapRoute(params, referrer)
135
136 return formatInt64(inputAmount), formatInt64(outputAmount)
137}
138
139// exactInSwapRoute 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) exactInSwapRoute(
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 "ExactInSwap",
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
203type ExactInSwapOperation struct {
204 baseSwapOperation
205 params ExactInParams
206 router *routerV1
207}
208
209func NewExactInSwapOperation(r *routerV1, pp ExactInParams) *ExactInSwapOperation {
210 return &ExactInSwapOperation{
211 router: r,
212 params: pp,
213 baseSwapOperation: baseSwapOperation{
214 userWrappedWugnot: INITIAL_WUGNOT_BALANCE,
215 sqrtPriceLimitX96: pp.SqrtPriceLimitX96,
216 },
217 }
218}
219
220// Validate validates the exact-in swap operation parameters.
221func (op *ExactInSwapOperation) Validate() error {
222 amountIn := op.params.AmountIn
223
224 if amountIn <= 0 {
225 return ufmt.Errorf("invalid amountIn(%d), must be positive", amountIn)
226 }
227
228 // when `SwapType` is `ExactIn`, assign `amountSpecified` the `amountIn`
229 // obtained from above.
230 op.amountSpecified = amountIn
231
232 routes, quotes, err := validateRoutesAndQuotes(op.params.RouteArr, op.params.QuoteArr)
233 if err != nil {
234 return err
235 }
236
237 op.routes = routes
238 op.quotes = quotes
239
240 return nil
241}
242
243// Process executes the exact-in swap operation.
244func (op *ExactInSwapOperation) Process() (*SwapResult, error) {
245 if err := op.handleNativeTokenWrapping(); err != nil {
246 return nil, err
247 }
248
249 resultAmountIn, resultAmountOut, err := op.processRoutes(op.router, ExactIn)
250 if err != nil {
251 return nil, err
252 }
253
254 return &SwapResult{
255 AmountIn: resultAmountIn,
256 AmountOut: resultAmountOut,
257 Routes: op.routes,
258 Quotes: op.quotes,
259 AmountSpecified: op.amountSpecified,
260 WithUnwrap: op.withUnwrap,
261 }, nil
262}
263
264// handleNativeTokenWrapping handles the wrapping of native tokens if needed.
265func (op *ExactInSwapOperation) handleNativeTokenWrapping() error {
266 return op.baseSwapOperation.handleNativeTokenWrapping(
267 op.params.InputToken,
268 op.params.OutputToken,
269 op.amountSpecified,
270 )
271}