Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)
Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 5 Min Lesezeit
0

How I Built a Counter Program in Anchor and Learned to Trust My Tests

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

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:




CODE
#[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





CODE
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:




CODE
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:




CODE
#[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:




CODE
#[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:




CODE
#[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)




CODE
// 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.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
Use custom web fonts in Google Sheets charts
2 Quellen
Introducing the new 1Password App for Google Chat
1 Quelle
Context-aware access controls are available for Gemini Enterprise in the Admin console
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How I Built a Counter Program in Anchor and Learned to Trust My Tests

Thematisch verwandte Begriffe: Built, Counter, Program, Anchor · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...