DeFiHardhat
21,000 USDC
View results
Submission Details
Severity: medium
Invalid

Lack of Slippage Protections in `UnripeFacet::chop` and `ConvertFacet::convert` functions

Summary

In the chop and convert functions lacks slippage protection, which can result in users receiving less value than anticipated during conversions.

Vulnerability Details

In the chop and convert functions there are no checks to ensure that the conversion results in a minimum expected amount of toAmount. This means users might receive less than expected due to slippage. Without slippage protection, the contract is vulnerable to front-running and other issues that can lead to significant losses for users.

function chop(
address unripeToken,
uint256 amount,
LibTransfer.From fromMode,
LibTransfer.To toMode
) external payable nonReentrant returns (uint256) {
// burn the token from the msg.sender address
uint256 supply = IBean(unripeToken).totalSupply();
amount = LibTransfer.burnToken(IBean(unripeToken), amount, msg.sender, fromMode);
// get ripe address and ripe amount
(address underlyingToken, uint256 underlyingAmount) = LibChop.chop(
unripeToken,
amount,
supply
);
// send the corresponding amount of ripe token to the user address
require(underlyingAmount > 0, "Chop: no underlying");
IERC20(underlyingToken).sendToken(underlyingAmount, msg.sender, toMode);
// emit the event
emit Chop(msg.sender, unripeToken, amount, underlyingAmount);
return underlyingAmount;
}
function convert(
bytes calldata convertData,
int96[] memory stems,
uint256[] memory amounts
)
external
payable
nonReentrant
returns (int96 toStem, uint256 fromAmount, uint256 toAmount, uint256 fromBdv, uint256 toBdv)
{
uint256 grownStalk;
LibConvert.convertParams memory cp = LibConvert.convert(convertData);
if (cp.decreaseBDV) {require(stems.length == 1 && amounts.length == 1, "Convert: DecreaseBDV only supports updating one deposit.");}
require(cp.fromAmount > 0, "Convert: From amount is 0.");
// Replace account with msg.sender if no account is specified.
if(cp.account == address(0)) cp.account = msg.sender;
LibSilo._mow(cp.account, cp.fromToken);
// If the fromToken and toToken are different, mow the toToken as well.
if (cp.fromToken != cp.toToken) LibSilo._mow(cp.account, cp.toToken);
// Withdraw the tokens from the deposit.
(grownStalk, fromBdv) = _withdrawTokens(
cp.fromToken,
stems,
amounts,
cp.fromAmount,
cp.account
);
// Calculate the bdv of the new deposit.
uint256 newBdv = LibTokenSilo.beanDenominatedValue(cp.toToken, cp.toAmount);
// If `decreaseBDV` flag is not enabled, set toBDV to the max of the two bdvs.
toBdv = (newBdv > fromBdv || cp.decreaseBDV) ? newBdv : fromBdv;
toStem = _depositTokensForConvert(cp.toToken, cp.toAmount, toBdv, grownStalk, cp.account);
// Retrieve the rest of return parameters from the convert struct.
toAmount = cp.toAmount;
fromAmount = cp.fromAmount;
emit Convert(cp.account, cp.fromToken, cp.toToken, cp.fromAmount, cp.toAmount);
}

The functions does not include a parameter to define the minimum acceptable toAmount for each conversion.

Impact

  • Front-running Attacks: The contract is vulnerable to front-running, where attackers can manipulate prices to their advantage.

Tools Used

Manual Review

Recommendations

  • Implement Slippage Protection:

    • Add a minToAmount parameter to the convert function signature.

    • Ensure the resulting toAmount meets the minToAmount requirement, and revert the transaction if it does not.

Updates

Lead Judging Commences

giovannidisiena Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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