Address
0x49259ecf0edac4ed6ebc1136de42c4d0b33f0bceCurrent 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);
}
}