DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: low
Valid

Loss of precision in `twapPrice`

Summary

When the Chainlink price feed has invalidFetchData or priceDeviation it will call the Uniswap WETH-USDC pool observe() function as a backup, but in its conversion to Ether, it has significant precision loss.

Vulnerability Details

The / division operator is used instead of .div() from PRBMathHelper, which leads to roundings to zero which diverts the stored price from the real price.

uint256 twapPriceInEther = (twapPrice / Constants.DECIMAL_USDC) * 1 ether;

POC

First, uncomment the console import.
Then, add this after line 85 of contracts/libraries/LibOracle.sol:

uint256 twapPriceInEther = (twapPrice / Constants.DECIMAL_USDC) * 1 ether; // @audit precision loss
+ console.log("Current"); // 1_902_000_000_000_000_000_000 -> 90% ETH price drop -> 190_200_000_000_000_000_0000
+ console.log(twapPriceInEther);
+ uint256 twapPriceInEtherNoPrecissionLoss = (twapPrice.div(Constants.DECIMAL_USDC)) * 10 /*<==> (1 ether / 1e17) to remove leading zeroes resulting of .div() precision scaling */; // no precission loss
+ console.log("No precission loss");
+ console.log(twapPriceInEtherNoPrecissionLoss); // 1_902_501_929_000_000_000_000 -> 90% ETH price drop -> 190_260_455_000_000_000_000

You can run this numbers with:
FOUNDRY_PROFILE=fork forge test --mt testFork_MultiAsset -vv

Impact

Ranging from 0.04% at current market price, which would be magnified to 0.4% if Eth price dropped by an order of magnitude.
At current market price -> (1_902_501_929_000_000_000_000 / 1_902_000_000_000_000_000_000) * 100 = 0.026%
At 90% below market price -> (190_260_455_000_000_000_000 / 190_200_000_000_000_000_000) * 100 = 0.032%
Percentages similar to AMM slippage, which would be unattractive in an Orderbook like Ditto is since one of its promises is being slippage free.

Tools Used

Manual review and Chisel

Recommendations

- uint256 twapPriceInEther = (twapPrice / Constants.DECIMAL_USDC) * 1 ether;
+ uint256 twapPriceInEther = (twapPrice.div(Constants.DECIMAL_USDC) / 1e17) * 1 ether; // division by 1e17 to remove leading zeroes resulting of .div() precision scaling
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-535

Support

FAQs

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