router_dry.gno
9.41 Kb ยท 310 lines
1package v1
2
3import (
4 "chain/runtime"
5 "strings"
6
7 "gno.land/p/nt/ufmt"
8
9 "gno.land/r/gnoswap/common"
10)
11
12// QuoteConstraints defines the valid range for swap quote percentages
13const (
14 MaxQuotePercentage = 100
15 MinQuotePercentage = 0
16 PERCENTAGE_DENOMINATOR = int64(100)
17)
18
19// ErrorMessages for DrySwapRoute operations
20const (
21 ErrOverflowResultAmountIn = "overflow in resultAmountIn"
22 ErrOverflowResultAmountOut = "overflow in resultAmountOut"
23 ErrUnknownSwapType = "unknown swapType(%s)"
24 ErrInvalidPositiveAmount = "invalid amount(%s), must be positive"
25 ErrInvalidZeroAmountLimit = "invalid amountLimit(%s), should not be zero"
26 ErrInvalidQuoteRange = "quote(%d) must be %d~%d"
27 ErrOverflowCalculateSwapAmount = "overflow in calculateSwapAmount"
28)
29
30// SwapProcessor handles the execution of swap operations
31type SwapProcessor struct {
32 router *routerV1
33}
34
35// ProcessSingleSwap handles a single-hop swap simulation.
36//
37// Parameters:
38// - route: Single pool path "TOKEN0:TOKEN1:FEE"
39// - amountSpecified: Input/output amount depending on swap type
40//
41// Returns:
42// - amountIn: Expected input amount
43// - amountOut: Expected output amount
44// - err: Error if swap simulation fails
45func (p *SwapProcessor) ProcessSingleSwap(route string, amountSpecified int64) (amountIn, amountOut int64, err error) {
46 input, output, fee := getDataForSinglePath(route)
47 singleParams := SingleSwapParams{
48 tokenIn: input,
49 tokenOut: output,
50 fee: fee,
51 amountSpecified: amountSpecified,
52 }
53
54 amountIn, amountOut = p.router.singleDrySwap(&singleParams)
55 return amountIn, amountOut, nil
56}
57
58// ProcessMultiSwap handles a multi-hop swap simulation.
59//
60// Parameters:
61// - swapType: ExactIn or ExactOut
62// - route: Multi-hop route with POOL_SEPARATOR
63// - numHops: Number of hops in the route
64// - amountSpecified: Input/output amount depending on swap type
65//
66// Returns:
67// - amountIn: Expected input amount
68// - amountOut: Expected output amount
69// - err: Error if swap simulation fails
70func (p *SwapProcessor) ProcessMultiSwap(
71 swapType SwapType,
72 route string,
73 numHops int,
74 amountSpecified int64,
75) (int64, int64, error) {
76 recipient := runtime.PreviousRealm().Address()
77 pathIndex := getPathIndex(swapType, numHops)
78
79 input, output, fee := getDataForMultiPath(route, pathIndex)
80 swapParams := newSwapParams(input, output, fee, recipient, amountSpecified, false)
81
82 switch swapType {
83 case ExactIn:
84 return p.router.multiDrySwap(*swapParams, numHops, route)
85 case ExactOut:
86 return p.router.multiDrySwapNegative(*swapParams, numHops, route)
87 default:
88 return 0, 0, ufmt.Errorf(ErrUnknownSwapType, swapType)
89 }
90}
91
92// ValidateSwapResults checks if the swap results meet the required constraints.
93//
94// Parameters:
95// - swapType: ExactIn or ExactOut
96// - resultAmountIn, resultAmountOut: Swap simulation results
97// - amountSpecified: User's specified exact amount
98// - amountLimit: Slippage protection limit
99// - swapCount: Number of swap operations (for tolerance)
100//
101// Returns:
102// - amountIn: Input amount (same as resultAmountIn)
103// - amountOut: Output amount (same as resultAmountOut)
104// - success: true if slippage constraints are met, false otherwise
105func (p *SwapProcessor) ValidateSwapResults(
106 swapType SwapType,
107 resultAmountIn, resultAmountOut int64,
108 amountSpecified, amountLimit int64,
109 swapCount int64,
110) (amountIn, amountOut int64, success bool) {
111 if resultAmountIn == 0 || resultAmountOut == 0 {
112 return 0, 0, false
113 }
114
115 validator := &SwapValidator{}
116
117 if swapType == ExactOut {
118 if err := validator.exactOutAmount(resultAmountOut, safeAbsInt64(amountSpecified), swapCount); err != nil {
119 return resultAmountIn, resultAmountOut, false
120 }
121 }
122
123 if err := validator.slippage(swapType, resultAmountIn, resultAmountOut, amountLimit); err != nil {
124 return resultAmountIn, resultAmountOut, false
125 }
126
127 return resultAmountIn, resultAmountOut, true
128}
129
130// AddSwapResults safely adds swap result amounts, checking for overflow.
131//
132// Parameters:
133// - resultAmountIn, resultAmountOut: Accumulated results from previous routes
134// - amountIn, amountOut: Results from current route
135//
136// Returns:
137// - newAmountIn: resultAmountIn + amountIn
138// - newAmountOut: resultAmountOut + amountOut
139// - err: Always nil (uses safe addition that panics on overflow)
140func (p *SwapProcessor) AddSwapResults(
141 resultAmountIn, resultAmountOut, amountIn, amountOut int64,
142) (int64, int64, error) {
143 newAmountIn := safeAddInt64(resultAmountIn, amountIn)
144 newAmountOut := safeAddInt64(resultAmountOut, amountOut)
145
146 return newAmountIn, newAmountOut, nil
147}
148
149// DrySwapRoute simulates a token swap route without executing the swap.
150//
151// Calculates expected amounts without modifying any state.
152// Useful for price quotes, UI previews, and slippage estimation.
153//
154// Parameters:
155// - inputToken, outputToken: Token contract paths
156// - specifiedAmount: Input amount (ExactIn) or output amount (ExactOut)
157// - swapTypeStr: "EXACT_IN" or "EXACT_OUT"
158// - strRouteArr: Swap routes (comma-separated, max 7)
159// - quoteArr: Route split percentages (must sum to 100)
160// - tokenAmountLimit: Min output (ExactIn) or max input (ExactOut)
161//
162// Returns:
163// - amountIn: Expected input amount as string
164// - amountOut: Expected output amount as string
165// - success: true if swap would succeed, false if slippage/validation fails
166//
167// Note: Does not validate deadline or execute actual transfers.
168func (r *routerV1) DrySwapRoute(
169 inputToken, outputToken string,
170 specifiedAmount string,
171 swapTypeStr string,
172 strRouteArr, quoteArr string,
173 tokenAmountLimit string,
174) (string, string, bool) {
175 inputAmount, outputAmount, success := r.drySwapRoute(
176 inputToken,
177 outputToken,
178 safeParseInt64(specifiedAmount),
179 swapTypeStr,
180 strRouteArr,
181 quoteArr,
182 safeParseInt64(tokenAmountLimit),
183 )
184
185 return formatInt64(inputAmount), formatInt64(outputAmount), success
186}
187
188// drySwapRoute is a function for applying cross realm.
189func (r *routerV1) drySwapRoute(
190 inputToken, outputToken string,
191 specifiedAmount int64,
192 swapTypeStr string,
193 strRouteArr, quoteArr string,
194 tokenAmountLimit int64,
195) (int64, int64, bool) {
196 common.MustRegistered(inputToken, outputToken)
197 // initialize components
198 validator := &SwapValidator{}
199 processor := &SwapProcessor{router: r}
200
201 // validate and parse inputs
202 swapType, err := validator.swapType(swapTypeStr)
203 if err != nil {
204 panic(addDetailToError(errInvalidSwapType, err.Error()))
205 }
206
207 amountSpecified, err := validator.amount(specifiedAmount)
208 if err != nil {
209 panic(addDetailToError(errInvalidInput, err.Error()))
210 }
211
212 amountLimit, err := validator.amountLimit(tokenAmountLimit)
213 if err != nil {
214 panic(addDetailToError(errInvalidInput, err.Error()))
215 }
216
217 routes, quotes, err := NewRouteParser().ParseRoutes(strRouteArr, quoteArr)
218 if err != nil {
219 panic(addDetailToError(errInvalidRoutesAndQuotes, err.Error()))
220 }
221
222 swapFee := r.store.GetSwapFee()
223
224 // Store original amount for validation (before router fee adjustment)
225 originalAmountSpecified := amountSpecified
226
227 // adjust amount sign for exact out swaps
228 if swapType == ExactOut {
229 amountSpecifiedWithRouterFee := calculateExactOutWithRouterFee(safeAbsInt64(amountSpecified), swapFee)
230 amountSpecified = -amountSpecifiedWithRouterFee
231 }
232
233 // initialize accumulators for swap results
234 resultAmountIn, resultAmountOut := int64(0), int64(0)
235 remainRequestAmount := amountSpecified
236 swapCount := int64(0)
237
238 // Process each route
239 for i, route := range routes {
240 toSwapAmount := int64(0)
241
242 // if it's the last route, use the remaining amount
243 isLastRoute := i == len(routes)-1
244 if !isLastRoute {
245 // calculate the amount to swap for this route
246 swapAmount, err := calculateSwapAmountByQuote(amountSpecified, quotes[i])
247 if err != nil {
248 return 0, 0, false
249 }
250
251 // update the remaining amount
252 resultRemainRequestAmount := safeSubInt64(remainRequestAmount, swapAmount)
253
254 remainRequestAmount = resultRemainRequestAmount
255 toSwapAmount = swapAmount
256 } else {
257 toSwapAmount = remainRequestAmount
258 }
259
260 // determine the number of hops and validate
261 numHops := strings.Count(route, POOL_SEPARATOR) + 1
262 assertHopsInRange(numHops)
263
264 // accumulate total swap count for validation
265 swapCount += int64(numHops)
266
267 // execute the appropriate swap type
268 var amountIn, amountOut int64
269 if numHops == 1 {
270 amountIn, amountOut, err = processor.ProcessSingleSwap(route, toSwapAmount)
271 } else {
272 amountIn, amountOut, err = processor.ProcessMultiSwap(swapType, route, numHops, toSwapAmount)
273 }
274
275 if err != nil {
276 panic(addDetailToError(errInvalidSwapType, err.Error()))
277 }
278
279 if amountIn == 0 || amountOut == 0 {
280 return 0, 0, false
281 }
282
283 // update accumulated results
284 resultAmountIn, resultAmountOut, err = processor.AddSwapResults(resultAmountIn, resultAmountOut, amountIn, amountOut)
285 if err != nil {
286 panic(addDetailToError(errInvalidInput, err.Error()))
287 }
288 }
289
290 // simulate deduct router fee
291 feeAmountInt64 := calculateRouterFee(resultAmountOut, swapFee)
292
293 resultAmountOut = safeSubInt64(resultAmountOut, feeAmountInt64)
294
295 return processor.ValidateSwapResults(swapType, resultAmountIn, resultAmountOut, originalAmountSpecified, amountLimit, swapCount)
296}
297
298// getPathIndex returns the path index based on swap type and number of hops.
299func getPathIndex(swapType SwapType, numHops int) int {
300 switch swapType {
301 case ExactIn:
302 // first data for exact input swaps
303 return 0
304 case ExactOut:
305 // last data for exact output swaps
306 return numHops - 1
307 default:
308 panic("should not happen")
309 }
310}