Search Apps Documentation Source Content File Folder Download Copy Actions Download

base.gno

9.00 Kb ยท 330 lines
  1package v1
  2
  3import (
  4	"chain/banker"
  5	"chain/runtime"
  6	"errors"
  7	"strings"
  8
  9	prbac "gno.land/p/gnoswap/rbac"
 10	u256 "gno.land/p/gnoswap/uint256"
 11	"gno.land/p/nt/ufmt"
 12
 13	"gno.land/r/gnoland/wugnot"
 14	"gno.land/r/gnoswap/access"
 15	"gno.land/r/gnoswap/common"
 16)
 17
 18const (
 19	SINGLE_HOP_ROUTE int = 1
 20
 21	INITIAL_WUGNOT_BALANCE int64 = 0
 22)
 23
 24// swap can be done by multiple pools
 25// to separate each pool, we use POOL_SEPARATOR
 26const (
 27	POOL_SEPARATOR = "*POOL*"
 28	wugnotPath     = "gno.land/r/gnoland/wugnot"
 29)
 30
 31type RouterOperation interface {
 32	Validate() error
 33	Process() (*SwapResult, error)
 34}
 35
 36// executeSwapOperation validates and processes a swap operation.
 37func executeSwapOperation(op RouterOperation) (*SwapResult, error) {
 38	if err := op.Validate(); err != nil {
 39		return nil, err
 40	}
 41
 42	result, err := op.Process()
 43	if err != nil {
 44		return nil, err
 45	}
 46
 47	return result, nil
 48}
 49
 50type BaseSwapParams struct {
 51	InputToken        string
 52	OutputToken       string
 53	RouteArr          string
 54	QuoteArr          string
 55	SqrtPriceLimitX96 *u256.Uint
 56	Deadline          int64
 57}
 58
 59// common swap operation
 60type baseSwapOperation struct {
 61	sqrtPriceLimitX96       *u256.Uint
 62	withUnwrap              bool
 63	userBeforeWugnotBalance int64
 64	userWrappedWugnot       int64
 65	routes                  []string
 66	quotes                  []string
 67	amountSpecified         int64
 68}
 69
 70// handleNativeTokenWrapping handles wrapping and unwrapping of native tokens.
 71func (op *baseSwapOperation) handleNativeTokenWrapping(
 72	inputToken string,
 73	outputToken string,
 74	specifiedAmount int64,
 75) error {
 76	isInputNativeToken := common.IsGNOTNativePath(inputToken)
 77	isOutputNativeToken := common.IsGNOTNativePath(outputToken)
 78
 79	// no native token
 80	if !isInputNativeToken && !isOutputNativeToken {
 81		return nil
 82	}
 83
 84	// save current user's WGNOT amount
 85	op.userBeforeWugnotBalance = wugnot.BalanceOf(runtime.PreviousRealm().Address())
 86
 87	// Handle input token independently
 88	if isInputNativeToken {
 89		sent := banker.OriginSend()
 90		ugnotSentByUser := sent.AmountOf(GNOT_DENOM)
 91
 92		if ugnotSentByUser != specifiedAmount {
 93			return ufmt.Errorf("ugnot sent by user(%d) is not equal to amountSpecified(%d)", ugnotSentByUser, specifiedAmount)
 94		}
 95
 96		// wrap user's GNOT to WUGNOT
 97		if ugnotSentByUser > 0 {
 98			if err := wrapWithTransfer(runtime.PreviousRealm().Address(), ugnotSentByUser); err != nil {
 99				return err
100			}
101		}
102
103		op.userWrappedWugnot = ugnotSentByUser
104	}
105
106	// Handle output token independently
107	if isOutputNativeToken {
108		op.withUnwrap = true
109	}
110
111	return nil
112}
113
114// processRoutes processes all swap routes and returns total amounts.
115func (op *baseSwapOperation) processRoutes(r *routerV1, swapType SwapType) (int64, int64, error) {
116	resultAmountIn, resultAmountOut := int64(0), int64(0)
117	remainRequestAmount := op.amountSpecified
118
119	for i, route := range op.routes {
120		toSwapAmount := int64(0)
121
122		// if it's the last route, use the remaining amount
123		isLastRoute := i == len(op.routes)-1
124		if !isLastRoute {
125			// calculate the amount to swap for this route
126			swapAmount, err := calculateSwapAmountByQuote(op.amountSpecified, op.quotes[i])
127			if err != nil {
128				return 0, 0, err
129			}
130
131			// update the remaining amount
132			remainRequestAmount = safeSubInt64(remainRequestAmount, swapAmount)
133			toSwapAmount = swapAmount
134		} else {
135			toSwapAmount = remainRequestAmount
136		}
137
138		amountIn, amountOut, err := op.processRoute(r, route, toSwapAmount, swapType)
139		if err != nil {
140			return 0, 0, err
141		}
142
143		resultAmountIn = safeAddInt64(resultAmountIn, amountIn)
144		resultAmountOut = safeAddInt64(resultAmountOut, amountOut)
145	}
146
147	return resultAmountIn, resultAmountOut, nil
148}
149
150// processRoute processes a single route with specified swap amount.
151func (op *baseSwapOperation) processRoute(
152	r *routerV1,
153	route string,
154	toSwap int64,
155	swapType SwapType,
156) (amountIn, amountOut int64, err error) {
157	numHops := strings.Count(route, POOL_SEPARATOR) + 1
158	assertHopsInRange(numHops)
159
160	switch numHops {
161	case SINGLE_HOP_ROUTE:
162		amountIn, amountOut = r.handleSingleSwap(route, toSwap, op.withUnwrap, op.sqrtPriceLimitX96)
163	default:
164		amountIn, amountOut = r.handleMultiSwap(swapType, route, numHops, toSwap, op.withUnwrap)
165	}
166
167	return amountIn, amountOut, nil
168}
169
170// handleSingleSwap executes a single-hop swap with the specified amount.
171func (r *routerV1) handleSingleSwap(route string, amountSpecified int64, withUnwrap bool, sqrtPriceLimitX96 *u256.Uint) (int64, int64) {
172	input, output, fee := getDataForSinglePath(route)
173	singleParams := SingleSwapParams{
174		tokenIn:           input,
175		tokenOut:          output,
176		fee:               fee,
177		amountSpecified:   amountSpecified,
178		withUnwrap:        withUnwrap,
179		sqrtPriceLimitX96: sqrtPriceLimitX96,
180	}
181
182	return r.singleSwap(&singleParams)
183}
184
185// handleMultiSwap processes multi-hop swaps across multiple pools.
186func (r *routerV1) handleMultiSwap(
187	swapType SwapType,
188	route string,
189	numHops int,
190	amountSpecified int64,
191	withUnwrap bool,
192) (int64, int64) {
193	recipient := access.MustGetAddress(prbac.ROLE_ROUTER.String())
194
195	switch swapType {
196	case ExactIn:
197		input, output, fee := getDataForMultiPath(route, 0) // first data
198		sp := newSwapParams(input, output, fee, recipient, amountSpecified, withUnwrap)
199		return r.multiSwap(*sp, numHops, route)
200	case ExactOut:
201		input, output, fee := getDataForMultiPath(route, numHops-1) // last data
202		sp := newSwapParams(input, output, fee, recipient, amountSpecified, withUnwrap)
203		return r.multiSwapNegative(*sp, numHops, route)
204	default:
205		panic(errInvalidSwapType)
206	}
207}
208
209// SwapRouteParams contains all parameters needed for swap route execution
210type SwapRouteParams struct {
211	inputToken        string
212	outputToken       string
213	routeArr          string
214	quoteArr          string
215	deadline          int64
216	typ               SwapType
217	exactAmount       int64      // amountIn for ExactIn, amountOut for ExactOut
218	limitAmount       int64      // amountOutMin for ExactIn, amountInMax for ExactOut
219	sqrtPriceLimitX96 *u256.Uint // if sqrtPriceLimitX96 is zero string, it will be set to MIN_PRICE or MAX_PRICE
220}
221
222func (p *SwapRouteParams) ExactAmount() int64 {
223	return p.exactAmount
224}
225
226// when exact out, calculate amount to fetch from pool including router fee
227func (p *SwapRouteParams) ExpectedExactAmountByFee(feeBps uint64) int64 {
228	if p.typ == ExactIn {
229		return p.exactAmount
230	}
231
232	return calculateExactOutWithRouterFee(p.exactAmount, feeBps)
233}
234
235// IsUnwrap checks if the swap output is native token.
236func (p *SwapRouteParams) IsUnwrap() bool {
237	return p.outputToken == GNOT_DENOM
238}
239
240func (p *SwapRouteParams) SwapCount() int64 {
241	swapCount := int64(0)
242
243	for _, route := range strings.Split(p.routeArr, ",") {
244		swapCount += int64(strings.Count(route, POOL_SEPARATOR) + 1)
245	}
246
247	return swapCount
248}
249
250func (p *SwapRouteParams) IsSetSqrtPriceLimitX96() bool {
251	return p.sqrtPriceLimitX96 != nil && !p.sqrtPriceLimitX96.IsZero()
252}
253
254// createSwapOperation creates the appropriate swap operation based on swap type.
255func createSwapOperation(r *routerV1, params SwapRouteParams) (RouterOperation, error) {
256	baseParams := BaseSwapParams{
257		InputToken:        params.inputToken,
258		OutputToken:       params.outputToken,
259		RouteArr:          params.routeArr,
260		QuoteArr:          params.quoteArr,
261		SqrtPriceLimitX96: params.sqrtPriceLimitX96,
262		Deadline:          params.deadline,
263	}
264
265	switch params.typ {
266	case ExactIn:
267		pp := NewExactInParams(baseParams, params.ExactAmount(), params.limitAmount)
268		return NewExactInSwapOperation(r, pp), nil
269	case ExactOut:
270		routerFee := r.store.GetSwapFee()
271		pp := NewExactOutParams(baseParams, params.ExpectedExactAmountByFee(routerFee), params.limitAmount)
272		return NewExactOutSwapOperation(r, pp), nil
273	default:
274		msg := addDetailToError(errInvalidSwapType, "unknown swap type")
275		return nil, errors.New(msg)
276	}
277}
278
279// extractSwapOperationData extracts common data from swap operation.
280func extractSwapOperationData(op RouterOperation) (int64, int64, error) {
281	var baseOp *baseSwapOperation
282	switch typedOp := op.(type) {
283	case *ExactInSwapOperation:
284		baseOp = &typedOp.baseSwapOperation
285	case *ExactOutSwapOperation:
286		baseOp = &typedOp.baseSwapOperation
287	default:
288		return 0, 0, ufmt.Errorf("unexpected operation type: %T", op)
289	}
290
291	return baseOp.userBeforeWugnotBalance, baseOp.userWrappedWugnot, nil
292}
293
294// commonSwapRoute handles the common logic for both ExactIn and ExactOut swaps.
295func (r *routerV1) commonSwapRoute(params SwapRouteParams) (int64, int64, error) {
296	op, err := createSwapOperation(r, params)
297	if err != nil {
298		return 0, 0, err
299	}
300
301	result, err := executeSwapOperation(op)
302	if err != nil {
303		msg := addDetailToError(
304			errInvalidInput,
305			ufmt.Sprintf("invalid %s SwapOperation: %s", params.typ.String(), err.Error()),
306		)
307		return 0, 0, errors.New(msg)
308	}
309
310	userBeforeWugnotBalance, userWrappedWugnot, err := extractSwapOperationData(op)
311	if err != nil {
312		return 0, 0, err
313	}
314
315	inputAmount, outputAmount := r.finalizeSwap(
316		params.inputToken,
317		params.outputToken,
318		result.AmountIn,
319		result.AmountOut,
320		params.typ,
321		params.limitAmount,
322		userBeforeWugnotBalance,
323		userWrappedWugnot,
324		params.ExactAmount(),
325		params.SwapCount(),
326		params.IsSetSqrtPriceLimitX96(),
327	)
328
329	return inputAmount, outputAmount, nil
330}