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