惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
爱范儿
爱范儿
V
Visual Studio Blog
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 三生石上(FineUI控件)
博客园 - Franky
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
罗磊的独立博客
L
LangChain Blog
Jina AI
Jina AI
IT之家
IT之家
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I Built a Counter Program in Anchor and Learned to Tr...
Gopichand · 2026-06-20 · via DEV Community

Gopichand

I spent Days 57–61 building the same counter program over and over.
Not because I kept breaking it (well, I did), but because each day
revealed something about Anchor that the previous day's green tests
had been quietly hiding.

This is that story.


The Accounts Struct — Where Anchor Earns Its Name

Every Anchor instruction starts with an accounts struct. Here's mine
for Initialize:

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = authority, space = 8 + 40)]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

Three fields, three jobs:

  • counter — the new on-chain account being created. Anchor's init constraint handles the create_account CPI to the System Program automatically. space = 8 + 40 is the discriminator (8 bytes) plus the actual struct data (40 bytes).
  • authority — the wallet paying for the account and signing the transaction. mut is required because its lamport balance will decrease.
  • system_program — Solana requires you to pass the System Program explicitly whenever you're creating accounts. Nothing happens if it's missing — the transaction just fails.

Coming from backend development, this was the first thing that
genuinely surprised me. In Web2 you pass data to a function. In
Anchor you first declare every account your instruction will touch,
with its permissions, and Anchor verifies all of it before your
handler code runs even one line.


The Handlers — Short by Design

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    let counter = &mut ctx.accounts.counter;
    counter.count = 0;
    counter.authority = ctx.accounts.authority.key();
    Ok(())
}

ctx.accounts gives you typed, validated access to every account
declared in the struct above. The handler is three lines because
Anchor already did the hard work — account creation, lamport
deduction, ownership checks — before this function was called.

Now increment:

pub fn increment(ctx: Context<Increment>) -> Result<()> {
    let counter = &mut ctx.accounts.counter;
    counter.count = counter.count.checked_add(1)
        .ok_or(ErrorCode::Overflow)?;
    Ok(())
}

And its accounts struct:

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut, has_one = authority)]
    pub counter: Account<'info, Counter>,
    pub authority: Signer<'info>,
}

has_one = authority is the constraint that does the real work.
Before increment runs, Anchor checks that counter.authority ==
authority.key()
. If a different wallet signs, the transaction
reverts with ConstraintHasOne (Error 2001) before a single line
of my handler executes. The constraint is a pre-condition, not a
runtime check I have to write myself.


The Tests — Two Paths That Matter

Happy path:

#[test]
fn initialize_then_increment() {
    // ... setup ...
    let res = svm.send_transaction(increment_tx);
    assert!(res.is_ok());
    let counter: Counter = svm.get_account_data(&counter_kp.pubkey());
    assert_eq!(counter.count, 1);
}

What would have to go wrong for this to fail? The increment
handler would have to not add 1 — either wrong arithmetic, a
missing account write-back, or a serialization error.

Failure path — wrong authority:

#[test]
fn increment_fails_when_wrong_authority_signs() {
    // wrong_wallet is a fresh keypair, not the counter's authority
    let res = svm.send_transaction(bad_tx);
    assert!(res.is_err(), "increment should fail when signed by wrong authority");
}

What would have to go wrong for this to fail? The has_one
constraint would have to be missing or wrong. Without it, any
wallet could increment anyone's counter — which is exactly the
exploit this test exists to prevent.


Day 61 — Mutation Testing: Proving the Tests Are Real

Green tests don't prove your code is correct. They prove your code
satisfies your tests. Those are different things if your tests are
wrong.

So on Day 61 I intentionally broke the program three ways and
watched the test suite respond.

Bug 2: I changed checked_add(1) to checked_add(2)

// Before
counter.count = counter.count.checked_add(1).ok_or(ErrorCode::Overflow)?;

// After (intentional bug)
counter.count = counter.count.checked_add(2).ok_or(ErrorCode::Overflow)?;

Test output:
thread 'initialize_then_increment' panicked at programs/counter/tests/counter.rs:97:5:
assertion left == right failed
left: 2
right: 1

The test caught it immediately. The assertion assert_eq!(counter.count, 1) exists precisely because of off-by-one errors like this. Before this experiment I had pattern-matched that assertion from a tutorial. After the experiment I understood why it has to be exactly 1 and not "nonzero" or "greater than 0."

I ran git restore and all three tests went green again.


What Writing This Revealed

The has_one constraint took me three drafts to explain cleanly.
I had been thinking of it as "check the authority" — vague enough
that I couldn't have told you exactly when it runs or what it
compares. Writing forced precision: it compares counter.authority
(stored on-chain at init time) against the authority account
passed in at increment time, and it runs before the handler, not
inside it.

That distinction — constraint vs. runtime check — is the thing
I would have gotten wrong in a code review before this week.


What's Next

Week 10 will push past single-account programs. I want to build
something with multiple accounts interacting — a vault, an escrow,
or a simple DEX instruction — where the account constraints do
more than guard a single field. That's where I expect the next
gap to show up.

Full code on GitHub

Day 62 of 100. Building daily.