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}