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
}
}}
Submitted on: 2025-10-12 18:18:13
Comments
Log in to comment.
No comments yet.