FlashArb

Description:

Decentralized Finance (DeFi) protocol contract providing Swap, Factory functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/FlashArb.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

/* ========== Interfaces mínimas ========== */

interface IERC20 {
    function balanceOf(address) external view returns (uint256);
    function transfer(address,uint256) external returns (bool);
    function approve(address,uint256) external returns (bool);
    function transferFrom(address,address,uint256) external returns (bool);
}

interface IAaveV3PoolLike {
    function flashLoanSimple(
        address receiverAddress,
        address asset,
        uint256 amount,
        bytes calldata params,
        uint16 referralCode
    ) external;
}

interface IUniswapV3Router {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24  fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }
    function exactInputSingle(ExactInputSingleParams calldata params) external returns (uint256 amountOut);
}

interface ISushiV2Router {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

/* ========== SafeERC20 mínimo (sin OZ para ahorrar deps) ========== */

library SafeERC20 {
    function _call(address token, bytes memory data) private returns (bytes memory) {
        (bool ok, bytes memory ret) = token.call(data);
        require(ok, "TOKEN_CALL_FAIL");
        return ret;
    }

    function safeTransfer(IERC20 token, address to, uint256 amount) internal {
        bytes memory ret = _call(address(token), abi.encodeWithSelector(token.transfer.selector, to, amount));
        if (ret.length > 0) require(abi.decode(ret, (bool)), "TOKEN_TRANSFER_FALSE");
    }

    function safeApprove(IERC20 token, address spender, uint256 amount) internal {
        bytes memory ret = _call(address(token), abi.encodeWithSelector(token.approve.selector, spender, amount));
        if (ret.length > 0) require(abi.decode(ret, (bool)), "TOKEN_APPROVE_FALSE");
    }
}

/* ========== Contrato ========== */

contract FlashArb {
    using SafeERC20 for IERC20;

    /* ---------- Errores custom que usan los tests ---------- */
    error NotExecutor();
    error PausedError();

    /* ---------- Eventos (usados por los tests) ---------- */
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event ExecutorUpdated(address indexed newExecutor);
    event Paused(bool status);

    event FlashStart(address indexed token, uint256 amount);
    event Swap1(address indexed tokenIn, address indexed tokenOut, uint256 inAmt, uint256 outAmt, bytes32 venue);
    event Swap2(address indexed tokenIn, address indexed tokenOut, uint256 inAmt, uint256 outAmt, bytes32 venue);
    event Profit(address indexed asset, uint256 amount);

    /* ---------- Estado ---------- */
    address public owner;
    address public executor;
    bool    public paused;

    IAaveV3PoolLike public immutable pool;
    IUniswapV3Router public immutable uni;
    ISushiV2Router   public immutable sushi;

    // Guard sencillo (los tests esperan revert con "REENTRANCY")
    bool private _locked;
    modifier nonReentrant() {
        require(!_locked, "REENTRANCY");
        _locked = true;
        _;
        _locked = false;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "ONLY_OWNER");
        _;
    }

    /* ---------- Struct de parámetros (usado por tests e integración) ---------- */
    struct ArbParams {
        address borrowToken;
        uint256 amount;
        address midToken;
        bool    firstOnUni;     // true: UniV3 -> SushiV2; false: SushiV2 -> UniV3
        uint24  uniFee;         // 500 (0.05%) o 3000 (0.3%)
        uint256 minOut1;
        uint256 minOut2;
        uint256 deadline;
    }

    /* ---------- Constructor ---------- */
    constructor(address _pool, address _uni, address _sushi) {
        if (_pool == address(0) || _uni == address(0) || _sushi == address(0)) revert("ZERO_ADDR");
        owner = msg.sender;
        executor = msg.sender;
        pool = IAaveV3PoolLike(_pool);
        uni  = IUniswapV3Router(_uni);
        sushi = ISushiV2Router(_sushi);
    }

    /* ---------- Ownership ---------- */
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "ZERO_ADDR");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /* ---------- Roles / Pausa ---------- */
    function setExecutor(address newExec) external onlyOwner {
        require(newExec != address(0), "ZERO_ADDR");
        executor = newExec;
        emit ExecutorUpdated(newExec);
    }

    function setPaused(bool p) external onlyOwner {
        paused = p;
        emit Paused(p);
    }

    /* ---------- Entry point ---------- */
    function start(ArbParams memory p) external nonReentrant {
        if (paused) revert PausedError();
        if (msg.sender != owner && msg.sender != executor) revert NotExecutor();

        if (p.borrowToken == address(0) || p.midToken == address(0)) revert("ZERO_ADDR");
        if (p.amount == 0) revert("AMOUNT=0");
        if (p.uniFee != 500 && p.uniFee != 3000) revert("BAD_FEE");
        if (p.deadline < block.timestamp) revert("DEADLINE_PAST");

        emit FlashStart(p.borrowToken, p.amount);

        pool.flashLoanSimple(
            address(this),
            p.borrowToken,
            p.amount,
            abi.encode(p),
            0 // referralCode
        );
    }

    /* ---------- Aave callback ---------- */
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external nonReentrant returns (bool) {
        // checks de seguridad
        require(msg.sender == address(pool), "ONLY_POOL");
        require(initiator == address(this), "BAD_INITIATOR");

        ArbParams memory p = abi.decode(params, (ArbParams));
        if (p.deadline < block.timestamp) revert("DEADLINE_PAST");

        uint256 midOut;
        uint256 backOut;

        if (p.firstOnUni) {
            // UniV3: borrowToken -> midToken
            midOut = _swapUniV3(p.borrowToken, p.midToken, p.uniFee, amount, p.minOut1, p.deadline);
            emit Swap1(p.borrowToken, p.midToken, amount, midOut, bytes32("UNI"));

            // SushiV2: midToken -> borrowToken
            backOut = _swapSushiV2(p.midToken, p.borrowToken, midOut, p.minOut2, p.deadline);
            emit Swap2(p.midToken, p.borrowToken, midOut, backOut, bytes32("SUSHI"));
        } else {
            // SushiV2: borrowToken -> midToken
            midOut = _swapSushiV2(p.borrowToken, p.midToken, amount, p.minOut1, p.deadline);
            emit Swap1(p.borrowToken, p.midToken, amount, midOut, bytes32("SUSHI"));

            // UniV3: midToken -> borrowToken
            backOut = _swapUniV3(p.midToken, p.borrowToken, p.uniFee, midOut, p.minOut2, p.deadline);
            emit Swap2(p.midToken, p.borrowToken, midOut, backOut, bytes32("UNI"));
        }

        // Repago a Aave
        uint256 owing = amount + premium;
        // aprobar a pool a retirar 'owing'
        SafeERC20.safeApprove(IERC20(asset), address(pool), 0);
        SafeERC20.safeApprove(IERC20(asset), address(pool), owing);

        // Profit → owner (si existe)
        if (backOut > owing) {
            uint256 profit = backOut - owing;
            SafeERC20.safeTransfer(IERC20(asset), owner, profit);
            emit Profit(asset, profit);
        }

        return true;
    }

    /* ---------- Swaps internos ---------- */

    function _swapUniV3(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountIn,
        uint256 minOut,
        uint256 deadline
    ) internal returns (uint256 out) {
        // approve exacto
        SafeERC20.safeApprove(IERC20(tokenIn), address(uni), 0);
        SafeERC20.safeApprove(IERC20(tokenIn), address(uni), amountIn);

        IUniswapV3Router.ExactInputSingleParams memory sp = IUniswapV3Router.ExactInputSingleParams({
            tokenIn: tokenIn,
            tokenOut: tokenOut,
            fee: fee,
            recipient: address(this),
            deadline: deadline,
            amountIn: amountIn,
            amountOutMinimum: minOut,
            sqrtPriceLimitX96: 0
        });

        out = IUniswapV3Router(uni).exactInputSingle(sp);

        // limpieza de allowance
        SafeERC20.safeApprove(IERC20(tokenIn), address(uni), 0);
    }

    function _swapSushiV2(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minOut,
        uint256 deadline
    ) internal returns (uint256 out) {
        // approve exacto
        SafeERC20.safeApprove(IERC20(tokenIn), address(sushi), 0);
        SafeERC20.safeApprove(IERC20(tokenIn), address(sushi), amountIn);

        address[] memory path_ = new address[](2);
        path_[0] = tokenIn;
        path_[1] = tokenOut;

        uint256[] memory amounts = ISushiV2Router(sushi).swapExactTokensForTokens(
            amountIn,
            minOut,
            path_,
            address(this),
            deadline
        );

        // limpieza de allowance
        SafeERC20.safeApprove(IERC20(tokenIn), address(sushi), 0);

        // amounts[amounts.length-1] = tokens recibidos
        out = amounts[amounts.length - 1];
    }

    /* ---------- Rescates y receive ---------- */

    function rescueTokens(address token, uint256 amount) external onlyOwner {
        SafeERC20.safeTransfer(IERC20(token), owner, amount);
    }

    function rescueETH(uint256 amount) external onlyOwner {
        (bool ok, ) = payable(owner).call{value: amount}("");
        require(ok, "ETH_SEND_FAIL");
    }

    receive() external payable {}
}"
    }
  },
  "settings": {
    "remappings": [
      "forge-std/=lib/forge-std/src/"
    ],
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "prague",
    "viaIR": false
  }
}}

Tags:
DeFi, Swap, Factory|addr:0xe9b82462e7f889576e5e5990b0c57d0c706450c6|verified:true|block:23557842|tx:0x3e5a6da2d8bcc0d36b99bd850410283c4024a120874f747e47a95e4795f0842e|first_check:1760285890

Submitted on: 2025-10-12 18:18:13

Comments

Log in to comment.

No comments yet.