A malicious user can create empty loans with the loan offer which is fully fulfilled
Summary
The miss checking in a fully fulfilled loan allows a malicious user to create limitless empty loans, whose lender is a normal user. These empty loans can spam the frontend protocol if mishandled and mislead the unconscious lender.
Root Cause
In validation logic, it only includes checks _assertFulfillAmountNotTooLow() and _assertFulfillAmountNotTooHigh() but not check whether the loan is already fully fulfilled.
Zero fulfillAmount value can pass the check when a loan is fully fulfilled and used to create empty loans.
Internal pre-conditions
Any valid and fully fulfilled loans.
External pre-conditions
The frontend protocol has not taken this kind of spam attack into consideration.
Attack Path
The malicious user calls acceptLoanOffer() with zero fulfillment and just needs to pay gas fee.
Impact
The protocal will suffer from spam if empty loans are mishandled frontend. The LoanStatus of an empty loan is Active unlike others with zero debt. It can mislead the lender of empty loans to try call() and seize() but get nothing.
PoC
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {IPredictDotLoan} from "../../contracts/interfaces/IPredictDotLoan.sol";
import {PredictDotLoan_Test} from "./PredictDotLoan.t.sol";
import {MockEIP1271Wallet} from "../mock/MockEIP1271Wallet.sol";
import {MockUmaCtfAdapter} from "../mock/MockUmaCtfAdapter.sol";
contract PredictDotLoan_Bug_Test is PredictDotLoan_Test {
function test_Bug() public {
wallet = new MockEIP1271Wallet(lender);
vm.label(address(wallet), "Lender's EIP-1271 Wallet");
mockERC20.mint(address(wallet), LOAN_AMOUNT);
vm.prank(address(wallet));
mockERC20.approve(address(predictDotLoan), LOAN_AMOUNT);
IPredictDotLoan.Proposal memory proposal = _generateLoanOffer(IPredictDotLoan.QuestionType.Binary);
proposal.from = address(wallet);
proposal.signature = _signProposal(proposal);
_assertBalanceAndFulfillmentBeforeExecution(borrower, lender, proposal);
_assertProposalAcceptedEmitted(predictDotLoan.hashProposal(proposal), borrower, address(wallet));
vm.prank(borrower);
predictDotLoan.acceptLoanOffer(proposal, proposal.loanAmount);
// A malicous user "borrower2" with even no loan or CTF tokens.
vm.startPrank(borrower2);
mockCTF.setApprovalForAll(address(predictDotLoan), true);
predictDotLoan.acceptLoanOffer(proposal, 0);
vm.stopPrank();
}
}
Mitigation
Check whether a loan is fully fulfilled in the validation logic:
function _assertFulfillAmountNotTooHigh(
uint256 fulfillAmount,
uint256 fulfilledAmount,
uint256 loanAmount
) private pure {
- if (fulfilledAmount + fulfillAmount > loanAmount) {
+ if (fulfilledAmount + fulfillAmount > loanAmount || fulfilledAmount == loanAmount) {
revert FulfillAmountTooHigh();
}
}
Hot Carrot Jaguar
Medium
A malicious user can create empty loans with the loan offer which is fully fulfilled
Summary
The miss checking in a fully fulfilled loan allows a malicious user to create limitless empty loans, whose
lender
is a normal user. These empty loans can spam the frontend protocol if mishandled and mislead the unconsciouslender
.Root Cause
In validation logic, it only includes checks
_assertFulfillAmountNotTooLow()
and_assertFulfillAmountNotTooHigh()
but not check whether the loan is already fully fulfilled.https://github.com/sherlock-audit/2024-09-predict-fun/blob/main/predict-dot-loan/contracts/PredictDotLoan.sol#L1269-L1289
Zero
fulfillAmount
value can pass the check when a loan is fully fulfilled and used to create empty loans.Internal pre-conditions
Any valid and fully fulfilled loans.
External pre-conditions
The frontend protocol has not taken this kind of spam attack into consideration.
Attack Path
The malicious user calls
acceptLoanOffer()
with zerofulfillment
and just needs to pay gas fee.Impact
The protocal will suffer from spam if empty loans are mishandled frontend. The
LoanStatus
of an empty loan isActive
unlike others with zero debt. It can mislead the lender of empty loans to trycall()
andseize()
but get nothing.PoC
Mitigation
Check whether a loan is fully fulfilled in the validation logic: