Initial commit
This commit is contained in:
931
skills/solana-development/references/builtin-programs.md
Normal file
931
skills/solana-development/references/builtin-programs.md
Normal file
@@ -0,0 +1,931 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user