196 lines
5.7 KiB
Solidity
196 lines
5.7 KiB
Solidity
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||
|
|
import "@openzeppelin/contracts/security/Pausable.sol";
|
||
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||
|
|
|
||
|
|
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,
|
||
|
|
bytes calldata params
|
||
|
|
) external returns (bool);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @title AtomicExecutor
|
||
|
|
* @notice Executes batches of calls atomically, with optional flash loan support
|
||
|
|
*/
|
||
|
|
contract AtomicExecutor is Ownable, Pausable, ReentrancyGuard {
|
||
|
|
mapping(address => bool) public allowedTargets;
|
||
|
|
mapping(address => bool) public allowedPools; // Aave pools that can call flash loan callback
|
||
|
|
bool public allowListEnabled;
|
||
|
|
|
||
|
|
event TargetAllowed(address indexed target, bool allowed);
|
||
|
|
event PoolAllowed(address indexed pool, bool allowed);
|
||
|
|
event BatchExecuted(address indexed caller, uint256 callCount);
|
||
|
|
event FlashLoanExecuted(address indexed asset, uint256 amount);
|
||
|
|
|
||
|
|
constructor(address _owner) Ownable(_owner) {
|
||
|
|
allowListEnabled = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Enable/disable allow list
|
||
|
|
*/
|
||
|
|
function setAllowListEnabled(bool _enabled) external onlyOwner {
|
||
|
|
allowListEnabled = _enabled;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Allow/deny a target address
|
||
|
|
*/
|
||
|
|
function setAllowedTarget(address _target, bool _allowed) external onlyOwner {
|
||
|
|
allowedTargets[_target] = _allowed;
|
||
|
|
emit TargetAllowed(_target, _allowed);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Batch allow/deny multiple targets
|
||
|
|
*/
|
||
|
|
function setAllowedTargets(address[] calldata _targets, bool _allowed) external onlyOwner {
|
||
|
|
for (uint256 i = 0; i < _targets.length; i++) {
|
||
|
|
allowedTargets[_targets[i]] = _allowed;
|
||
|
|
emit TargetAllowed(_targets[i], _allowed);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Execute a batch of calls atomically
|
||
|
|
* @param targets Array of target addresses
|
||
|
|
* @param calldatas Array of calldata for each call
|
||
|
|
*/
|
||
|
|
function executeBatch(
|
||
|
|
address[] calldata targets,
|
||
|
|
bytes[] calldata calldatas
|
||
|
|
) external whenNotPaused nonReentrant {
|
||
|
|
require(targets.length == calldatas.length, "Length mismatch");
|
||
|
|
require(targets.length > 0, "Empty batch");
|
||
|
|
|
||
|
|
for (uint256 i = 0; i < targets.length; i++) {
|
||
|
|
if (allowListEnabled) {
|
||
|
|
require(allowedTargets[targets[i]], "Target not allowed");
|
||
|
|
}
|
||
|
|
|
||
|
|
(bool success, bytes memory returnData) = targets[i].call(calldatas[i]);
|
||
|
|
require(success, string(returnData));
|
||
|
|
}
|
||
|
|
|
||
|
|
emit BatchExecuted(msg.sender, targets.length);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Execute a flash loan and callback
|
||
|
|
* @param pool Aave v3 Pool address
|
||
|
|
* @param asset Asset to borrow
|
||
|
|
* @param amount Amount to borrow
|
||
|
|
* @param targets Array of target addresses for callback
|
||
|
|
* @param calldatas Array of calldata for callback
|
||
|
|
*/
|
||
|
|
function executeFlashLoan(
|
||
|
|
address pool,
|
||
|
|
address asset,
|
||
|
|
uint256 amount,
|
||
|
|
address[] calldata targets,
|
||
|
|
bytes[] calldata calldatas
|
||
|
|
) external whenNotPaused nonReentrant {
|
||
|
|
require(targets.length == calldatas.length, "Length mismatch");
|
||
|
|
|
||
|
|
bytes memory params = abi.encode(targets, calldatas, msg.sender);
|
||
|
|
|
||
|
|
IPool(pool).flashLoanSimple(
|
||
|
|
address(this),
|
||
|
|
asset,
|
||
|
|
amount,
|
||
|
|
params,
|
||
|
|
0
|
||
|
|
);
|
||
|
|
|
||
|
|
emit FlashLoanExecuted(asset, amount);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Allow/deny a pool address for flash loan callbacks
|
||
|
|
*/
|
||
|
|
function setAllowedPool(address _pool, bool _allowed) external onlyOwner {
|
||
|
|
allowedPools[_pool] = _allowed;
|
||
|
|
emit PoolAllowed(_pool, _allowed);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Flash loan callback (called by Aave Pool)
|
||
|
|
*/
|
||
|
|
function executeOperation(
|
||
|
|
address asset,
|
||
|
|
uint256 amount,
|
||
|
|
uint256 premium,
|
||
|
|
bytes calldata params
|
||
|
|
) external returns (bool) {
|
||
|
|
// Verify caller is an allowed Aave Pool
|
||
|
|
require(allowedPools[msg.sender], "Unauthorized pool");
|
||
|
|
|
||
|
|
// Decode params
|
||
|
|
(address[] memory targets, bytes[] memory calldatas, address initiator) = abi.decode(
|
||
|
|
params,
|
||
|
|
(address[], bytes[], address)
|
||
|
|
);
|
||
|
|
|
||
|
|
// Verify initiator is authorized (optional check)
|
||
|
|
require(initiator == tx.origin || initiator == address(this), "Unauthorized initiator");
|
||
|
|
|
||
|
|
// Execute callback operations
|
||
|
|
for (uint256 i = 0; i < targets.length; i++) {
|
||
|
|
if (allowListEnabled) {
|
||
|
|
require(allowedTargets[targets[i]], "Target not allowed");
|
||
|
|
}
|
||
|
|
|
||
|
|
(bool success, bytes memory returnData) = targets[i].call(calldatas[i]);
|
||
|
|
require(success, string(returnData));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Approve repayment
|
||
|
|
IERC20(asset).approve(msg.sender, amount + premium);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Pause contract
|
||
|
|
*/
|
||
|
|
function pause() external onlyOwner {
|
||
|
|
_pause();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Unpause contract
|
||
|
|
*/
|
||
|
|
function unpause() external onlyOwner {
|
||
|
|
_unpause();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Emergency withdraw (owner only)
|
||
|
|
*/
|
||
|
|
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
|
||
|
|
IERC20(token).transfer(owner(), amount);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
interface IERC20 {
|
||
|
|
function transfer(address to, uint256 amount) external returns (bool);
|
||
|
|
function approve(address spender, uint256 amount) external returns (bool);
|
||
|
|
}
|
||
|
|
|