autorenew
버그의 전조 #3: 선택적 계정이 Solana 스왑에서 사용자들의 수수료 회피를 허용한 방법

버그의 전조 #3: 선택적 계정이 Solana 스왑에서 사용자들의 수수료 회피를 허용한 방법

Ever felt like your smart contract is bulletproof until an audit reveals a sneaky loophole? That's exactly what happened in this Solana DeFi project we audited at Accretion. Welcome to Advent of Bugs #3, where we're diving into how optional accounts, good intentions, and the nuances of Solana's state machine can turn a fee collection mechanism into a free-for-all.

If you're knee-deep in building or auditing Solana programs, this one's for you. We'll break down the issue, why it slipped through testing, and the elegant fix that keeps things secure without bloating your instruction sizes. No PhD required—just clear, practical insights to level up your blockchain game.

The Setup: Fees, Swaps, and a Helping Hand from Another Program

Picture this: You've got a slick DeFi swap protocol on Solana. To keep the lights on, you charge a small fee on every trade. Simple, right? But here's the twist—most users don't interact directly with your core swap instruction. Instead, they go through a frontend or SDK that taps into a separate "fee program." This fee program collects the toll, then uses a Cross-Program Invocation (CPI)—Solana's way of letting one program call another—to hand off to your main swap logic.

Why the extra hop? Efficiency and modularity. The fee program handles payments, logging, or whatever else, while your swap stays lean and mean. But to avoid double-dipping (collecting fees twice), the main swap instruction marks its fee account as optional. If it's provided, great—charge away. If not (like in the CPI flow), skip it and proceed.

Sounds smart. Until it's not.

The Bug: Optional Accounts Open the Door to Fee-Free Swaps

During our audit, we spotted the Achilles' heel: Anyone could call the main swap instruction directly, skipping the fee program entirely. By simply omitting the fee account, users bypassed charges altogether. Poof—free swaps for savvy (or malicious) traders.

This wasn't some rookie mistake. The dev team tested the happy path: SDK flows and website interactions, where the fee program acts as the gatekeeper. Direct calls? Rarely tested, especially in DeFi where abstractions hide the raw program guts. Plus, Solana's instruction size limits (about 1,232 bytes) make devs wary of extra accounts, so optional ones feel like a win.

But in blockchain, "feels like" isn't enough. One overlooked vector, and your protocol's economics crumble. Low to medium severity? Sure—but in a high-volume swapper, that's real revenue at risk.

Why It's Tricky: Balancing Intention, Size, and Security

Getting this right isn't straightforward. You want:

  • Direct users to pay fees (enforce the account).
  • CPI users (from the fee program) to skip double-charging.
  • No bloat—extra accounts inflate compute units and hit that size cap.

Wild CPIs from untrusted programs? Nightmare fuel. And testing every edge case? Time sink.

The root? Solana programs are stateless machines reacting to instructions. Without explicit checks, "optional" becomes "skippable"—and intention doesn't enforce itself.

The Fix: PDAs, Signers, and CPI Validation Done Right

The solution? Leverage Solana's Program-Derived Addresses (PDAs) for cryptographic proof of origin. Here's the gist, straight from the code we reviewed:

In the fee program:

  • Derive a PDA seed from a fixed string like "cpi_authority".
  • Sign the CPI to the main swap with this PDA as the authority.
  • Pass the PDA as a signer account in the instruction.

In the main swap program:

  • Expect the PDA address for the CPI authority.
  • Verify the provided signer matches: If signer.key() != expected, enforce the fee account (unwrap it to ensure it's there and collect).
  • For safety, unwrap the fee account post-check—Solana's Rust SDK makes this a breeze for enforcement.

This way:

  • Direct calls fail the signer check, forcing fees.
  • Legit CPIs from the fee program pass, keeping things optional.
  • No extra accounts needed; the PDA doubles as proof.

It's a masterclass in Solana's security primitives: PDAs for deterministic, program-controlled keys; signers for intent verification; and unwraps for runtime guarantees.

Want to see it in action? Check out the annotated code snippet below, highlighting the key lines.

Solana Rust 코드 스니펫: PDA로 CPI에 서명하는 fee program과 unwrap으로 수수료 강제를 검증하는 main swap

Lessons for Solana Builders: Test Beyond the Happy Path

This bug underscores a golden rule: Functionality ≠ Correctness. Always audit for bypass vectors, especially in token flows or economics. Tools like our audits at Accretion catch these early.

Pro tips:

  • Use PDAs religiously for inter-program trust.
  • Make optionals conditional with signer checks.
  • Simulate direct calls in tests—tools like Anchor's test framework shine here.
  • And yeah, double-check that unwrap; it's your safety net.

If you're auditing or building on Solana, drop a DM to @r0bre—we're hiring top talent and love geeking out over these puzzles.

Shoutout to @brymko for the sharp spot. What's your wildest "optional" bug story? Reply below.

Stay secure out there—bugs accrete if you let 'em.

This article is inspired by real-world audits at Accretion. For more Solana security deep dives, follow Meme Insider for the latest in meme tokens, DeFi hacks, and blockchain know-how.

추천 기사