DeFiHardhat
35,000 USDC
View results
Submission Details
Severity: low
Invalid

Some functions of `LibUnripeConvert` might return less amount than `minAmountOut` due to the precision loss.

Summary

convertLPToBeans() and convertBeansToLP() check the minAmountOut requirement after converting to the underlying amount and the final result might be less than the original minimum amount.

Vulnerability Details

Let me explain with convertLPToBeans().

function convertLPToBeans(bytes memory convertData)
internal
returns (
address tokenOut,
address tokenIn,
uint256 amountOut,
uint256 amountIn
)
{
tokenOut = C.UNRIPE_BEAN;
tokenIn = C.UNRIPE_LP;
(uint256 lp, uint256 minBeans) = convertData.basicConvert(); //@audit original min - minBeans
uint256 minAmountOut = LibUnripe //@audit convert minBeans to minAmountOut(in underlying)
.unripeToUnderlying(tokenOut, minBeans, IBean(C.UNRIPE_BEAN).totalSupply())
.mul(LibUnripe.percentLPRecapped())
.div(LibUnripe.percentBeansRecapped());
(
uint256 outUnderlyingAmount, //@audit outUnderlyingAmount >= minAmountOut
uint256 inUnderlyingAmount
) = LibWellConvert._wellRemoveLiquidityTowardsPeg( //@audit swap with minAmountOut
LibUnripe.unripeToUnderlying(tokenIn, lp, IBean(C.UNRIPE_LP).totalSupply()),
minAmountOut,
LibBarnRaise.getBarnRaiseWell()
);
amountIn = LibUnripe.underlyingToUnripe(tokenIn, inUnderlyingAmount);
LibUnripe.removeUnderlying(tokenIn, inUnderlyingAmount);
IBean(tokenIn).burn(amountIn);
amountOut = LibUnripe //@audit final amountOut in Beans, amountOut might be less than minBeans
.underlyingToUnripe(tokenOut, outUnderlyingAmount)
.mul(LibUnripe.percentBeansRecapped())
.div(LibUnripe.percentLPRecapped());
LibUnripe.addUnderlying(tokenOut, outUnderlyingAmount);
IBean(tokenOut).mint(address(this), amountOut);
}

It converts minBeans to minAmountOut in underlying and uses it during the swap. After that, the final amountOut is calculated back from outUnderlyingAmount.

A rounding loss is expected while converting minBeans to minAmountOut, and outUnderlyingAmount to amountOut.

So if the swap result(outUnderlyingAmount) is exactly the same as minAmountOut, amountOut will be less than minBeans after the conversion.

Impact

In convertLPToBeans() and convertBeansToLP(), the slippage protection wouldn't work as intended.

Tools Used

Manual Review

Recommendations

We should validate the slippage again as a final step.

function convertLPToBeans(bytes memory convertData)
internal
returns (
address tokenOut,
address tokenIn,
uint256 amountOut,
uint256 amountIn
)
{
tokenOut = C.UNRIPE_BEAN;
tokenIn = C.UNRIPE_LP;
(uint256 lp, uint256 minBeans) = convertData.basicConvert();
uint256 minAmountOut = LibUnripe
.unripeToUnderlying(tokenOut, minBeans, IBean(C.UNRIPE_BEAN).totalSupply())
.mul(LibUnripe.percentLPRecapped())
.div(LibUnripe.percentBeansRecapped());
(
uint256 outUnderlyingAmount,
uint256 inUnderlyingAmount
) = LibWellConvert._wellRemoveLiquidityTowardsPeg(
LibUnripe.unripeToUnderlying(tokenIn, lp, IBean(C.UNRIPE_LP).totalSupply()),
minAmountOut,
LibBarnRaise.getBarnRaiseWell()
);
amountIn = LibUnripe.underlyingToUnripe(tokenIn, inUnderlyingAmount);
LibUnripe.removeUnderlying(tokenIn, inUnderlyingAmount);
IBean(tokenIn).burn(amountIn);
amountOut = LibUnripe
.underlyingToUnripe(tokenOut, outUnderlyingAmount)
.mul(LibUnripe.percentBeansRecapped())
.div(LibUnripe.percentLPRecapped());
+ require(amountOut >= minBeans, "too less Beans");
LibUnripe.addUnderlying(tokenOut, outUnderlyingAmount);
IBean(tokenOut).mint(address(this), amountOut);
}
Updates

Lead Judging Commences

giovannidisiena Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Precision loss

Support

FAQs

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