Slippage Prevention In Redeem Functions: A Deep Dive
Hey guys! Today, we're diving deep into a critical vulnerability identified by the awesome Blue Piranha team in the redeem()
and redeemUnderlying()
functions. This issue could lead to users losing funds due to slippage. Let's break it down, keep it casual, and figure out how to keep our DeFi space safer.
The Heart of the Matter: No Slippage Protection
The core problem? The redeem()
and redeemUnderlying()
functions are missing a crucial check: a user-provided minimum slippage. What does this mean in plain English? Imagine you're trading tokens, and you expect to get a certain amount back. A front-running bot, a sneaky MEV (Miner Extractable Value) bot, can manipulate the exchangeRate()
between the time you sign your transaction and when it actually goes through. This manipulation can result in you receiving less underlying tokens than you anticipated. Ouch!
Think of it like this: you're ordering a pizza online, and by the time your order is processed, the price has gone up, but you're still getting the same pizza. Except in DeFi, this "price change" means you're losing actual value. This is especially concerning because the mint()
function already has a minAmountOut
parameter to protect users from this exact scenario. We need that same protection in redeem()
and redeemUnderlying()
to keep things fair for everyone.
function redeem(uint256 redeemTokens) external {
_redeem(msg.sender, redeemTokens, true);
}
This piece of code, specifically, highlights the issue. There's no consideration for slippage, leaving users vulnerable to front-running attacks. This is a serious concern because, without slippage protection, users are essentially playing a rigged game where they can lose funds without even realizing it. The absence of a slippage parameter is like leaving the door open for malicious actors to exploit the system, leading to financial losses for unsuspecting users. It’s crucial to address this vulnerability to ensure the integrity and trustworthiness of the DeFi platform.
Diving Deeper: The Root Cause
The root cause is simple yet impactful: the lack of a slippage parameter in the redeem()
and redeemUnderlying()
functions.
redeem()
function: https://github.com/sherlock-audit/2025-07-malda/blob/798d00b879b8412ca4049ba09dba5ae42464cfe7/malda-lending/src/mToken/mErc20.sol#L99C1-L101C6redeemUnderlying()
function: https://github.com/sherlock-audit/2025-07-malda/blob/798d00b879b8412ca4049ba09dba5ae42464cfe7/malda-lending/src/mToken/mErc20.sol#L106
These links point directly to the vulnerable code, making it clear where the problem lies. The absence of a mechanism to account for potential fluctuations in the exchange rate opens the door for malicious actors to manipulate the system to their advantage, potentially leading to significant financial losses for users. It’s a critical oversight that needs to be addressed to ensure the security and fairness of the platform.
Understanding the Pre-Conditions
To make this vulnerability exploitable, we need to consider both internal and external factors.
External Pre-conditions
- Manipulation of Protocol State: An attacker needs the ability to manipulate the protocol state, such as depositing/redeeming or repaying/borrowing, to influence the
exchangeRate
before theredeem
transaction is executed. This requires the attacker to have sufficient capital and control over the market dynamics. - Low-Cost L2 and MEV Environment: A low-cost Layer 2 (L2) environment and the presence of MEV bots make this attack economically viable. L2 solutions offer faster transaction times and lower fees, making it easier and cheaper for attackers to execute front-running attacks. MEV bots are constantly scanning the mempool for profitable opportunities, and they can quickly react to user transactions and manipulate the exchange rate.
Internal Pre-conditions
- There are no specific internal pre-conditions listed in this report, which further emphasizes the straightforward nature of the vulnerability arising directly from the absence of slippage protection. This lack of internal safeguards makes the system more susceptible to external manipulation, as there are no built-in mechanisms to mitigate the risk.
Attack Path: How It All Goes Down
Let's walk through how an attacker can exploit this vulnerability in both redeem()
and redeemUnderlying()
functions.
redeem()
Attack
- User Prepares
redeemTokens
Transaction: A user initiates a transaction to redeem tokens, expecting to receive a certain amount (X
) of the underlying asset. - Attacker Observes Mempool and Frontruns: The attacker monitors the mempool for this transaction and frontruns it by executing a transaction that reduces the
exchangeRate
. This can be achieved by performing actions that decrease the value of the underlying asset or increase the value of the mTokens. - User Transaction Executes with Worse
exchangeRate
: The user's transaction is executed, but due to the manipulatedexchangeRate
, they receive less underlying tokens than initially expected. This difference in the amount received represents the user's loss. - Large-Volume Cases Cause Significant Value Loss: For large-volume transactions, even a small change in the
exchangeRate
can result in a significant loss of value for the user. This makes the vulnerability particularly impactful for users with substantial holdings.
redeemUnderlying()
Attack
- User Submits
redeemUnderlying(amount)
: A user submits a transaction to redeem a specific amount of the underlying asset, expecting to burn a certain amount (X
) of mTokens. - Attacker Observes Mempool and Frontruns: The attacker observes the mempool for this transaction and frontruns it with a transaction that reduces the
exchangeRate
. This makes mTokens appear more valuable relative to the underlying asset. - User Transaction Executes with Increased
redeemTokens
: When the user's transaction executes, the amount of mTokens required to be burned (redeemTokens
) is higher than the intended amount (X
) due to the reducedexchangeRate
. - Contract Burns Larger
redeemTokens
from User: The contract burns the larger amount ofredeemTokens
from the user's account, resulting in the user losing extra mTokens. This is a direct financial loss for the user, as they are burning more tokens than they anticipated.
These attack paths demonstrate how the absence of slippage protection can be exploited to manipulate the exchangeRate
and cause financial harm to users. The front-running attacks can be executed quickly and efficiently, especially in environments with low transaction costs and the presence of MEV bots. This underscores the need for robust slippage protection mechanisms in the redeem()
and redeemUnderlying()
functions.
Impact: Silent Losses
The impact is clear: loss of funds. Users receive less underlying tokens than they expected, and it's a silent loss because there's no error message or alert. They simply get fewer tokens, which can be incredibly frustrating and damaging to trust in the platform.
Imagine you're redeeming a large amount, expecting a significant return, only to find out you've lost a chunk of it due to this vulnerability. It's not just about the money; it's about the confidence in the system. Silent losses erode trust and can drive users away from the platform. This is why it's crucial to address this issue and implement robust slippage protection mechanisms.
Proof of Concept (PoC)
Let's solidify our understanding with a practical example of how these attacks can play out.
redeem()
PoC
- User Prepares
redeemTokens
Transaction: Alice prepares a transaction to redeem 100 mTokens, expecting to receive 10 ETH based on the currentexchangeRate
. - Attacker Observes Mempool and Frontruns: A malicious actor, Bob, sees Alice's transaction in the mempool. Bob quickly executes a transaction that manipulates the
exchangeRate
, making it less favorable for Alice. For instance, Bob could borrow a large amount of ETH and sell it, temporarily driving down the price of ETH and, consequently, theexchangeRate
. - User Transaction Executes with Worse
exchangeRate
: Alice's transaction executes, but due to Bob's manipulation, theexchangeRate
has decreased. Instead of receiving 10 ETH, Alice now receives only 9.5 ETH. - Value Loss for User: Alice has lost 0.5 ETH due to the manipulated
exchangeRate
. This loss occurs silently, without any explicit error message or warning.
redeemUnderlying()
PoC
- User Submits
redeemUnderlying(amount)
: Charlie submits a transaction to redeem 10 ETH, expecting to burn 100 mTokens based on the currentexchangeRate
. - Attacker Observes Mempool and Frontruns: Bob sees Charlie's transaction and frontruns it by manipulating the
exchangeRate
. Bob could deposit a large amount of ETH into the protocol, increasing the value of ETH relative to mTokens and reducing theexchangeRate
. - User Transaction Executes with Increased
redeemTokens
: When Charlie's transaction executes, the amount of mTokens required to be burned is higher than expected. Instead of burning 100 mTokens, Charlie is now required to burn 105 mTokens to redeem 10 ETH. - Contract Burns Larger
redeemTokens
from User: The contract burns 105 mTokens from Charlie's account, resulting in Charlie losing 5 extra mTokens. This is a direct financial loss for Charlie, as they are burning more tokens than they anticipated.
These PoCs clearly illustrate how the absence of slippage protection can lead to financial losses for users. The attackers can exploit the vulnerability by manipulating the exchangeRate
and front-running user transactions. This underscores the urgent need for mitigation measures to protect users from these types of attacks.
Mitigation: Let's Fix This!
The solution is straightforward: add an explicit minAmount
parameter in the redeem()
function, similar to what we already have in the mint()
function. This gives users control over the minimum amount of underlying tokens they're willing to accept.
function _redeem(
address user,
uint256 redeemTokens,
+ uint256 minAmount,
bool doTransfer
) internal nonReentrant returns (uint256 underlyingAmount) {
_accrueInterest();
underlyingAmount = __redeem(payable(user), redeemTokens, 0, doTransfer);
+ require(underlyingAmount >= minAmount, "error: minAmount");
}
This simple addition can make a world of difference in protecting users from slippage attacks. By allowing users to specify the minimum amount they're willing to accept, we ensure that their transactions will revert if the exchangeRate
fluctuates too much. This gives users peace of mind and prevents them from silently losing funds.
We should also consider adding a maxBurnTokens
parameter for redeemUnderlying()
. Here's a sample code snippet:
function redeemUnderlying(uint256 redeemAmount, uint256 maxBurnTokens, uint256 deadline) external {
_accrueInterest();
uint256 burn = divCeil(redeemAmount, _exchangeRateStored());
require(burn <= maxBurnTokens, "mt_MaxBurnExceeded()");
__redeem(payable(msg.sender), burn, 0, true);
}
This addition allows users to specify the maximum amount of mTokens they are willing to burn, providing an extra layer of protection against adverse exchangeRate
fluctuations. The deadline
parameter adds a time constraint, further mitigating the risk of front-running attacks. By implementing these measures, we can significantly enhance the security and user experience of the platform.
Conclusion: Protecting Our Users
This vulnerability highlights the importance of thorough auditing and proactive security measures in DeFi. By adding slippage protection to redeem()
and redeemUnderlying()
, we can safeguard users from potential losses and build a more trustworthy and robust platform. Let's keep building a safer DeFi space, one line of code at a time! Remember, security is not just a feature; it's a fundamental requirement for any successful DeFi platform.