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

base.gno

8.42 Kb · 297 lines
  1package router
  2
  3import (
  4	"errors"
  5	"strings"
  6
  7	gnsmath "gno.land/p/gnoswap/gnsmath/v1"
  8	prbac "gno.land/p/gnoswap/rbac/v1"
  9	u256 "gno.land/p/gnoswap/uint256/v1"
 10	ufmt "gno.land/p/nt/ufmt/v0"
 11
 12	"gno.land/r/gnoswap/access/v1"
 13)
 14
 15const (
 16	SINGLE_HOP_ROUTE int = 1
 17)
 18
 19// swap can be done by multiple pools
 20// to separate each pool, we use POOL_SEPARATOR
 21const (
 22	POOL_SEPARATOR = "*POOL*"
 23)
 24
 25type RouterOperation interface {
 26	// Validate checks operation parameters before processing.
 27	//
 28	// Returns:
 29	//   - err: nil when operation input is valid; validation error otherwise
 30	Validate() error
 31	// Process executes the operation in the propagated realm context.
 32	//
 33	// Parameters:
 34	//   - _: leading operation discriminator; callers pass 0
 35	//   - rlm: propagated current realm context used for pool interaction
 36	//
 37	// Returns:
 38	//   - result: swap amounts and route metadata, or nil when processing fails
 39	//   - err: nil on success; execution error otherwise
 40	Process(_ int, rlm realm) (*SwapResult, error)
 41}
 42
 43// executeSwapOperation validates and processes a swap operation.
 44func executeSwapOperation(_ int, rlm realm, op RouterOperation) (*SwapResult, error) {
 45	if err := op.Validate(); err != nil {
 46		return nil, err
 47	}
 48
 49	result, err := op.Process(0, rlm)
 50	if err != nil {
 51		return nil, err
 52	}
 53
 54	return result, nil
 55}
 56
 57type BaseSwapParams struct {
 58	InputToken        string
 59	OutputToken       string
 60	RouteArr          string
 61	QuoteArr          string
 62	SqrtPriceLimitX96 *u256.Uint
 63	Deadline          int64
 64}
 65
 66// common swap operation
 67type baseSwapOperation struct {
 68	sqrtPriceLimitX96 *u256.Uint
 69	routes            []string
 70	quotes            []string
 71	amountSpecified   int64
 72}
 73
 74// processRoutes processes all swap routes and returns total amounts.
 75func (op *baseSwapOperation) processRoutes(_ int, rlm realm, r *routerV1, swapType SwapType) (int64, int64, error) {
 76	resultAmountIn, resultAmountOut := int64(0), int64(0)
 77	remainRequestAmount := op.amountSpecified
 78
 79	for i, route := range op.routes {
 80		toSwapAmount := int64(0)
 81
 82		// if it's the last route, use the remaining amount
 83		isLastRoute := i == len(op.routes)-1
 84		if !isLastRoute {
 85			// calculate the amount to swap for this route
 86			swapAmount, err := calculateSwapAmountByQuote(op.amountSpecified, op.quotes[i])
 87			if err != nil {
 88				return 0, 0, err
 89			}
 90
 91			// update the remaining amount
 92			remainRequestAmount = gnsmath.SafeSubInt64(remainRequestAmount, swapAmount)
 93			toSwapAmount = swapAmount
 94		} else {
 95			toSwapAmount = remainRequestAmount
 96		}
 97
 98		amountIn, amountOut, err := op.processRoute(0, rlm, r, route, toSwapAmount, swapType)
 99		if err != nil {
100			return 0, 0, err
101		}
102
103		resultAmountIn = gnsmath.SafeAddInt64(resultAmountIn, amountIn)
104		resultAmountOut = gnsmath.SafeAddInt64(resultAmountOut, amountOut)
105	}
106
107	return resultAmountIn, resultAmountOut, nil
108}
109
110// processRoute processes a single route with specified swap amount.
111func (op *baseSwapOperation) processRoute(
112	_ int,
113	rlm realm,
114	r *routerV1,
115	route string,
116	toSwap int64,
117	swapType SwapType,
118) (amountIn, amountOut int64, err error) {
119	numHops := strings.Count(route, POOL_SEPARATOR) + 1
120	assertHopsInRange(numHops)
121
122	switch numHops {
123	case SINGLE_HOP_ROUTE:
124		amountIn, amountOut = r.handleSingleSwap(0, rlm, route, toSwap, op.sqrtPriceLimitX96)
125	default:
126		amountIn, amountOut = r.handleMultiSwap(0, rlm, swapType, route, numHops, toSwap)
127	}
128
129	return amountIn, amountOut, nil
130}
131
132// handleSingleSwap executes a single-hop swap with the specified amount.
133func (r *routerV1) handleSingleSwap(_ int, rlm realm, route string, amountSpecified int64, sqrtPriceLimitX96 *u256.Uint) (int64, int64) {
134	input, output, fee := getDataForSinglePath(route)
135	singleParams := SingleSwapParams{
136		tokenIn:           input,
137		tokenOut:          output,
138		fee:               fee,
139		amountSpecified:   amountSpecified,
140		sqrtPriceLimitX96: sqrtPriceLimitX96,
141	}
142
143	return r.singleSwap(0, rlm, &singleParams)
144}
145
146// handleMultiSwap processes multi-hop swaps across multiple pools.
147func (r *routerV1) handleMultiSwap(
148	_ int,
149	rlm realm,
150	swapType SwapType,
151	route string,
152	numHops int,
153	amountSpecified int64,
154) (int64, int64) {
155	recipient := access.MustGetAddress(prbac.ROLE_ROUTER.String())
156
157	switch swapType {
158	case ExactIn:
159		input, output, fee := getDataForMultiPath(route, 0) // first data
160		sp := newSwapParams(input, output, fee, recipient, amountSpecified)
161		return r.multiSwap(0, rlm, *sp, numHops, route)
162	case ExactOut:
163		input, output, fee := getDataForMultiPath(route, numHops-1) // last data
164		sp := newSwapParams(input, output, fee, recipient, amountSpecified)
165		return r.multiSwapNegative(0, rlm, *sp, numHops, route)
166	default:
167		panic(errors.New(errInvalidSwapType))
168	}
169}
170
171// SwapRouteParams contains all parameters needed for swap route execution
172type SwapRouteParams struct {
173	inputToken        string
174	outputToken       string
175	routeArr          string
176	quoteArr          string
177	deadline          int64
178	typ               SwapType
179	exactAmount       int64      // amountIn for ExactIn, amountOut for ExactOut
180	limitAmount       int64      // amountOutMin for ExactIn, amountInMax for ExactOut
181	sqrtPriceLimitX96 *u256.Uint // if sqrtPriceLimitX96 is zero string, it will be set to MIN_PRICE or MAX_PRICE
182}
183
184// ExactAmount returns the requested exact input or output amount.
185//
186// Returns:
187//   - amount: exactAmount configured for the route
188func (p *SwapRouteParams) ExactAmount() int64 {
189	return p.exactAmount
190}
191
192// ExpectedExactAmountByFee returns the amount needed by pool execution after router-fee adjustment.
193//
194// Parameters:
195//   - feeBps: router fee in basis points
196//
197// Returns:
198//   - amount: exact input amount for ExactIn, or gross pool amount for ExactOut
199func (p *SwapRouteParams) ExpectedExactAmountByFee(feeBps uint64) int64 {
200	if p.typ == ExactIn {
201		return p.exactAmount
202	}
203
204	return calculateExactOutWithRouterFee(p.exactAmount, feeBps)
205}
206
207// SwapCount counts pool hops across all comma-separated routes.
208//
209// Returns:
210//   - count: total number of pool swaps represented by routeArr
211func (p *SwapRouteParams) SwapCount() int64 {
212	swapCount := int64(0)
213
214	for _, route := range strings.Split(p.routeArr, ",") {
215		swapCount += int64(strings.Count(route, POOL_SEPARATOR) + 1)
216	}
217
218	return swapCount
219}
220
221func buildRouteEventAttrs(routeArr string) []string {
222	routes := strings.Split(routeArr, ",")
223	routeEventAttrs := make([]string, 0, len(routes)*2)
224
225	for index, route := range routes {
226		routeEventAttrs = append(routeEventAttrs, ufmt.Sprintf("routes[%d]", index))
227		routeEventAttrs = append(routeEventAttrs, route)
228	}
229
230	return routeEventAttrs
231}
232
233// IsSetSqrtPriceLimitX96 reports whether a nonzero caller price limit is configured.
234//
235// Returns:
236//   - set: true when sqrtPriceLimitX96 is non-nil and nonzero
237func (p *SwapRouteParams) IsSetSqrtPriceLimitX96() bool {
238	return p.sqrtPriceLimitX96 != nil && !p.sqrtPriceLimitX96.IsZero()
239}
240
241// createSwapOperation creates the appropriate swap operation based on swap type.
242func createSwapOperation(r *routerV1, params SwapRouteParams) (RouterOperation, error) {
243	baseParams := BaseSwapParams{
244		InputToken:        params.inputToken,
245		OutputToken:       params.outputToken,
246		RouteArr:          params.routeArr,
247		QuoteArr:          params.quoteArr,
248		SqrtPriceLimitX96: params.sqrtPriceLimitX96,
249		Deadline:          params.deadline,
250	}
251
252	switch params.typ {
253	case ExactIn:
254		pp := NewExactInParams(baseParams, params.ExactAmount(), params.limitAmount)
255		return NewExactInSwapOperation(r, pp), nil
256	case ExactOut:
257		routerFee := r.store.GetSwapFee()
258		pp := NewExactOutParams(baseParams, params.ExpectedExactAmountByFee(routerFee), params.limitAmount)
259		return NewExactOutSwapOperation(r, pp), nil
260	default:
261		msg := addDetailToError(errInvalidSwapType, "unknown swap type")
262		return nil, errors.New(msg)
263	}
264}
265
266// commonSwapRoute handles the common logic for both ExactIn and ExactOut swaps.
267func (r *routerV1) commonSwapRoute(_ int, rlm realm, params SwapRouteParams) (int64, int64, error) {
268	op, err := createSwapOperation(r, params)
269	if err != nil {
270		return 0, 0, err
271	}
272
273	result, err := executeSwapOperation(0, rlm, op)
274	if err != nil {
275		msg := addDetailToError(
276			errInvalidInput,
277			ufmt.Sprintf("invalid %s SwapOperation: %s", params.typ.String(), err.Error()),
278		)
279		return 0, 0, errors.New(msg)
280	}
281
282	inputAmount, outputAmount := r.finalizeSwap(
283		0,
284		rlm,
285		params.inputToken,
286		params.outputToken,
287		result.AmountIn,
288		result.AmountOut,
289		params.typ,
290		params.limitAmount,
291		params.ExactAmount(),
292		params.SwapCount(),
293		params.IsSetSqrtPriceLimitX96(),
294	)
295
296	return inputAmount, outputAmount, nil
297}