How I Analyzed $107K Jupiter Lend Before Contest Starts

Published: (February 8, 2026 at 10:56 AM EST)
2 min read
Source: Dev.to

Source: Dev.to

The Setup: $107K in Bounties, Zero Published Code

When Jupiter announced their lending protocol audit contest with $107 K in rewards, I knew preparation would be everything. The catch? The code wasn’t public yet.
But you don’t need the code to start analyzing.

Attack‑surface mapping

Before touching any code, I mapped out the attack surface every Solana lending protocol shares:

// Common lending protocol state structure
#[account]
pub struct LendingMarket {
    pub authority: Pubkey,
    pub token_mint: PubKey,
    pub total_deposits: u64,
    pub total_borrows: u64,
    pub interest_rate: u64,
    pub last_update_slot: u64,
    // Oracle price feed – critical attack vector
    pub oracle: Pubkey,
}

From studying previous Solana lending exploits (Mango, Solend incidents), I compiled the following classes of vulnerabilities:

  • Oracle manipulation – Can price feeds be sandwiched?
  • Interest‑rate calculation – Precision loss in fixed‑point math?
  • Liquidation logic – Race conditions in health‑factor checks?
  • Cross‑program invocations – Re‑entrancy via CPI callbacks?

Jupiter’s existing products gave hints about their lending design:

// Predicted account validation pattern based on Jupiter Perps
pub fn validate_position(ctx: Context) -> Result {
    let position = &ctx.accounts.position;
    let market = &ctx.accounts.market;

    // Health factor check – where bugs often hide
    let health = calculate_health_factor(
        position.collateral_value,
        position.borrow_value,
        market.liquidation_threshold,
    )?;

    require!(health >= MINIMUM_HEALTH, ErrorCode::Unhealthy);
    Ok(())
}

Exploit templates for common vulnerability classes

// Solana uses u64, precision loss is common
fn calculate_interest(principal: u64, rate: u64, time: u64) -> u64 {
    // BUG: If rate * time overflows u64
    // Upcast to u128 before multiplication
    let result = principal
        .checked_mul(rate).unwrap()
        .checked_mul(time).unwrap()
        .checked_div(PRECISION_FACTOR).unwrap();
    result as u64
}
pub fn check_oracle_freshness(
    oracle_price: &PriceAccount,
    current_slot: u64,
    max_staleness: u64,
) -> Result {
    // CRITICAL: Many protocols forget this check
    let price_slot = oracle_price.last_update_slot;

    require!(
        current_slot.saturating_sub(price_slot) <= max_staleness,
        // (truncated in original)
    );
    Ok(())
}
  • Attack patterns are universal – Oracle issues, precision loss, and race conditions appear in every lending protocol.
  • Reusable templates save time – My template library saved 6+ hours on Jupiter Lend.
  • Team’s coding style repeats – Studying other projects from the same team helps anticipate design choices.
0 views
Back to Blog

Related posts

Read more »

Happy women in STEM day!! <3

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as we...