Solend Protocol
Summary
ExpandThe Solend Protocol is a Solana-native lending program implementing deposit/borrow/liquidation mechanics with dual oracle support (Pyth and Switchboard). It uses PDA-based market authority, obligation accounts for tracking user positions, and a reserve model for pooled liquidity. The program supports flash loans, configurable reserve parameters, and compound interest accrual.
The codebase demonstrates strong security practices overall: thorough account owner checks, signer validation, PDA verification via `create_program_address`, staleness checks for reserves and obligations, and checked arithmetic throughout. The `validate_reserve_config` function enforces sane bounds on all configuration parameters.
The primary concern is in the Switchboard v1 oracle integration path, where insufficient price validation could lead to a zero market price being accepted under specific conditions. The Switchboard v2 path correctly validates for negative prices, but the v1 path lacks this protection. A secondary finding relates to the protocol liquidation fee being hardcoded to 0% despite a configurable parameter existing in the reserve config.
Findings
8 issues identified
Switchboard V1 Oracle Silently Returns Zero Price on Missing Result
token-lending/program/src/processor.rs:1157
View Details
Description
In get_switchboard_price, when the Switchboard v1 aggregator has no result, round_result.result.unwrap_or(0.0) silently defaults to a zero price instead of returning an error. A zero price propagated through RefreshReserve would set the reserve’s market_price to zero, causing all collateral valued in that reserve to appear worthless during RefreshObligation, enabling mass liquidation of healthy positions.
Impact
All obligations using the affected reserve as collateral would become liquidatable regardless of actual health. Liquidators could seize collateral from healthy positions. Funds at risk: all collateral in obligations backed by the affected reserve. Attack requires Pyth to be stale (>240 slots) and Switchboard v1 result to be None.
Recommendation
Return an error when the Switchboard v1 aggregator result is None instead of defaulting to zero price.
Fix
Instead of silently defaulting to 0.0 when the oracle has no result, this returns an InvalidOracleConfig error, forcing the transaction to fail rather than propagating a zero price through the lending protocol.
token-lending/program/src/processor.rs
First-Depositor Inflation Attack Enabled by Reduced Collateral Ratio
token-lending/program/src/state/mod.rs:12
View Details
Description
INITIAL_COLLATERAL_RATIO is set to 1 instead of the intended 5 (marked with @FIXME comment). This makes the protocol significantly more vulnerable to the well-known vault share inflation attack where an early depositor can manipulate the exchange rate via direct token donation to steal from subsequent depositors.
Impact
Subsequent depositors into newly created reserves can lose a significant portion of their deposit. With ratio 1, the attack is 5x cheaper to execute compared to the intended ratio of 5. The first depositor can profit at the expense of later depositors through exchange rate manipulation.
Recommendation
Restore INITIAL_COLLATERAL_RATIO to 5 as indicated by the FIXME comment.
Fix
Restoring the ratio to 5 means the first depositor receives 5x more cTokens per unit of liquidity, making inflation attacks 5x more expensive to execute.
token-lending/program/src/state/mod.rs
Switchboard Oracle Price Used Without Confidence/Spread Validation
token-lending/program/src/processor.rs:1188-1210 (get_switchboard_price) and 1212-1234 (get_switchboard_price_v2)
View Details
Description
Both get_switchboard_price (v1) and get_switchboard_price_v2 functions read oracle prices without checking confidence intervals or response spread. The v1 path uses round_result.result without examining min_response/max_response spread. The v2 path calls feed.get_result() without checking feed.latest_confirmed_round.std_deviation or validating the spread between min and max oracle responses. In contrast, the Pyth oracle path (get_pyth_price) enforces a confidence check (conf * 10 < price), but Switchboard has no equivalent validation.
Impact
An attacker who can influence a thin Switchboard oracle feed (e.g., by manipulating the underlying data sources or exploiting low-liquidity oracle feeds) can push an arbitrarily wide or manipulated price through to the lending protocol. This inflated price would be accepted without question and used for collateral valuation, enabling the attacker to over-borrow against inflated collateral or trigger unfair liquidations. This is the same class of vulnerability exploited in the Mango Markets $116M exploit.
Recommendation
Add confidence/spread validation to both Switchboard v1 and v2 price paths, similar to the Pyth confidence check. For v2, check std_deviation relative to the price. For v1, check the spread between min_result and max_result. Reject prices where the confidence range exceeds a configurable threshold (e.g., 10% of the price).
Fix
Switchboard Oracle Feed Denomination Not Validated — Potential Cross-Asset Ratio Treated as USD Price
token-lending/program/src/processor.rs:1377-1393 (validate_switchboard_keys)
View Details
Description
The validate_pyth_keys function correctly validates that the Pyth product’s quote_currency matches the lending market’s quote_currency (line 1355-1358), ensuring the price feed returns a value denominated in the expected quote (e.g., USD). However, validate_switchboard_keys only verifies the feed account is owned by a recognized Switchboard program — it performs NO denomination or quote currency validation. A Switchboard feed returning a cross-asset ratio (e.g., cbETH/ETH ≈ 1.05) could be configured as the oracle for a reserve, and the lending protocol would treat that ratio as the USD price. Since Switchboard is the fallback oracle (used when Pyth returns zero in get_price at line 1220), and since process_update_reserve_config allows the market owner to change the Switchboard feed address at runtime (line 1159), a misconfigured or malicious governance action could set a cross-denomination feed.
Impact
If a Switchboard feed with a non-USD denomination (e.g., TOKEN/ETH returning ~1.0) is set for a reserve whose actual USD price is much higher (e.g., ~$2,000 for ETH-denominated assets), all collateral valued through that feed would be massively undervalued. This enables: (1) borrowers to be liquidated when they shouldn’t be, (2) attackers to borrow far more than their collateral is worth by depositing the mispriced asset, or (3) liquidation failures where undercollateralized positions cannot be properly liquidated because collateral appears nearly worthless.
Recommendation
Add denomination validation for Switchboard feeds. Since Switchboard v2 AggregatorAccountData does not natively expose a quote currency field, implement one or more of: (1) maintain an on-chain allowlist of approved Switchboard feed addresses per reserve, verified during process_update_reserve_config; (2) add price sanity bounds to get_switchboard_price that reject values outside an expected USD range for the asset; (3) require a two-step governance process for oracle changes with a timelock to allow community review.
Fix
Protocol Liquidation Fee Hardcoded to Zero, Config Value Ignored
token-lending/program/src/state/reserve.rs:259
View Details
Description
The calculate_protocol_liquidation_fee function hardcodes Rate::from_percent(0) instead of using self.config.protocol_liquidation_fee. The config field is validated (0-100) in validate_reserve_config and stored correctly, but the actual calculation ignores it. Due to std::cmp::max(…, 1), exactly 1 lamport is always charged regardless of the configured fee percentage.
Impact
The protocol loses its configured share of liquidation bonuses. For example, with protocol_liquidation_fee = 10 and a $5,000 liquidation bonus, the expected $500 protocol fee is reduced to 1 lamport (~$0). This represents near-total loss of protocol liquidation revenue.
Recommendation
Use the config value self.config.protocol_liquidation_fee instead of hardcoded 0.
Fix
Replaces the hardcoded 0% fee rate with the configured protocol_liquidation_fee value from the reserve config, ensuring the protocol collects its intended share of liquidation bonuses.
token-lending/program/src/state/reserve.rs
Unvalidated token_program_id, oracle_program_id, and switchboard_oracle_program_id stored at lending market initialization
token-lending/program/src/processor.rs:136-145
View Details
Description
In process_init_lending_market, the token_program_id, oracle_program_id, and switchboard_oracle_program_id are read from transaction accounts and stored in the lending market state without validating that they correspond to the actual expected program IDs (e.g., spl_token::ID, the real Pyth program, or Switchboard program). All subsequent CPI calls (token transfers, mints, burns) validate the token program against this stored value rather than a hardcoded constant. A malicious actor could initialize a lending market with a fake token program ID. If users are directed to interact with this market (e.g., through a compromised frontend), all spl_token_transfer, spl_token_mint_to, and spl_token_burn CPI calls would invoke the attacker’s program instead of the real SPL Token program, allowing the attacker to steal deposited funds.
Impact
An attacker could create a lending market with a malicious token program that mimics the SPL Token interface. Users who deposit liquidity or interact with this market would have their tokens routed through the attacker-controlled program, potentially resulting in theft of funds.
Recommendation
Validate that token_program_id is the actual SPL Token program ID at market initialization time. Similarly, validate oracle program IDs against known constants.
Fix
No Price Deviation Circuit Breaker Between Oracle Updates
token-lending/program/src/processor.rs:1147-1155 (get_price) and 309-316 (_refresh_reserve)
View Details
Description
The get_price function and _refresh_reserve accept any oracle price regardless of how much it deviates from the previously stored reserve.liquidity.market_price. There is no circuit breaker or maximum deviation check between successive price updates. A sudden large price swing (legitimate or manipulated) is accepted and immediately used for all collateral valuations, borrowing, and liquidation calculations.
Impact
An attacker who can cause a large oracle price movement (via flash loan manipulation of the underlying market, thin oracle feeds, or coordinated trading) can instantly change collateral valuations across the entire lending market. This enables over-borrowing against temporarily inflated collateral or triggering mass liquidations when prices are artificially depressed. Without a circuit breaker, the protocol has no defense against rapid oracle manipulation.
Recommendation
Implement a price deviation circuit breaker in _refresh_reserve that compares the new oracle price against the previously stored market_price. If the deviation exceeds a configurable threshold (e.g., 20% per update), either reject the price update or apply a dampened TWAP/EMA price instead of the spot price.
Fix
Pyth-to-Switchboard Oracle Fallback Enables Price Discrepancy Exploitation
token-lending/program/src/processor.rs:1147-1155 (get_price)
View Details
Description
The get_price function silently falls back from Pyth to Switchboard when Pyth returns an error or zero price (unwrap_or_default). This means if Pyth reports a stale price, invalid confidence, or non-trading status, the system falls through to Switchboard without any cross-validation between the two oracles. An attacker can time their transaction to exploit moments when Pyth is unavailable (e.g., during Pyth network congestion) and the Switchboard feed reports a manipulated or divergent price.
Impact
By timing transactions during Pyth oracle downtime or staleness, an attacker can force the protocol to use the Switchboard oracle exclusively. If the Switchboard feed is easier to manipulate (fewer data sources, lower liquidity), this creates an attack window for oracle manipulation that wouldn’t exist when both oracles are functioning.
Recommendation
When both oracles are configured (non-null), require that at least one is valid and consider cross-referencing prices when both are available. If both oracles are configured but only one returns a valid price, apply a more conservative confidence threshold or require the single oracle price to be within a reasonable range of the last known good price.
Fix
Conclusion
ExpandThe Solend Protocol demonstrates mature security engineering with thorough account validation, checked arithmetic, and staleness enforcement across all instruction handlers. The codebase follows Solana program security best practices consistently, with manual owner checks, signer verification, and PDA validation applied uniformly.
The primary concern is the Switchboard v1 oracle integration, which lacks the negative/zero price validation present in the v2 path. While exploitation requires specific conditions (v1-only oracle configuration and oracle malfunction), the impact of a zero-price injection into the lending market would be severe. The straightforward fix is to mirror the validation already implemented in the v2 handler.
The protocol liquidation fee hardcode is a known temporary measure per inline comments, but should be resolved to prevent configuration confusion. Overall, the program is well-structured for mainnet deployment, with the Switchboard v1 validation gap being the only finding that warrants attention before launch.
Legal Disclaimer: This report covers the code submitted for analysis. It does not account for infrastructure, deployment configuration, third-party dependencies, or changes made after the audit date. Automated analysis may produce false positives or miss context-dependent vulnerabilities. audited.xyz provides this report “as is” without warranty of any kind.