DEV Community

Cover image for I Built a Hacking Playground for Solana So You Don't Have to Lose Real Money Learning
Ola Adesoye
Ola Adesoye

Posted on

I Built a Hacking Playground for Solana So You Don't Have to Lose Real Money Learning

TL;DR: I created an open-source educational repository with intentionally vulnerable Solana smart contracts. You can legally hack them, watch exploits work in real-time, then study the secure versions to understand exactly what went wrong. It's like a flight simulator for blockchain security.


The $300 Million Wake-Up Call

In 2022, the Wormhole bridge lost $320 million in a single exploit. The Cashio stablecoin collapsed overnight. Slope wallet leaked private keys. These weren't random accidents—they were predictable security failures that could have been prevented.

What's crazy is not only the money you realize was lost in those exploits, but also that the same vulnerability patterns keep appearing over and over. Missing signature checks, unchecked math overflow, and authority validation failures.

The same mistakes, different projects, millions gone.

Anyway, here's a safe place to experience these vulnerabilities firsthand.


The project

Anchor/Pinocchio Security Template is a collection of six vulnerability patterns, each containing:

  1. A deliberately broken smart contract — with intentional security flaws
  2. A secure version — showing exactly how to fix it
  3. Working exploit code — that actually drains the vulnerable contract
  4. Tests proving the secure version blocks attacks

Everything runs on a local Solana validator. No real money. No devnet tokens. Just you, the code, and a safe environment to break things.

The six patterns

Pattern What Goes Wrong Real-World Parallel
Missing Validation No check if caller is authorized Leaving your front door unlocked
Authority Failures Admin functions anyone can call Giving everyone the master key
Unsafe Arithmetic Numbers overflow and wrap around Your bank balance going from $1 to $18 quintillion
CPI Re-entrancy Contract calls back into itself mid-transaction Someone withdrawing money while the ATM is still counting
PDA Derivation Wrong address generation lets attackers substitute accounts Someone redirecting your mail to their house
Token Validation Accepting fake tokens as real ones A vending machine that accepts photocopied bills

A side-by-side comparison that changes everything

Most security tutorials show you the "right way" and leave it at that. It might also be great to learn by seeing what not to do first.

Here's the thing: when you only see correct code, you don't develop intuition for what's dangerous. You follow patterns without understanding why they exist.

But when you see this vulnerable code:

// VULNERABILITY: Anyone can call this!
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    // No check if caller is the owner
    ctx.accounts.vault.balance -= amount;
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

And then run an exploit that actually steals funds from it... suddenly the secure version makes visceral sense:

// SECURITY: Only the owner can transfer
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    require!(
        ctx.accounts.authority.key() == ctx.accounts.vault.owner,
        ErrorCode::Unauthorized
    );
    ctx.accounts.vault.balance -= amount;
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

That require! check isn't just "best practice" anymore. It's the line between your program working and someone draining everything.


The dual-framework approach

One thing that sets this project apart: every vulnerability is implemented in both Anchor (the popular high-level framework) and Pinocchio (a minimal, lower-level library).

Why bother?

Because security principles are framework-agnostic. Whether you're using Anchor's convenient macros or writing raw account parsing with Pinocchio, the same vulnerabilities exist.

Seeing them in both contexts drives home that security isn't about memorizing framework-specific tricks - it's about understanding the underlying model.

Plus, Pinocchio implementations show you what Anchor is doing "under the hood." When you see the 50 lines of manual validation that Anchor's #[account(...)] macro generates for you, you appreciate both the convenience AND understand what could go wrong.


How to use this

1. Clone and set up:

git clone https://github.com/Zolldyk/anchor-pinochio-security-template.git
cd anchor-pinochio-security-template
npm install
Enter fullscreen mode Exit fullscreen mode

2. Start with Pattern 01 (Missing Validation):

npm run test:01
Enter fullscreen mode Exit fullscreen mode

You'll see output like:

Vulnerable program
  ✗ allows unauthorized balance update (demonstrates vulnerability)
Secure Program
  ✓ blocks unauthorized balance update (demonstrates fix)
Enter fullscreen mode Exit fullscreen mode

The means the exploit succeeded. The means the attack was blocked. Both are expected!

3. Read the code, modify the exploit, break things.

The LEARNING_PATH.md provides a structured progression from beginner to advanced patterns.


What I learned building this

A few things surprised me:

Anchor does a lot of heavy lifting. Starting from the Signer<'info> type, to the has_one constraint, and the #[account(...)] macro. They aren't just conveniences; they're guardrails that prevent entire categories of vulnerabilities.

The Pinocchio implementations made me appreciate how much validation Anchor handles automatically.

Arithmetic bugs are sneakier than I expected. Integer overflow doesn't throw an error in Rust by default (in release mode). Your balance just quietly wraps from 0 to 18,446,744,073,709,551,615. The checked math functions (checked_add, checked_sub) exist for a reason.

Re-entrancy in Solana is different from Ethereum. Solana's account model makes traditional re-entrancy harder, but CPI (Cross-Program Invocation) introduces its own version. The "checks-effects-interactions" pattern matters here too.

Security is about layers. No single check makes a program secure. It's the combination of signer validation, owner checks, PDA derivation, bounds checking, and careful state management that creates defense in depth.


Who this is for

  • Solana developers who want to level up their security knowledge
  • Auditors looking for a reference of common vulnerability patterns
  • Web3 security enthusiasts interested in hands-on learning
  • Anyone curious about how smart contract exploits actually work

You don't need to be a Solana expert. If you know basic Rust and understand that blockchains have "smart contracts," you can follow along.


Happy (secure) coding!

Top comments (0)