Skip to main content
PulseScanner.io

Address

0x49259ecf0edac4ed6ebc1136de42c4d0b33f0bce
Current Holdings
$0.00
TXs sent
not counted
First Active
2026-08-23
block 27,360,408
Last Active
26 days ago
block 27,360,408
Funded By
not identified

Net worth historyi

No net-worth snapshots recorded yet
partial matchAlgebraPoolAdaptersolc 0.8.19+commit.7dd6d404runtime partial · creation partial
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { IRouterAllowlist } from "contracts/interfaces/IRouterAllowlist.sol";
import { IDexAdapter } from "contracts/dex/IDexAdapter.sol";
import { ISwapRouter } from "contracts/dex/ISwapRouter.sol";
import { SwapStep } from "contracts/dex/SwapTypes.sol";

/// @title DexAdapterBase
/// @notice Shared base for DEX adapters. Adapters in this codebase are
/// pure orchestrators — they decode `step.dexData`, build the target
/// router's calldata, and tell `SwapRouter` what to approve, transfer,
/// or call. They never hold tokens or ETH between (or during) swap
/// steps. `SwapRouter` itself remains `msg.sender` for every external
/// call, which means pool callbacks land on the existing callbacks on
/// `SwapRouter`, FOT accounting stays consistent, and there is no
/// per-step custody surface that a buggy adapter could leak.
/// @dev Mirrors `contracts/bridges/adapters/BridgeAdapterBase.sol` in
/// spirit (admin + rescue helpers); guardian role is intentionally
/// omitted to match `SwapRouter`'s admin-only governance model.
abstract contract DexAdapterBase is IDexAdapter {
    error NotAdmin();
    error NotSwapRouter();
    error InvalidAddress();

    event AdminUpdated(address oldAdmin, address newAdmin);
    event SwapRouterUpdated(address oldSwapRouter, address newSwapRouter);

    /// @notice Router that dispatches swap steps to this adapter and
    /// holds all custody for the swap.
    ///
    /// Deliberately NOT immutable. As an immutable it made every adapter a
    /// dependent of `SwapRouter`'s address, so any change to SwapRouter forced
    /// a redeploy of every adapter on every chain and a re-registration of the
    /// new addresses. One fee-rounding fix cost 621 contract deploys across 35
    /// chains that way. As storage it is a single batched `setSwapRouter` call
    /// per chain through SickleMultisig, and adapter addresses never move, so
    /// nothing downstream needs re-registering.
    ///
    /// This is safe because the guard that protects funds lives in the ROUTER,
    /// not here: `SwapRouter` gates `adapterApprove` / `adapterCall` /
    /// `adapterPull` on its `_activeAdapter` slot, set immediately before
    /// dispatch and cleared immediately after, and it never transfers custody
    /// to the adapter -- it measures its own balance delta as the authoritative
    /// output. Pointing this at a hostile contract therefore breaks the adapter
    /// (its callbacks go somewhere with no authority over the real router)
    /// rather than draining it. `setDexAdapter` never validated this
    /// back-pointer either, so immutability was not enforcing a router-side
    /// invariant.
    ISwapRouter public swapRouter;

    /// @notice Router allowlist shared with `SwapRouter`. Pool/factory-
    /// style adapters read this directly to authorise their factory or
    /// custom deployer checks.
    IRouterAllowlist public immutable allowlist;

    /// @notice Admin authorised to rotate this adapter's admin and
    /// rescue any stuck balances. Adapter swap dispatch itself is
    /// gated by `SwapRouter`'s active-adapter slot, not by this admin.
    address public admin;

    constructor(address admin_, address swapRouter_, address allowlist_) {
        if (admin_ == address(0)) revert InvalidAddress();
        if (swapRouter_ == address(0)) revert InvalidAddress();
        if (allowlist_ == address(0)) revert InvalidAddress();
        admin = admin_;
        swapRouter = ISwapRouter(swapRouter_);
        allowlist = IRouterAllowlist(allowlist_);
    }

    modifier onlyAdmin() {
        if (msg.sender != admin) revert NotAdmin();
        _;
    }

    /// @notice Repoint this adapter at a redeployed `SwapRouter`.
    /// @dev The trade-off this exists for is described on `swapRouter`. Emits
    /// so the binding stays auditable: as an immutable it was readable from
    /// constructor args and could never change, and anyone verifying "this
    /// adapter points at the real router" must now read state instead.
    /// @custom:access Restricted to the adapter admin.
    function setSwapRouter(
        address newSwapRouter
    ) external onlyAdmin {
        if (newSwapRouter == address(0)) revert InvalidAddress();
        emit SwapRouterUpdated(address(swapRouter), newSwapRouter);
        swapRouter = ISwapRouter(newSwapRouter);
    }

    /// @notice Defensive — adapters are not supposed to hold ETH, but
    /// stay payable so an over-refund from a router (e.g. universal
    /// router excess ETH on a partial swap) doesn't get rejected and
    /// can be swept by admin afterwards.
    receive() external payable { }

    /// @notice External `swap` entry point implementing `IDexAdapter`.
    /// Centralised here so every concrete adapter inherits the
    /// `msg.sender == swapRouter` guard structurally — impossible to
    /// forget per-impl. Without this guard, a reentrant callee invoked
    /// from inside the adapter's own helpers (token transferFrom, V4
    /// hook, malicious router) could call `swap` directly with an
    /// attacker-chosen `SwapStep`, satisfy `_activeAdapter` since this
    /// contract is still the active adapter, and instruct `SwapRouter`
    /// to approve / call / pull against attacker-controlled targets.
    /// Concrete adapters override `_swap` instead.
    function swap(
        SwapStep calldata step,
        uint256 amountIn
    ) external payable override returns (uint256) {
        if (msg.sender != address(swapRouter)) revert NotSwapRouter();
        return _swap(step, amountIn);
    }

    /// @dev Concrete per-DEX implementation. Always invoked via the
    /// guarded `swap` entry point above.
    function _swap(
        SwapStep calldata step,
        uint256 amountIn
    ) internal virtual returns (uint256);

    function setAdmin(
        address newAdmin
    ) external onlyAdmin {
        if (newAdmin == address(0)) revert InvalidAddress();
        emit AdminUpdated(admin, newAdmin);
        admin = newAdmin;
    }

    function rescueTokens(
        address token,
        address to,
        uint256 amount
    ) external virtual onlyAdmin {
        SafeTransferLib.safeTransfer(token, to, amount);
    }

    function rescueETH(
        address to
    ) external virtual onlyAdmin {
        SafeTransferLib.safeTransferETH(to, address(this).balance);
    }

    // ── Internal helpers shared by adapter implementations ────────────────

    /// @dev Pull `amount` of `token` from `SwapRouter` directly into
    /// `recipient` (used for pre-deposit-style routers such as
    /// Velodrome's UniversalRouter with `payerIsUser = false`).
    function _adapterPullTo(
        address token,
        uint256 amount,
        address recipient
    ) internal {
        swapRouter.adapterPull(token, amount, recipient);
    }

    /// @dev Set an ERC20 approval from `SwapRouter` to `spender`. Use
    /// `amount = 0` to revoke. Wraps `swapRouter.adapterApprove`.
    function _adapterApprove(
        address token,
        address spender,
        uint256 amount
    ) internal {
        swapRouter.adapterApprove(token, spender, amount);
    }

    /// @dev Execute an external call from `SwapRouter`'s address.
    function _adapterCall(
        address target,
        uint256 value,
        bytes memory data
    ) internal returns (bytes memory) {
        return swapRouter.adapterCall(target, value, data);
    }

    /// @dev Arm the V3 / Algebra pool callback before invoking a direct
    /// pool swap via `_adapterCall`.
    function _armPoolCallback(
        address pool,
        address tokenToPay,
        uint256 maxAmountToPay
    ) internal {
        swapRouter.adapterArmPoolCallback(pool, tokenToPay, maxAmountToPay);
    }

    /// @dev Resolve the target pool for a direct pool-swap step: the
    /// `(address pool)` (optionally followed by a custom deployer) encoded
    /// in `step.dexData`, falling back to `step.router` when no pool is
    /// encoded. Shared by the V3 and Algebra pool adapters.
    function _decodePoolAddress(
        SwapStep calldata step
    ) internal pure returns (address pool) {
        if (step.dexData.length >= 32) {
            pool = abi.decode(step.dexData, (address));
        } else {
            pool = step.router;
        }
    }

    /// @dev Common router-style swap: `SwapRouter` approves the router,
    /// calls it (with `msg.sender == SwapRouter`), revokes the approval.
    /// Output recipient must be baked into `data` as `address(swapRouter)`.
    function _swapViaRouter(
        address token,
        uint256 amount,
        address router,
        bytes memory data
    ) internal {
        _adapterApprove(token, router, amount);
        _adapterCall(router, 0, data);
        _adapterApprove(token, router, 0);
    }
}