Reentrancy in liquidate() Allows Complete Collateral Drainage
Description
Liquidators should partially liquidate underwater users, allowing them to recover remaining collateral after debt repayment.
The liquidate() function calls _redeem_collateral() (external transfer) before _burn_dsc() (debt update), enabling reentrancy via callback tokens to repeatedly liquidate before debt state updates.
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
// ... checks ...
@> self._redeem_collateral( // External call FIRST - reentrancy point
collateral,
token_amount_from_debt_covered + bonus_collateral,
user,
msg.sender,
)
@> self._burn_dsc(debt_to_cover, user, msg.sender) // Debt updated AFTER
Risk
Likelihood:
Admin whitelists ERC777 or upgradeable token with transfer hooks as collateral
Attacker deploys contract exploiting transfer callback to reenter liquidate()
Impact:
Underwater users lose 100% of collateral in single transaction instead of partial liquidation
Attacker repeatedly extracts collateral + 10% bonus until position fully drained
Proof of Concept
// Attacker contract receives collateral via ERC777 tokensReceived callback
function tokensReceived(address operator, address from, address to, uint256 amount, bytes data, bytes operatorData) external {
// Victim's debt NOT updated yet - health factor still broken
if (attackCount < 3) {
attackCount++;
dscEngine.liquidate(collateral, victim, debtAmount);
}
}
Victim has 200 collateral and 100 debt. Attacker calls liquidate() and reenters 3 times via callback, extracting 55 collateral each iteration (165 total) instead of a single 55 partial liquidation.
Recommended Mitigation
from snekmate.utils import reentrancy_guard
initializes: reentrancy_guard
@external
@nonreentrant
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
Add a @nonreentrant decorator to prevent reentrant calls during liquidation.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.