Search Apps Documentation Source Content File Folder Download Copy Actions Download

base.gno

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