swap_callback.gno

package router

import (
	"errors"
	prbac "gno.land/p/gnoswap/rbac"
	u256 "gno.land/p/gnoswap/uint256"
	ufmt "gno.land/p/nt/ufmt/v0"

	"gno.land/r/gnoswap/access"
	"gno.land/r/gnoswap/common"
	"gno.land/r/gnoswap/halt"
)

// SwapCallback implements the pool's SwapCallback interface.
// This is called by the pool after it has sent output tokens to the recipient.
// The router must transfer the required input tokens to the pool.
//
// This callback pattern enables:
// 1. Flash swaps (receive tokens before paying)
// 2. Just-in-time token transfers
// 3. Complex multi-hop swaps without intermediate transfers
//
// Parameters:
//   - token0Path, token1Path: Pool token paths
//   - amount0Delta, amount1Delta: Token deltas (positive = owe, negative = received)
//   - payer: Address that will pay for the swap
//
// Access:
//   - Only callable by Router v1 address (self-callback from pool)
//
// Returns:
//   - error: nil on success, error if transfer fails
//
// Delta convention (Uniswap V3):
//   - Positive delta: tokens the pool must receive (input token)
//   - Negative delta: tokens the pool has sent (output token)
func (r *routerV1) SwapCallback(
	_ int,
	rlm realm,
	token0Path, token1Path string,
	amount0Delta, amount1Delta int64,
	payer address,
) error {
	if !rlm.IsCurrent() {
		return errors.New(errSpoofedRealm)
	}

	halt.AssertIsNotHaltedRouter()

	caller := rlm.Previous().Address()
	assertIsRouterImplementation(caller)

	var tokenToPay string
	var amountToPay int64

	// amount0Delta > 0 means pool wants token0
	// amount1Delta > 0 means pool wants token1
	if amount0Delta > 0 {
		amountToPay = amount0Delta
		tokenToPay = token0Path
	} else if amount1Delta > 0 {
		amountToPay = amount1Delta
		tokenToPay = token1Path
	} else {
		return nil
	}

	// Transfer tokens from router to pool
	// The router should already have the tokens from the user
	r.transferToPool(0, rlm, tokenToPay, u256.NewUintFromInt64(amountToPay), payer)

	return nil
}

// transferToPool transfers tokens from router to pool
func (r *routerV1) transferToPool(_ int, rlm realm, token string, amount *u256.Uint, payer address) {
	balance := common.BalanceOf(token, payer)

	if u256.NewUintFromInt64(balance).Lt(amount) {
		panic(makeErrorWithDetails(
			errInsufficientBalance,
			ufmt.Sprintf("token=%s, required=%d, available=%d, payer=%s", token, amount.Int64(), balance, payer.String()),
		))
	}

	poolAddr := access.MustGetAddress(prbac.ROLE_POOL.String())
	routerAddr := access.MustGetAddress(prbac.ROLE_ROUTER.String())

	if payer == routerAddr {
		common.SafeGRC20Transfer(cross(rlm), token, poolAddr, amount.Int64())
	} else {
		common.SafeGRC20TransferFrom(cross(rlm), token, payer, poolAddr, amount.Int64())
	}
}