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评论加载中…