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

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}