All the following accounting variables will store the incorrect amount of tokens:
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Lender.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
contract TERC20 is ERC20 {
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);
}
}
contract WrappedLender is Lender {
function getLoanDebtDetail(uint256 loanId) external view returns (uint256 fullDebt, uint256 interest, uint256 fees) {
Loan memory loan = loans[loanId];
(interest, fees) = _calculateInterest(loan);
fullDebt = loan.debt + interest + fees;
}
function getPoolInfo(bytes32 poolId) external view returns (Pool memory) {
return pools[poolId];
}
function getLoanInfo(uint256 loanId) external view returns (Loan memory) {
return loans[loanId];
}
}
contract BaseLender is Test {
WrappedLender public lender;
TERC20 public loanToken;
TERC20 public collateralToken;
address public lender1 = address(0x1);
address public lender2 = address(0x2);
address public borrower = address(0x3);
address public fees = address(0x4);
function setUp() public virtual {
lender = new WrappedLender();
loanToken = new TERC20();
collateralToken = new TERC20();
loanToken.mint(address(lender1), 100000 ether);
loanToken.mint(address(lender2), 100000 ether);
collateralToken.mint(address(borrower), 100000 ether);
vm.startPrank(lender1);
loanToken.approve(address(lender), 1000000 ether);
collateralToken.approve(address(lender), 1000000 ether);
vm.startPrank(lender2);
loanToken.approve(address(lender), 1000000 ether);
collateralToken.approve(address(lender), 1000000 ether);
vm.startPrank(borrower);
loanToken.approve(address(lender), 1000000 ether);
collateralToken.approve(address(lender), 1000000 ether);
}
function borrow(address _borrower, bytes32 poolId) public {
vm.startPrank(_borrower);
Borrow memory b = Borrow({
poolId: poolId,
debt: 100 ether,
collateral: 100 ether
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
vm.stopPrank();
}
function createPool(address _lender) public returns (bytes32){
vm.startPrank(_lender);
Pool memory p = Pool({
lender: _lender,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100 ether,
poolBalance: 1000 ether,
maxLoanRatio: 2 ether,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
vm.stopPrank();
return poolId;
}
function startAuction(address _lender, uint256 loanId) public {
uint256[] memory loans = new uint256[](1);
loans[0] = loanId;
vm.prank(_lender);
lender.startAuction(loans);
}
function seizeLoan(uint256 loanId) public {
uint256[] memory loans = new uint256[](1);
loans[0] = loanId;
lender.seizeLoan(loans);
}
function getPoolId(address _lender) public returns (bytes32) {
return keccak256(
abi.encode(
address(_lender),
address(loanToken),
address(collateralToken)
)
);
}
}
pragma solidity ^0.8.13;
import "./BaseLender.sol";
contract ERC20WithFee is ERC20 {
uint256 public constant FEE_BPS = 50;
uint256 public feeAccumulated;
function name() public pure override returns (string memory) {
return "Test ERC20WithFee";
}
function symbol() public pure override returns (string memory) {
return "ERC20WithFee";
}
function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}
function transfer(address to, uint256 amount) public override returns (bool) {
uint256 protocolFee = amount * FEE_BPS / 10_000;
feeAccumulated += protocolFee;
return super.transfer(to, amount - protocolFee);
}
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
uint256 protocolFee = amount * FEE_BPS / 10_000;
feeAccumulated += protocolFee;
return super.transferFrom(from, to, amount - protocolFee);
}
}
contract ERC20FeeTest is BaseLender {
function setUp() override public {
super.setUp();
}
function testERC20WithFee() public {
ERC20WithFee loanTokenWithFee = new ERC20WithFee();
uint256 loanTokenForPool = 10 ether;
vm.startPrank(lender1);
loanTokenWithFee.mint(address(lender1), loanTokenForPool);
loanTokenWithFee.approve(address(lender), loanTokenForPool);
vm.stopPrank();
vm.prank(lender1);
bytes32 poolId1 = lender.setPool(Pool({
lender: lender1,
loanToken: address(loanTokenWithFee),
collateralToken: address(collateralToken),
minLoanSize: 10 ether,
poolBalance: loanTokenForPool,
maxLoanRatio: 2 ether,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
}));
assertLt(loanTokenWithFee.balanceOf(address(lender)), loanTokenForPool);
assertEq(loanTokenWithFee.balanceOf(address(lender)), loanTokenForPool - (loanTokenForPool * loanTokenWithFee.FEE_BPS() / 10_000));
assertEq(lender.getPoolInfo(poolId1).poolBalance, loanTokenForPool);
}
}