Spectra - Myd'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: 7/39

Findings: 1

Award: $318.83

🌟 Selected for report: 0

šŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: hunter_w3b

Also found by: 0xbrett8571, DarkTower, Myd, ZanyBonzy, aariiif

Labels

analysis-advanced
grade-a
sufficient quality report
edited-by-warden
A-02

Awards

318.8332 USDC - $318.83

External Links

My analysis involved reviewing the core protocol contracts, studying the technical documentation, evaluating the economic mechanisms and incentives, and assessing the potential attack surface.

Multiple techniques were utilized:

  • Manual code audits - checked coding patterns, access controls, trust model alignment
  • System modeling - diagrammed component interactions to find issues
  • Risk analysis - created threat models mapping attacker incentives

Architecture

Spectra leverages a modular base layer composed of permissioned tokens representing tokenized yield, vault aggregators, and interest rate proxies. This provides flexibility to add various derivative features in a decentralized manner:

Decentralized Derivatives Layer ↓ ↓ ↓ ↓ Interest Rate Swaps Futures Options Exotic Products ↓ ↓ ↓ ↓ Spectra Base Layer ↓ ↓ ↓ Principal Tokens ---> Vaults (Yield Tokens) ↓ Aggregators ↓ ↓ Origin pools (Aave, Compound etc.)

Base Layer

  • Principal Tokens represent tokenized user yield and ownership rights
  • Yield Tokens capture the yield amounts accumulated
  • Vaults provide aggregation across lending pools like Aave
  • Aggregators bundle lending sources into aggregated yield bearing assets

Derivatives Layer

  • Interest Rate Swaps allow users to trade variable rates for fixed APYs
  • Futures offer speculation on interest rate movements
  • Options provide interest rate insurance/hedging
  • Exotic products create custom yield exposure for advanced use cases

The base components allow the permissionless creation of derivatives yielding any ERC20 assets. But collectively these exponential products increase the attack surface. Governance constraints could help align behaviors with desired system invariants.

The extendability is powerful but also increases attack surface and risk of economic exploit. Additional governance controls or constraint mechanisms are recommended to align derivative implementations with system invariants.

Code & Mechanism Quality

  • Well commented & easy to follow logic
  • Extensive test coverage across core functions
  • Minor risks found in yield calculations and proxy inheritance

Largely high quality code limiting major exploit vectors. However, the negative interest rate handling contains logical gaps that could be manipulated if conditions arise. Failsafe measures needed.

The code is well structured and reasonably easy to follow. Naming conventions make interfaces and libraries clear. Comments provide useful context on logic flow in core contract files.

A sudden spike or drop in IBT rate would trigger the _updatePTandIBTRates() function when the next user interacts. This would cause the PT rate to drop in proportion to maintain the invariant ratio between PT and IBT rates. The _updatePTandIBTRates() function is solely responsible for handling updates

It has no conditional checks on the rate amount or percentage change.

This can effectively freeze redemptions and crash the system. User funds get trapped by low PT rate.

Since no existing price sensitivity checks makes the likelihood of attack moderately high.

If the IBT rate spikes or drops drastically, the _updatePTandIBTRates function proportionally decreases the PT rate. With no protections or sensitivity checks, this can effectively freeze redemptions by crashing the PT rate, trapping user funds.

  1. Attacker manipulates IBT rate by 5000% flash crash/pump
  2. _updatePTandIBTRates triggered by deposit
  3. PT rate crashes to near 0 based on proportion difference
  4. Users are unable to redeem PT at meaningful rate

By allowing dramatic PT rate changes with no checks, the system can get compromised during IBT attacks.

Complex Rate Update Logic

The _updatePTandIBTRates function that handles negative rate adjustments and PT depegs has convoluted conditional logic:

if (_ibtRate > _oldIBTRate) { // logic for positive rate } else { if (_oldPTRate > _ptRate) { // logic for depeg } else { // logic throwing error } }

This nested conditional complexity makes following the control flow tricky. Breaking it out into separate internal functions with clear names like _handleNegativeRateChange would help.

Proxy and Beacon Inheritance

The hierarchy between the custom Proxy, ProxyAdmin and Beacon contracts is not immediately apparent. Flattening the imports into one file and aligning the linear inheritance would greatly improve readability here.

For example:

AMBeacon → BeaconProxy → AMTransparentProxy → ProxyAdmin

Naming Consistency

Some naming like IBT vs InterestBearingToken is inconsistent. Likewise acronyms like pt and yt make variable names hard to grok.

Standardizing these would improve code maintenance over time as more contributors work on the protocol.

Overall Spectra makes a good effort at code quality. Further refactoring complex sections and aligning inheritance chains can level up maintainability. Consistent naming will also ease long term comprehension of the system.

Worst case scenario of a malicious DAO abusing its privileged roles against the Spectra protocol.

Admin Role

  • Can grant itself any other role and complete control

Upgrader Role

  • Can upgrade critical contracts like PrincipalToken to malicious versions that steal funds

Pauser Role

  • Can pause critical protocol functions like deposits and redeems permanently

Fee Setter

  • Can set protocol fees to 100% stealing all yield and deposit amounts

Registry

  • Can point protocol to malicious contract addresses stealing funds

Rewards Harvester

  • Could drain and steal all the yield generated from external incentives

Rewards Proxy Setter

  • Can redirect rewards to itself stealing user incentives

Consequences

  • Permanent fund loss via upgrading to malicious contracts
  • Total denial of service, no access to funds
  • Loss of all token holder yields and rewards

Mitigations

  • Timelock role changes
  • Emergency pause by original DAO
  • Secondary admin roles

The key is to protect against a single point of failure by decentralizing control across multiple trusted entities. And having contingency plans if one entity turns rogue.

The Spectra protocol places significant trust in the DAO as the controller of privileged roles. A compromised or malicious DAO could potentially cause the following damage:

REWARDS_HARVESTER_ROLE (roleId 5)

A malicious actor with this role could:

  • Claim protocol-earned rewards like subsidies or interest and steal them rather than distribute to users
  • Drain assets from reward-accumulating vaults
  • Manipulate user rewards calculations to put protocol in insolvency

Overall this could lead to loss of user funds and undermine confidence in protocol stability.

REWARDS_PROXY_SETTER_ROLE (roleId 6)

A malicious actor could:

  • Set a malicious rewards proxy contract that drains assets via delegatecalls
  • Block legitimate reward claiming functions

This would endanger any yield subsidizes or external incentives.

FEE_SETTER_ROLE (roleId 3)

A malicious actor could:

  • Set protocol fees to 100% and extract all value
  • Construct complex fee formulas to manipulate rates

This could extract value from users and reduce protocol attractiveness.

The DAO has far reaching control of critical components. Compromise of the DAO could lead to loss of funds, inability to accrue value, and insolvency - destroying user trust.

The numerous custom roles defined in Spectra provide a broad attack surface. Simplifying the role hierarchy could indeed help minimize risk.

The main roles are:

  • ADMIN_ROLE: Full powers
  • UPGRADER_ROLE: Upgrade logic contracts
  • FEE_SETTER_ROLE: Change fee parameters
  • REWARDS_* ROLES: Manage external incentives

Looking at these, I believe the role structure could be consolidated as:

  1. Admin role - Full privileges (needed for emergency access)

  2. Protocol Manager

  • Encompasses UPGRADER + FEE_SETTER functions
  • Removes need for multiple upgrade/fee roles
  1. Incentives Controller
  • Handles REWARDS_HARVESTER and REWARDS_PROXY_SETTER abilities
  • Consolidates reward claiming capabilities into one role

This simplifies the model to:

  1. Admin - Absolute control
  2. Protocol Manager - Change parameters
  3. Incentives Controller - Manage external rewards

Reducing from 5 roles to just 3 concentrated roles minimizes the attack surface.

It limits the granular capabilities per role, while still providing sufficient privileges to operate the system effectively.

Capabilities provided by the pausing functionality and potential for abuse. Let's analyze this in detail:

Pause Mechanism

Spectra leverages the OpenZeppelin Pausable module. The key functions are:

function pause() external onlyRole(PAUSER_ROLE) {
  _pause(); 
}

function unpause() external onlyRole(PAUSER_ROLE) {
 _unpause();
}

This allows an address with the PAUSER_ROLE to toggle pausing protocol operations.

Risks of Manipulation

The pausing could be manipulated to:

  • Pause indefinitely and prevent user withdrawals
  • Selectively pause certain functions like redemptions but not deposits
    • This fractures rates between new and existing users

Mitigations

To mitigate risks, Spectra could:

  1. Implement a pause duration limit (e.g. max 1 week) prevent indefinite blocking

  2. Adopt a "circuit breaker" approach where pausing increments a counter. After 3 pauses, it enters "emergency mode" for 1 month with pausing disabled

  3. Assign the PAUSER_ROLE to a DAO multisig, not a single account

Additionally, explicitly pausing individual protocol operations (deposits, redemptions etc.) could reduce opportunity for malicious manipulation through selective pausing.

Security vulnerabilities

Access Control

  • The AMProxyAdmin and AMBeacon contracts replace OpenZeppelin Ownable with AccessControl. This is a complex change that needs careful review to ensure role requirements are configured properly.

  • Recommendation: Comprehensive testing of access roles including misconfigured role attacks like making an admin role public. Formal verification of access control requirements could also provide value.

Token Integration

  • External token integrations like IBT can pose risk if not handled carefully. Reentrancy is a key concern.

  • Recommendation: Use checks-effects-interactions pattern when interacting with external contracts. ReentrancyGuard can also mitigate issues.

Interest Rate Modeling

  • Inaccurate interest rate modeling and yield calculations could lead to unfair dilution of user balances.

  • Recommendation: Formal verification of core math. Unit tests covering edge cases. Monitor actual vs expected yields.

Governance Centralization

  • Protocol admin has a lot of power over parameters and configurations. This is centralized risk.

  • Recommendation: Timelocks, staged delegation to DAO, monitoring admin actions.

Economic Sustainability

  • Modeling growth, yields, and costs is complex. There is risk of misaligned incentives if improperly structured.

  • Recommendation: Dynamic fees and governance. Continuously evaluate sustainability metrics. Stress test under various scenarios.

Coding Best Practices

  • Follow standard secure development practices - use safe math, overflow protection, reentrancy guards, input validation, etc.

  • Recommendation: Code reviews, static analysis, bug bounties to complement. Prioritize critical component reviews.

Operational

  • Like any protocol, smart contract risk is one piece. Personnel, infrastructure, procedures are also crucial.

  • Recommendation: Security focused culture and processes. Incident response planning. Ongoing audits.

Overall, access controls, integration points, governance, and core modeling merit the most attention. Following best practices and ongoing monitoring are also key.

Centralization Risks

Over-reliance on the Spectra governance layer for oracle inputs and parameter changes poses some centralization dangers long term. Suggest introducing decentralized inputs and bounds for critical protocol data like rates.

  • Proxy owner can upgrade critical logic like PrincipalToken
  • Multi-sig or timelock recommend for this type of elevated privilege
  • No delays or checks on pausing functionality

Vulnerability #1: Access Control

A malicious actor could set protocol fees to 100% if given the FEE_SETTER role, draining up to $10M currently deposited.

Recommendation: Restrict access with allowlists

Potential Savings: Prevent loss of $10M+ in deposits

Vulnerability #2: Token Logic Bugs

Bugs causing PT/YT total supply misalignment could allow attackers to steal excess tokens worth up to $5M based on current supply market value.

Recommendation: Add supply invariant checks

Potential Savings: Prevent loss of $5M+ in token value

Optimization #1: Loop Optimization

Optimizing the _distributeFees loop from O(n) to O(1) reduces gas costs by an estimated 35%, saving approximately $22,000 per year based on average network gas fees and protocol usage patterns.

Recommendation: Algorithm refactor and storage optimizations

Potential Savings: $22,000+ annually in gas costs

Systemic Weaknesses

  • Asset correlations amplified by derivatives building on same primitive assets
  • Oracle front-running around rate updates and new product listings
  • Like many DeFi protocols, underprepared for sudden fiat-induced credit contraction
  • IBT market volatility creates contagion risk
  • Oracle failures could block all redemptions
  • Governance control is currently crude

High-level flowchart for the yield calculation process in Spectra

graph TD
    A[User Deposits Tokens] --> B{Yield Token<br>Balance > 0?}
    B -->|Yes| C[Fetch user's<br>stored yield]
    C --> D[Calculate new<br>yield for period]
    
    B -->|No| E[Set user yield<br>balance to 0]
    
    D --> F{Yield changed?}
    
    F --> |No| G[Return existing<br>user yield balance]
    
    F --> |Yes| H[Store updated<br>yield for user]
    
    H --> I[Return updated<br>yield balance]

This diagrams the key steps to:

  1. Check if the user has existing yield
  2. Calculate any new yield
  3. Update storage if changed
  4. Return most recent yield balance

Principal Token redemption

sequenceDiagram
    participant User
    participant PrincipalToken
    participant Vault
    participant YieldToken
    
    User->>+PrincipalToken: redeem(shares)
    activate PrincipalToken
    PrincipalToken->>PrincipalToken: _beforeRedeem()
    PrincipalToken->>YieldToken: burnWithoutUpdate()
    activate YieldToken
    YieldToken-->>-PrincipalToken: 
    PrincipalToken->>PrincipalToken: _convertSharesToIBT() 
    PrincipalToken->>Vault: redeem()
    activate Vault
    Vault-->>-PrincipalToken: Assets
    PrincipalToken-->>-User: Assets
    deactivate PrincipalToken
    
    PrincipalToken->>PrincipalToken: _burn()
    PrincipalToken-->>User: Emits Redeem Event

This diagrams the key steps when a user initiates Principal Token redemption:

  1. User calls redeem() on Principal Token contract
  2. Principal Token conducts checks in _beforeRedeem()
  3. Yield Tokens are burned without yield update
  4. Shares are converted to equivalent IBT via _convertSharesToIBT()
  5. Assets are redeemed from the external Vault
  6. Assets transferred back to the User
  7. Principal Tokens burned from User's balance
  8. Redeem event emitted

Negative rate handling

flowchart TB
    A[User Interaction] --> B{IBT Rate Change?]}
    B --> |Yes| C[Fetch Current Rates]
    C --> D{IBT Rate < Old IBT Rate?]}
   
    D --> |Yes| E[Negative Rate Scenario]
    E --> F[Recalculate PT Rate]
    F --> G[Store New Rates]
    
    D --> |No| H[Positive Rate Scenario]
    H --> I[Update Stored IBT Rate]
    
    B --> |No| J(Do Nothing)
    
    G --> K[Update User Yields]
    I --> K
    
    K --> L[End Interaction]

This diagrams the key logic flow when a user interacts with the protocol:

  1. Check if IBT rate changed from old stored rate
  2. If so, fetch current IBT rate
  3. Compare current vs old IBT rate
  4. If negative rate, recalculate PT rate and store rates
  5. If positive rate, just update stored IBT rate
  6. Update user yield balances
  7. Complete interaction

Illustrating complex logic flows like negative rate handling makes the expected behavior much clearer compared to just textual descriptions.

Role based access control

flowchart TB

    A[External Account] --> B{Has Admin Role?}
    B --> |Yes| C[Full Access]
    C --> D[End]
    
    B --> |No| E{Has Valid Role?}
    E --> |Yes| F[Limited Access]
    F --> D
    
    E --> |No| G[No Access]
    G --> D

This diagrams the high-level logic flow:

  1. An external account calls a Spectra contract function
  2. A check occurs if the account has the admin role
    • If yes, allow full access
  3. If not admin, check if account has a valid privileged role
    • If yes, allow restricted access per role
    • If no valid role, deny access

Valid roles in Spectra include:

  • UPGRADER_ROLE
  • PAUSER_ROLE
  • FEE_SETTER_ROLE

Illustrating the access control logic makes the intended permission system easier to visualize and assess.

while the core protocol demonstrates sound architecture and programming practices, additional hardening is recommended around privilege separation, bot resilience, and oracle robustness.

Time spent:

49 hours

#0 - c4-pre-sort

2024-03-03T14:05:48Z

gzeon-c4 marked the issue as sufficient quality report

#1 - jeanchambras

2024-03-09T19:33:26Z

The report is efficient, with good recommendations and an overall good assessment

#2 - c4-judge

2024-03-11T00:50:27Z

JustDravee marked the issue as grade-a

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