Initial commit: add .gitignore and README
This commit is contained in:
160
contracts/test/AtomicExecutor.t.sol
Normal file
160
contracts/test/AtomicExecutor.t.sol
Normal file
@@ -0,0 +1,160 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {AtomicExecutor} from "../AtomicExecutor.sol";
|
||||
|
||||
// Mock target contract for testing
|
||||
contract MockTarget {
|
||||
bool public testCalled = false;
|
||||
uint256 public value;
|
||||
|
||||
function test() external {
|
||||
testCalled = true;
|
||||
}
|
||||
|
||||
function setValue(uint256 _value) external {
|
||||
value = _value;
|
||||
}
|
||||
|
||||
function revertTest() external pure {
|
||||
revert("Test revert");
|
||||
}
|
||||
}
|
||||
|
||||
contract AtomicExecutorTest is Test {
|
||||
AtomicExecutor executor;
|
||||
MockTarget target;
|
||||
address owner = address(1);
|
||||
address user = address(2);
|
||||
|
||||
function setUp() public {
|
||||
vm.prank(owner);
|
||||
executor = new AtomicExecutor(owner);
|
||||
|
||||
target = new MockTarget();
|
||||
|
||||
vm.prank(owner);
|
||||
executor.setAllowedTarget(address(target), true);
|
||||
}
|
||||
|
||||
function testBatchExecute() public {
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("test()");
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
|
||||
assertTrue(target.testCalled());
|
||||
}
|
||||
|
||||
function testBatchExecuteMultiple() public {
|
||||
address[] memory targets = new address[](2);
|
||||
bytes[] memory calldatas = new bytes[](2);
|
||||
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("setValue(uint256)", 100);
|
||||
|
||||
targets[1] = address(target);
|
||||
calldatas[1] = abi.encodeWithSignature("setValue(uint256)", 200);
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
|
||||
assertEq(target.value(), 200);
|
||||
}
|
||||
|
||||
function testAllowListEnforcement() public {
|
||||
address newTarget = address(3);
|
||||
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = newTarget;
|
||||
calldatas[0] = abi.encodeWithSignature("test()");
|
||||
|
||||
vm.prank(user);
|
||||
vm.expectRevert("Target not allowed");
|
||||
executor.executeBatch(targets, calldatas);
|
||||
}
|
||||
|
||||
function testAllowListDisabled() public {
|
||||
address newTarget = address(3);
|
||||
|
||||
vm.prank(owner);
|
||||
executor.setAllowListEnabled(false);
|
||||
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = newTarget;
|
||||
calldatas[0] = abi.encodeWithSignature("test()");
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas); // Should succeed
|
||||
}
|
||||
|
||||
function testPause() public {
|
||||
vm.prank(owner);
|
||||
executor.pause();
|
||||
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("test()");
|
||||
|
||||
vm.prank(user);
|
||||
vm.expectRevert();
|
||||
executor.executeBatch(targets, calldatas);
|
||||
}
|
||||
|
||||
function testUnpause() public {
|
||||
vm.prank(owner);
|
||||
executor.pause();
|
||||
|
||||
vm.prank(owner);
|
||||
executor.unpause();
|
||||
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("test()");
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas); // Should succeed
|
||||
}
|
||||
|
||||
function testRevertPropagation() public {
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("revertTest()");
|
||||
|
||||
vm.prank(user);
|
||||
vm.expectRevert("Test revert");
|
||||
executor.executeBatch(targets, calldatas);
|
||||
}
|
||||
|
||||
function testSetAllowedPool() public {
|
||||
address pool = address(0x123);
|
||||
|
||||
vm.prank(owner);
|
||||
executor.setAllowedPool(pool, true);
|
||||
|
||||
assertTrue(executor.allowedPools(pool));
|
||||
}
|
||||
|
||||
function testOnlyOwnerCanSetPool() public {
|
||||
address pool = address(0x123);
|
||||
|
||||
vm.prank(user);
|
||||
vm.expectRevert();
|
||||
executor.setAllowedPool(pool, true);
|
||||
}
|
||||
}
|
||||
134
contracts/test/AtomicExecutorEdgeCases.t.sol
Normal file
134
contracts/test/AtomicExecutorEdgeCases.t.sol
Normal file
@@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {AtomicExecutor} from "../AtomicExecutor.sol";
|
||||
|
||||
contract MockTarget {
|
||||
uint256 public value;
|
||||
|
||||
function setValue(uint256 _value) external {
|
||||
value = _value;
|
||||
}
|
||||
|
||||
function revertTest() external pure {
|
||||
revert("Test revert");
|
||||
}
|
||||
|
||||
receive() external payable {}
|
||||
}
|
||||
|
||||
contract AtomicExecutorEdgeCasesTest is Test {
|
||||
AtomicExecutor executor;
|
||||
MockTarget target;
|
||||
address owner = address(1);
|
||||
address user = address(2);
|
||||
|
||||
function setUp() public {
|
||||
vm.prank(owner);
|
||||
executor = new AtomicExecutor(owner);
|
||||
|
||||
target = new MockTarget();
|
||||
|
||||
vm.prank(owner);
|
||||
executor.setAllowedTarget(address(target), true);
|
||||
}
|
||||
|
||||
function testEmptyBatch() public {
|
||||
address[] memory targets = new address[](0);
|
||||
bytes[] memory calldatas = new bytes[](0);
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
// Should succeed (no-op)
|
||||
}
|
||||
|
||||
function testVeryLargeBatch() public {
|
||||
// Test with 50 calls (near gas limit)
|
||||
address[] memory targets = new address[](50);
|
||||
bytes[] memory calldatas = new bytes[](50);
|
||||
|
||||
for (uint i = 0; i < 50; i++) {
|
||||
targets[i] = address(target);
|
||||
calldatas[i] = abi.encodeWithSignature("setValue(uint256)", i);
|
||||
}
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
|
||||
assertEq(target.value(), 49); // Last value set
|
||||
}
|
||||
|
||||
function testReentrancyAttempt() public {
|
||||
// Create a contract that tries to reenter
|
||||
ReentrancyAttacker attacker = new ReentrancyAttacker(executor, target);
|
||||
|
||||
vm.prank(owner);
|
||||
executor.setAllowedTarget(address(attacker), true);
|
||||
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = address(attacker);
|
||||
calldatas[0] = abi.encodeWithSignature("attack()");
|
||||
|
||||
vm.prank(user);
|
||||
// Should revert due to ReentrancyGuard
|
||||
vm.expectRevert();
|
||||
executor.executeBatch(targets, calldatas);
|
||||
}
|
||||
|
||||
function testValueHandling() public {
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("setValue(uint256)", 100);
|
||||
|
||||
vm.deal(address(executor), 1 ether);
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
|
||||
// Executor should not send value unless explicitly in call
|
||||
assertEq(address(executor).balance, 1 ether);
|
||||
}
|
||||
|
||||
function testDelegatecallProtection() public {
|
||||
// Attempt delegatecall (should not be possible with standard call)
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
// Standard call, not delegatecall
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("setValue(uint256)", 100);
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
|
||||
// Should succeed (delegatecall protection is implicit with standard call)
|
||||
assertEq(target.value(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
contract ReentrancyAttacker {
|
||||
AtomicExecutor executor;
|
||||
MockTarget target;
|
||||
|
||||
constructor(AtomicExecutor _executor, MockTarget _target) {
|
||||
executor = _executor;
|
||||
target = _target;
|
||||
}
|
||||
|
||||
function attack() external {
|
||||
// Try to reenter executor
|
||||
address[] memory targets = new address[](1);
|
||||
bytes[] memory calldatas = new bytes[](1);
|
||||
|
||||
targets[0] = address(target);
|
||||
calldatas[0] = abi.encodeWithSignature("setValue(uint256)", 999);
|
||||
|
||||
executor.executeBatch(targets, calldatas);
|
||||
}
|
||||
}
|
||||
|
||||
236
contracts/test/AtomicExecutorFlashLoan.t.sol
Normal file
236
contracts/test/AtomicExecutorFlashLoan.t.sol
Normal file
@@ -0,0 +1,236 @@
|
||||
// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
70
contracts/test/AtomicExecutorLargeBatch.t.sol
Normal file
70
contracts/test/AtomicExecutorLargeBatch.t.sol
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {AtomicExecutor} from "../AtomicExecutor.sol";
|
||||
|
||||
contract MockTarget {
|
||||
uint256 public value;
|
||||
|
||||
function setValue(uint256 _value) external {
|
||||
value = _value;
|
||||
}
|
||||
}
|
||||
|
||||
contract AtomicExecutorLargeBatchTest is Test {
|
||||
AtomicExecutor executor;
|
||||
MockTarget target;
|
||||
address owner = address(1);
|
||||
address user = address(2);
|
||||
|
||||
function setUp() public {
|
||||
vm.prank(owner);
|
||||
executor = new AtomicExecutor(owner);
|
||||
|
||||
target = new MockTarget();
|
||||
|
||||
vm.prank(owner);
|
||||
executor.setAllowedTarget(address(target), true);
|
||||
}
|
||||
|
||||
function testVeryLargeBatch() public {
|
||||
// Test with 100 calls (near gas limit)
|
||||
address[] memory targets = new address[](100);
|
||||
bytes[] memory calldatas = new bytes[](100);
|
||||
|
||||
for (uint i = 0; i < 100; i++) {
|
||||
targets[i] = address(target);
|
||||
calldatas[i] = abi.encodeWithSignature("setValue(uint256)", i);
|
||||
}
|
||||
|
||||
uint256 gasBefore = gasleft();
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
uint256 gasUsed = gasBefore - gasleft();
|
||||
|
||||
// Verify last value
|
||||
assertEq(target.value(), 99);
|
||||
|
||||
// Log gas usage for optimization
|
||||
console.log("Gas used for 100 calls:", gasUsed);
|
||||
}
|
||||
|
||||
function testGasLimitBoundary() public {
|
||||
// Test with calls that approach block gas limit
|
||||
// This helps identify optimal batch size
|
||||
address[] memory targets = new address[](50);
|
||||
bytes[] memory calldatas = new bytes[](50);
|
||||
|
||||
for (uint i = 0; i < 50; i++) {
|
||||
targets[i] = address(target);
|
||||
calldatas[i] = abi.encodeWithSignature("setValue(uint256)", i);
|
||||
}
|
||||
|
||||
vm.prank(user);
|
||||
executor.executeBatch(targets, calldatas);
|
||||
|
||||
assertEq(target.value(), 49);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user