🔧 Programmierung 🕛 vor 2 Monaten 9 Min Lesezeit
0

CPI on Solana: The Mental Model I Wish I Had on Day 71

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

I stared at CpiContext::new(...) for a solid ten minutes on Day 71. I knew

the arguments existed. I had no idea what they meant. I copied working code,

ran the test, watched it go green, and moved on. Three days later a PDA-signed

withdraw blew up on me and I realised I had been cargo-culting the pattern

without actually understanding it.



Here is everything I wish someone had told me before I wrote a single line of CPI code.





The one sentence that unlocks all of it



A CPI is a function call with a guest list. You name the program you want to

call, you pass it the accounts it needs, and you prove who is allowed to sign.



Every line of CPI code is doing one of those three things.





What the three pieces actually are



The program being called



Every program on Solana lives at a public key. When you write

CpiContext::new(ctx.accounts.system_program.key(), ...), that first argument

is the on-chain address of the program you want to hand execution over to. You

are not importing a library. You are calling an address that holds executable

bytecode. Once that framing clicked for me, the rest of the pattern made sense.



The accounts it needs



The callee has its own #[derive(Accounts)] struct with expectations. Your job

as the caller is to satisfy those expectations by passing the right accounts

through. Anchor ships typed structs for common programs — Transfer { from, to }

for the System Program, MintTo { mint, to, authority } for Token-2022. You

pull those from your own ctx.accounts, fill in the struct, and bundle it into

a CpiContext.



The rule I missed on Day 71: every program you CPI into must appear as an

account in your own #[derive(Accounts)]
. It is not enough to know the

address. You need pub system_program: Program<'info, System> in your struct

or Anchor will not compile.



Who is authorised to sign



There are exactly two cases.



Case one is a real user wallet. The user already signed the outer transaction

and the Solana runtime carries that authority down into every CPI automatically.

You write zero extra code.



Case two is a PDA. A PDA has no private key so it cannot sign the normal way.

Instead you re-supply the same seeds you used to derive the PDA. The runtime

re-derives the address from those seeds, checks it against the account you

passed, and if they match that counts as the signature. Seeds stand in for a

private key. That is the entire trick behind CpiContext::new_with_signer.





Day 71: The smallest possible CPI — SOL transfer to the System Program



This is the complete sol-mover handler from my repo. This is already as

small as a CPI gets.




CODE
use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer};

declare_id!("2RuhecMfTQqGwfgEC47ca965VqGUbTGefypkSY5Re6ob");

#[program]
pub mod sol_mover {
use super::*;

pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.sender.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
};
let cpi_context = CpiContext::new(
ctx.accounts.system_program.key(),
cpi_accounts,
);
transfer(cpi_context, amount)?;
Ok(())
}
}

#[derive(Accounts)]
pub struct SolTransfer<'info> {
#[account(mut)]
pub sender: Signer<'info>,
#[account(mut)]
pub recipient: SystemAccount<'info>,
pub system_program: Program<'info, System>,
}






Map it to the three pieces: system_program.key() is the program,

Transfer { from: sender, to: recipient } is the guest list, sender: Signer

is the authority — the user signed the outer transaction so the runtime forwards

it automatically.



Full source:





Day 73: PDA-signed CPI — the vault withdraw



Day 73 introduced CpiContext::new_with_signer. The vault PDA holds SOL

and the program needs to sign for it without a private key. The only thing

that changes from Day 71 is new becomes new_with_signer and you pass

signer_seeds.




CODE
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let user_key = ctx.accounts.user.key();
let bump = ctx.bumps.vault;
let signer_seeds: &[&[&[u8]]] = &[&[b"vault", user_key.as_ref(), &[bump]]];

let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.system_program.key(),
Transfer {
from: ctx.accounts.vault.to_account_info(),
to: ctx.accounts.user.to_account_info(),
},
signer_seeds,
);
transfer(cpi_ctx, amount)?;
Ok(())
}

#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
mut,
seeds = [b"vault", user.key().as_ref()],
bump,
)]
pub vault: SystemAccount<'info>,
pub system_program: Program<'info, System>,
}






The seeds in signer_seeds and the seeds in #[account(seeds = [...])] must

match byte for byte. The bump comes from ctx.bumps.vault — Anchor finds the

canonical bump during account validation and stores it there. Never hardcode it.



Test output: vault balance after deposit was 500_000_000 lamports, vault

balance after withdraw was 0.



Full source:






Day 75: The error that taught me the most



On Day 75 I deliberately broke three CPIs to learn how to read the logs.

The most useful failure was changing b"vault" to b"vaultX" in the

signer seeds on the Day 73 vault withdraw. My terminal printed:

Program log: AnchorError caused by account: vault.

Error Code: ConstraintSeeds

Error Number: 2006

Error Message: A seeds constraint was violated.



Program 11111111111111111111111111111111 invoke

Program 11111111111111111111111111111111 failed: privilege escalation


ConstraintSeeds is Anchor telling you the seeds you passed do not reproduce

the PDA it expects. The privilege escalation below it is the System Program

refusing to move lamports from an account you do not control. These two errors

arrive together every time this happens. Start by checking that every byte in

your signer_seeds matches the corresponding byte in your

#[account(seeds = [...])] attribute, including the bump, and that the bump

comes from ctx.bumps not a hardcoded value.



The three-category mental model I built from that session:
























What you see Where to look

ConstraintSeeds + privilege escalation
Seeds or bump mismatch in signer_seeds

ConstraintHasOne with an account name
Caller did not satisfy callee's has_one constraint

invalid instruction data from a wrong program

CpiContext is pointing at the wrong program ID


Full source:





  • This post draws from Days 71 through 75 of my #100DaysOfSolana journey.

    Day 71 was the first CPI to the System Program, Day 72 was Token-2022,

    Day 73 was the PDA vault with a PDA-signed withdraw, Day 74 was one Anchor

    program calling another via declare_program!, and Day 75 was breaking each

    of those deliberately and reading the logs. All the code is at

    github.com/gopichandchalla16/100-days-of-solana.

    Vollständiger Original-Artikel
    Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
    ↗ 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
    9 Quellen
    CVE-2022-44169 | Tenda AC15 15.03.05.18 formSetVirtualSer buffer overflow (EUVD-2022-47119)
    1 Quelle
    Best early October Prime Day deals: Save on TVs, smartwatches, and more tech
    1 Quelle
    I gave Claude Code $100 and 30 days to make a profit. Day 1, it built a product. Here's the pattern it used.
    Ähnliche Beiträge
    🔍 Verwandte News

    Auch interessante Nachrichten CPI on Solana: The Mental Model I Wish I Had on Day 71

    Thematisch verwandte Begriffe: Solana, Mental, Model, Wish · 6 Treffer

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