swap_multi.gno

package router

import (
	prbac "gno.land/p/gnoswap/rbac"
	u256 "gno.land/p/gnoswap/uint256"
	"gno.land/r/gnoswap/access"
)

// SwapDirection represents the direction of swap execution in multi-hop swaps.
// It determines whether swaps are processed in forward order (first to last pool)
// or backward order (last to first pool).
type SwapDirection int

const (
	_ SwapDirection = iota
	// Forward indicates a swap processing direction from the first pool to the last pool.
	// Used primarily for exactIn swaps where the input amount is known.
	Forward

	// Backward indicates a swap processing direction from the last pool to the first pool.
	// Used primarily for exactOut swaps where the output amount is known and input amounts
	// Need to be calculated in reverse order.
	Backward
)

// MultiSwapExecutor defines the interface for multi-hop swap operation execution.
type MultiSwapExecutor interface {
	// Run performs the swap operation and returns pool received and pool output amounts.
	Run(p SwapParams, data SwapCallbackData, recipient address) (int64, int64)
}

// DryMultiSwapExecutor implements MultiSwapExecutor for dry run simulations.
type DryMultiSwapExecutor struct {
	router *routerV1
}

// Run performs a dry swap operation without changing state.
func (e *DryMultiSwapExecutor) Run(p SwapParams, data SwapCallbackData, _ address) (int64, int64) {
	return e.router.swapDryInner(p.amountSpecified, u256.Zero(), data)
}

// RealMultiSwapExecutor implements MultiSwapExecutor for actual swap operations.
type RealMultiSwapExecutor struct {
	rlm    realm
	router *routerV1
}

// Run performs a real swap operation with state changes.
func (e *RealMultiSwapExecutor) Run(p SwapParams, data SwapCallbackData, recipient address) (int64, int64) {
	return e.router.swapInner(0, e.rlm, p.amountSpecified, recipient, u256.Zero(), data)
}

// MultiSwapProcessor handles the execution flow for multi-hop swaps.
type MultiSwapProcessor struct {
	executor   MultiSwapExecutor
	direction  SwapDirection
	router     *routerV1
	isSimulate bool
	// payer is the address used to identify the user. For real swaps it is the
	// PreviousRealm address; for dry-run paths it is resolved at the entry point.
	payer address
}

var (
	_ MultiSwapExecutor = (*DryMultiSwapExecutor)(nil)
	_ MultiSwapExecutor = (*RealMultiSwapExecutor)(nil)
)

// newRealMultiSwapProcessor creates a processor that performs real swaps.
func newRealMultiSwapProcessor(_ int, rlm realm, r *routerV1, direction SwapDirection, payer address) *MultiSwapProcessor {
	return &MultiSwapProcessor{
		executor:   &RealMultiSwapExecutor{rlm: rlm, router: r},
		direction:  direction,
		router:     r,
		isSimulate: false,
		payer:      payer,
	}
}

// newDryMultiSwapProcessor creates a processor that performs dry-run simulations.
func newDryMultiSwapProcessor(r *routerV1, direction SwapDirection, payer address) *MultiSwapProcessor {
	return &MultiSwapProcessor{
		executor:   &DryMultiSwapExecutor{router: r},
		direction:  direction,
		router:     r,
		isSimulate: true,
		payer:      payer,
	}
}

// processForwardSwap handles forward direction swaps (exactIn).
func (p *MultiSwapProcessor) processForwardSwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
	payer := p.payer // Initial payer is the user
	routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())

	firstAmountIn := int64(0)
	currentPoolIndex := 0

	for {
		currentPoolIndex++

		// Execute the swap operation
		callbackData := newSwapCallbackData(sp, payer)
		amountIn, amountOut := p.executor.Run(sp, callbackData, sp.recipient)

		// Record the first hop's input amount
		if currentPoolIndex == 1 {
			firstAmountIn = amountIn
		}

		// Check if we've processed all hops
		if currentPoolIndex >= numPools {
			return firstAmountIn, amountOut, nil
		}

		// Update parameters for the next hop
		payer = routerAddr
		nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, currentPoolIndex)
		sp.tokenIn = nextInput
		sp.tokenOut = nextOutput
		sp.fee = nextFee
		sp.amountSpecified = amountOut
	}
}

// processBackwardSwap handles backward direction swaps (exactOut).
func (p *MultiSwapProcessor) processBackwardSwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
	if !p.isSimulate {
		return p.processBackwardRealSwap(sp, numPools, swapPath)
	}
	return p.processBackwardDrySwap(sp, numPools, swapPath)
}

// processBackwardDrySwap handles backward simulated swaps.
func (p *MultiSwapProcessor) processBackwardDrySwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
	firstAmountIn := int64(0)
	currentPoolIndex := numPools - 1
	routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())
	payer := routerAddr

	for {
		callbackData := newSwapCallbackData(sp, payer)
		amountIn, amountOut := p.executor.Run(sp, callbackData, sp.recipient)

		if currentPoolIndex == 0 {
			firstAmountIn = amountIn
		}

		currentPoolIndex--

		if currentPoolIndex == -1 {
			return firstAmountIn, amountOut, nil
		}

		// Update parameters for the next hop
		nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, currentPoolIndex)

		sp.amountSpecified = -amountIn
		sp.tokenIn = nextInput
		sp.tokenOut = nextOutput
		sp.fee = nextFee
	}
}

// processBackwardRealSwap handles backward real swaps.
func (p *MultiSwapProcessor) processBackwardRealSwap(sp SwapParams, numPools int, swapPath string) (int64, int64, error) {
	// First collect all swap information by simulating backward
	swapInfo := p.collectBackwardSwapInfo(sp, numPools, swapPath)

	// Then execute swaps in forward order
	return p.executeCollectedSwaps(swapInfo, sp.recipient)
}

// collectBackwardSwapInfo simulates swaps backward to collect parameters.
func (p *MultiSwapProcessor) collectBackwardSwapInfo(sp SwapParams, numPools int, swapPath string) []SingleSwapParams {
	currentPoolIndex := numPools - 1
	swapInfo := make([]SingleSwapParams, 0, currentPoolIndex)

	for currentPoolIndex >= 0 {
		thisSwap := SingleSwapParams{
			tokenIn:         sp.tokenIn,
			tokenOut:        sp.tokenOut,
			fee:             sp.fee,
			amountSpecified: sp.amountSpecified,
		}

		// dry simulation to calculate input amount
		amountIn, _ := p.router.singleDrySwap(p.payer, &thisSwap)
		swapInfo = append(swapInfo, thisSwap)

		if currentPoolIndex == 0 {
			break
		}
		currentPoolIndex--

		// Update parameters for the next simulation
		nextInput, nextOutput, nextFee := getDataForMultiPath(swapPath, currentPoolIndex)

		sp.tokenIn = nextInput
		sp.tokenOut = nextOutput
		sp.fee = nextFee
		sp.amountSpecified = -amountIn
	}

	return swapInfo
}

// executeCollectedSwaps performs the collected swaps in forward order.
// Only invoked from the real (non-simulated) backward swap path, so the
// executor is always a *RealMultiSwapExecutor carrying the realm value.
func (p *MultiSwapProcessor) executeCollectedSwaps(swapInfo []SingleSwapParams, recipient address) (int64, int64, error) {
	firstAmountIn := int64(0)
	currentPoolIndex := len(swapInfo) - 1
	payer := p.payer // Initial payer is the user
	routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())
	rlm := p.executor.(*RealMultiSwapExecutor).rlm

	for currentPoolIndex >= 0 {
		// Execute the swap
		callbackData := newSwapCallbackData(
			swapInfo[currentPoolIndex],
			payer,
		)

		amountIn, amountOut := p.router.swapInner(
			0,
			rlm,
			swapInfo[currentPoolIndex].amountSpecified,
			recipient,
			u256.Zero(),
			callbackData,
		)

		// Record the first hop's input amount
		if currentPoolIndex == len(swapInfo)-1 {
			firstAmountIn = amountIn
		}

		if currentPoolIndex == 0 {
			return firstAmountIn, amountOut, nil
		}

		// Update parameters for the next swap
		swapInfo[currentPoolIndex-1].amountSpecified = amountOut
		payer = routerAddr
		currentPoolIndex--
	}

	return firstAmountIn, 0, nil
}

// multiSwap performs a multi-hop swap in forward direction.
func (r *routerV1) multiSwap(_ int, rlm realm, p SwapParams, numPools int, swapPath string) (int64, int64) {
	payer := rlm.Previous().Address()
	result, output, err := newRealMultiSwapProcessor(0, rlm, r, Forward, payer).
		processForwardSwap(p, numPools, swapPath)
	if err != nil {
		panic(err)
	}
	return result, output
}

// multiSwapNegative performs a multi-hop swap in backward direction.
func (r *routerV1) multiSwapNegative(_ int, rlm realm, p SwapParams, numPools int, swapPath string) (int64, int64) {
	payer := rlm.Previous().Address()
	result, output, err := newRealMultiSwapProcessor(0, rlm, r, Backward, payer).
		processBackwardSwap(p, numPools, swapPath)
	if err != nil {
		panic(err)
	}
	return result, output
}

// multiDrySwap simulates a multi-hop swap in forward direction.
func (r *routerV1) multiDrySwap(payer address, p SwapParams, numPool int, swapPath string) (int64, int64, error) {
	return newDryMultiSwapProcessor(r, Forward, payer).
		processForwardSwap(p, numPool, swapPath)
}

// multiDrySwapNegative simulates a multi-hop swap in backward direction.
func (r *routerV1) multiDrySwapNegative(payer address, p SwapParams, numPool int, swapPath string) (int64, int64, error) {
	return newDryMultiSwapProcessor(r, Backward, payer).
		processBackwardSwap(p, numPool, swapPath)
}