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");
}
}
}
Submitted on: 2025-10-20 13:57:16
Comments
Log in to comment.
No comments yet.