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