教程区块链区块链技术ch1717.3 投票智能合约:create_poll 与 cast_vote

本页目录

graph TD
    User[用户 via 钱包] --> F[前端 React<br/>Anchor Provider]
    F --> C1[指令交易 create_poll]
    F --> C2[指令交易 cast_vote]
    F --> C3[指令交易 get_results]
    C1 --> P[Solana 程序<br/>Rust/Anchor]
    C2 --> P
    C3 --> P
    P --> Q[账户数据<br/>投票状态/选项计数]
    Q --> A[后端 RPC/历史索引]
    A --> F
    style P fill:#c8e6c9
    style User fill:#e3f2fd

Anchor 框架自动生成 TypeScript IDL——前端可直接调用合约指令,无需手写原始交易。


17.3.1 数据结构

rust
// programs/vote/src/lib.rs
use anchor_lang::prelude::*;

// 声明 program_id
#[program]
pub mod vote {
    use super::*;
    
    /// 创建新投票
    pub fn create_poll(ctx: Context<CreatePoll>, question: String, options: Vec<String>, end_time: i64) -> Result<()> {
        let vote_account = &mut ctx.accounts.vote_account;
        let clock = Clock::get()?;
        
        vote_account.creator = *ctx.accounts.creator.key;
        vote_account.question = question;
        vote_account.options = options;
        vote_account.votes = vec![0u32; vote_account.options.len()];
        vote_account.end_time = end_time;
        vote_account.is_active = true;
        vote_account.created_at = clock.unix_timestamp;
        
        Ok(())
    }
    
    /// 投一票
    pub fn cast_vote(ctx: Context<CastVote>, option_index: u8) -> Result<()> {
        let vote_account = &mut ctx.accounts.vote_account;
        let clock = Clock::get()?;
        let voter = *ctx.accounts.voter.key;
        
        // 检查投票是否过期
        require!(clock.unix_timestamp < vote_account.end_time, VoteError::PollExpired);
        require!(vote_account.is_active, VoteError::PollInactive);
        
        // 检查选项有效
        require!((option_index as usize) < vote_account.options.len(), VoteError::InvalidOption);
        
        // 防重复投票: 每个地址只能投一次
        if vote_account.has_voted.contains(&voter) {
            return Err(VoteError::AlreadyVoted.into());
        }
        
        // 计票
        vote_account.votes[option_index as usize] += 1;
        vote_account.has_voted.push(voter);
        
        // 触发事件(前端可监听)
        emit!(VoteCast { 
            poll: vote_account.key(),
            voter,
            option: option_index,
            timestamp: clock.unix_timestamp,
        });
        
        Ok(())
    }
    
    /// 结束投票
    pub fn close_poll(ctx: Context<ClosePoll>) -> Result<()> {
        let vote_account = &mut ctx.accounts.vote_account;
        
        // 仅创建者可关闭
        require!(vote_account.creator == *ctx.accounts.creator.key, VoteError::Unauthorized);
        
        vote_account.is_active = false;
        Ok(())
    }
}

/// 数据账户(每个投票一个账户)
#[account]
pub struct VoteAccount {
    pub creator: Pubkey,
    pub question: String,
    pub options: Vec<String>,
    pub votes: Vec<u32>,
    pub end_time: i64,
    pub is_active: bool,
    pub created_at: i64,
    pub has_voted: Vec<Pubkey>,  // 已投票地址
}

/// 创建投票的指令上下文
#[derive(Accounts)]
#[instruction(question: String, options: Vec<String>)]
pub struct CreatePoll<'info> {
    #[account(init, payer = creator, space = 8 + VoteAccount::MAX_SIZE)]
    pub vote_account: Account<'info, VoteAccount>,
    
    #[account(mut)]
    pub creator: Signer<'info>,
    
    pub system_program: Program<'info, System>,
}

/// 投票的指令上下文
#[derive(Accounts)]
pub struct CastVote<'info> {
    #[account(mut)]
    pub vote_account: Account<'info, VoteAccount>,
    
    pub voter: Signer<'info>,
    
    pub system_program: Program<'info, System>,
}

/// 关闭投票的指令上下文
#[derive(Accounts)]
pub struct ClosePoll<'info> {
    #[account(mut, has_one = creator)]
    pub vote_account: Account<'info, VoteAccount>,
    
    pub creator: Signer<'info>,
}

/// 事件
#[event]
pub struct VoteCast {
    pub poll: Pubkey,
    pub voter: Pubkey,
    pub option: u8,
    pub timestamp: i64,
}

/// 错误
#[error_code]
pub enum VoteError {
    #[msg("Poll has expired")]
    PollExpired,
    #[msg("Poll is not active")]
    PollInactive,
    #[msg("Invalid option index")]
    InvalidOption,
    #[msg("You have already voted")]
    AlreadyVoted,
    #[msg("Unauthorized")]
    Unauthorized,
}

// MAX_SIZE 简化计算(实际应精确计算)
impl VoteAccount {
    const MAX_SIZE: usize = 32 + 4 + 256 + 4 + 200 + 8 + 1 + 8 + 5000; // 预留空间
}

17.3.2 关键设计决策

设计原因替代方案
has_voted 数组简单追踪重复投票Merkle 树(大型投票场景)
单个 VoteAccount每个投票一个账户,SOL 存款支付多投票聚合(复杂)
事件日志前端可监听实时更新轮询查询
PDA (未使用)本例用随机 keypair 生成账户PDA 适合确定性地址

17.3.3 测试/部署

bash
# 编译
anchor build

# 测试(本地验证器自动启动)
anchor test

# 部署到 devnet
anchor deploy --provider.cluster devnet
# 记录 program_id,更新 Anchor.toml

, 前往 → 17.4 前端交互 |*

评论

0

评论加载中…

发表评论

0/2000