swap_multi.gno
8.94 Kb · 283 lines
1package v1
2
3import (
4 prbac "gno.land/p/gnoswap/rbac"
5 "gno.land/r/gnoswap/access"
6)
7
8// SwapDirection represents the direction of swap execution in multi-hop swaps.
9// It determines whether swaps are processed in forward order (first to last pool)
10// or backward order (last to first pool).
11type SwapDirection int
12
13const (
14 _ SwapDirection = iota
15 // Forward indicates a swap processing direction from the first pool to the last pool.
16 // Used primarily for exactIn swaps where the input amount is known.
17 Forward
18
19 // Backward indicates a swap processing direction from the last pool to the first pool.
20 // Used primarily for exactOut swaps where the output amount is known and input amounts
21 // Need to be calculated in reverse order.
22 Backward
23)
24
25// MultiSwapExecutor defines the interface for multi-hop swap operation execution.
26type MultiSwapExecutor interface {
27 // Run performs the swap operation and returns pool received and pool output amounts.
28 Run(p SwapParams, data SwapCallbackData, recipient address) (int64, int64)
29}
30
31// DryMultiSwapExecutor implements MultiSwapExecutor for dry run simulations.
32type DryMultiSwapExecutor struct {
33 router *routerV1
34}
35
36// Run performs a dry swap operation without changing state.
37func (e *DryMultiSwapExecutor) Run(p SwapParams, data SwapCallbackData, _ address) (int64, int64) {
38 return e.router.swapDryInner(p.amountSpecified, zero, data)
39}
40
41// RealMultiSwapExecutor implements MultiSwapExecutor for actual swap operations.
42type RealMultiSwapExecutor struct {
43 rlm realm
44 router *routerV1
45}
46
47// Run performs a real swap operation with state changes.
48func (e *RealMultiSwapExecutor) Run(p SwapParams, data SwapCallbackData, recipient address) (int64, int64) {
49 return e.router.swapInner(0, e.rlm, p.amountSpecified, recipient, zero, data)
50}
51
52// MultiSwapProcessor handles the execution flow for multi-hop swaps.
53type MultiSwapProcessor struct {
54 executor MultiSwapExecutor
55 direction SwapDirection
56 router *routerV1
57 isSimulate bool
58 // payer is the address used to identify the user. For real swaps it is the
59 // PreviousRealm address; for dry-run paths it is resolved at the entry point.
60 payer address
61}
62
63var (
64 _ MultiSwapExecutor = (*DryMultiSwapExecutor)(nil)
65 _ MultiSwapExecutor = (*RealMultiSwapExecutor)(nil)
66)
67
68// newRealMultiSwapProcessor creates a processor that performs real swaps.
69func newRealMultiSwapProcessor(_ int, rlm realm, r *routerV1, direction SwapDirection, payer address) *MultiSwapProcessor {
70 return &MultiSwapProcessor{
71 executor: &RealMultiSwapExecutor{rlm: rlm, router: r},
72 direction: direction,
73 router: r,
74 isSimulate: false,
75 payer: payer,
76 }
77}
78
79// newDryMultiSwapProcessor creates a processor that performs dry-run simulations.
80func newDryMultiSwapProcessor(r *routerV1, direction SwapDirection, payer address) *MultiSwapProcessor {
81 return &MultiSwapProcessor{
82 executor: &DryMultiSwapExecutor{router: r},
83 direction: direction,
84 router: r,
85 isSimulate: true,
86 payer: payer,
87 }
88}
89
90// processForwardSwap handles forward direction swaps (exactIn).
91func (p *MultiSwapProcessor) processForwardSwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
92 payer := p.payer // Initial payer is the user
93 routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())
94
95 firstAmountIn := int64(0)
96 currentPoolIndex := 0
97
98 for {
99 currentPoolIndex++
100
101 // Execute the swap operation
102 callbackData := newSwapCallbackData(sp, payer)
103 amountIn, amountOut := p.executor.Run(sp, callbackData, sp.recipient)
104
105 // Record the first hop's input amount
106 if currentPoolIndex == 1 {
107 firstAmountIn = amountIn
108 }
109
110 // Check if we've processed all hops
111 if currentPoolIndex >= numPools {
112 return firstAmountIn, amountOut, nil
113 }
114
115 // Update parameters for the next hop
116 payer = routerAddr
117 nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, currentPoolIndex)
118 sp.tokenIn = nextInput
119 sp.tokenOut = nextOutput
120 sp.fee = nextFee
121 sp.amountSpecified = amountOut
122 }
123}
124
125// processBackwardSwap handles backward direction swaps (exactOut).
126func (p *MultiSwapProcessor) processBackwardSwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
127 if !p.isSimulate {
128 return p.processBackwardRealSwap(sp, numPools, swapPath)
129 }
130 return p.processBackwardDrySwap(sp, numPools, swapPath)
131}
132
133// processBackwardDrySwap handles backward simulated swaps.
134func (p *MultiSwapProcessor) processBackwardDrySwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
135 firstAmountIn := int64(0)
136 currentPoolIndex := numPools - 1
137 routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())
138 payer := routerAddr
139
140 for {
141 callbackData := newSwapCallbackData(sp, payer)
142 amountIn, amountOut := p.executor.Run(sp, callbackData, sp.recipient)
143
144 if currentPoolIndex == 0 {
145 firstAmountIn = amountIn
146 }
147
148 currentPoolIndex--
149
150 if currentPoolIndex == -1 {
151 return firstAmountIn, amountOut, nil
152 }
153
154 // Update parameters for the next hop
155 nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, currentPoolIndex)
156
157 sp.amountSpecified = -amountIn
158 sp.tokenIn = nextInput
159 sp.tokenOut = nextOutput
160 sp.fee = nextFee
161 }
162}
163
164// processBackwardRealSwap handles backward real swaps.
165func (p *MultiSwapProcessor) processBackwardRealSwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
166 // First collect all swap information by simulating backward
167 swapInfo := p.collectBackwardSwapInfo(sp, numPools, swapPath)
168
169 // Then execute swaps in forward order
170 return p.executeCollectedSwaps(swapInfo, sp.recipient)
171}
172
173// collectBackwardSwapInfo simulates swaps backward to collect parameters.
174func (p *MultiSwapProcessor) collectBackwardSwapInfo(sp SwapParams, numPools int, swapPath string) []SingleSwapParams {
175 currentPoolIndex := numPools - 1
176 swapInfo := make([]SingleSwapParams, 0, currentPoolIndex)
177
178 for currentPoolIndex >= 0 {
179 thisSwap := SingleSwapParams{
180 tokenIn: sp.tokenIn,
181 tokenOut: sp.tokenOut,
182 fee: sp.fee,
183 amountSpecified: sp.amountSpecified,
184 }
185
186 // dry simulation to calculate input amount
187 amountIn, _ := p.router.singleDrySwap(p.payer, &thisSwap)
188 swapInfo = append(swapInfo, thisSwap)
189
190 if currentPoolIndex == 0 {
191 break
192 }
193 currentPoolIndex--
194
195 // Update parameters for the next simulation
196 nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, currentPoolIndex)
197
198 sp.tokenIn = nextInput
199 sp.tokenOut = nextOutput
200 sp.fee = nextFee
201 sp.amountSpecified = -amountIn
202 }
203
204 return swapInfo
205}
206
207// executeCollectedSwaps performs the collected swaps in forward order.
208// Only invoked from the real (non-simulated) backward swap path, so the
209// executor is always a *RealMultiSwapExecutor carrying the realm value.
210func (p *MultiSwapProcessor) executeCollectedSwaps(swapInfo []SingleSwapParams, recipient address) (int64, int64, error) {
211 firstAmountIn := int64(0)
212 currentPoolIndex := len(swapInfo) - 1
213 payer := p.payer // Initial payer is the user
214 routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())
215 rlm := p.executor.(*RealMultiSwapExecutor).rlm
216
217 for currentPoolIndex >= 0 {
218 // Execute the swap
219 callbackData := newSwapCallbackData(
220 swapInfo[currentPoolIndex],
221 payer,
222 )
223
224 amountIn, amountOut := p.router.swapInner(
225 0,
226 rlm,
227 swapInfo[currentPoolIndex].amountSpecified,
228 recipient,
229 zero,
230 callbackData,
231 )
232
233 // Record the first hop's input amount
234 if currentPoolIndex == len(swapInfo)-1 {
235 firstAmountIn = amountIn
236 }
237
238 if currentPoolIndex == 0 {
239 return firstAmountIn, amountOut, nil
240 }
241
242 // Update parameters for the next swap
243 swapInfo[currentPoolIndex-1].amountSpecified = amountOut
244 payer = routerAddr
245 currentPoolIndex--
246 }
247
248 return firstAmountIn, 0, nil
249}
250
251// multiSwap performs a multi-hop swap in forward direction.
252func (r *routerV1) multiSwap(_ int, rlm realm, p SwapParams, numPools int, swapPath string) (int64, int64) {
253 payer := rlm.Previous().Address()
254 result, output, err := newRealMultiSwapProcessor(0, rlm, r, Forward, payer).
255 processForwardSwap(p, numPools, swapPath)
256 if err != nil {
257 panic(err)
258 }
259 return result, output
260}
261
262// multiSwapNegative performs a multi-hop swap in backward direction.
263func (r *routerV1) multiSwapNegative(_ int, rlm realm, p SwapParams, numPools int, swapPath string) (int64, int64) {
264 payer := rlm.Previous().Address()
265 result, output, err := newRealMultiSwapProcessor(0, rlm, r, Backward, payer).
266 processBackwardSwap(p, numPools, swapPath)
267 if err != nil {
268 panic(err)
269 }
270 return result, output
271}
272
273// multiDrySwap simulates a multi-hop swap in forward direction.
274func (r *routerV1) multiDrySwap(payer address, p SwapParams, numPool int, swapPath string) (int64, int64, error) {
275 return newDryMultiSwapProcessor(r, Forward, payer).
276 processForwardSwap(p, numPool, swapPath)
277}
278
279// multiDrySwapNegative simulates a multi-hop swap in backward direction.
280func (r *routerV1) multiDrySwapNegative(payer address, p SwapParams, numPool int, swapPath string) (int64, int64, error) {
281 return newDryMultiSwapProcessor(r, Backward, payer).
282 processBackwardSwap(p, numPool, swapPath)
283}