Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

router.gno

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