Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}