# Migration Guide: Anchor to Pinocchio

Step-by-step guide for migrating Solana programs from Anchor to Pinocchio.

## Why Migrate?

| Metric | Anchor | Pinocchio | Improvement |
|--------|--------|-----------|-------------|
| Transfer CU | ~6,000 | ~600 | **90% reduction** |
| Initialize CU | ~25,000 | ~5,000 | **80% reduction** |
| Binary Size | ~200KB+ | ~50KB | **75% smaller** |
| Dependencies | Many | Zero | No dep conflicts |

## Migration Checklist

- [ ] Understand program structure
- [ ] Convert account definitions
- [ ] Migrate instruction handlers
- [ ] Replace Anchor macros with manual validation
- [ ] Update CPI calls
- [ ] Set up IDL generation with Shank
- [ ] Generate client with Codama
- [ ] Test thoroughly

---

## 1. Project Structure Conversion

### Anchor Structure

```
program/
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── state.rs
│   ├── instructions/
│   │   ├── mod.rs
│   │   └── *.rs
│   └── error.rs
├── Anchor.toml
└── tests/
```

### Pinocchio Structure

```
program/
├── Cargo.toml           # Different dependencies
├── src/
│   ├── lib.rs           # Manual entrypoint
│   ├── state.rs         # Bytemuck structs
│   ├── instructions/
│   │   ├── mod.rs
│   │   └── *.rs
│   └── error.rs
├── idl.json             # Generated by Shank
└── tests/
```

### Cargo.toml Conversion

**Anchor:**
```toml
[dependencies]
anchor-lang = "0.30"
anchor-spl = "0.30"
```

**Pinocchio:**
```toml
[dependencies]
pinocchio = "0.10"
pinocchio-system = "0.4"
pinocchio-token = "0.4"
bytemuck = { version = "1.14", features = ["derive"] }
shank = "0.4"  # For IDL generation

[features]
bpf-entrypoint = []
```

---

## 2. Account Definition Conversion

### Anchor Account

```rust
use anchor_lang::prelude::*;

#[account]
#[derive(InitSpace)]
pub struct Vault {
    pub owner: Pubkey,
    pub balance: u64,
    pub bump: u8,
}

impl Vault {
    pub const SEED: &'static [u8] = b"vault";
}
```

### Pinocchio Account

```rust
use bytemuck::{Pod, Zeroable};
use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};

pub const VAULT_DISCRIMINATOR: u8 = 1;

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Vault {
    pub discriminator: u8,      // Manual discriminator
    pub owner: [u8; 32],        // Pubkey as bytes
    pub balance: u64,
    pub bump: u8,
    pub _padding: [u8; 6],      // Align to 8 bytes
}

impl Vault {
    pub const LEN: usize = core::mem::size_of::<Self>();
    pub const SEED: &'static [u8] = b"vault";

    pub fn from_account(account: &AccountInfo) -> Result<&Self, ProgramError> {
        let data = account.try_borrow_data()?;
        if data.len() < Self::LEN || data[0] != VAULT_DISCRIMINATOR {
            return Err(ProgramError::InvalidAccountData);
        }
        Ok(bytemuck::from_bytes(&data[..Self::LEN]))
    }

    pub fn from_account_mut(account: &AccountInfo) -> Result<&mut Self, ProgramError> {
        let mut data = account.try_borrow_mut_data()?;
        if data.len() < Self::LEN {
            return Err(ProgramError::InvalidAccountData);
        }
        Ok(bytemuck::from_bytes_mut(&mut data[..Self::LEN]))
    }
}
```

### Key Differences

| Anchor | Pinocchio |
|--------|-----------|
| `#[account]` macro adds 8-byte discriminator | Manual 1-byte discriminator |
| `Pubkey` type | `[u8; 32]` array |
| Auto-calculated size | Manual `LEN` constant |
| Auto-derived traits | Manual `Pod`, `Zeroable` |
| Borsh serialization | Bytemuck zero-copy |

---

## 3. Instruction Handler Conversion

### Anchor Handler

```rust
use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = owner,
        space = 8 + Vault::INIT_SPACE,
        seeds = [Vault::SEED, owner.key().as_ref()],
        bump
    )]
    pub vault: Account<'info, Vault>,

    #[account(mut)]
    pub owner: Signer<'info>,

    pub system_program: Program<'info, System>,
}

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    let vault = &mut ctx.accounts.vault;
    vault.owner = ctx.accounts.owner.key();
    vault.balance = 0;
    vault.bump = ctx.bumps.vault;
    Ok(())
}
```

### Pinocchio Handler

```rust
use pinocchio::{
    account_info::AccountInfo,
    program_error::ProgramError,
    pubkey::Pubkey,
    sysvar::rent::Rent,
    ProgramResult,
};
use pinocchio_system::instructions::CreateAccount;

use crate::state::{Vault, VAULT_DISCRIMINATOR};

pub fn process(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
    // === PARSE ACCOUNTS ===
    let [vault, owner, system_program, ..] = accounts else {
        return Err(ProgramError::NotEnoughAccountKeys);
    };

    // === MANUAL VALIDATIONS (replacing Anchor constraints) ===

    // Signer check (replaces `Signer<'info>`)
    if !owner.is_signer() {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Writable check (replaces `#[account(mut)]`)
    if !vault.is_writable() || !owner.is_writable() {
        return Err(ProgramError::InvalidAccountData);
    }

    // Program check (replaces `Program<'info, System>`)
    if system_program.key() != &pinocchio_system::ID {
        return Err(ProgramError::IncorrectProgramId);
    }

    // PDA derivation (replaces `seeds = [...], bump`)
    let (expected_pda, bump) = Pubkey::find_program_address(
        &[Vault::SEED, owner.key().as_ref()],
        program_id,
    );
    if vault.key() != &expected_pda {
        return Err(ProgramError::InvalidSeeds);
    }

    // === CREATE ACCOUNT (replaces `init`) ===
    let rent = Rent::get()?;
    let lamports = rent.minimum_balance(Vault::LEN);

    CreateAccount {
        from: owner,
        to: vault,
        lamports,
        space: Vault::LEN as u64,
        owner: program_id,
    }
    .invoke_signed(&[&[Vault::SEED, owner.key().as_ref(), &[bump]]])?;

    // === INITIALIZE DATA ===
    let vault_data = Vault::from_account_mut(vault)?;
    vault_data.discriminator = VAULT_DISCRIMINATOR;
    vault_data.owner = owner.key().to_bytes();
    vault_data.balance = 0;
    vault_data.bump = bump;

    Ok(())
}
```

---

## 4. Constraint Mapping

| Anchor Constraint | Pinocchio Equivalent |
|-------------------|---------------------|
| `#[account(signer)]` | `if !account.is_signer() { return Err(...) }` |
| `#[account(mut)]` | `if !account.is_writable() { return Err(...) }` |
| `#[account(owner = program)]` | `if account.owner() != &program_id { return Err(...) }` |
| `#[account(init, payer, space)]` | `CreateAccount { ... }.invoke()` + manual init |
| `#[account(seeds, bump)]` | `Pubkey::find_program_address()` validation |
| `#[account(close = dest)]` | Manual lamport transfer + data zeroing |
| `#[account(constraint = expr)]` | `if !expr { return Err(...) }` |
| `#[account(has_one = field)]` | Manual field comparison |
| `Signer<'info>` | `is_signer()` check |
| `Account<'info, T>` | Manual deserialization + owner check |
| `Program<'info, T>` | Key comparison with program ID |

---

## 5. CPI Conversion

### Anchor CPI

```rust
use anchor_lang::system_program::{transfer, Transfer};

let cpi_ctx = CpiContext::new(
    ctx.accounts.system_program.to_account_info(),
    Transfer {
        from: ctx.accounts.from.to_account_info(),
        to: ctx.accounts.to.to_account_info(),
    },
);
transfer(cpi_ctx, amount)?;
```

### Pinocchio CPI

```rust
use pinocchio_system::instructions::Transfer;

Transfer {
    from: source,
    to: destination,
    lamports: amount,
}
.invoke()?;
```

### Anchor CPI with PDA Signer

```rust
let seeds = &[b"vault", owner.key.as_ref(), &[bump]];
let signer_seeds = &[&seeds[..]];

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

### Pinocchio CPI with PDA Signer

```rust
Transfer {
    from: vault_pda,
    to: destination,
    lamports: amount,
}
.invoke_signed(&[&[b"vault", owner.key().as_ref(), &[bump]]])?;
```

---

## 6. Error Handling

### Anchor Errors

```rust
#[error_code]
pub enum VaultError {
    #[msg("Insufficient balance")]
    InsufficientBalance,
    #[msg("Invalid owner")]
    InvalidOwner,
}

// Usage
require!(balance >= amount, VaultError::InsufficientBalance);
```

### Pinocchio Errors

```rust
use pinocchio::program_error::ProgramError;

#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum VaultError {
    InsufficientBalance = 0,
    InvalidOwner = 1,
}

impl From<VaultError> for ProgramError {
    fn from(e: VaultError) -> Self {
        ProgramError::Custom(e as u32)
    }
}

// Usage
if balance < amount {
    return Err(VaultError::InsufficientBalance.into());
}
```

---

## 7. Entrypoint Conversion

### Anchor Entrypoint

```rust
use anchor_lang::prelude::*;

declare_id!("...");

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

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        // ...
    }

    pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
        // ...
    }
}
```

### Pinocchio Entrypoint

```rust
use pinocchio::{
    account_info::AccountInfo,
    entrypoint,
    program_error::ProgramError,
    pubkey::Pubkey,
    ProgramResult,
};

pinocchio::declare_id!("...");

pub const INITIALIZE: u8 = 0;
pub const DEPOSIT: u8 = 1;

#[cfg(feature = "bpf-entrypoint")]
entrypoint!(process_instruction);

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let (discriminator, data) = instruction_data
        .split_first()
        .ok_or(ProgramError::InvalidInstructionData)?;

    match discriminator {
        &INITIALIZE => instructions::initialize::process(program_id, accounts),
        &DEPOSIT => {
            let amount = u64::from_le_bytes(
                data[..8].try_into().map_err(|_| ProgramError::InvalidInstructionData)?
            );
            instructions::deposit::process(program_id, accounts, amount)
        }
        _ => Err(ProgramError::InvalidInstructionData),
    }
}
```

---

## 8. IDL Generation

### Anchor IDL

Anchor auto-generates IDL from macros.

### Pinocchio IDL with Shank

```rust
use shank::{ShankAccount, ShankInstruction};

#[derive(ShankAccount)]
pub enum AccountType {
    Vault,
}

#[derive(ShankInstruction)]
pub enum ProgramInstruction {
    /// Initialize a new vault
    #[account(0, writable, name = "vault", desc = "Vault PDA")]
    #[account(1, writable, signer, name = "owner", desc = "Vault owner")]
    #[account(2, name = "system_program", desc = "System Program")]
    Initialize,

    /// Deposit SOL into vault
    #[account(0, writable, name = "vault", desc = "Vault PDA")]
    #[account(1, writable, signer, name = "depositor", desc = "Depositor")]
    #[account(2, name = "system_program", desc = "System Program")]
    Deposit { amount: u64 },
}
```

Generate IDL:
```bash
shank idl -o idl.json -p src/lib.rs
```

---

## 9. Testing Conversion

### Anchor Test

```typescript
import * as anchor from "@coral-xyz/anchor";

describe("vault", () => {
  const provider = anchor.AnchorProvider.env();
  const program = anchor.workspace.Vault;

  it("initializes", async () => {
    await program.methods
      .initialize()
      .accounts({
        vault: vaultPda,
        owner: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      })
      .rpc();
  });
});
```

### Pinocchio Test (using solana-sdk)

```typescript
import {
  Connection,
  Keypair,
  PublicKey,
  Transaction,
  TransactionInstruction,
  SystemProgram,
} from "@solana/web3.js";

describe("vault", () => {
  it("initializes", async () => {
    const [vaultPda] = PublicKey.findProgramAddressSync(
      [Buffer.from("vault"), owner.publicKey.toBuffer()],
      programId
    );

    const ix = new TransactionInstruction({
      programId,
      keys: [
        { pubkey: vaultPda, isSigner: false, isWritable: true },
        { pubkey: owner.publicKey, isSigner: true, isWritable: true },
        { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
      ],
      data: Buffer.from([0]), // Discriminator for Initialize
    });

    const tx = new Transaction().add(ix);
    await sendAndConfirmTransaction(connection, tx, [owner]);
  });
});
```

---

## 10. Common Pitfalls

### 1. Forgetting Discriminator

```rust
// BAD: No discriminator
pub struct Vault {
    pub owner: [u8; 32],
}

// GOOD: Explicit discriminator
pub struct Vault {
    pub discriminator: u8,  // First field
    pub owner: [u8; 32],
}
```

### 2. Wrong Padding

```rust
// BAD: Misaligned struct
pub struct Bad {
    pub a: u8,
    pub b: u64,  // Not aligned!
}

// GOOD: Properly padded
#[repr(C)]
pub struct Good {
    pub a: u8,
    pub _padding: [u8; 7],
    pub b: u64,
}
```

### 3. Missing Owner Check

```rust
// BAD: No owner verification
let vault = Vault::from_account(vault_account)?;

// GOOD: Verify program owns account
if vault_account.owner() != program_id {
    return Err(ProgramError::IllegalOwner);
}
let vault = Vault::from_account(vault_account)?;
```

### 4. Forgetting `.invoke()` or `.invoke_signed()`

```rust
// BAD: CPI struct without invocation
Transfer { from, to, lamports };

// GOOD: Actually invoke the CPI
Transfer { from, to, lamports }.invoke()?;
```

---

## Migration Effort Estimate

| Program Complexity | Anchor LOC | Pinocchio LOC | Effort |
|-------------------|------------|---------------|--------|
| Simple (1-3 instructions) | ~200 | ~400 | 1-2 days |
| Medium (5-10 instructions) | ~500 | ~1000 | 3-5 days |
| Complex (15+ instructions) | ~1500 | ~3000 | 1-2 weeks |

The LOC increase is due to manual validation that Anchor macros provide automatically. However, the performance gains often justify the extra code.
