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}