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