sherlock-audit / 2024-01-napier-judging

8 stars 5 forks source link

dany.armstrong90 - Yield can be distributed when tranche is paused. #115

Closed sherlock-admin closed 5 months ago

sherlock-admin commented 5 months ago

dany.armstrong90

medium

Yield can be distributed when tranche is paused.

Summary

Tranche.sol#issue, updateUnclaimedYield, collect functions which process yields are all implemented with whenNotPaused modifier. But the whenNotPaused modifier is not applied to redeemWithYT function.

Vulnerability Detail

Tranche.sol#redeemWithYT function is as follows.

    function redeemWithYT(address from, address to, uint256 pyAmount) external nonReentrant returns (uint256) {
        uint256 _lscale = lscales[from];
        uint256 accruedInTarget = unclaimedYields[from];

        // Calculate the accrued interest in Target token
        // The lscale should not be 0 because the user should have some YT balance
        if (_lscale == 0) revert NoAccruedYield();

        GlobalScales memory _gscales = gscales;
        _updateGlobalScalesCache(_gscales);

        // Compute the accrued yield from the time when the YT balance is updated last to now
        // The accrued yield in units of target is computed as:
        // Formula: yield = ytBalance * (1/lscale - 1/maxscale)
        // Sum up the accrued yield, plus the unclaimed yield from the last time to now
        accruedInTarget += _computeAccruedInterestInTarget(
            _gscales.maxscale,
            _lscale,
            // Use yt balance instead of `pyAmount`
            // because we'll update the user's lscale to the current maxscale after this line
            // regardless of whether the user redeems all of their yt or not.
            // Otherwise, the user will lose some accrued yield from the last time to now.
            _yt.balanceOf(from)
        );
        // Compute shares equivalent to the amount of principal token to redeem
        uint256 sharesRedeemed = pyAmount.divWadDown(_gscales.maxscale);

        // Update the local scale and accrued yield of `from`
        lscales[from] = _gscales.maxscale;
275     delete unclaimedYields[from];
        gscales = _gscales;

        // Burn PT and YT tokens from `from`
        _burnFrom(from, pyAmount);
        _yt.burnFrom(from, msg.sender, pyAmount);

        // Withdraw underlying tokens from the adapter and transfer them to the user
283     _target.safeTransfer(address(adapter), sharesRedeemed + accruedInTarget);
284     (uint256 amountWithdrawn, ) = adapter.prefundedRedeem(to);

        emit RedeemWithYT(from, to, amountWithdrawn);
        return amountWithdrawn;
    }

As we can see above, on L275, 283, 284, it processes yields. But whenNotPaused is not applied.

Impact

Yield can be distributed when paused.

Code Snippet

https://github.com/sherlock-audit/2024-01-napier/blob/main/napier-v1/src/Tranche.sol#L246

Tool used

Manual Review

Recommendation

The whenNotPaused modifier has to be applied to Tranche.sol#redeemWithYT as follows. Tranche.sol#redeemWithYT function has to be modified as follows.

-   function redeemWithYT(address from, address to, uint256 pyAmount) external nonReentrant returns (uint256) {
+   function redeemWithYT(address from, address to, uint256 pyAmount) external nonReentrant whenNotPaused returns (uint256) {
        ...
    }

Duplicate of #75