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