Summary
The liquidate function in dsc_engine.vy does not validate that the collateral parameter is one of the accepted collateral tokens. An attacker can pass an arbitrary address as the collateral parameter, which could lead to unexpected behavior or manipulation of the liquidation process.
Description
In the liquidate function, the collateral parameter is used directly without validation:
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
assert debt_to_cover > 0, "DSCEngine__NeedsMoreThanZero"
starting_user_health_factor: uint256 = self._health_factor(user)
assert (
starting_user_health_factor < MIN_HEALTH_FACTOR
), "DSCEngine__HealthFactorOk"
token_amount_from_debt_covered: uint256 = self._get_token_amount_from_usd(
collateral, debt_to_cover
)
bonus_collateral: uint256 = (
token_amount_from_debt_covered * LIQUIDATION_BONUS
)
self._redeem_collateral(
collateral,
token_amount_from_debt_covered + bonus_collateral,
user,
msg.sender,
)
# ... rest of function
The _get_token_amount_from_usd function uses self.token_address_to_price_feed[collateral] to get the price feed. If collateral is not in the mapping, it returns the zero address (default value for HashMap), which could cause issues in the oracle call.
Unlike _deposit_collateral which validates the token:
assert self.token_address_to_price_feed[token_collateral_address] != empty(
address
), "DSCEngine__TokenNotAllowed"
The liquidate function lacks this validation.
Risk
Severity: Medium
Likelihood: Medium
Impact: Medium
An attacker can:
Call liquidate with an arbitrary collateral address
If the address is not in token_address_to_price_feed, the oracle call may fail or return unexpected results
This could cause the liquidation to fail unexpectedly or behave in unintended ways
Could be used to grief legitimate liquidations or manipulate the protocol
Proof of Concept
import boa
from eth_utils import to_wei
def test_liquidate_with_invalid_collateral(dsce, weth, dsc, some_user, liquidator, eth_usd):
with boa.env.prank(some_user):
weth.approve(dsce.address, to_wei(10, "ether"))
dsce.deposit_collateral_and_mint_dsc(weth.address, to_wei(10, "ether"), to_wei(100, "ether"))
eth_usd.updateAnswer(18 * 10**8)
invalid_collateral = boa.env.generate_address()
with boa.env.prank(liquidator):
weth.approve(dsce.address, to_wei(1, "ether"))
dsce.deposit_collateral(weth.address, to_wei(1, "ether"))
dsc.approve(dsce.address, to_wei(100, "ether"))
try:
dsce.liquidate(invalid_collateral, some_user, to_wei(100, "ether"))
print("️ Liquidate accepted invalid collateral address!")
except Exception as e:
print(f"✅ Liquidate rejected invalid collateral: {e}")
Run: mox test -k test_liquidate_with_invalid_collateral
Recommended Mitigation
Add validation that the collateral parameter is one of the accepted collateral tokens:
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
assert debt_to_cover > 0, "DSCEngine__NeedsMoreThanZero"
assert self.token_address_to_price_feed[collateral] != empty(
address
), "DSCEngine__TokenNotAllowed"
starting_user_health_factor: uint256 = self._health_factor(user)
assert (
starting_user_health_factor < MIN_HEALTH_FACTOR
), "DSCEngine__HealthFactorOk"
# ... rest of function
Why This Works:
This ensures that only accepted collateral tokens can be used in liquidations, preventing unexpected behavior and maintaining protocol integrity.