前言:
前面我们介绍了 solana 关于 anchor 智能合约的环境搭建 配置,简单编写 发布 部署 调用 的一些落地。等等。接下来我们借着案例。详细剖析下智能合约编写。
案例 vote 简介:
这是一个 关于调查投票 的智能合约; 主要功能 记录 基于某个 事情 或 主题,对获选人进行投票。
1. poll 投票 (关于一个事情的调查)
2. candidate 获选人 基于poll_id 和 candidate_name ,
智能合约功能分析:
只能合约的结构:instruction
- Initialize_poll 初始化 调查 (给你设置一个 给一个事情设置 poll_id)
- Initialize_candidate 初始化 候选人 (基于 poll_id 和 candidate_name 作为种子 生成 pda )
- vote 投票
很多都是固定写法 和 案例之 favorites ,我们着重 看下不一样的地方。一样的地方大家可以回到 favorites 案例中 详细看下
以下是 智能合约的 方法定义:
- Initialize_poll
rust//初始化 poll pub fn initialize_poll(ctx: Context<InitializePoll>, poll_id:u64, poll_description:String, poll_start:u64, poll_end:u64) -> Result<()> { //#[derive(Accounts)] 通过derive 指令,这里的 poll 以帮只我们创建好了,这里只是做得 赋值操作 let poll = &mut ctx.accounts.poll; poll.poll_id = poll_id; poll.description = poll_description; poll.poll_start = poll_start; poll.poll_end = poll_end; poll.candidate_amount = 0; Ok(()) }
- Initialize_candidate
rust//初始化 获选人 pub fn initialize_candidate(_ctx: Context<InitializeCandidate>, _candidate_name: String, _poll_id: u64, ) -> Result<()> { let candidate = &mut _ctx.accounts.candidate; candidate.candidate_name = _candidate_name; candidate.candidate_votes = 0; let poll = &mut _ctx.accounts.poll; poll.candidate_amount += 1; Ok(()) }
- vote 投票
rust//投票 pub fn vote(_ctx: Context<Vote>,candidate_name:String, _poll_id: u64,) -> Result<()> { let candidate = &mut _ctx.accounts.candidate; candidate.candidate_votes += 1; // msg!("candidate.candidate_votes:{}", candidate.candidate_votes); // msg!("candidate.candidate_name:{}", candidate.candidate_name); // msg!("candidate.candidate_poll_id:{}", _poll_id); Ok(()) }
以下是 智能合约的 接头体定义:(这里的结构体用了一个新姿势 等下着重介绍下)
InitializePoll
instruction 宏 可以从长下文中获取 智能合约 调用参数
rust#[derive(Accounts)] #[instruction(poll_id:u64)] //可以获取上下文中的 传递参数 pub struct InitializePoll<'info> { #[account(mut)] pub signer: Signer<'info>, #[account( init, payer = signer, space = 8 + Poll::INIT_SPACE, seeds = [poll_id.to_le_bytes().as_ref()], bump, )] pub poll: Account<'info,Poll>, pub system_program: Program<'info, System>, } #[account] #[derive(InitSpace)] pub struct Poll { pub poll_id: u64, #[max_len(64)] pub description:String, pub poll_start: u64, pub poll_end: u64, pub candidate_amount: u64, }
InitializeCandidate
rust#[derive(Accounts)] #[instruction(candidate_name: String, poll_id: u64)] //从上下文中的 传递参数 pub struct InitializeCandidate<'info> { #[account(mut)] pub signer: Signer<'info>, //这里区别于创建,这里只是通过 随机数寻找到 对应的引用,所以不需要init 不需要payer //这里 poll 需要通过 智能合约修改 所以需要添加mut #[account( mut, seeds = [poll_id.to_le_bytes().as_ref()], bump, )] pub poll: Account<'info,Poll>, // 这里是需要创建一个 candidate, 是用的是 创建相关的写法 #[account( init, payer = signer, space = 8 + Candidate::INIT_SPACE, seeds = [poll_id.to_le_bytes().as_ref(),candidate_name.as_bytes().as_ref()], bump, )] pub candidate: Account<'info, Candidate>, pub system_program: Program<'info, System>, } #[account] #[derive(InitSpace)] pub struct Candidate{ #[max_len(32)] pub candidate_name: String, pub candidate_votes: u64, }
Vote
rust#[derive(Accounts)] #[instruction(candidate_name: String, poll_id: u64)] //获取智能合约 调用函数的 传参 pub struct Vote<'info>{ //不需要mut,因为这里书需要创建 不用付费 相关,只需要签名 #[account()] pub signer: Signer<'info>, //这里区别于创建,这里只是通过 随机数寻找到 对应的引用,所以不需要init 不需要payer #[account( seeds = [poll_id.to_le_bytes().as_ref()], bump, )] pub poll: Account<'info,Poll>, //这里区别于创建,这里只是通过 随机数寻找到 对应的引用,所以不需要init 不需要payer //这里 candidate 智能合约修改 所以需要添加mut #[account( mut, seeds = [poll_id.to_le_bytes().as_ref(),candidate_name.as_bytes().as_ref()], bump, )] pub candidate: Account<'info, Candidate>, pub system_program: Program<'info, System>, }
总结:
该 案例和 anchor 智能合约 案例之 favorites ,中的 使用差不多一致,具体注释 可以参考前文。
完整脚本
rustuse anchor_lang::prelude::*; declare_id!("8t44emZSpEShL9QtNSLsLEyq1GZFCExwdpfefGR5PjVe"); #[program] pub mod anchor_voting { use super::*; //初始化 poll pub fn initialize_poll(ctx: Context<InitializePoll>, poll_id:u64, poll_description:String, poll_start:u64, poll_end:u64) -> Result<()> { //#[derive(Accounts)] 通过derive 指令,这里的 poll 以帮只我们创建好了,这里只是做得 赋值操作 let poll = &mut ctx.accounts.poll; poll.poll_id = poll_id; poll.description = poll_description; poll.poll_start = poll_start; poll.poll_end = poll_end; poll.candidate_amount = 0; Ok(()) } //初始化 获选人 pub fn initialize_candidate(_ctx: Context<InitializeCandidate>, _candidate_name: String, _poll_id: u64, ) -> Result<()> { let candidate = &mut _ctx.accounts.candidate; candidate.candidate_name = _candidate_name; candidate.candidate_votes = 0; let poll = &mut _ctx.accounts.poll; poll.candidate_amount += 1; Ok(()) } //投票 pub fn vote(_ctx: Context<Vote>,candidate_name:String, _poll_id: u64,) -> Result<()> { let candidate = &mut _ctx.accounts.candidate; candidate.candidate_votes += 1; // msg!("candidate.candidate_votes:{}", candidate.candidate_votes); // msg!("candidate.candidate_name:{}", candidate.candidate_name); // msg!("candidate.candidate_poll_id:{}", _poll_id); Ok(()) } } #[derive(Accounts)] #[instruction(candidate_name: String, poll_id: u64)] pub struct Vote<'info>{ //不需要mut,因为这里书需要创建 不用付费 相关,只需要签名 #[account()] pub signer: Signer<'info>, //这里区别于创建,这里只是通过 随机数寻找到 对应的引用,所以不需要init 不需要payer #[account( seeds = [poll_id.to_le_bytes().as_ref()], bump, )] pub poll: Account<'info,Poll>, //这里区别于创建,这里只是通过 随机数寻找到 对应的引用,所以不需要init 不需要payer //这里 candidate 智能合约修改 所以需要添加mut #[account( mut, seeds = [poll_id.to_le_bytes().as_ref(),candidate_name.as_bytes().as_ref()], bump, )] pub candidate: Account<'info, Candidate>, pub system_program: Program<'info, System>, } #[derive(Accounts)] #[instruction(candidate_name: String, poll_id: u64)] pub struct InitializeCandidate<'info> { #[account(mut)] pub signer: Signer<'info>, //这里区别于创建,这里只是通过 随机数寻找到 对应的引用,所以不需要init 不需要payer //这里 poll 需要通过 智能合约修改 所以需要添加mut #[account( mut, seeds = [poll_id.to_le_bytes().as_ref()], bump, )] pub poll: Account<'info,Poll>, // 这里是需要创建一个 candidate, 是用的是 创建相关的写法 #[account( init, payer = signer, space = 8 + Candidate::INIT_SPACE, seeds = [poll_id.to_le_bytes().as_ref(),candidate_name.as_bytes().as_ref()], bump, )] pub candidate: Account<'info, Candidate>, pub system_program: Program<'info, System>, } #[account] #[derive(InitSpace)] pub struct Candidate{ #[max_len(32)] pub candidate_name: String, pub candidate_votes: u64, } #[derive(Accounts)] #[instruction(poll_id:u64)] //可以获取上下文中的 传递参数 pub struct InitializePoll<'info> { #[account(mut)] pub signer: Signer<'info>, #[account( init, payer = signer, space = 8 + Poll::INIT_SPACE, seeds = [poll_id.to_le_bytes().as_ref()], bump, )] pub poll: Account<'info,Poll>, pub system_program: Program<'info, System>, } #[account] #[derive(InitSpace)] pub struct Poll { pub poll_id: u64, #[max_len(64)] pub description:String, pub poll_start: u64, pub poll_end: u64, pub candidate_amount: u64, }
测试脚本
rustimport * as anchor from "@coral-xyz/anchor"; import { BankrunProvider, startAnchor } from "anchor-bankrun"; import { Program } from "@coral-xyz/anchor"; import { Keypair,PublicKey } from "@solana/web3.js" import {program} from "@coral-xyz/anchor/dist/cjs/native/system"; import { AnchorVoting } from "../target/types/anchor_voting" const IDL = require("./anchor_voting.json"); const votingAddress = new PublicKey("8t44emZSpEShL9QtNSLsLEyq1GZFCExwdpfefGR5PjVe") describe("anchor-voting", () => { let context; let provider; let votingProgram; before(async ()=>{ context = await startAnchor("",[{name:"anchor_voting",programId:votingAddress}],[]) provider = new BankrunProvider(context); votingProgram = new Program<AnchorVoting>( IDL, provider, ); }) it("initialized! poll", async () => { //初始化 民意调查 await votingProgram.methods.initializePoll( new anchor.BN(1), "投票开始", new anchor.BN(0), new anchor.BN(18212646480) ).rpc(); const [pollAddress] = PublicKey.findProgramAddressSync( [new anchor.BN(1).toArrayLike(Buffer,'le',8)], votingAddress ); const poll = await votingProgram.account.poll.fetch(pollAddress); console.log("pollAddress:",pollAddress) console.log("poll:",poll) }); it("initialized! candidate", async()=>{ //初始化 候选人 await votingProgram.methods.initializeCandidate( "野猪", new anchor.BN(1), ).rpc(); await votingProgram.methods.initializeCandidate( "鸵鸟", new anchor.BN(1), ).rpc(); const [crunchyAddress] = PublicKey.findProgramAddressSync( [new anchor.BN(1).toArrayLike(Buffer,'le',8),Buffer.from("鸵鸟")], votingAddress, ) const crunchyCandidate = await votingProgram.account.candidate.fetch(crunchyAddress); console.log("crunchyAddress:",crunchyAddress) console.log("crunchyCandidate:",crunchyCandidate) const [crunchyAddress1] = PublicKey.findProgramAddressSync( [new anchor.BN(1).toArrayLike(Buffer,'le',8),Buffer.from("野猪")], votingAddress, ) const crunchyCandidate1 = await votingProgram.account.candidate.fetch(crunchyAddress1); console.log("crunchyAddress1:",crunchyAddress1) console.log("crunchyCandidate1:",crunchyCandidate1) }) it("vote", async()=>{ await votingProgram.methods.vote( "鸵鸟", new anchor.BN(1),).rpc(); await votingProgram.methods.vote( "鸵鸟", new anchor.BN(1),).rpc(); const [crunchyAddress] = PublicKey.findProgramAddressSync( [new anchor.BN(1).toArrayLike(Buffer,'le',8),Buffer.from("鸵鸟")], votingAddress, ) const crunchyCandidate = await votingProgram.account.candidate.fetch(crunchyAddress); console.log("crunchyAddress:",crunchyAddress) console.log("crunchyCandidate:",crunchyCandidate) }) });