Suppose there's an ongoing attack on the protocol. The admin calls pauseRedemptions (Redeemer.sol#L193-L199) to save user funds. However, since not all the redemption functions are paused, the attacker is able to steal user funds.
function authRedeem(
address u,
uint256 m,
address f,
address t,
uint256 a
)
external
authorized(IMarketPlace(marketPlace).token(u, m, 0))
returns (uint256)
{
// Get the principal token for the given market
IERC5095 pt = IERC5095(IMarketPlace(marketPlace).token(u, m, 0));
// Make sure the market has matured
uint256 maturity = pt.maturity();
if (block.timestamp < maturity) {
revert Exception(7, maturity, 0, address(0), address(0));
}
// Calculate the amount redeemed
uint256 redeemed = (a * holdings[u][m]) / pt.totalSupply();
// Update holdings of underlying
holdings[u][m] = holdings[u][m] - redeemed;
// Burn the user's principal tokens
pt.authBurn(f, a);
// Transfer the original underlying token back to the user
Safe.transfer(IERC20(u), t, redeemed);
emit Redeem(0, u, m, redeemed, msg.sender);
return a;
}
function autoRedeem(
address u,
uint256 m,
address[] calldata f
) external returns (uint256) {
// Get the principal token for the given market
IERC5095 pt = IERC5095(IMarketPlace(marketPlace).token(u, m, 0));
// Make sure the market has matured
uint256 maturity = pt.maturity();
if (block.timestamp < maturity) {
revert Exception(7, maturity, 0, address(0), address(0));
}
// Retrieve the underlying
IERC20 uToken = IERC20(u);
// Sum up the fees received by the caller
uint256 incentiveFee;
// Get the number of owners to loop through
uint256 length = f.length;
// Loop through the provided arrays and mature each individual position
for (uint256 i; i != length; ) {
// Fetch the allowance set by the holder of the principal tokens
uint256 allowance = uToken.allowance(f[i], address(this));
// Get the amount of tokens held by the owner
uint256 amount = pt.balanceOf(f[i]);
// Calculate how many tokens the user should receive
uint256 redeemed = (amount * holdings[u][m]) / pt.totalSupply();
// Calculate the fees to be received (currently .025%)
uint256 fee = redeemed / feenominator;
// Verify allowance
if (allowance < amount) {
revert Exception(20, allowance, amount, address(0), address(0));
}
// Burn the tokens from the user
pt.authBurn(f[i], amount);
// Update the holdings for this market
holdings[u][m] = holdings[u][m] - redeemed;
// Transfer the underlying to the user
Safe.transfer(uToken, f[i], redeemed - fee);
unchecked {
// Track the fees gained by the caller
incentiveFee += fee;
++i;
}
}
// Transfer the fee to the caller
Safe.transfer(uToken, msg.sender, incentiveFee);
return incentiveFee;
}
Tool used
Manual Review
Recommendation
Consider adding the missing unpaused modifier to all the functions that redeem iPT tokens.
Jeiwan
medium
iPT redeeming is possible when iPT redemptions are paused
Summary
iPT redeeming is possible when iPT redemptions are paused
Vulnerability Detail
The
authRedeem
andautoRedeem
functions don't check whether redeeming is paused or not (Redeemer.sol#L443-L477, Redeemer.sol#L485-L548).Impact
Suppose there's an ongoing attack on the protocol. The admin calls
pauseRedemptions
(Redeemer.sol#L193-L199) to save user funds. However, since not all the redemption functions are paused, the attacker is able to steal user funds.Code Snippet
Redeemer.sol#L443:
Redeemer.sol#L485:
Tool used
Manual Review
Recommendation
Consider adding the missing
unpaused
modifier to all the functions that redeem iPT tokens.Duplicate of #113