Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}