Account model
Ethereum bundles code and state inside one contract. Solana keeps the program stateless - every piece of state lives in a separate on-chain account the caller passes in.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// State lives inside the contract. `value` is a storage slot owned by
/// `Counter`, modified by `Counter`, and never escapes.
contract Counter {
uint64 public value;
A Solidity contract is one thing: code + state, glued together at the same address. value is a storage slot inside Counter, owned by Counter, and the contract never has to be told where to find it.
function increment() external {
value += 1;
}
}
increment() reads and writes value directly. No accounts, no addresses passed in - the contract resolves storage internally.
// State lives outside the program. The program is pure code; `Counter` is a
// separate on-chain account that callers hand in.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
pub mod counter {
The Solana program has no state of its own. pub mod counter is just executable logic; whatever data each handler operates on lives outside the program and gets passed in on every call.
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.value = 0;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.value = ctx
.accounts
.counter
.value
.checked_add(1)
.ok_or(ProgramError::ArithmeticOverflow)?;
Ok(())
}
}
ctx.accounts.counter.value reads from the counter account the caller handed in. The program doesn't look up storage - it receives the account through the Context<Increment>.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + 8,
seeds = [b"counter"],
bump,
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, seeds = [b"counter"], bump)]
pub counter: Account<'info, Counter>,
}
#[account]
pub struct Counter {
pub value: u64,
}
Counter is a typed on-chain account, owned by the program. A client calling increment() derives this account's address from the seeds ([b"counter"]), includes it in the transaction's account list, signs, and sends. The program never looks it up - the transaction hands it over.