RustitrageExecutorOptimized

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * RustitrageExecutorOptimized
 * ------------------------------------------------------------
 * This variant of the original RustitrageExecutor contract makes several
 * changes aimed at reducing gas consumption when executing multi‑hop swaps:
 *
 *  1. The `SwapParams` struct now carries a `zeroForOne` boolean. This
 *     indicates whether the hop swaps token0→token1 (`true`) or the
 *     opposite direction. By supplying this from off‑chain, the executor
 *     avoids reading `token0()`/`token1()` from Uniswap V2/V3 pools at
 *     runtime, saving an external call per hop.
 *  2. The `isAuthorizedFactory` storage mapping and its associated admin
 *     functions have been removed. Performing factory authorisation via
 *     storage reads costs additional gas. Callers should only pass
 *     trustworthy factory addresses in the `extraData` field. In the
 *     V3 callback we still verify the pool address via the factory
 *     interface but do not gate the call behind an allowlist.
 *  3. Loop increments are wrapped in `unchecked` blocks to avoid
 *     redundant overflow checks on the loop counter.
 *  4. Custom errors can be added later to further reduce gas, but for
 *     clarity this example uses brief revert strings.
 */

// ----------------- ERC20 + safe wrappers -----------------
interface IERC20 {
    function balanceOf(address) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

library SafeERC20 {
    function _call(address token, bytes memory data) private returns (bytes memory) {
        (bool ok, bytes memory ret) = token.call(data);
        require(ok, "TOK_CALL_FAIL");
        if (ret.length > 0) require(abi.decode(ret, (bool)), "TOK_FALSE");
        return ret;
    }
    function safeTransfer(IERC20 t, address to, uint256 v) internal {
        _call(address(t), abi.encodeWithSelector(t.transfer.selector, to, v));
    }
    function safeTransferFrom(IERC20 t, address f, address to, uint256 v) internal {
        _call(address(t), abi.encodeWithSelector(t.transferFrom.selector, f, to, v));
    }
    function safeApprove(IERC20 t, address s, uint256 v) internal {
        _call(address(t), abi.encodeWithSelector(t.approve.selector, s, v));
    }
}

// ----------------- V2 -----------------
interface IUniswapV2Pair {
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}

// ----------------- V3 -----------------
interface IUniswapV3Pool {
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);
    function token0() external view returns (address);
    function token1() external view returns (address);
}

interface IUniswapV3SwapCallback {
    function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external;
}
interface IPancakeV3SwapCallback {
    function pancakeV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external;
}
// Factory interface (Uniswap/Pancake style)
interface IV3FactoryLike {
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address);
}

// ----------------- DODO (DVM/DPP-like) -----------------
interface IDODOLike {
    function sellBaseToken(uint256 amount, uint256 minReceive, bytes calldata data) external returns (uint256);
    function sellQuoteToken(uint256 amount, uint256 minReceive, bytes calldata data) external returns (uint256);
    function _BASE_TOKEN_() external view returns (address);
    function _QUOTE_TOKEN_() external view returns (address);
}

// ----------------- Ownable (minimal) -----------------
abstract contract Ownable {
    address public owner;
    event OwnershipTransferred(address indexed prev, address indexed next);
    constructor() { owner = msg.sender; emit OwnershipTransferred(address(0), msg.sender); }
    modifier onlyOwner() { require(msg.sender == owner, "ONLY_OWNER"); _; }
    function transferOwnership(address next) external onlyOwner { require(next != address(0), "ZERO"); emit OwnershipTransferred(owner, next); owner = next; }
}

// ----------------- Executor -----------------
contract RustitrageExecutorOptimized is IUniswapV3SwapCallback, Ownable,IPancakeV3SwapCallback {
    using SafeERC20 for IERC20;

    // Pool type constants
    uint8 internal constant PT_V2   = 1;
    uint8 internal constant PT_V3   = 2;
    uint8 internal constant PT_DODO = 3;
    // add at top of contract
error UNAUTH_FACTORY();
error BAD_V3_POOL();
error NO_DELTA();

address immutable FACTORY_A; // e.g. Uniswap V3 factory (mainnet)
address immutable FACTORY_B; // e.g. Pancake V3 factory (optional)
address immutable FACTORY_C; // add more if needed


  constructor(address factoryA, address factoryB, address factoryC) {
    FACTORY_A = factoryA;
    FACTORY_B = factoryB;
    FACTORY_C = factoryC;
}

/// @dev pure/view check without storage reads
function _isAllowedFactory(address f) internal view returns (bool) {
    // zero addr means "skip factory check" for legacy hops
    if (f == address(0)) return false; // treat zero as NOT allowed here (we’ll gate separately)
    return (f == FACTORY_A) || (f == FACTORY_B) || (f == FACTORY_C);
}

    /// @notice parameters describing one hop of a multi‑hop swap
    struct SwapParams {
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint256 minAmountOut;
        address pool;
        uint8   poolType;
        bool    zeroForOne;
        bytes   extraData;
    }

    /**
     * @notice Execute a sequence of swaps. The caller is expected to ensure
     *         that the contract holds `hops[0].amountIn` of the first token.
     * @param hops An array of SwapParams describing each hop
     * @param minTotalOut Minimum amount of final output tokens required
     */
    function execute(SwapParams[] calldata hops, uint256 minTotalOut) external onlyOwner returns (uint256 outAmount) {
        require(hops.length > 0, "NO_HOPS");
        uint256 amountIn = hops[0].amountIn;
        for (uint256 i; i < hops.length; ) {
            SwapParams calldata p = hops[i];
            if (p.poolType == PT_V2) {
                amountIn = _swapV2(p.pool, p.tokenIn, p.tokenOut, amountIn, p.minAmountOut, p.zeroForOne);
            } else if (p.poolType == PT_V3) {
                amountIn = _swapV3(p.pool, p.tokenIn, p.tokenOut, amountIn, p.minAmountOut, p.zeroForOne, p.extraData);
            } else {
                revert("BAD_POOL_TYPE");
            }
            unchecked { ++i; }
        }
        outAmount = amountIn;
        require(outAmount >= minTotalOut, "TOTAL_SLIPPAGE");
      
    }

    // --- V2 swap ---
    /**
     * @dev Executes a Uniswap V2 style swap. The `zeroForOne` boolean controls
     *      whether tokenIn is token0 (true) or token1 (false). No external
     *      calls to token0()/token1() are made, so the caller must set
     *      `zeroForOne` correctly. If it is incorrect the swap may revert or
     *      produce the wrong token flow.
     */
    function _swapV2(
        address pair,
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minOut,
        bool zeroForOne
    ) internal returns (uint256 amountOut) {
        // trust zeroForOne: do not call token0/token1
        uint256 balBefore = IERC20(tokenOut).balanceOf(address(this));
        IERC20(tokenIn).safeTransfer(pair, amountIn);
        uint amount0Out = zeroForOne ? 0 : minOut;
        uint amount1Out = zeroForOne ? minOut : 0;
        // call V2 swap
        IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), new bytes(0));
        // compute balance difference
        // swap returns funds directly, so we can check minOut by reading balance after
        // but reading balance twice is expensive; here we reuse minOut since
        // caller calculates next amountIn based on return value.
        // Strictly, we still read balance to compute amountOut.
        uint256 balAfter = IERC20(tokenOut).balanceOf(address(this));
        amountOut = balAfter - balBefore;
        require(amountOut >= minOut, "V2_SLIPPAGE");
    }

    // --- V3 swap ---
    /**
     * @dev Executes a Uniswap V3 style swap. The caller must supply
     *      zeroForOne to indicate the direction of the swap. The extraData
     *      parameter may contain:
     *        - empty: uses default sqrtPrice limits
     *        - 32 bytes: sqrtPriceLimitX96
     *        - 64 bytes: fee + factory
     *        - 96 bytes: sqrtPriceLimitX96 + fee + factory
     */
    function _swapV3(
        address pool,
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minOut,
        bool zeroForOne,
        bytes calldata extra
    ) internal returns (uint256 amountOut) {
        (uint160 sqrtLimit, uint24 fee, address factory) = _parseV3Extra(extra, zeroForOne);
        bytes memory cb = abi.encode(tokenIn, tokenOut, fee, factory);
        int256 amntIn = int256(amountIn);
        (int256 a0, int256 a1) = IUniswapV3Pool(pool).swap(
            address(this),
            zeroForOne,
            amntIn,
            sqrtLimit,
            cb
        );
        int256 deltaOut = zeroForOne ? a1 : a0;
        require(deltaOut <= 0, "V3_POS_DELTA");
        amountOut = uint256(-deltaOut);
        require(amountOut >= minOut, "V3_SLIPPAGE");
    }

    // Extract sqrtPriceLimit, fee and factory from extra data. See header for details.
    function _parseV3Extra(bytes calldata extra, bool zeroForOne) internal pure returns (uint160 sqrtLimit, uint24 fee, address factory) {
        // default extremes: avoid hitting virtual pool limits
        uint160 defaultLimit = zeroForOne ? (type(uint160).min + 1) : (type(uint160).max - 1);
        sqrtLimit = defaultLimit;
        fee = 0;
        factory = address(0);
        if (extra.length == 0) {
            return (sqrtLimit, fee, factory);
        } else if (extra.length == 32) {
            uint256 raw256 = abi.decode(extra, (uint256));
            uint160 raw = uint160(raw256);
            sqrtLimit = _clampSqrtPriceLimit(raw);
        } else if (extra.length == 64) {
            (fee, factory) = abi.decode(extra, (uint24, address));
        } else if (extra.length == 96) {
            (sqrtLimit, fee, factory) = abi.decode(extra, (uint160, uint24, address));
            sqrtLimit = _clampSqrtPriceLimit(sqrtLimit);
        }
    }

    function _clampSqrtPriceLimit(uint160 x) internal pure returns (uint160) {
        if (x <= 1) return 1;
        if (x >= type(uint160).max) return type(uint160).max - 1;
        return x;
    }

    /**
     * @dev Uniswap V3 callback. Verifies the pool via the provided factory if
     *      one is supplied in extraData. Does not rely on a storage mapping for
     *      factory authorisation to save gas.
     */
function uniswapV3SwapCallback(
    int256 amount0Delta,
    int256 amount1Delta,
    bytes calldata data
) external override {
    if (!(amount0Delta > 0 || amount1Delta > 0)) revert NO_DELTA();

    (address tokenIn, address tokenOut, uint24 fee, address factory)
        = abi.decode(data, (address, address, uint24, address));

    // 1) If factory is supplied, it MUST be whitelisted immutably
    if (factory != address(0)) {
        if (!_isAllowedFactory(factory)) revert UNAUTH_FACTORY();

        // 2) Derive canonical pool from trusted factory and enforce msg.sender
        address expected = IV3FactoryLike(factory).getPool(tokenIn, tokenOut, fee);
        // also ensure pool actually exists
        if (expected == address(0) || msg.sender != expected) revert BAD_V3_POOL();
    } else {
        revert UNAUTH_FACTORY();
    }

    // 3) Pay the correct token to the pool that invoked us
    uint256 amountToPay = uint256(amount0Delta > 0 ? amount0Delta : amount1Delta);

    // These are cheap view calls on the pool; you can avoid one call if you already know zeroForOne
    address t0 = IUniswapV3Pool(msg.sender).token0();
    address payToken = amount0Delta > 0 ? t0 : IUniswapV3Pool(msg.sender).token1();

    IERC20(payToken).safeTransfer(msg.sender, amountToPay);

}
function pancakeV3SwapCallback(
    int256 amount0Delta,
    int256 amount1Delta,
    bytes calldata data
) external override {
    if (!(amount0Delta > 0 || amount1Delta > 0)) revert NO_DELTA();

    (address tokenIn, address tokenOut, uint24 fee, address factory)
        = abi.decode(data, (address, address, uint24, address));

    // 1) If factory is supplied, it MUST be whitelisted immutably
    if (factory != address(0)) {
        if (!_isAllowedFactory(factory)) revert UNAUTH_FACTORY();

        // 2) Derive canonical pool from trusted factory and enforce msg.sender
        address expected = IV3FactoryLike(factory).getPool(tokenIn, tokenOut, fee);
        // also ensure pool actually exists
        if (expected == address(0) || msg.sender != expected) revert BAD_V3_POOL();
    } else {
        revert UNAUTH_FACTORY();
    }

    // 3) Pay the correct token to the pool that invoked us
    uint256 amountToPay = uint256(amount0Delta > 0 ? amount0Delta : amount1Delta);

    // These are cheap view calls on the pool; you can avoid one call if you already know zeroForOne
    address t0 = IUniswapV3Pool(msg.sender).token0();
    address payToken = amount0Delta > 0 ? t0 : IUniswapV3Pool(msg.sender).token1();

    IERC20(payToken).safeTransfer(msg.sender, amountToPay);

}

    // --- Utilities ---
    function sweep(address token, address to) external onlyOwner {
        uint256 bal = IERC20(token).balanceOf(address(this));
        IERC20(token).safeTransfer(to, bal);
    }
}

Tags:
ERC20, Multisig, Swap, Multi-Signature|addr:0x13b043bf4febaf2ed9711c615f26b51da6309eed|verified:true|block:23652562|tx:0x8c052ce1d09173930b09764486b7768aba283e340f059e40858ba86bde1f9260|first_check:1761386167

Submitted on: 2025-10-25 11:56:08

Comments

Log in to comment.

No comments yet.