Files
gh-tenequm-claude-plugins-s…/skills/solana-development/references/builtin-programs.md
2025-11-30 09:01:25 +08:00

932 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Built-in Programs
This reference provides comprehensive coverage of Solana's built-in programs for native Rust development, focusing on the System Program and Compute Budget Program.
## Table of Contents
1. [Overview of Built-in Programs](#overview-of-built-in-programs)
2. [System Program](#system-program)
3. [Compute Budget Program](#compute-budget-program)
4. [Other Built-in Programs](#other-built-in-programs)
5. [CPI Patterns](#cpi-patterns)
6. [Best Practices](#best-practices)
---
## Overview of Built-in Programs
**Built-in programs** (also called native programs) are fundamental Solana programs that provide core blockchain functionality.
### Key Built-in Programs
| Program | Program ID | Purpose |
|---------|-----------|---------|
| **System Program** | `11111111111111111111111111111111` | Account creation, transfers, allocation |
| **Compute Budget** | `ComputeBudget111111111111111111111111111111` | CU limits, heap size, priority fees |
| **BPF Loader** | Various | Loading and executing programs |
| **Config Program** | `Config1111111111111111111111111111111111111` | Validator configuration |
| **Stake Program** | `Stake11111111111111111111111111111111111111` | Staking and delegation |
| **Vote Program** | `Vote111111111111111111111111111111111111111` | Validator voting |
This reference focuses on the two most commonly used in program development: **System Program** and **Compute Budget Program**.
---
## System Program
**Program ID:** `solana_program::system_program::ID` (`11111111111111111111111111111111`)
The System Program is responsible for account creation, lamport transfers, and account management.
### Core Functionality
1. **Create accounts** (regular and PDAs)
2. **Transfer lamports** between accounts
3. **Allocate space** for account data
4. **Assign ownership** to programs
5. **Create nonce accounts** for durable transactions
### System Program Instructions
```rust
use solana_program::system_instruction;
pub enum SystemInstruction {
CreateAccount, // Create new account
Assign, // Assign account to program
Transfer, // Transfer lamports
CreateAccountWithSeed,// Create account with seed
AdvanceNonceAccount, // Advance nonce
WithdrawNonceAccount, // Withdraw from nonce
InitializeNonceAccount, // Initialize nonce
Allocate, // Allocate account space
AllocateWithSeed, // Allocate with seed
AssignWithSeed, // Assign with seed
TransferWithSeed, // Transfer with seed
UpgradeNonceAccount, // Upgrade nonce (v4)
}
```
---
### CreateAccount
**Creates a new account with lamports and data space.**
#### Function Signature
```rust
pub fn create_account(
from_pubkey: &Pubkey, // Funding account (must be signer)
to_pubkey: &Pubkey, // New account address
lamports: u64, // Lamports to fund account
space: u64, // Bytes of data space
owner: &Pubkey, // Program that will own the account
) -> Instruction
```
#### Usage in Native Rust
```rust
use solana_program::{
system_instruction,
program::invoke,
};
pub fn create_new_account(
payer: &AccountInfo,
new_account: &AccountInfo,
system_program: &AccountInfo,
program_id: &Pubkey,
) -> ProgramResult {
let space = 100; // Account data size
let rent = Rent::get()?;
let lamports = rent.minimum_balance(space);
let create_account_ix = system_instruction::create_account(
payer.key,
new_account.key,
lamports,
space as u64,
program_id,
);
invoke(
&create_account_ix,
&[
payer.clone(),
new_account.clone(),
system_program.clone(),
],
)?;
msg!("Created account with {} bytes", space);
Ok(())
}
```
#### Creating PDA Accounts
```rust
use solana_program::program::invoke_signed;
pub fn create_pda_account(
payer: &AccountInfo,
pda_account: &AccountInfo,
system_program: &AccountInfo,
program_id: &Pubkey,
seeds: &[&[u8]],
bump: u8,
) -> ProgramResult {
// Verify PDA
let (expected_pda, _bump) = Pubkey::find_program_address(seeds, program_id);
if expected_pda != *pda_account.key {
return Err(ProgramError::InvalidSeeds);
}
let space = 200;
let rent = Rent::get()?;
let lamports = rent.minimum_balance(space);
let create_account_ix = system_instruction::create_account(
payer.key,
pda_account.key,
lamports,
space as u64,
program_id,
);
// Create full seeds with bump
let mut full_seeds = seeds.to_vec();
full_seeds.push(&[bump]);
let signer_seeds: &[&[&[u8]]] = &[&full_seeds];
invoke_signed(
&create_account_ix,
&[payer.clone(), pda_account.clone(), system_program.clone()],
signer_seeds,
)?;
msg!("Created PDA account at {}", pda_account.key);
Ok(())
}
```
---
### Transfer
**Transfers lamports from one account to another.**
#### Function Signature
```rust
pub fn transfer(
from_pubkey: &Pubkey, // Source account (must be signer)
to_pubkey: &Pubkey, // Destination account
lamports: u64, // Amount to transfer
) -> Instruction
```
#### Usage in Native Rust
```rust
pub fn transfer_lamports(
from: &AccountInfo,
to: &AccountInfo,
system_program: &AccountInfo,
amount: u64,
) -> ProgramResult {
let transfer_ix = system_instruction::transfer(
from.key,
to.key,
amount,
);
invoke(
&transfer_ix,
&[from.clone(), to.clone(), system_program.clone()],
)?;
msg!("Transferred {} lamports from {} to {}",
amount, from.key, to.key);
Ok(())
}
```
#### Transfer from PDA
```rust
pub fn transfer_from_pda(
pda: &AccountInfo,
to: &AccountInfo,
system_program: &AccountInfo,
amount: u64,
seeds: &[&[u8]],
bump: u8,
) -> ProgramResult {
let transfer_ix = system_instruction::transfer(
pda.key,
to.key,
amount,
);
let mut full_seeds = seeds.to_vec();
full_seeds.push(&[bump]);
let signer_seeds: &[&[&[u8]]] = &[&full_seeds];
invoke_signed(
&transfer_ix,
&[pda.clone(), to.clone(), system_program.clone()],
signer_seeds,
)?;
Ok(())
}
```
---
### Allocate
**Allocates space for an account's data.**
#### Function Signature
```rust
pub fn allocate(
pubkey: &Pubkey, // Account to allocate (must be signer)
space: u64, // Bytes to allocate
) -> Instruction
```
#### Usage in Native Rust
```rust
pub fn allocate_account_space(
account: &AccountInfo,
system_program: &AccountInfo,
space: u64,
) -> ProgramResult {
let allocate_ix = system_instruction::allocate(
account.key,
space,
);
invoke(
&allocate_ix,
&[account.clone(), system_program.clone()],
)?;
msg!("Allocated {} bytes for account", space);
Ok(())
}
```
**⚠️ Note:** The account must be owned by the System Program before allocating. Most programs use `create_account` instead, which combines allocation with ownership assignment.
---
### Assign
**Assigns an account to a program (changes owner).**
#### Function Signature
```rust
pub fn assign(
pubkey: &Pubkey, // Account to assign (must be signer)
owner: &Pubkey, // New owner program
) -> Instruction
```
#### Usage in Native Rust
```rust
pub fn assign_to_program(
account: &AccountInfo,
system_program: &AccountInfo,
new_owner: &Pubkey,
) -> ProgramResult {
let assign_ix = system_instruction::assign(
account.key,
new_owner,
);
invoke(
&assign_ix,
&[account.clone(), system_program.clone()],
)?;
msg!("Assigned account to program {}", new_owner);
Ok(())
}
```
**⚠️ Note:** Most programs use `create_account` which handles assignment during creation.
---
### Complete Example: Account Lifecycle
```rust
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program::invoke_signed,
pubkey::Pubkey,
system_instruction,
sysvar::{rent::Rent, Sysvar},
};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct UserData {
pub user: Pubkey,
pub balance: u64,
pub created_at: i64,
}
pub fn create_user_account(
program_id: &Pubkey,
accounts: &[AccountInfo],
user_pubkey: Pubkey,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let payer = next_account_info(account_info_iter)?;
let user_account = next_account_info(account_info_iter)?;
let system_program = next_account_info(account_info_iter)?;
// 1. Derive PDA
let seeds = &[b"user", user_pubkey.as_ref()];
let (pda, bump) = Pubkey::find_program_address(seeds, program_id);
if pda != *user_account.key {
return Err(ProgramError::InvalidSeeds);
}
// 2. Calculate space and rent
let space = std::mem::size_of::<UserData>();
let rent = Rent::get()?;
let lamports = rent.minimum_balance(space);
// 3. Create account via System Program CPI
let create_ix = system_instruction::create_account(
payer.key,
user_account.key,
lamports,
space as u64,
program_id,
);
let signer_seeds: &[&[&[u8]]] = &[&[b"user", user_pubkey.as_ref(), &[bump]]];
invoke_signed(
&create_ix,
&[payer.clone(), user_account.clone(), system_program.clone()],
signer_seeds,
)?;
// 4. Initialize account data
let clock = Clock::get()?;
let user_data = UserData {
user: user_pubkey,
balance: 0,
created_at: clock.unix_timestamp,
};
user_data.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
msg!("Created user account for {}", user_pubkey);
Ok(())
}
```
---
## Compute Budget Program
**Program ID:** `solana_program::compute_budget::ID` (`ComputeBudget111111111111111111111111111111`)
The Compute Budget Program allows transactions to request specific compute unit limits, heap sizes, and priority fees.
### Core Functionality
1. **Set compute unit limit** - Maximum CUs for transaction
2. **Set compute unit price** - Priority fee per CU
3. **Request heap size** - Heap memory allocation
### Compute Budget Instructions
```rust
use solana_program::compute_budget::ComputeBudgetInstruction;
pub enum ComputeBudgetInstruction {
RequestUnitsDeprecated, // Deprecated
RequestHeapFrame(u32), // Request heap frame (bytes)
SetComputeUnitLimit(u32), // Set max CUs
SetComputeUnitPrice(u64), // Set priority fee (microlamports per CU)
SetLoadedAccountsDataSizeLimit(u32), // Set loaded accounts data limit
}
```
---
### SetComputeUnitLimit
**Sets the maximum compute units available to the transaction.**
#### Function Signature
```rust
pub fn set_compute_unit_limit(units: u32) -> Instruction
```
#### Default Limits
- **Default per instruction:** 200,000 CUs
- **Default per transaction:** 1,400,000 CUs (with requested CU limit)
- **Maximum:** 1,400,000 CUs
#### Usage in Native Rust
**Important:** Compute Budget instructions are added to the transaction by the **client**, not inside the program.
**Client-side example (for reference):**
```rust
// This code runs CLIENT-SIDE, not in the program
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
transaction::Transaction,
};
let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(400_000);
let transaction = Transaction::new_signed_with_payer(
&[
compute_budget_ix, // Must be first
your_program_ix,
],
Some(&payer.pubkey()),
&[&payer],
recent_blockhash,
);
```
**⚠️ Note:** Programs cannot modify their own compute budget. These instructions must be added client-side before sending the transaction.
---
### SetComputeUnitPrice
**Sets the priority fee per compute unit (for transaction prioritization).**
#### Function Signature
```rust
pub fn set_compute_unit_price(microlamports: u64) -> Instruction
```
#### Priority Fee Calculation
```
Total Priority Fee = (CUs Used × microlamports) / 1,000,000
```
**Example:**
- CUs used: 50,000
- Price: 10,000 microlamports per CU
- Fee: (50,000 × 10,000) / 1,000,000 = 500 lamports
#### Usage (Client-side)
```rust
// Client-side code
let compute_unit_price_ix = ComputeBudgetInstruction::set_compute_unit_price(20_000);
let transaction = Transaction::new_signed_with_payer(
&[
compute_unit_price_ix, // Set priority fee
your_program_ix,
],
Some(&payer.pubkey()),
&[&payer],
recent_blockhash,
);
```
**Use cases:**
- High-priority transactions (arbitrage, liquidations)
- Congested network periods
- Time-sensitive operations
---
### RequestHeapFrame
**Requests additional heap memory for the transaction.**
#### Function Signature
```rust
pub fn request_heap_frame(bytes: u32) -> Instruction
```
#### Default Heap
- **Default:** 32 KB
- **Maximum:** 256 KB
#### Usage (Client-side)
```rust
// Client-side code
let heap_size_ix = ComputeBudgetInstruction::request_heap_frame(256 * 1024); // 256 KB
let transaction = Transaction::new_signed_with_payer(
&[
heap_size_ix, // Request more heap
your_program_ix,
],
Some(&payer.pubkey()),
&[&payer],
recent_blockhash,
);
```
**When to use:**
- Large data structures
- Complex deserialization
- Temporary buffers
**⚠️ Cost:** Requesting heap increases CU consumption.
---
### SetLoadedAccountsDataSizeLimit
**Sets the maximum total size of loaded account data.**
#### Function Signature
```rust
pub fn set_loaded_accounts_data_size_limit(bytes: u32) -> Instruction
```
#### Default Limit
- **Default:** 64 MB per transaction
#### Usage (Client-side)
```rust
// Client-side code
let accounts_data_limit_ix =
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(128 * 1024 * 1024);
let transaction = Transaction::new_signed_with_payer(
&[
accounts_data_limit_ix,
your_program_ix,
],
Some(&payer.pubkey()),
&[&payer],
recent_blockhash,
);
```
**Use cases:**
- Transactions with many large accounts
- Bulk processing operations
---
### Complete Client-side Example
```rust
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
transaction::Transaction,
signature::{Keypair, Signer},
pubkey::Pubkey,
};
pub fn build_optimized_transaction(
payer: &Keypair,
program_id: &Pubkey,
program_ix_data: &[u8],
accounts: Vec<AccountMeta>,
recent_blockhash: Hash,
) -> Transaction {
// 1. Set compute unit limit (if default 200k is insufficient)
let compute_limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(300_000);
// 2. Set priority fee (for faster processing)
let compute_price_ix = ComputeBudgetInstruction::set_compute_unit_price(10_000);
// 3. Request additional heap if needed
let heap_size_ix = ComputeBudgetInstruction::request_heap_frame(128 * 1024); // 128 KB
// 4. Your program instruction
let program_ix = Instruction {
program_id: *program_id,
accounts,
data: program_ix_data.to_vec(),
};
// 5. Build transaction (compute budget instructions FIRST)
Transaction::new_signed_with_payer(
&[
compute_limit_ix,
compute_price_ix,
heap_size_ix,
program_ix,
],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
)
}
```
---
## Other Built-in Programs
### BPF Loader
**Purpose:** Loads and executes Solana programs.
**Program IDs:**
- `BPFLoader1111111111111111111111111111111111` (deprecated)
- `BPFLoader2111111111111111111111111111111111` (upgradeable)
- `BPFLoaderUpgradeab1e11111111111111111111111` (current)
**Usage:** Primarily used by the runtime. Programs rarely interact with BPF Loader directly.
### Stake Program
**Program ID:** `Stake11111111111111111111111111111111111111`
**Purpose:** Staking SOL to validators.
**Common operations:**
- Create stake accounts
- Delegate stake
- Deactivate stake
- Withdraw stake
**Use case:** Staking pools, liquid staking protocols.
### Vote Program
**Program ID:** `Vote111111111111111111111111111111111111111`
**Purpose:** Validator voting and consensus.
**Use case:** Validator operations, rarely used by general programs.
---
## CPI Patterns
### System Program CPI Pattern
**Standard pattern for calling System Program:**
```rust
use solana_program::{
program::invoke,
system_instruction,
};
pub fn system_program_cpi(
from: &AccountInfo,
to: &AccountInfo,
system_program: &AccountInfo,
) -> ProgramResult {
// 1. Verify System Program
if system_program.key != &solana_program::system_program::ID {
return Err(ProgramError::IncorrectProgramId);
}
// 2. Create instruction
let ix = system_instruction::transfer(from.key, to.key, 1_000_000);
// 3. Invoke
invoke(&ix, &[from.clone(), to.clone(), system_program.clone()])?;
Ok(())
}
```
### PDA Signing Pattern
**When PDAs need to sign:**
```rust
pub fn pda_system_cpi(
pda: &AccountInfo,
to: &AccountInfo,
system_program: &AccountInfo,
program_id: &Pubkey,
seeds: &[&[u8]],
bump: u8,
) -> ProgramResult {
// 1. Verify PDA
let (expected_pda, _) = Pubkey::find_program_address(seeds, program_id);
if expected_pda != *pda.key {
return Err(ProgramError::InvalidSeeds);
}
// 2. Create instruction
let ix = system_instruction::transfer(pda.key, to.key, 500_000);
// 3. Prepare signer seeds
let mut full_seeds = seeds.to_vec();
full_seeds.push(&[bump]);
let signer_seeds: &[&[&[u8]]] = &[&full_seeds];
// 4. Invoke with PDA signature
invoke_signed(
&ix,
&[pda.clone(), to.clone(), system_program.clone()],
signer_seeds,
)?;
Ok(())
}
```
### Validation Pattern
**Always validate accounts before CPI:**
```rust
pub fn safe_system_cpi(
from: &AccountInfo,
to: &AccountInfo,
system_program: &AccountInfo,
amount: u64,
) -> ProgramResult {
// ✅ Validate System Program
if system_program.key != &solana_program::system_program::ID {
msg!("Invalid System Program");
return Err(ProgramError::IncorrectProgramId);
}
// ✅ Validate signer
if !from.is_signer {
msg!("From account must be signer");
return Err(ProgramError::MissingRequiredSignature);
}
// ✅ Validate sufficient balance
if from.lamports() < amount {
msg!("Insufficient balance");
return Err(ProgramError::InsufficientFunds);
}
// Execute CPI
let ix = system_instruction::transfer(from.key, to.key, amount);
invoke(&ix, &[from.clone(), to.clone(), system_program.clone()])?;
Ok(())
}
```
---
## Best Practices
### 1. Always Validate Program IDs
```rust
// ✅ Validate before CPI
if system_program.key != &solana_program::system_program::ID {
return Err(ProgramError::IncorrectProgramId);
}
```
### 2. Use Rent Exemption
```rust
// ✅ Always create accounts with rent exemption
let rent = Rent::get()?;
let lamports = rent.minimum_balance(space);
// ❌ Don't use arbitrary amounts
let lamports = 1_000_000; // May not be rent-exempt!
```
### 3. Verify PDA Before Creation
```rust
// ✅ Verify PDA derivation
let (expected_pda, bump) = Pubkey::find_program_address(seeds, program_id);
if expected_pda != *pda_account.key {
return Err(ProgramError::InvalidSeeds);
}
```
### 4. Use invoke_signed for PDAs
```rust
// ✅ PDAs sign with invoke_signed
invoke_signed(&ix, accounts, signer_seeds)?;
// ❌ Regular invoke won't work for PDA signers
invoke(&ix, accounts)?; // Fails if PDA needs to sign
```
### 5. Set Compute Budget Client-side
```rust
// ✅ Add compute budget instructions in client
let ixs = vec![
ComputeBudgetInstruction::set_compute_unit_limit(400_000),
your_program_ix,
];
// ❌ Cannot set from within program
// Programs cannot modify their own compute budget
```
### 6. Order Compute Budget Instructions First
```rust
// ✅ Compute budget instructions FIRST
let ixs = vec![
compute_limit_ix,
compute_price_ix,
heap_size_ix,
program_ix,
];
// ❌ Wrong order - may not apply
let ixs = vec![
program_ix,
compute_limit_ix, // Too late!
];
```
### 7. Check Account Ownership Before Transfer
```rust
// ✅ Validate ownership for security
if from_account.owner != &solana_program::system_program::ID {
msg!("Can only transfer from System-owned accounts");
return Err(ProgramError::IllegalOwner);
}
```
---
## Summary
**Key Takeaways:**
1. **System Program** handles account creation, transfers, and allocation
2. **Compute Budget Program** instructions are added **client-side**, not in programs
3. **Always validate** program IDs before CPI
4. **Use rent exemption** when creating accounts
5. **PDAs require invoke_signed** for signing operations
**Most Common Operations:**
| Operation | Instruction | Use Case |
|-----------|------------|----------|
| Create account | `create_account` | New program accounts |
| Transfer lamports | `transfer` | SOL transfers |
| Set CU limit | `set_compute_unit_limit` | High-CU transactions |
| Set priority fee | `set_compute_unit_price` | Fast transaction processing |
| Request heap | `request_heap_frame` | Large data operations |
**System Program CPI Template:**
```rust
// Validate
if system_program.key != &solana_program::system_program::ID {
return Err(ProgramError::IncorrectProgramId);
}
// Create instruction
let ix = system_instruction::transfer(from.key, to.key, amount);
// Invoke (or invoke_signed for PDAs)
invoke(&ix, &[from.clone(), to.clone(), system_program.clone()])?;
```
**Compute Budget Client Template:**
```rust
// Client-side
let ixs = vec![
ComputeBudgetInstruction::set_compute_unit_limit(300_000),
ComputeBudgetInstruction::set_compute_unit_price(10_000),
your_program_ix,
];
```
Master these built-in programs for efficient account management and transaction optimization in production Solana programs.