M-1: PriceWatcher weights can exceed the `1e6` scale
Summary:
Weights can potentially fall outside the expected scale, affecting the PriceWatcher consumers.
Description:
The implementation of getCurrentWeight() fetches the reUSD price from the Oracle and adjusts the scale to return a value within 1e6 precision.
153: function getCurrentWeight() public view returns (uint64) {
154: uint256 price = IReusdOracle(oracle).price();
155: uint256 weight = price > 1e18 ? 0 : 1e18 - price;
156: //our oracle has a floor that matches redemption fee
157: //e.g. it returns a minimum price of 0.9900 when there is a 1% redemption fee
158: //at this point a price of 0.99000 has a weight of 0.010000 or 1e16
159: //reduce precision to 1e6
160: return uint64(weight / 1e10);
161: }
The expected behavior here is that the Oracle price is clamped to $1 minus the redemption fee (1%), making the resulting weight fall within the [0, 0.01] range, which is then scaled down to the 1e6 region
However, two dependencies could potentially break this invariant
- The ReusdOracle
price()function multiplies the reUSD price (floored to the redemption fee) by the crvUSD price, potentially allowing the resulting value to be actually less than $0.99 - The redemption fee can be updated, breaking the floored price assumption.
Impact:
Medium. Users of the PriceWatcher contract consume the weight as a rate while assuming the returned scale is 1e6, potentially causing downstream calculations to exceed 100%.
Recommendation:
The weight calculation should use priceAsCrvusd(), which returns the floored price without the crvUSD price scaling. Additionally, ensure the redemption fee is not changed, as this might also break the calculations.
Developer Response:
Fixed in 4cc34fb.