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.