Description:
Decentralized Finance (DeFi) protocol contract providing Swap functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/* ====================== Minimal Interfaces ====================== */
interface IERC20 {
function balanceOf(address a) external view returns (uint256);
function allowance(address o, address s) external view returns (uint256);
function approve(address s, uint256 a) external returns (bool);
function transfer(address to, uint256 a) external returns (bool);
function transferFrom(address f, address t, uint256 a) external returns (bool);
function decimals() external view returns (uint8);
}
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint256) external;
}
/* UniswapV2 / SushiV2 router */
interface IUniswapV2Router02 {
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
}
/* 1inch Aggregation Router v5 (minimal) */
interface IOneInchAggregationRouterV5 {
struct SwapDescription {
address srcToken;
address dstToken;
address receiver;
address dstReceiver; // not used here; kept for ABI compatibility
uint256 amount;
uint256 minReturnAmount;
uint256 flags;
bytes permit;
}
function swap(
address executor,
SwapDescription calldata desc,
bytes calldata data
) external payable returns (uint256 returnAmount, uint256 spentAmount);
}
/* Aave v3 flash loan (simple) */
interface IPool {
function flashLoanSimple(
address receiverAddress,
address asset,
uint256 amount,
bytes calldata params,
uint16 referralCode
) external;
}
interface IFlashLoanSimpleReceiver {
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool);
}
/* ====================== Ownable + Reentrancy ====================== */
abstract contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; }
constructor() { owner = msg.sender; emit OwnershipTransferred(address(0), msg.sender); }
function transferOwnership(address next) external onlyOwner {
require(next != address(0), "zero");
emit OwnershipTransferred(owner, next);
owner = next;
}
}
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() { require(locked == 1, "reentrancy"); locked = 2; _; locked = 1; }
}
/* ============================= MAIN ============================= */
contract FlashArb_Aave_1inchOr0x_Sushi is IFlashLoanSimpleReceiver, Ownable, ReentrancyGuard {
/* -------- Hard-coded mainnet addresses -------- */
IPool public immutable aavePool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);
IERC20 public immutable USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
IWETH public immutable WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IOneInchAggregationRouterV5 public immutable oneInch =
IOneInchAggregationRouterV5(0x1111111254EEB25477B68fb85Ed929f73A960582);
IUniswapV2Router02 public immutable sushiRouter =
IUniswapV2Router02(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F);
/* -------- Strategy params -------- */
uint256 public flashLoanAmountUSDC = 5_002_155 * 1e6; // $5,002,155 USDC (6 dp)
uint256 public buyPriceMaxUsdE6 = 4_482_000000; // buy ceiling: $4,482 * 1e6 per ETH
uint256 public sellPriceMinUsdE6 = 4_488_000000; // sell floor: $4,488 * 1e6 per ETH
address public profitReceiver = msg.sender;
event Executed(
uint8 route, // 1 = 1inch, 2 = 0x
uint256 usdcSpent,
uint256 wethBought,
uint256 usdcReceived,
uint256 premium,
int256 profit
);
event ParamsUpdated(uint256 amountUSDC, uint256 buyMax, uint256 sellMin);
event ProfitReceiverUpdated(address to);
/* ---------------- Admin ---------------- */
function setParams(uint256 amountUSDC, uint256 buyMaxE6, uint256 sellMinE6) external onlyOwner {
require(amountUSDC > 0 && buyMaxE6 > 0 && sellMinE6 > 0, "bad params");
flashLoanAmountUSDC = amountUSDC;
buyPriceMaxUsdE6 = buyMaxE6;
sellPriceMinUsdE6 = sellMinE6;
emit ParamsUpdated(amountUSDC, buyMaxE6, sellMinE6);
}
function setProfitReceiver(address to) external onlyOwner {
require(to != address(0), "zero");
profitReceiver = to;
emit ProfitReceiverUpdated(to);
}
/* ---------------- Start (1inch path) ----------------
Pack: routeType=1 | executor | desc | data
----------------------------------------------------- */
function startArb1inch(
address executor,
IOneInchAggregationRouterV5.SwapDescription calldata desc,
bytes calldata data
) external onlyOwner nonReentrant {
require(executor != address(0), "executor=0");
require(desc.srcToken == address(USDC) && desc.dstToken == address(WETH), "bad tokens");
require(desc.amount == flashLoanAmountUSDC, "amount mismatch");
bytes memory packed = abi.encode(uint8(1), executor, desc, data);
aavePool.flashLoanSimple(address(this), address(USDC), flashLoanAmountUSDC, packed, 0);
}
/* ---------------- Start (0x path) ----------------
Pack: routeType=2 | target | allowanceTarget | callData | minWethOutHint
- target: 0x Exchange Proxy (resp.to)
- allowance: 0x allowanceTarget (resp.allowanceTarget)
- callData: resp.data
- minWethOutHint: minWETH from your script; contract will max(this, ceiling)
-------------------------------------------------- */
function startArb0x(
address target,
address allowanceTarget,
bytes calldata callData,
uint256 minWethOutHint
) external onlyOwner nonReentrant {
require(target != address(0) && allowanceTarget != address(0), "bad target");
bytes memory packed = abi.encode(uint8(2), target, allowanceTarget, callData, minWethOutHint);
aavePool.flashLoanSimple(address(this), address(USDC), flashLoanAmountUSDC, packed, 0);
}
/* ---------------- Aave callback ---------------- */
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override returns (bool) {
require(msg.sender == address(aavePool), "caller!=pool");
require(initiator == address(this), "initiator!=this");
require(asset == address(USDC), "asset!=USDC");
require(amount == flashLoanAmountUSDC, "amount mismatch");
(uint8 route,) = abi.decode(params, (uint8, bytes));
uint256 wethBought;
if (route == 1) {
wethBought = _buyVia1inch(amount, params);
} else if (route == 2) {
wethBought = _buyVia0x(amount, params);
} else {
revert("unknown route");
}
uint256 usdcReceived = _sellOnSushi(wethBought);
_repayAndDistribute(amount, premium);
emit Executed(route, amount, wethBought, usdcReceived, premium, int256(0));
return true;
}
/* ---------------- Internals ---------------- */
// routeType=1 payload: (uint8, address executor, SwapDescription desc, bytes data)
function _buyVia1inch(uint256 usdcIn, bytes calldata packed) private returns (uint256 bought) {
( , address executor, IOneInchAggregationRouterV5.SwapDescription memory desc, bytes memory data) =
abi.decode(packed, (uint8, address, IOneInchAggregationRouterV5.SwapDescription, bytes));
// Price ceiling check
uint256 ethOutMinByPrice = (usdcIn * 1e18) / buyPriceMaxUsdE6;
require(desc.minReturnAmount >= ethOutMinByPrice, "1inch minReturn < ceiling");
_approveIfNeeded(USDC, address(oneInch), usdcIn);
uint256 wBefore = WETH.balanceOf(address(this));
(uint256 ret, ) = oneInch.swap(executor, desc, data);
bought = ret;
if (bought == 0) { bought = WETH.balanceOf(address(this)) - wBefore; }
require(bought >= ethOutMinByPrice, "bought < min");
}
// routeType=2 payload: (uint8, address target, address allowanceTarget, bytes data, uint256 minWethOutHint)
function _buyVia0x(uint256 usdcIn, bytes calldata packed) private returns (uint256 bought) {
( , address target, address allowanceTarget, bytes memory data, uint256 minWethOutHint) =
abi.decode(packed, (uint8, address, address, bytes, uint256));
_approveIfNeeded(USDC, allowanceTarget, usdcIn);
uint256 wBefore = WETH.balanceOf(address(this));
(bool ok, ) = target.call(data);
require(ok, "0x swap failed");
bought = WETH.balanceOf(address(this)) - wBefore;
// Enforce hard ceiling on-chain: max(hint, ceiling)
uint256 ceiling = (usdcIn * 1e18) / buyPriceMaxUsdE6;
if (minWethOutHint < ceiling) { minWethOutHint = ceiling; }
require(bought >= minWethOutHint, "bought < min");
}
function _sellOnSushi(uint256 wethIn) private returns (uint256 usdcReceived) {
// USDC_out_min = wethIn * sellPriceMinUsdE6 / 1e18
uint256 minOut = (wethIn * sellPriceMinUsdE6) / 1e18;
_approveIfNeeded(WETH, address(sushiRouter), wethIn);
address[] memory path = new address[](2);
path[0] = address(WETH);
path[1] = address(USDC);
uint256 beforeBal = USDC.balanceOf(address(this));
sushiRouter.swapExactTokensForTokens(
wethIn,
minOut,
path,
address(this),
block.timestamp
);
usdcReceived = USDC.balanceOf(address(this)) - beforeBal;
require(usdcReceived >= minOut, "sell below floor");
}
function _repayAndDistribute(uint256 amount, uint256 premium) private {
uint256 repayAmt = amount + premium;
_approveIfNeeded(USDC, address(aavePool), repayAmt); // Aave pulls via allowance
uint256 bal = USDC.balanceOf(address(this));
if (bal > repayAmt) {
unchecked { USDC.transfer(profitReceiver, bal - repayAmt); }
}
}
function _approveIfNeeded(IERC20 token, address spender, uint256 amount) private {
if (token.allowance(address(this), spender) < amount) {
require(token.approve(spender, type(uint256).max), "approve fail");
}
}
/* Rescue tokens (if any dust remains) */
function rescue(address token, uint256 amount, address to) external onlyOwner {
IERC20(token).transfer(to, amount);
}
}
Submitted on: 2025-09-21 03:22:47
Comments
Log in to comment.
No comments yet.