sherlock-audit / 2022-10-illuminate-judging

3 stars 0 forks source link

Jeiwan - iPT redeeming is possible when iPT redemptions are paused #221

Closed sherlock-admin closed 1 year ago

sherlock-admin commented 1 year ago

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 and autoRedeem 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:

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;
}

Redeemer.sol#L485:

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.

Duplicate of #113