Summary
A vulnerability exists in the VirtualToken's loan mechanism where miners can perform a targeted denial of service through strategic transaction inclusion. The contract implements a per-block loan limit but lacks protections against MEV-based transaction ordering exploitation.
function takeLoan(address to, uint256 amount) external payable onlyValidFactory { if (block.number > lastLoanBlock) { lastLoanBlock = block.number; loanedAmountThisBlock = 0; } require(loanedAmountThisBlock + amount <= MAX_LOAN_PER_BLOCK, "Loan limit per block exceeded"); loanedAmountThisBlock += amount; _mint(to, amount); _increaseDebt(to, amount); }
When a legitimate user submits a loan request, a miner can observe this transaction in the mempool and front-run it by including their own loan transactions first or other bad actors' transactions, deliberately consuming the MAX_LOAN_PER_BLOCK limit. This forces the target transaction to fail due to exceeding the block limit. The attack requires no special permissions beyond normal miner capabilities and can be used to consistently block legitimate users from accessing the loan facility.
Scenario
-
Alice, a legitimate user, attempts to take out a loan of 100 ETH by submitting a transaction with
takeLoan(alice, 100 ether)
to the mempool. -
A malicious miner Bob observes Alice's transaction and constructs a front-running transaction:
takeLoan(bob, 300 ether)
-
When mining the next block, Bob orders the transactions as follows:
- His transaction first, consuming the entire MAX_LOAN_PER_BLOCK limit of 300 ETH
- Alice's loan transaction, which fails with "Loan limit per block exceeded"
-
Bob can repeat this pattern for every block where Alice attempts to take a loan by:
- Monitoring the mempool for her transactions
- Front-running with his own transaction that consumes the full block limit
- Placing his transaction before hers in the block
The economic cost to the attacker is minimal since:
- The minted tokens can be immediately transferred and recovered
- The only cost is transaction gas fees
- If the attacker is a miner, they receive the gas fees back anyway
The impact is severe as it allows targeted denial of service against legitimate users, preventing them from accessing the loan facility. This is particularly damaging in scenarios where timely access to loans is crucial, such as during market volatility or liquidation events.
Recommended mitigation steps
Implement a first-come-first-served queue system for loan requests:
contract VirtualToken is ERC20, Ownable { struct LoanRequest { uint256 blockNumber; uint256 amount; bool executed; } mapping(address => LoanRequest) public loanRequests; function requestLoan(uint256 amount) external { require(loanRequests[msg.sender].blockNumber == 0 || loanRequests[msg.sender].executed, "Pending request exists"); loanRequests[msg.sender] = LoanRequest({ blockNumber: block.number, amount: amount, executed: false }); } function executeLoan(address borrower) external onlyFactory { LoanRequest storage request = loanRequests[borrower]; require(request.blockNumber > 0 && !request.executed, "Invalid request"); require(block.number > request.blockNumber, "Same block execution not allowed"); if (block.number > lastLoanBlock) { lastLoanBlock = block.number; loanedAmountThisBlock = 0; } require(loanedAmountThisBlock + request.amount <= MAX_LOAN_PER_BLOCK); loanedAmountThisBlock += request.amount; request.executed = true; _mint(borrower, request.amount); _increaseDebt(borrower, request.amount); } }
This solution enforces a one-block delay between loan request and execution, preventing miners from performing real-time transaction ordering attacks while maintaining the block-level loan limits. The queue system ensures fair access to the loan facility and makes targeted denial of service attacks significantly more difficult to execute.