Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect implementation of the EIP-5095 standard for maxRedeem() and maxWithdraw() #212

Closed
c4-bot-3 opened this issue Mar 1, 2024 · 4 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-210 partial-75 Incomplete articulation of vulnerability; eligible for partial credit only (75%) 🤖_33_group AI based duplicate group recommendation sufficient quality report This report is of sufficient quality

Comments

@c4-bot-3
Copy link
Contributor

c4-bot-3 commented Mar 1, 2024

Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L482-L485
https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L458-L462

Vulnerability details

Impact

The EIP-5095 standard states that the function MUST factor in both global and user-specific limits, like if redemption is entirely disabled (even temporarily) it MUST return 0.

When the protocol is paused and redemption is temporarily disabled, this function should return 0.

Yet, both functions returns the value as if redemption is still enabled, which is a clear-cut violation of the EIP-5095 standard.

Proof of Concept

From: EIP-5095

maxRedeem  

Maximum amount of principal tokens that can be redeemed from the holder balance, through a redeem call.

MUST return the maximum amount of principal tokens that could be transferred from holder through redeem and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary).

MUST factor in both global and user-specific limits, like if redemption is entirely disabled (even temporarily) it MUST return 0.

MUST NOT revert.
maxWithdraw

Maximum amount of the underlying asset that can be redeemed from the holder principal token balance, through a withdraw call.

MUST return the maximum amount of underlying tokens that could be redeemed from holder through withdraw and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary).

MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0.

MUST NOT revert.

PrincipalToken.sol

    /** @dev See {IPrincipalToken-maxWithdraw}.
     */
    function maxWithdraw(address owner) public view override whenNotPaused returns (uint256) {
        return convertToUnderlying(_maxBurnable(owner));
    }

PrincipalToken.sol

    /** @dev See {IPrincipalToken-convertToUnderlying}. */
    function convertToUnderlying(uint256 principalAmount) public view override returns (uint256) {
        return IERC4626(ibt).previewRedeem(_convertSharesToIBTs(principalAmount, false));
    }

PrincipalToken.sol


    /** @dev See {IPrincipalToken-maxRedeem}. */
    function maxRedeem(address owner) public view override returns (uint256) {
        return _maxBurnable(owner);
    }

PrincipalToken.sol

    /**
     * @notice Computes the maximum amount of burnable shares for a user
     * @param _user The address of the user
     * @return maxBurnable The maximum amount of burnable shares
     */
    function _maxBurnable(address _user) internal view returns (uint256 maxBurnable) {
        if (block.timestamp >= expiry) {
            maxBurnable = balanceOf(_user);
        } else {
            uint256 ptBalance = balanceOf(_user);
            uint256 ytBalance = IYieldToken(yt).balanceOf(_user);
            maxBurnable = (ptBalance > ytBalance) ? ytBalance : ptBalance;
        }
    }

Tools Used

Manual Review

Recommended Mitigation Steps

Change the functions so that it will return 0 when the protocol is paused.

Assessed type

Other

@c4-bot-3 c4-bot-3 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Mar 1, 2024
c4-bot-7 added a commit that referenced this issue Mar 1, 2024
@c4-bot-13 c4-bot-13 added the 🤖_33_group AI based duplicate group recommendation label Mar 1, 2024
@c4-pre-sort
Copy link

gzeon-c4 marked the issue as duplicate of #33

@c4-pre-sort
Copy link

gzeon-c4 marked the issue as sufficient quality report

@c4-pre-sort c4-pre-sort added the sufficient quality report This report is of sufficient quality label Mar 3, 2024
@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Mar 11, 2024
@c4-judge
Copy link
Contributor

JustDravee marked the issue as satisfactory

@c4-judge c4-judge added partial-75 Incomplete articulation of vulnerability; eligible for partial credit only (75%) and removed satisfactory satisfies C4 submission criteria; eligible for awards labels Mar 11, 2024
@c4-judge
Copy link
Contributor

JustDravee marked the issue as partial-75

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-210 partial-75 Incomplete articulation of vulnerability; eligible for partial credit only (75%) 🤖_33_group AI based duplicate group recommendation sufficient quality report This report is of sufficient quality
Projects
None yet
Development

No branches or pull requests

4 participants