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