router.gno
6.08 Kb · 216 lines
1package v1
2
3import (
4 "errors"
5 "strconv"
6
7 u256 "gno.land/p/gnoswap/uint256"
8 ufmt "gno.land/p/nt/ufmt/v0"
9)
10
11var one = u256.One()
12
13// ErrorMessages define all error message templates used throughout the router
14const (
15 // slippage validation
16 errExactOutAmountExceeded = "received more than requested in requested=%d, actual=%d"
17
18 // route validation
19 errInvalidRouteLength = "route length(%d) must be 1~7"
20
21 // quote validation
22 errRoutesQuotesMismatch = "mismatch between routes(%d) and quotes(%d) length"
23 errInvalidQuote = "invalid quote(%s) at index(%d)"
24 errInvalidQuoteValue = "quote(%s) at index(%d) must be positive value"
25 errQuoteExceedsMax = "quote(%s) at index(%d) must be less than or equal to %d"
26 errQuoteSumExceedsMax = "quote sum exceeds 100 at index(%d)"
27 errInvalidQuoteSum = "quote sum(%d) must be 100"
28
29 // swap type validation
30 errExactInTooFewReceived = "ExactIn: too few received (min:%d, got:%d)"
31 errExactOutTooMuchSpent = "ExactOut: too much spent (max:%d, used:%d)"
32
33 // route parsing validation
34 errEmptyRoutes = "routes cannot be empty"
35)
36
37// SwapValidator provides validation methods for swap operations
38type SwapValidator struct{}
39
40// exactOutAmount checks if output amount meets specified requirements.
41// For exact-out swaps, the output must be exactly the specified amount with tolerance up to swapCount units for rounding.
42func (v *SwapValidator) exactOutAmount(resultAmount, specifiedAmount int64, swapCount int64) error {
43 diff := int64(0)
44
45 if resultAmount >= specifiedAmount {
46 diff = resultAmount - specifiedAmount
47 } else {
48 diff = specifiedAmount - resultAmount
49 }
50
51 if diff > swapCount {
52 return ufmt.Errorf(errExactOutAmountExceeded, specifiedAmount, resultAmount)
53 }
54
55 return nil
56}
57
58// slippage ensures swap amounts meet slippage requirements.
59func (v *SwapValidator) slippage(swapType SwapType, amountIn, amountOut, limit int64) error {
60 switch swapType {
61 case ExactIn:
62 if amountOut < limit {
63 return ufmt.Errorf(errExactInTooFewReceived, limit, amountOut)
64 }
65 case ExactOut:
66 if amountIn > limit {
67 return ufmt.Errorf(errExactOutTooMuchSpent, limit, amountIn)
68 }
69 default:
70 return errInvalidSwapType
71 }
72 return nil
73}
74
75// swapType ensures the swap type string is valid.
76func (v *SwapValidator) swapType(swapTypeStr string) (SwapType, error) {
77 swapType, err := trySwapTypeFromStr(swapTypeStr)
78 if err != nil {
79 return Unknown, errInvalidSwapType
80 }
81 return swapType, nil
82}
83
84// amount ensures the amount is properly formatted and positive.
85func (v *SwapValidator) amount(amount int64) (int64, error) {
86 if amount <= 0 {
87 return 0, ufmt.Errorf(ErrInvalidPositiveAmount, amount)
88 }
89 return amount, nil
90}
91
92// amountLimit ensures the amount limit is properly formatted and non-zero.
93func (v *SwapValidator) amountLimit(amountLimit int64) (int64, error) {
94 if amountLimit <= 0 {
95 return 0, ufmt.Errorf(ErrInvalidZeroAmountLimit, amountLimit)
96 }
97 return amountLimit, nil
98}
99
100// RouteParser handles parsing and validation of routes and quotes
101type RouteParser struct{}
102
103// NewRouteParser creates a new route parser instance.
104func NewRouteParser() *RouteParser {
105 return &RouteParser{}
106}
107
108// ParseRoutes parses route and quote strings into slices and validates them.
109func (p *RouteParser) ParseRoutes(routes, quotes string) ([]string, []string, error) {
110 // Check for empty routes
111 if routes == "" || quotes == "" {
112 return nil, nil, errors.New(errEmptyRoutes)
113 }
114
115 routesArr := splitSingleChar(routes, ',')
116 quotesArr := splitSingleChar(quotes, ',')
117
118 if err := p.ValidateRoutesAndQuotes(routesArr, quotesArr); err != nil {
119 return nil, nil, err
120 }
121
122 return routesArr, quotesArr, nil
123}
124
125// ValidateRoutesAndQuotes ensures routes and quotes meet required criteria.
126func (p *RouteParser) ValidateRoutesAndQuotes(routes, quotes []string) error {
127 rr := len(routes)
128 qq := len(quotes)
129
130 if rr < 1 || rr > 7 {
131 return ufmt.Errorf(errInvalidRouteLength, rr)
132 }
133
134 if rr != qq {
135 return ufmt.Errorf(errRoutesQuotesMismatch, rr, qq)
136 }
137
138 return p.ValidateQuoteSum(quotes)
139}
140
141// ValidateQuoteSum ensures all quotes add up to 100%.
142func (p *RouteParser) ValidateQuoteSum(quotes []string) error {
143 const (
144 maxQuote int8 = 100
145 minQuote int8 = 0
146 )
147
148 var sum int8
149
150 for i, quote := range quotes {
151 qt, err := strconv.ParseInt(quote, 10, 8)
152 if err != nil {
153 return ufmt.Errorf(errInvalidQuote, quote, i)
154 }
155 intQuote := int8(qt)
156
157 // Quote must be positive (> 0) as each route needs a non-zero allocation.
158 // A quote of 0 would mean no swap through that route, which is invalid.
159 if intQuote <= minQuote { // minQuote = 0, so this rejects quote = 0
160 return ufmt.Errorf(errInvalidQuoteValue, quote, i)
161 }
162
163 if intQuote > maxQuote {
164 return ufmt.Errorf(errQuoteExceedsMax, quote, i, maxQuote)
165 }
166
167 if sum > maxQuote-intQuote {
168 return ufmt.Errorf(errQuoteSumExceedsMax, i)
169 }
170
171 sum += intQuote
172 }
173
174 if sum != maxQuote {
175 return ufmt.Errorf(errInvalidQuoteSum, sum)
176 }
177
178 return nil
179}
180
181// finalizeSwap handles post-swap operations and validations.
182func (r *routerV1) finalizeSwap(
183 _ int,
184 rlm realm,
185 inputToken, outputToken string,
186 resultAmountIn, resultAmountOut int64,
187 swapType SwapType,
188 tokenAmountLimit int64,
189 amountSpecified int64,
190 swapCount int64,
191 isSetSqrtPriceLimitX96 bool,
192) (int64, int64) {
193 validator := &SwapValidator{}
194
195 // Handle swap fee
196 resultAmountOutWithoutFee := r.handleSwapFee(0, rlm, outputToken, resultAmountOut)
197
198 // Validate exact out amount if applicable
199 // If sqrtPriceLimitX96 is set, slippage check is not needed
200 if swapType == ExactOut && !isSetSqrtPriceLimitX96 {
201 if err := validator.exactOutAmount(resultAmountOutWithoutFee, amountSpecified, swapCount); err != nil {
202 panic(addDetailToError(errSlippage, err.Error()))
203 }
204 }
205
206 if err := validator.slippage(swapType, resultAmountIn, resultAmountOutWithoutFee, tokenAmountLimit); err != nil {
207 panic(addDetailToError(errSlippage, err.Error()))
208 }
209
210 return resultAmountIn, resultAmountOutWithoutFee
211}
212
213// validateRoutesAndQuotes is a convenience function that parses and validates routes in one call.
214func validateRoutesAndQuotes(routes, quotes string) ([]string, []string, error) {
215 return NewRouteParser().ParseRoutes(routes, quotes)
216}