swap_multi.gno
5.62 Kb · 188 lines
1package router
2
3import (
4 prbac "gno.land/p/gnoswap/rbac/v1"
5 u256 "gno.land/p/gnoswap/uint256/v1"
6
7 "gno.land/r/gnoswap/access/v1"
8)
9
10// multiSwap performs a multi-hop exact-input swap in forward order.
11func (r *routerV1) multiSwap(_ int, rlm realm, sp SwapParams, numPools int, swapPath string) (int64, int64) {
12 payer := rlm.Previous().Address()
13 routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())
14 firstAmountIn := int64(0)
15
16 for currentPoolIndex := 0; ; currentPoolIndex++ {
17 callbackData := newSwapCallbackData(sp, payer)
18 amountIn, amountOut := r.swapInner(
19 0,
20 rlm,
21 sp.amountSpecified,
22 sp.recipient,
23 u256.Zero(),
24 callbackData,
25 )
26
27 // every hop of a forward multi-hop swap is exact-in
28 assertHopFullyConsumed(currentPoolIndex, sp.amountSpecified, amountIn)
29
30 if currentPoolIndex == 0 {
31 firstAmountIn = amountIn
32 }
33
34 nextPoolIndex := currentPoolIndex + 1
35 if nextPoolIndex >= numPools {
36 return firstAmountIn, amountOut
37 }
38
39 payer = routerAddr
40 nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, nextPoolIndex)
41 sp.tokenIn = nextInput
42 sp.tokenOut = nextOutput
43 sp.fee = nextFee
44 sp.amountSpecified = amountOut
45 }
46}
47
48// multiSwapNegative performs a multi-hop exact-output swap. It first quotes
49// every hop backward, then executes the collected swaps forward.
50func (r *routerV1) multiSwapNegative(_ int, rlm realm, sp SwapParams, numPools int, swapPath string) (int64, int64) {
51 swapInfo := r.collectBackwardDrySwapInfo(sp.SingleSwapParams, numPools, swapPath)
52 return r.executeCollectedSwaps(0, rlm, swapInfo, sp.recipient)
53}
54
55// multiDrySwap simulates a multi-hop exact-input swap in forward order.
56func (r *routerV1) multiDrySwap(sp SingleSwapParams, numPools int, swapPath string) (int64, int64, error) {
57 firstAmountIn := int64(0)
58
59 for currentPoolIndex := 0; ; currentPoolIndex++ {
60 amountIn, amountOut := r.swapDryInner(
61 sp.amountSpecified,
62 u256.Zero(),
63 newDrySwapCallbackData(sp),
64 )
65
66 // mirror the real-swap full-consumption guard so dry runs predict reverts
67 err := validateHopFullyConsumed(currentPoolIndex, sp.amountSpecified, amountIn)
68 if err != nil {
69 return 0, 0, err
70 }
71
72 if currentPoolIndex == 0 {
73 firstAmountIn = amountIn
74 }
75
76 nextPoolIndex := currentPoolIndex + 1
77 if nextPoolIndex >= numPools {
78 return firstAmountIn, amountOut, nil
79 }
80
81 nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, nextPoolIndex)
82 sp.tokenIn = nextInput
83 sp.tokenOut = nextOutput
84 sp.fee = nextFee
85 sp.amountSpecified = amountOut
86 }
87}
88
89// multiDrySwapNegative simulates a multi-hop exact-output swap.
90// It first quotes every hop backward, then simulates the collected swaps forward.
91func (r *routerV1) multiDrySwapNegative(sp SingleSwapParams, numPools int, swapPath string) (int64, int64, error) {
92 swapInfo := r.collectBackwardDrySwapInfo(sp, numPools, swapPath)
93 return r.executeCollectedDrySwaps(swapInfo)
94}
95
96// collectBackwardDrySwapInfo quotes an exact-output route backward before real execution.
97func (r *routerV1) collectBackwardDrySwapInfo(sp SingleSwapParams, numPools int, swapPath string) []SingleSwapParams {
98 swapInfo := make([]SingleSwapParams, 0, numPools)
99
100 for currentPoolIndex := numPools - 1; currentPoolIndex >= 0; currentPoolIndex-- {
101 amountIn, _ := r.singleDrySwap(&sp)
102 swapInfo = append(swapInfo, sp)
103
104 if currentPoolIndex == 0 {
105 break
106 }
107
108 nextPoolIndex := currentPoolIndex - 1
109 nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, nextPoolIndex)
110 sp.tokenIn = nextInput
111 sp.tokenOut = nextOutput
112 sp.fee = nextFee
113 sp.amountSpecified = -amountIn
114 }
115
116 return swapInfo
117}
118
119// executeCollectedSwaps executes the backward-quoted exact-output swaps in forward order.
120func (r *routerV1) executeCollectedSwaps(_ int, rlm realm, swapInfo []SingleSwapParams, recipient address) (int64, int64) {
121 payer := rlm.Previous().Address()
122 routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())
123 firstAmountIn := int64(0)
124
125 for currentPoolIndex := len(swapInfo) - 1; currentPoolIndex >= 0; currentPoolIndex-- {
126 amountIn, amountOut := r.swapInner(
127 0,
128 rlm,
129 swapInfo[currentPoolIndex].amountSpecified,
130 recipient,
131 u256.Zero(),
132 newSwapCallbackData(swapInfo[currentPoolIndex], payer),
133 )
134
135 // hops re-specified from the previous hop's actual output run as exact-in
136 // (positive amountSpecified) and must consume that amount entirely
137 if spec := swapInfo[currentPoolIndex].amountSpecified; spec > 0 {
138 assertHopFullyConsumed(currentPoolIndex, spec, amountIn)
139 }
140
141 if currentPoolIndex == len(swapInfo)-1 {
142 firstAmountIn = amountIn
143 }
144
145 if currentPoolIndex == 0 {
146 return firstAmountIn, amountOut
147 }
148
149 nextPoolIndex := currentPoolIndex - 1
150 swapInfo[nextPoolIndex].amountSpecified = amountOut
151 payer = routerAddr
152 }
153
154 return firstAmountIn, 0
155}
156
157func (r *routerV1) executeCollectedDrySwaps(swapInfo []SingleSwapParams) (int64, int64, error) {
158 firstAmountIn := int64(0)
159
160 for currentPoolIndex := len(swapInfo) - 1; currentPoolIndex >= 0; currentPoolIndex-- {
161 amountIn, amountOut := r.swapDryInner(
162 swapInfo[currentPoolIndex].amountSpecified,
163 u256.Zero(),
164 newDrySwapCallbackData(swapInfo[currentPoolIndex]),
165 )
166
167 // mirror the real-swap full-consumption guard so dry runs predict reverts
168 if spec := swapInfo[currentPoolIndex].amountSpecified; spec > 0 {
169 err := validateHopFullyConsumed(currentPoolIndex, spec, amountIn)
170 if err != nil {
171 return 0, 0, err
172 }
173 }
174
175 if currentPoolIndex == len(swapInfo)-1 {
176 firstAmountIn = amountIn
177 }
178
179 if currentPoolIndex == 0 {
180 return firstAmountIn, amountOut, nil
181 }
182
183 nextPoolIndex := currentPoolIndex - 1
184 swapInfo[nextPoolIndex].amountSpecified = amountOut
185 }
186
187 return firstAmountIn, 0, nil
188}