DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: medium
Invalid

Chainlink price feed decimals assumption allows malicious user to unfairly mint arbitrarily many tokens

Assuming Chainlink price feed decimals is dangerous

Summary

The protocol assumes that the Chainlink price feed will always have 8 decimals, which is not necessarily the case, even when only using USD price feeds.

Vulnerability Details

LibOracle#getOraclePrice is used to fetch the price of an asset using Chainlink price feeds. During the process, the returned price value is assumed to have 8 decimals, and the value is multiplied by 10**10 to scale it up to 18 decimals precision:

File: contracts\libraries\LibOracle.sol
38: //@dev multiply base oracle by 10**10 to give it 18 decimals of precision
39: uint256 basePriceInEth = basePrice > 0
40: ? uint256(basePrice * Constants.BASE_ORACLE_DECIMALS).inv()
41: : 0;
42: basePriceInEth = baseOracleCircuitBreaker(
43: protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth
44: );
45: return basePriceInEth;

The issue here is that the price feed may not have exactly 8 decimals. Despite most Chainlink USD feeds using 8 decimals, this is not guaranteed. For example, the AMPL/USD feed has 18 decimals, and there is no guarantee that USD price feeds added in the future would conform to the 8 decimal standard.

Impact

The price returned by LibOracle#getOraclePrice may be too large by 10 orders of magnitude depending on the asset, in which case the protocol would vastly overestimate the amount of collateral provided by a user, allowing them to mint up to 10**10 times as many stablecoins as they should be able to, severely affecting the collateralization and effectively stealing value from other stablecoin holders.

Tools Used

Manual review

Recommendations

Instead of using a constant BASE_ORACLE_DECIMALS, call decimals() on the oracle to ensure that the price is being scaled up appropriately.

if (oracle == baseOracle) {
//@dev multiply base oracle by 10**10 to give it 18 decimals of precision
uint256 basePriceInEth = basePrice > 0
- ? uint256(basePrice * Constants.BASE_ORACLE_DECIMALS).inv()
+ ? uint256(basePrice * 10 ** (18 - oracle.decimals())).inv()
: 0;
basePriceInEth = baseOracleCircuitBreaker(
protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth
);
return basePriceInEth;
Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issues

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.