Search Apps Documentation Source Content File Folder Download Copy Actions Download

swap_inner.gno

5.23 Kb ยท 200 lines
  1package v1
  2
  3import (
  4	"chain/runtime"
  5
  6	"gno.land/p/nt/ufmt"
  7
  8	"gno.land/r/gnoswap/access"
  9	"gno.land/r/gnoswap/common"
 10	"gno.land/r/gnoswap/router"
 11
 12	i256 "gno.land/p/gnoswap/int256"
 13	prbac "gno.land/p/gnoswap/rbac"
 14	u256 "gno.land/p/gnoswap/uint256"
 15
 16	pl "gno.land/r/gnoswap/pool"
 17)
 18
 19const (
 20	MIN_SQRT_RATIO string = "4295128739"                                        // same as TickMathGetSqrtRatioAtTick(MIN_TICK)
 21	MAX_SQRT_RATIO string = "1461446703485210103287273052203988822378723970342" // same as TickMathGetSqrtRatioAtTick(MAX_TICK)
 22)
 23
 24// swapInner executes the core swap logic by interacting with the pool contract.
 25// Returns poolRecv (tokens received by pool) and poolOut (tokens sent by pool).
 26func (r *routerV1) swapInner(
 27	amountSpecified int64,
 28	recipient address,
 29	sqrtPriceLimitX96 *u256.Uint,
 30	data SwapCallbackData,
 31) (int64, int64) {
 32	token0Path, token1Path := data.tokenIn, data.tokenOut
 33	zeroForOne := data.tokenIn < data.tokenOut
 34
 35	if !zeroForOne {
 36		token0Path, token1Path = token1Path, token0Path
 37	}
 38
 39	sqrtPriceLimitX96 = calculateSqrtPriceLimitForSwap(zeroForOne, data.fee, sqrtPriceLimitX96)
 40
 41	amount0Str, amount1Str := pl.Swap(
 42		cross,
 43		token0Path,
 44		token1Path,
 45		data.fee,
 46		recipient,
 47		zeroForOne,
 48		formatInt64(amountSpecified),
 49		sqrtPriceLimitX96.ToString(),
 50		data.payer,
 51		func(cur realm, amount0Delta, amount1Delta int64, _ *pl.CallbackMarker) error {
 52			// assert is pool to prevent unauthorized callback
 53			caller := runtime.PreviousRealm().Address()
 54			access.AssertIsPool(caller)
 55
 56			return router.SwapCallback(token0Path, token1Path, amount0Delta, amount1Delta, data.payer)
 57		},
 58	)
 59
 60	amount0 := i256.MustFromDecimal(amount0Str)
 61	amount1 := i256.MustFromDecimal(amount1Str)
 62
 63	poolOut, poolRecv := i256MinMax(amount0, amount1)
 64	if poolRecv.IsOverflow() || poolOut.IsOverflow() {
 65		panic("overflow in swapInner")
 66	}
 67
 68	return poolRecv.Int64(), poolOut.Int64()
 69}
 70
 71// swapDryInner performs a dry-run of a swap operation without executing it.
 72func (r *routerV1) swapDryInner(
 73	amountSpecified int64,
 74	sqrtPriceLimitX96 *u256.Uint,
 75	data SwapCallbackData,
 76) (int64, int64) {
 77	zeroForOne := data.tokenIn < data.tokenOut
 78	sqrtPriceLimitX96 = calculateSqrtPriceLimitForSwap(zeroForOne, data.fee, sqrtPriceLimitX96)
 79
 80	// check possible
 81	amount0Str, amount1Str, ok := pl.DrySwap(
 82		data.tokenIn,
 83		data.tokenOut,
 84		data.fee,
 85		zeroForOne,
 86		formatInt64(amountSpecified),
 87		sqrtPriceLimitX96.ToString(),
 88	)
 89	if !ok {
 90		return 0, 0
 91	}
 92
 93	amount0 := i256.MustFromDecimal(amount0Str)
 94	amount1 := i256.MustFromDecimal(amount1Str)
 95
 96	poolOut, poolRecv := i256MinMax(amount0, amount1)
 97	if poolRecv.IsOverflow() || poolOut.IsOverflow() {
 98		panic("overflow in swapDryInner")
 99	}
100
101	return poolRecv.Int64(), poolOut.Int64()
102}
103
104// RealSwapExecutor implements SwapExecutor for actual swaps.
105type RealSwapExecutor struct {
106	router *routerV1
107}
108
109// execute performs the actual swap execution.
110func (e *RealSwapExecutor) execute(p *SingleSwapParams) (int64, int64) {
111	caller := runtime.PreviousRealm().Address()
112	recipient := access.MustGetAddress(prbac.ROLE_ROUTER.String())
113
114	return e.router.swapInner(
115		p.amountSpecified,
116		recipient,             // if single swap => user will receive
117		p.SqrtPriceLimitX96(), // sqrtPriceLimitX96
118		newSwapCallbackData(p, caller),
119	)
120}
121
122// DrySwapExecutor implements SwapExecutor for dry swaps.
123type DrySwapExecutor struct {
124	router *routerV1
125}
126
127// execute performs the dry swap execution.
128func (e *DrySwapExecutor) execute(p *SingleSwapParams) (int64, int64) {
129	previousRealmAddr := runtime.PreviousRealm().Address()
130
131	return e.router.swapDryInner(
132		p.amountSpecified,
133		zero,
134		newSwapCallbackData(p, previousRealmAddr),
135	)
136}
137
138// calculateSqrtPriceLimitForSwap calculates the price limit for a swap operation.
139func calculateSqrtPriceLimitForSwap(zeroForOne bool, fee uint32, sqrtPriceLimitX96 *u256.Uint) *u256.Uint {
140	if !sqrtPriceLimitX96.IsZero() {
141		return sqrtPriceLimitX96
142	}
143
144	if zeroForOne {
145		minTick := getMinTick(fee) + 1
146		sqrtPriceLimitX96 = u256.Zero().Set(common.TickMathGetSqrtRatioAtTick(minTick))
147		if sqrtPriceLimitX96.IsZero() {
148			sqrtPriceLimitX96 = u256.MustFromDecimal(MIN_SQRT_RATIO)
149		}
150		return u256.Zero().Add(sqrtPriceLimitX96, one)
151	}
152
153	maxTick := getMaxTick(fee) - 1
154	sqrtPriceLimitX96 = u256.Zero().Set(common.TickMathGetSqrtRatioAtTick(maxTick))
155	if sqrtPriceLimitX96.IsZero() {
156		sqrtPriceLimitX96 = u256.MustFromDecimal(MAX_SQRT_RATIO)
157	}
158	return u256.Zero().Sub(sqrtPriceLimitX96, one)
159}
160
161// getMinTick returns the minimum tick value for a given fee tier.
162// The implementation follows Uniswap V3's tick spacing rules where
163// lower fee tiers allow for finer price granularity.
164func getMinTick(fee uint32) int32 {
165	switch fee {
166	case 100:
167		return -887272
168	case 500:
169		return -887270
170	case 3000:
171		return -887220
172	case 10000:
173		return -887200
174	default:
175		panic(addDetailToError(
176			errInvalidPoolFeeTier,
177			ufmt.Sprintf("unknown fee(%d)", fee),
178		))
179	}
180}
181
182// getMaxTick returns the maximum tick value for a given fee tier.
183// The max tick values are the exact negatives of min tick values.
184func getMaxTick(fee uint32) int32 {
185	switch fee {
186	case 100:
187		return 887272
188	case 500:
189		return 887270
190	case 3000:
191		return 887220
192	case 10000:
193		return 887200
194	default:
195		panic(addDetailToError(
196			errInvalidPoolFeeTier,
197			ufmt.Sprintf("unknown fee(%d)", fee),
198		))
199	}
200}