The Setup: $107K in Bounties, Zero Published Code
When Jupiter announced their lending protocol audit contest with $107K in rewards, I knew preparation would be everything. The catch? The code wasn't public yet.
But here's what most auditors miss: you don't need the code to start analyzing.
Phase 1: Protocol Intelligence Gathering
Understanding Solana Lending Primitives
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,
}
The Vulnerability Checklist I Built
From studying previous Solana lending exploits (Mango, Solend incidents), I compiled:
- 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 - Reentrancy via CPI callbacks?
Phase 2: Architecture Prediction
Jupiter's existing products gave hints about their lending design:
// Predicted account validation pattern based on Jupiter Perps
pub fn validate_position(ctx: Context<ValidatePosition>) -> 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(())
}
Phase 3: Attack Pattern Templates
I prepared exploit templates for common vulnerability classes:
Template 1: Precision Loss Attack
// Solana uses u64, precision loss is common
fn calculate_interest(principal: u64, rate: u64, time: u64) -> u64 {
// BUG: If rate * time < PRECISION_FACTOR, result = 0
// Attacker can borrow dust amounts, pay zero interest
principal
.checked_mul(rate).unwrap()
.checked_mul(time).unwrap()
.checked_div(PRECISION_FACTOR).unwrap()
}
// Fixed version:
fn calculate_interest_safe(principal: u128, rate: u128, time: u128) -> 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
}
Template 2: Stale Oracle Exploitation
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,
ErrorCode::StaleOracle
);
// Also check confidence interval
require!(
oracle_price.confidence < oracle_price.price / 20, // 5% max
ErrorCode::OracleUncertain
);
Ok(())
}
What I Found When Code Dropped
When the actual Jupiter Lend code was released, my preparation paid off:
Finding 1: Interest Accrual Edge Case
The protocol's interest calculation had a subtle issue when time_elapsed = 0:
// Actual vulnerable pattern (simplified)
pub fn accrue_interest(market: &mut Market, current_time: i64) {
let time_elapsed = current_time - market.last_accrual_time;
// Missing: time_elapsed == 0 case
// Allows same-slot manipulation
let interest = calculate_compound_interest(
market.total_borrows,
market.interest_rate,
time_elapsed as u64,
);
market.total_borrows += interest;
market.last_accrual_time = current_time;
}
Finding 2: Liquidation Threshold Bypass
A rounding issue in health factor calculation:
// The bug: integer division truncates
let health_factor = collateral_value * 100 / borrow_value;
// Attack: With collateral=99, borrow=100
// health_factor = 9900 / 100 = 99 (looks healthy)
// But actual ratio is 0.99, should be liquidatable
The Methodology That Works
My Pre-Contest Workflow
1. Study similar protocols (2-3 days)
└── Extract common patterns and known vulnerabilities
2. Build attack templates (1-2 days)
└── Generic exploits you can adapt quickly
3. Set up testing environment (1 day)
└── Local Solana validator, fuzzing harness ready
4. Monitor announcement channels
└── Be first to clone when code drops
Tools I Use
- Anchor framework - For rapid PoC development
- solana-program-test - Local testing without devnet
- Custom fuzzer - Property-based testing for math functions
- Slither alternatives for Solana - Static analysis (limited but useful)
Key Takeaways
Preparation > Raw skill - Understanding the protocol class matters more than reading speed
Attack patterns are universal - Oracle issues, precision loss, race conditions appear in every lending protocol
Build reusable templates - My template library saved 6+ hours on Jupiter Lend
Study the team's other code - Coding style and patterns repeat
Currently auditing Solana DeFi protocols. DMs open for collaboration.
Want more content like this? Follow for deep dives into smart contract security.
Top comments (0)