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

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