The preset PRECISION of 1e18 in DSCEngine contract is not compatible with all collateral tokens (eg. wBTC), potentially causing loss of fund
There are two functions within the contract affected by this PRECISION configuration.
[1]. In DSCEngine.sol:347, the getTokenAmountFromUsd function in DSCEngine.sol incorrectly calculates the expected wBTC amount where the returned amount is not properly derived due to a hardcoded precision value. Specifically, the precision value PRECISION is preset as 1e18, which works fine with wETH but not with wBTC. The wBTC.decimals function returns only 8 as can be seen here: https://etherscan.io/token/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599#readContract#F4.
This results in the returned wBTC token amount being scaled up by 1e10 which drastically overvalues the usdAmountInWei.
The vulnerable code snippet is as follows:
[2]. The getUsdValue function also wrongly computes the value in USD of wBTC token due to the hardcoded PRECISION which makes the returned USD value 1e10 times smaller than expected. As a result, the user's wBTC collateral becomes much cheaper than its real value rendering an under-collateralized situation.
The vulnerable code snippet is as follows:
From the reasoning from [1] & [2], the user's wBTC collateral could be drained by just a tiny amount of the debt-cover DSC.
For instance, a user, who has a health factor less than 200% collateralized by wETH and is facing the risk of liquidation.
Bad User: $1400 in ETH, $1000 DSC -> health factor: 1400e18 * 50% * 1e18 / 1000e18 = 0.7e18
He decides to increase the health factor by depositing a significant amount of wBTC.
Bad User: +1 BTC ~ 29500e18 in DSC -> under-collateralized issue causing BTC value to drop 1e10 times -> 29500e8 wei in DSC accounted -> new health factor: (1400e18 + 29500e8) * 50% * 1e18 / 1000e18 = 0.700000001475e18 (almost unchanged)
Since the heath factor remains unhealthy, a malicious actor carries out the exploit by calling DSCEngine.liquidate on a user's wBTC collateral with a dust amount of DSC tokens.
Bad User: $1400 in ETH, 1 BTC, $1000 - $debtToCover DSC
--> due to the overvalued usdAmountInWei, $debtToCover value could be manifested to match the totalCollateralToRedeem value of 1 BTC.
The exact numbers could be deduced from above function:
startingUserHealthFactor = 0.700000001475e18 (as computed above)
tokenAmountFromDebtCovered = (debtToCoverInWei * 1e18) / (29500e8 * 1e10) = debtToCoverInWei / 29500
bonusCollateral = debtToCoverInWei * 10% / 29500
totalCollateralToRedeem = debtToCoverInWei * 110% / 29500 = 1e8 BTC
=> debtToCoverInWei = 29500 * 1e8 / 110% = 268e10 DSC
endingUserHealthFactor = 1400e18 * 50% * 1e18 / (1000e18 - 268e10) = 0.700000001876e18
Since endingUserHealthFactor > startingUserHealthFactor and _revertIfHealthFactorIsBroken(msg.sender) checks for attacker's health factor which is irrelevant, the attacker can successfully liquidate user's debt. This proved that the actor caused 1 BTC in loss for user with just 268e10 wei DSC or $0.00000268 DSC.
Manual Review
To properly scale the collateral amount, it is recommended to call IERC20(token).decimals to obtain the token's decimal value and adjust the precision accordingly.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.