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);
}
}
Submitted on: 2025-10-25 11:56:08
Comments
Log in to comment.
No comments yet.