Files
strategic/contracts/test/AtomicExecutorFlashLoan.t.sol
2026-02-09 21:51:54 -08:00

237 lines
6.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {AtomicExecutor} from "../AtomicExecutor.sol";
// Mock Aave Pool for flash loan testing
contract MockAavePool {
AtomicExecutor public executor;
address public asset;
uint256 public amount;
bool public callbackExecuted = false;
function setExecutor(address _executor) external {
executor = AtomicExecutor(_executor);
}
function flashLoanSimple(
address receiverAddress,
address _asset,
uint256 _amount,
bytes calldata params,
uint16
) external {
asset = _asset;
amount = _amount;
// Transfer asset to receiver (simulating flash loan)
IERC20(_asset).transfer(receiverAddress, _amount);
// Call executeOperation callback
IFlashLoanSimpleReceiver(receiverAddress).executeOperation(
_asset,
_amount,
_amount / 1000, // 0.1% premium
params
);
// Require repayment
uint256 repayment = _amount + (_amount / 1000);
require(
IERC20(_asset).balanceOf(receiverAddress) >= repayment,
"Insufficient repayment"
);
// Transfer repayment back
IERC20(_asset).transferFrom(receiverAddress, address(this), repayment);
callbackExecuted = true;
}
}
// Mock ERC20 for testing
contract MockERC20 {
mapping(address => uint256) public balanceOf;
function mint(address to, uint256 amount) external {
balanceOf[to] += amount;
}
function transfer(address to, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
balanceOf[from] -= amount;
balanceOf[to] += amount;
return true;
}
}
interface IFlashLoanSimpleReceiver {
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
bytes calldata params
) external returns (bool);
}
interface IERC20 {
function balanceOf(address) external view returns (uint256);
function transfer(address, uint256) external returns (bool);
function transferFrom(address, address, uint256) external returns (bool);
}
contract AtomicExecutorFlashLoanTest is Test {
AtomicExecutor executor;
MockAavePool pool;
MockERC20 token;
address owner = address(1);
address user = address(2);
function setUp() public {
vm.prank(owner);
executor = new AtomicExecutor(owner);
pool = new MockAavePool();
token = new MockERC20();
// Mint tokens to pool
token.mint(address(pool), 1000000e18);
// Set executor in pool
pool.setExecutor(address(executor));
// Allow pool for flash loans
vm.prank(owner);
executor.setAllowedPool(address(pool), true);
// Allow executor to receive tokens
vm.prank(owner);
executor.setAllowedTarget(address(token), true);
}
function testExecuteFlashLoan() public {
uint256 loanAmount = 1000e18;
// Encode callback operations (empty for this test)
bytes memory params = abi.encode(new address[](0), new bytes[](0));
vm.prank(user);
executor.executeFlashLoan(
address(pool),
address(token),
loanAmount,
params
);
assertTrue(pool.callbackExecuted());
}
function testFlashLoanRepayment() public {
uint256 loanAmount = 1000e18;
uint256 premium = loanAmount / 1000; // 0.1%
uint256 repayment = loanAmount + premium;
// Mint tokens to executor for repayment
token.mint(address(executor), repayment);
bytes memory params = abi.encode(new address[](0), new bytes[](0));
vm.prank(user);
executor.executeFlashLoan(
address(pool),
address(token),
loanAmount,
params
);
// Check pool has repayment
assertGe(token.balanceOf(address(pool)), repayment);
}
function testFlashLoanUnauthorizedPool() public {
MockAavePool unauthorizedPool = new MockAavePool();
unauthorizedPool.setExecutor(address(executor));
bytes memory params = abi.encode(new address[](0), new bytes[](0));
vm.prank(user);
vm.expectRevert("Unauthorized pool");
executor.executeFlashLoan(
address(unauthorizedPool),
address(token),
1000e18,
params
);
}
function testFlashLoanUnauthorizedInitiator() public {
address attacker = address(999);
bytes memory params = abi.encode(new address[](0), new bytes[](0));
// Try to call executeOperation directly (should fail)
vm.prank(address(pool));
vm.expectRevert("Unauthorized initiator");
executor.executeOperation(
address(token),
1000e18,
1e18,
params
);
}
function testFlashLoanWithMultipleOperations() public {
uint256 loanAmount = 1000e18;
// Encode multiple operations in callback
address[] memory targets = new address[](2);
bytes[] memory calldatas = new bytes[](2);
targets[0] = address(token);
calldatas[0] = abi.encodeWithSignature("transfer(address,uint256)", address(1), 500e18);
targets[1] = address(token);
calldatas[1] = abi.encodeWithSignature("transfer(address,uint256)", address(2), 500e18);
bytes memory params = abi.encode(targets, calldatas);
vm.prank(user);
executor.executeFlashLoan(
address(pool),
address(token),
loanAmount,
params
);
assertTrue(pool.callbackExecuted());
}
function testFlashLoanRepaymentValidation() public {
uint256 loanAmount = 1000e18;
uint256 premium = loanAmount / 1000; // 0.1%
uint256 requiredRepayment = loanAmount + premium;
// Don't mint enough for repayment
token.mint(address(executor), loanAmount); // Not enough!
bytes memory params = abi.encode(new address[](0), new bytes[](0));
vm.prank(user);
// Should revert due to insufficient repayment
vm.expectRevert();
executor.executeFlashLoan(
address(pool),
address(token),
loanAmount,
params
);
}
}