Summary
Cap exceed penalty was introduced in Beanstalk 3.
Convert can't be performed if penalty should be applied. Documentation states that any deposit can be converted to the same deposit (it increases deposit's BDV) - it's called Lambda convert. However according to this new exceed cap penalty Lambda converts can't be performed most of the times.
Vulnerability Details
After conversion is executed it checks that penalty is 0:
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/beanstalk/silo/ConvertFacet.sol#L90
function convert(
bytes calldata convertData,
int96[] memory stems,
uint256[] memory amounts
)
external
payable
fundsSafu
noSupplyChange
nonReentrant
returns (int96 toStem, uint256 fromAmount, uint256 toAmount, uint256 fromBdv, uint256 toBdv)
{
address toToken;
address fromToken;
LibPipelineConvert.PipelineConvertData memory pipeData = LibPipelineConvert.getConvertState(
convertData
);
@> (toToken, fromToken, toAmount, fromAmount) = LibConvert.convert(convertData);
...
@> LibPipelineConvert.checkForValidConvertAndUpdateConvertCapacity(
pipeData,
convertData,
fromToken,
toToken,
fromBdv
);
...
}
function checkForValidConvertAndUpdateConvertCapacity(
PipelineConvertData memory pipeData,
bytes calldata convertData,
address fromToken,
address toToken,
uint256 fromBdv
) public {
...
require(
@> pipeData.stalkPenaltyBdv == 0,
"Convert: Penalty would be applied to this convert, use pipeline convert"
);
}
}
When we dive into that penalty logic, we stop here. As you can see it cpplies penalty if overallConvertCapacityUsed exceeds overallCappedDeltaB:
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/libraries/Convert/LibConvert.sol#L284
function calculateConvertCapacityPenalty(
uint256 overallCappedDeltaB,
uint256 overallAmountInDirectionOfPeg,
address inputToken,
uint256 inputTokenAmountInDirectionOfPeg,
address outputToken,
uint256 outputTokenAmountInDirectionOfPeg
) internal view returns (uint256 cumulativePenalty, PenaltyData memory pdCapacity) {
AppStorage storage s = LibAppStorage.diamondStorage();
ConvertCapacity storage convertCap = s.sys.convertCapacity[block.number];
if (convertCap.overallConvertCapacityUsed >= overallCappedDeltaB) {
cumulativePenalty = overallAmountInDirectionOfPeg;
} else if (
overallAmountInDirectionOfPeg >
overallCappedDeltaB.sub(convertCap.overallConvertCapacityUsed)
) {
cumulativePenalty =
overallAmountInDirectionOfPeg -
overallCappedDeltaB.sub(convertCap.overallConvertCapacityUsed);
}
...
}
Impact
Lambda Converts cannot be performed due to cap exceed penalty. It breaks invariant stated in docs:
*Any token on the Deposit Whitelist can be Converted to the same token in order to allow Stalkholders to update the BDV of their LP tokens when their BDV increases due to impermanent loss.
Tools Used
Manual Review
Recommendations
Refactor Convert logic to exclude cap exceed penalty from Lambda Convert.