DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

Slippage in `LibWellBdv.bdv()` overestimates Well LP price by at most 0.025%

Summary

LibWellBdv.bdv() is used to calculate BDV of Well LP token. It's calculated by removing 1 Bean from reserve and observing how much LP totalSupply was reduced. There is WELL_MINIMUM_BEAN_BALANCE = 1000e6 to not allow pricing with low reserves.

However at the minimum reserve LP's BDV will be overestimated by 0.025% as shown later.

Vulnerability Details

Here is formula used:

function bdv(address well, uint amount) internal view returns (uint _bdv) {
...
uint lpTokenSupplyBefore = IWellFunction(wellFunction.target).calcLpTokenSupply(
reserves,
wellFunction.data
);
reserves[beanIndex] = reserves[beanIndex].sub(BEAN_UNIT); // remove one Bean
uint deltaLPTokenSupply = lpTokenSupplyBefore.sub(
IWellFunction(wellFunction.target).calcLpTokenSupply(reserves, wellFunction.data)
);
_bdv = amount.mul(BEAN_UNIT).div(deltaLPTokenSupply);
}

Let's take a test scenario where Well Function is ConstantProduct2, Bean reserve is 1000e6, USDC reserve is 1000e6

  1. According to formula LpSupply is sqrt(1000e6 * 1000e6 * 1e12) = 1000e12

  2. Actual LP price is idealPrice = LpSupply / BdvOfReserves = 1000e12 / 2000 = 0.5e12. It means 0.5e12 Well LP is worth 1 Bean

  3. Let's calculate price using formula in bdv():

actualPrice = lpSupplyBefore - lpSupplyAfter

lpSupplyAfter = sqrt((1000e6 - 1e6) * 1000e6 * 1e12) = 999.49987e12

actualPrice = 1000e12 - 999.49987e12 = 0.50013e12

  1. Slippage is slippage = actualPrice / idealPrice - 1 = 0.00026, but it was simplified during calculations so actual slippage is 0.025%.

Impact

In the worst scenario Well LP price will be overestimated by 0.025%. Therefore Silo will overestimate user's deposit of Well LP and grant more Stalk than it deserves impacting other users.

Tools Used

Manual Review

Recommendations

Refactor formula to reduce 1 wei Bean instead of 1e6 Bean to reduce slippage by 1e6 times:

function bdv(address well, uint amount) internal view returns (uint _bdv) {
...
uint lpTokenSupplyBefore = IWellFunction(wellFunction.target).calcLpTokenSupply(
reserves,
wellFunction.data
);
- reserves[beanIndex] = reserves[beanIndex].sub(BEAN_UNIT); // remove one Bean
+ reserves[beanIndex] = reserves[beanIndex].sub(1);
uint deltaLPTokenSupply = lpTokenSupplyBefore.sub(
IWellFunction(wellFunction.target).calcLpTokenSupply(reserves, wellFunction.data)
);
- _bdv = amount.mul(BEAN_UNIT).div(deltaLPTokenSupply);
+ _bdv = amount.mul(BEAN_UNIT).div(deltaLPTokenSupply * BEAN_UNIT);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational/Gas

Invalid as per docs https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

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