Spectra - Giorgio's results

A permissionless interest rate derivatives protocol on Ethereum.

General Information

Platform: Code4rena

Start Date: 23/02/2024

Pot Size: $36,500 USDC

Total HM: 2

Participants: 39

Period: 7 days

Judge: Dravee

Id: 338

League: ETH

Spectra

Findings Distribution

Researcher Performance

Rank: 21/39

Findings: 1

Award: $80.57

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

80.5733 USDC - $80.57

Labels

bug
2 (Med Risk)
partial-75
sufficient quality report
:robot:_04_group
duplicate-210

External Links

Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/25603ac27c3488423a0739b66e784c01a3db7d75/src/tokens/PrincipalToken.sol#L484-L486 https://github.com/code-423n4/2024-02-spectra/blob/25603ac27c3488423a0739b66e784c01a3db7d75/src/tokens/PrincipalToken.sol#L867-L875

Vulnerability details

Vulnerability detail

The principalToken.sol contract claims to be ERC5095 comliant. The EIP 5095 standard wants the maxRedeem() function to return 0 if redemption are paused. But this is not the current case .

Impact

The contract fails to be ERC5095 compliant, which is a requirement stated in the contest page.

Proof of Concept

Here

    function maxRedeem(address owner) public view override returns (uint256) {
        return _maxBurnable(owner);
    }

We can see that the redeem function makes an internal call to the _maxBurnable internal function

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

It is here flagrant that the results won't be altered if the redemptions are paused.

Tools Used

EIP-5095 page

MUST return the maximum amount of principal tokens that could be transferred from holder through redeem and not cause a revert

return 0 if the redemption are paused. As such:


    function maxRedeem(address owner) public view override returns (uint256 result) {
        if(paused()) result = 0;
        result = _maxBurnable(owner);
    }

Assessed type

Other

#0 - c4-pre-sort

2024-03-03T13:07:19Z

gzeon-c4 marked the issue as duplicate of #33

#1 - c4-pre-sort

2024-03-03T13:07:22Z

gzeon-c4 marked the issue as sufficient quality report

#2 - c4-judge

2024-03-11T00:26:44Z

JustDravee marked the issue as satisfactory

#3 - c4-judge

2024-03-11T00:26:56Z

JustDravee marked the issue as partial-75

Awards

80.5733 USDC - $80.57

Labels

bug
2 (Med Risk)
partial-75
sufficient quality report
:robot:_04_group
duplicate-210

External Links

Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/25603ac27c3488423a0739b66e784c01a3db7d75/src/tokens/PrincipalToken.sol#L461-L463

Vulnerability details

Vulnerability explanation

According to the almighty EIP-5095, the maxWithdraw() function "MUST return the maximum amount of underlying tokens that could be redeemed from holder through withdraw and not cause a revert". However instead of this the cheeky maxWithdraw() function in the contract will revert when paused because of the whenNotPaused modifier

Impact

The function reverts when the redemptions are paused, which is not in line with the requirements of the EIP-5095.

Proof of Concept

The maxWithdraw()

    function maxWithdraw(address owner) public view override whenNotPaused returns (uint256) {
        return convertToUnderlying(_maxBurnable(owner)); //@note inclusive of fees
    }

reverts when the DAO decides to paused the contract. If the function is called while paused the function reverts.

function _requireNotPaused() internal view virtual { if (paused()) { @> revert EnforcedPause(); } }

Tools Used

EIP-5095 docs

If the contract happens to be paused, return 0 instead of revertin'.


   -- function maxWithdraw(address owner) public view override whenNotPaused returns (uint256) {
     
   ++ function maxWithdraw(address owner) public view override returns (uint256) {
 ++    if(paused()) return 0;
       return convertToUnderlying(_maxBurnable(owner)); //@note inclusive of fees
    }

Assessed type

Other

#0 - c4-pre-sort

2024-03-03T13:07:12Z

gzeon-c4 marked the issue as duplicate of #33

#1 - c4-pre-sort

2024-03-03T13:07:24Z

gzeon-c4 marked the issue as sufficient quality report

#2 - c4-judge

2024-03-11T00:27:09Z

JustDravee marked the issue as partial-75

#3 - c4-judge

2024-03-11T00:27:12Z

JustDravee marked the issue as satisfactory

#4 - c4-judge

2024-03-14T06:23:59Z

JustDravee marked the issue as partial-75

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter