exact_in.gno
7.40 Kb · 263 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// ExactInSwapRoute swaps an exact amount of input tokens for output tokens.
17//
18// Executes multi-hop swaps through specified route.
19// Supports splitting across multiple paths for price optimization.
20// Applies slippage protection via minimum output amount.
21//
22// Parameters:
23// - inputToken, outputToken: Token contract paths
24// - amountIn: Exact input amount to swap
25// - routeArr: Swap route (max 3 hops per path, multiple paths separated by comma)
26// - quoteArr: Split percentages "70,30" (must sum to 100)
27// - amountOutMin: Minimum acceptable output (slippage protection)
28// - deadline: Unix timestamp for expiration
29// - referrer: Optional referral address
30//
31// Route format:
32// - Single-hop: "TOKEN0:TOKEN1:FEE"
33// - Multi-hop: "TOKEN0:TOKEN1:FEE*POOL*TOKEN1:TOKEN2:FEE" (use *POOL* separator)
34// - Multi-path: "route1,route2" (use comma separator between different routes)
35//
36// Returns:
37// - amountIn: Actual input consumed
38// - amountOut: Actual output received
39//
40// Reverts if output < amountOutMin or deadline passed.
41func (r *routerV1) ExactInSwapRoute(
42 _ int,
43 rlm realm,
44 inputToken string,
45 outputToken string,
46 amountIn string,
47 routeArr string,
48 quoteArr string,
49 amountOutMin 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: ExactIn,
74 exactAmount: safeParseInt64(amountIn),
75 limitAmount: safeParseInt64(amountOutMin),
76 sqrtPriceLimitX96: u256.Zero(), // multi-hop swap is not allowed to set sqrtPriceLimitX96
77 }
78
79 inputAmount, outputAmount := r.exactInSwapRoute(0, rlm, params, referrer)
80
81 return formatInt64(inputAmount), formatInt64(outputAmount)
82}
83
84// ExactInSingleSwapRoute swaps an exact amount of input tokens for output tokens 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 minimum output amount.
89//
90// Parameters:
91// - inputToken, outputToken: Token contract paths
92// - amountIn: Exact input amount to swap
93// - routeArr: Single swap route (just 1 hop)
94// - amountOutMin: Minimum acceptable output (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: Actual output received
105//
106// Reverts the transaction if the output amount is less than amountOutMin,
107// the deadline has passed, or the price limit is exceeded.
108func (r *routerV1) ExactInSingleSwapRoute(
109 _ int,
110 rlm realm,
111 inputToken string,
112 outputToken string,
113 amountIn string,
114 routeArr string,
115 amountOutMin 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: ExactIn,
142 exactAmount: safeParseInt64(amountIn),
143 limitAmount: safeParseInt64(amountOutMin),
144 sqrtPriceLimitX96: u256.MustFromDecimal(sqrtPriceLimitX96), // single swap is allowed to set sqrtPriceLimitX96
145 }
146
147 inputAmount, outputAmount := r.exactInSwapRoute(0, rlm, params, referrer)
148
149 return formatInt64(inputAmount), formatInt64(outputAmount)
150}
151
152// exactInSwapRoute 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) exactInSwapRoute(
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 "ExactInSwap",
204 eventAttrs...,
205 )
206
207 return resultInputAmount, resultOutputAmount
208}
209
210type ExactInSwapOperation struct {
211 baseSwapOperation
212 params ExactInParams
213 router *routerV1
214}
215
216func NewExactInSwapOperation(r *routerV1, pp ExactInParams) *ExactInSwapOperation {
217 return &ExactInSwapOperation{
218 router: r,
219 params: pp,
220 baseSwapOperation: baseSwapOperation{
221 sqrtPriceLimitX96: pp.SqrtPriceLimitX96,
222 },
223 }
224}
225
226// Validate validates the exact-in swap operation parameters.
227func (op *ExactInSwapOperation) Validate() error {
228 amountIn := op.params.AmountIn
229
230 if amountIn <= 0 {
231 return ufmt.Errorf("invalid amountIn(%d), must be positive", amountIn)
232 }
233
234 // when `SwapType` is `ExactIn`, assign `amountSpecified` the `amountIn`
235 // obtained from above.
236 op.amountSpecified = amountIn
237
238 routes, quotes, err := validateRoutesAndQuotes(op.params.RouteArr, op.params.QuoteArr)
239 if err != nil {
240 return err
241 }
242
243 op.routes = routes
244 op.quotes = quotes
245
246 return nil
247}
248
249// Process executes the exact-in swap operation.
250func (op *ExactInSwapOperation) Process(_ int, rlm realm) (*SwapResult, error) {
251 resultAmountIn, resultAmountOut, err := op.processRoutes(0, rlm, op.router, ExactIn)
252 if err != nil {
253 return nil, err
254 }
255
256 return &SwapResult{
257 AmountIn: resultAmountIn,
258 AmountOut: resultAmountOut,
259 Routes: op.routes,
260 Quotes: op.quotes,
261 AmountSpecified: op.amountSpecified,
262 }, nil
263}