FlashArbMultiWithTWAP

Description:

Decentralized Finance (DeFi) protocol contract providing Pausable functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

/**
 * @title FlashArbMultiWithTWAP
 * @notice Multi-source flash loan arbitrage contract skeleton with essential safety checks.
 * NOTE: This version focuses on compiling cleanly with Hardhat and correct Balancer flashLoan usage.
 */

interface IERC20 {
    function totalSupply() external view returns (uint256);
    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 safeTransfer(IERC20 token, address to, uint256 value) internal {
        require(_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)), "TRANSFER_FAILED");
    }
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        require(_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)), "TRANSFER_FROM_FAILED");
    }
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        require(_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)), "APPROVE_FAILED");
    }
    function _callOptionalReturn(IERC20 token, bytes memory data) private returns (bool) {
        (bool ok, bytes memory ret) = address(token).call(data);
        if (!ok) return false;
        if (ret.length == 0) return true;
        return abi.decode(ret, (bool));
    }
}

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, "NOT_OWNER"); _; }
    function transferOwnership(address next) external onlyOwner {
        require(next != address(0), "ZERO_ADDR");
        emit OwnershipTransferred(owner, next);
        owner = next;
    }
}

abstract contract Pausable is Ownable {
    bool public paused;
    event Paused(address indexed by);
    event Unpaused(address indexed by);
    modifier whenNotPaused() { require(!paused, "PAUSED"); _; }
    function pause() external onlyOwner { paused = true; emit Paused(msg.sender); }
    function unpause() external onlyOwner { paused = false; emit Unpaused(msg.sender); }
}

abstract contract ReentrancyGuard {
    uint256 private _status = 1;
    uint256 private constant _ENTERED = 2;
    modifier nonReentrant() {
        require(_status != _ENTERED, "REENTRANCY");
        _status = _ENTERED;
        _;
        _status = 1;
    }
}

// External protocols (minimal interfaces)

interface IPool {
    function flashLoanSimple(address receiverAddress, address asset, uint256 amount, bytes calldata params, uint16 referralCode) external;
    function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128);
}

interface IBalancerVault {
    function flashLoan(address recipient, address[] calldata tokens, uint256[] calldata amounts, bytes calldata userData) external;
    function getProtocolFeesCollector() external view returns (address);
}

interface IProtocolFeesCollector {
    function getFlashLoanFeePercentage() external view returns (uint256);
}

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);
}

contract FlashArbMultiWithTWAP is Ownable, Pausable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    enum Provider { AaveV3, BalancerV2 }

    struct ProviderSpec {
        Provider kind;
        address addr;
    }

    struct V3Leg {
        address router;
        address tokenIn;
        address tokenOut;
        uint24 fee;
        uint256 amountOutMin;
        uint160 sqrtPriceLimitX96;
        address twapPool; // reserved; not used in this minimal compile-clean version
    }

    struct Params {
        address loanToken;
        uint256 loanAmount;
        uint256 minProfit;
        uint256 deadline;
        address profitRecipient;
        V3Leg[] v3Legs;
        ProviderSpec[] candidates;
    }

    mapping(address => bool) public keeper;
    mapping(address => bool) public allowedRouter;
    mapping(address => bool) public allowedToken;
    mapping(address => bool) public allowedProvider;

    event ArbExecuted(address provider, address asset, uint256 amount, uint256 premium, uint256 profit, address recipient);
    event KeeperSet(address indexed account, bool allowed);
    event RouterSet(address indexed router, bool allowed);
    event TokenSet(address indexed token, bool allowed);
    event ProviderSet(address indexed provider, bool allowed);

    modifier onlyAuth() {
        require(msg.sender == owner || keeper[msg.sender], "NOT_AUTH");
        _;
    }

    // --- Admin ---
    function setKeeper(address account, bool allowed) external onlyOwner { keeper[account] = allowed; emit KeeperSet(account, allowed); }
    function setRouter(address router, bool allowed) external onlyOwner { allowedRouter[router] = allowed; emit RouterSet(router, allowed); }
    function setToken(address token, bool allowed) external onlyOwner { allowedToken[token] = allowed; emit TokenSet(token, allowed); }
    function setProvider(address provider, bool allowed) external onlyOwner { allowedProvider[provider] = allowed; emit ProviderSet(provider, allowed); }

    // --- Entrypoint ---
    function startFlashArb(Params calldata p) external whenNotPaused nonReentrant onlyAuth {
        require(block.timestamp <= p.deadline, "DEADLINE");
        require(allowedToken[p.loanToken], "TOKEN_NOT_ALLOWED");

        (ProviderSpec memory best,) = _bestProvider(p);
        if (best.kind == Provider.AaveV3) {
            IPool(best.addr).flashLoanSimple(address(this), p.loanToken, p.loanAmount, abi.encode(p, best), 0);
        } else if (best.kind == Provider.BalancerV2) {
            // DECLARE ARRAYS PROPERLY (this was the bug)
            address[] memory tokens = new address[](1);
	    uint256[] memory amts = new uint256[](1);
            tokens[0] = p.loanToken;
            amts[0] = p.loanAmount;
            IBalancerVault(best.addr).flashLoan(address(this), tokens, amts, abi.encode(p, best));
        } else {
            revert("UNSUPPORTED_PROVIDER");
        }
    }

    // --- Callbacks ---
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address, /* initiator */
        bytes calldata data
    ) external returns (bool) {
        (Params memory p, ProviderSpec memory provider) = abi.decode(data, (Params, ProviderSpec));
        require(provider.kind == Provider.AaveV3 && allowedProvider[msg.sender], "NOT_AAVE");
        _executeArb(p, provider, asset, amount, premium);
        return true;
    }

    function receiveFlashLoan(
        address[] calldata tokens,
        uint256[] calldata amounts,
        uint256[] calldata fees,
        bytes calldata data
    ) external {
        (Params memory p, ProviderSpec memory provider) = abi.decode(data, (Params, ProviderSpec));
        require(provider.kind == Provider.BalancerV2 && allowedProvider[msg.sender], "NOT_BAL");
        _executeArb(p, provider, tokens[0], amounts[0], fees[0]);
    }

    // --- Core execution ---
    function _executeArb(
        Params memory p,
        ProviderSpec memory provider,
        address asset,
        uint256 amount,
        uint256 premium
    ) internal {
        require(amount == p.loanAmount && asset == p.loanToken, "MISMATCH");

        // Execute UniV3 legs
        for (uint256 i = 0; i < p.v3Legs.length; ++i) {
            V3Leg memory leg = p.v3Legs[i];
            require(allowedRouter[leg.router], "ROUTER_NOT_ALLOWED");

            uint256 balIn = IERC20(leg.tokenIn).balanceOf(address(this));
            if (balIn == 0) continue; // WHY: prevent revert if previous leg produced 0

            IERC20(leg.tokenIn).safeApprove(leg.router, 0);
            IERC20(leg.tokenIn).safeApprove(leg.router, balIn);

            IUniswapV3Router(leg.router).exactInputSingle(
                IUniswapV3Router.ExactInputSingleParams({
                    tokenIn: leg.tokenIn,
                    tokenOut: leg.tokenOut,
                    fee: leg.fee,
                    recipient: address(this),
                    deadline: p.deadline,
                    amountIn: balIn,
                    amountOutMinimum: leg.amountOutMin,
                    sqrtPriceLimitX96: leg.sqrtPriceLimitX96
                })
            );
        }

        // Repay + profit
        uint256 repay = amount + premium;
        uint256 endBal = IERC20(p.loanToken).balanceOf(address(this));
        uint256 profit = endBal > repay ? endBal - repay : 0;
        require(profit >= p.minProfit, "LOW_PROFIT");

        // For Aave: approve; For Balancer: transfer from this contract (vault pulls in receiveFlashLoan)
        IERC20(p.loanToken).safeApprove(provider.addr, 0);
        IERC20(p.loanToken).safeApprove(provider.addr, repay);

        if (profit > 0 && p.profitRecipient != address(0)) {
            IERC20(p.loanToken).safeTransfer(p.profitRecipient, profit);
        }

        emit ArbExecuted(provider.addr, asset, amount, premium, profit, p.profitRecipient);
    }

    // --- Provider selection ---
    function _bestProvider(Params calldata p) internal view returns (ProviderSpec memory best, uint256 bestPremium) {
        bestPremium = type(uint256).max;
        for (uint256 i = 0; i < p.candidates.length; ++i) {
            ProviderSpec memory prov = p.candidates[i];
            if (!allowedProvider[prov.addr]) continue;
            uint256 premium = _quotePremium(prov, p.loanToken, p.loanAmount);
            if (premium < bestPremium) {
                bestPremium = premium;
                best = prov;
            }
        }
    }

    function _quotePremium(ProviderSpec memory prov, address token, uint256 amount) internal view returns (uint256 premium) {
        if (prov.kind == Provider.AaveV3) {
            premium = (amount * IPool(prov.addr).FLASHLOAN_PREMIUM_TOTAL()) / 10_000;
        } else if (prov.kind == Provider.BalancerV2) {
            address pfc = IBalancerVault(prov.addr).getProtocolFeesCollector();
            premium = (amount * IProtocolFeesCollector(pfc).getFlashLoanFeePercentage()) / 1e18;
        } else {
            revert("UNSUPPORTED_PROVIDER");
        }
    }
}

Tags:
ERC20, DeFi, Pausable|addr:0xcea0de6f1fd43dc6994240bbbb29c5fb26640c6a|verified:true|block:23616518|tx:0x8b6d0aff360bed8be3c482ad0e09939ddfc87d3a9ce68bd85f8679fea9f0f070|first_check:1760961435

Submitted on: 2025-10-20 13:57:16

Comments

Log in to comment.

No comments yet.