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