合约-Day 02 - Solana基础知识

1 阅读3分钟

Day 02 - Solana基础知识

日期: 第2天
目标: 理解 Solana 账户模型、PDA 和 Instruction 架构

📋 任务清单

  • 理解 Solana 账户模型
  • 学习 Program Derived Address (PDA)
  • 掌握 Instruction 设计模式
  • 理解 Signer 和 Owner 概念
  • 认识账户生命周期

💻 核心代码

1. 账户模型基础

// Solana 账户包含:
// 1. pubkey - 账户地址
// 2. lamports - SOL 余额(1 SOL = 10^9 lamports)
// 3. data - 账户存储的数据
// 4. owner - 账户的所有者(通常是程序)
// 5. executable - 是否是可执行程序
// 6. rent_epoch - 下一个需要支付租金的时期

use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct ExampleAccounts<'info> {
    // 需要签署的账户
    #[account(mut)]
    pub authority: Signer<'info>,
    
    // 程序账户
    #[account(mut, owner = system_program::ID)]
    pub program_account: AccountInfo<'info>,
    
    pub system_program: Program<'info, System>,
}

2. Program Derived Address (PDA)

use anchor_lang::prelude::*;

// PDA 是由程序生成的确定性地址
// 格式:find_program_address(&[seeds], program_id)

#[derive(Accounts)]
#[instruction(name: String)]
pub struct CreateAccount<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    
    // 使用 PDA 创建账户
    #[account(
        init,
        payer = payer,
        space = 8 + 32 + 8,
        seeds = [b"user", name.as_ref()],
        bump
    )]
    pub user_account: Account<'info, UserAccount>,
    
    pub system_program: Program<'info, System>,
}

#[account]
pub struct UserAccount {
    pub authority: Pubkey,
    pub balance: u64,
}

impl<'info> CreateAccount<'info> {
    pub fn process(&mut self, name: String) -> Result<()> {
        let user = &mut self.user_account;
        user.authority = self.payer.key();
        user.balance = 0;
        Ok(())
    }
}

3. Instruction 架构

// Instruction = Context + 指令参数

use anchor_lang::prelude::*;

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

    // 指令 1: 初始化
    pub fn initialize(
        ctx: Context<Initialize>,
        fee: u64,
    ) -> Result<()> {
        ctx.accounts.process(fee)
    }

    // 指令 2: 更新状态
    pub fn update(
        ctx: Context<Update>,
        new_value: u64,
    ) -> Result<()> {
        ctx.accounts.process(new_value)
    }
}

// Context 定义账户需求
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = payer, space = 8 + 32 + 8)]
    pub state: Account<'info, GlobalState>,
    
    #[account(mut)]
    pub payer: Signer<'info>,
    
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut, has_one = authority)]
    pub state: Account<'info, GlobalState>,
    
    pub authority: Signer<'info>,
}

#[account]
pub struct GlobalState {
    pub authority: Pubkey,
    pub fee: u64,
}

impl<'info> Initialize<'info> {
    pub fn process(&mut self, fee: u64) -> Result<()> {
        let state = &mut self.state;
        state.authority = self.payer.key();
        state.fee = fee;
        Ok(())
    }
}

impl<'info> Update<'info> {
    pub fn process(&mut self, new_value: u64) -> Result<()> {
        let state = &mut self.state;
        state.fee = new_value;
        Ok(())
    }
}

4. 权限检查模式

use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct AdminOnly<'info> {
    #[account(
        mut,
        constraint = config.authority == admin.key() @ CustomError::Unauthorized
    )]
    pub config: Account<'info, Config>,
    
    pub admin: Signer<'info>,
}

#[derive(Accounts)]
pub struct TransferOwnership<'info> {
    #[account(mut, has_one = owner)]
    pub vault: Account<'info, Vault>,
    
    pub owner: Signer<'info>,
}

#[account]
pub struct Config {
    pub authority: Pubkey,
}

#[account]
pub struct Vault {
    pub owner: Pubkey,
    pub amount: u64,
}

#[error_code]
pub enum CustomError {
    #[msg("Unauthorized access")]
    Unauthorized,
}

5. 账户生命周期

use anchor_lang::prelude::*;

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

    // 创建账户
    pub fn create(ctx: Context<Create>) -> Result<()> {
        let account = &mut ctx.accounts.state;
        account.initialized = true;
        msg!("Account created: {}", account.key());
        Ok(())
    }

    // 修改账户
    pub fn modify(ctx: Context<Modify>, new_value: u64) -> Result<()> {
        let account = &mut ctx.accounts.state;
        account.value = new_value;
        msg!("Account modified");
        Ok(())
    }

    // 关闭账户(收回租金)
    pub fn close(ctx: Context<Close>) -> Result<()> {
        msg!("Closing account, returning {} lamports to {}",
            ctx.accounts.state.to_account_info().lamports(),
            ctx.accounts.receiver.key()
        );
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Create<'info> {
    #[account(init, payer = payer, space = 8 + 1 + 8)]
    pub state: Account<'info, State>,
    
    #[account(mut)]
    pub payer: Signer<'info>,
    
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Modify<'info> {
    #[account(mut)]
    pub state: Account<'info, State>,
}

#[derive(Accounts)]
pub struct Close<'info> {
    #[account(mut, close = receiver)]
    pub state: Account<'info, State>,
    
    #[account(mut)]
    pub receiver: SystemAccount<'info>,
}

#[account]
pub struct State {
    pub initialized: bool,
    pub value: u64,
}

⚠️ 常见问题

问题1:账户不属于程序

现象: error: Account owner must be the program in order to deserialize as this type

解决方案: 确保账户由程序所有

#[derive(Accounts)]
pub struct MyInstruction<'info> {
    // 正确:指定 owner
    #[account(owner = crate::ID)]
    pub my_account: Account<'info, MyState>,
    
    // 或使用 Anchor 的默认检查(已自动指定)
    #[account]
    pub state: Account<'info, GlobalState>,
}

问题2:PDA 碰撞

现象: 多个不同用户得到相同的 PDA

解决方案: 在 seed 中包含唯一标识符

// 不好:没有唯一标识
#[account(
    seeds = [b"user"],  // 所有用户相同!
    bump
)]

// 好:包含唯一标识
#[account(
    seeds = [b"user", user_pubkey.as_ref()],  // 每个用户不同
    bump
)]

📝 总结

✅ 理解了 Solana 的核心概念:

  • 账户模型和账户结构
  • Program Derived Address (PDA) 的作用
  • Instruction 和 Context 的关系
  • 权限检查和约束模式
  • 账户生命周期管理

下一步: Day 03 将设计合约状态和数据结构


时间估计: 2-3 小时 | 难度: ⭐⭐ 中等 | 关键词: 账户模型、PDA、Instruction、权限管理