本教程将展示如何在 Solana Anchor 中用不同签名者(Signer)初始化和更新账户,并探讨权限控制机制。
此前文章中,我们仅使用单一签名者初始化并修改账户,功能受限。现实场景中,如 Alice 向 Bob 转移积分,需允许 Alice 修改 Bob 创建的账户。本教程将展示如何在 Solana Anchor 中用不同签名者(Signer)初始化和更新账户,并探讨权限控制机制。
初始化账户的 Rust 代码保持不变,使用标准 Anchor 模式创建账户。
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("26Kiu5LSV5xXDN3yGwE8L6aU59kKRKdyyKtSQv5Vu5VC");
#[program]
pub mod other_write {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space=size_of::<MyStorage>() + 8,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyStorage {
x: u64,
}
客户端调整
测试代码引入新签名者,需显式指定:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { OtherWrite } from "../target/types/other_write";
// this airdrops sol to an address
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("other_write", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.OtherWrite as Program<OtherWrite>;
it("Is initialized!", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 1e9); // 1 SOL
let seeds = [];
const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
await program.methods.initialize().accounts({
myStorage: myStorage,
signer: newKeypair.publicKey // ** THIS MUST BE EXPLICITLY SPECIFIED **
}).signers([newKeypair]).rpc();
});
});
为何两次指定签名者?
字段名可自定义(如 fren 替代 signer),只需 Rust 与客户端一致。
Signer<'info> 表示交易签名者,Anchor 自动验证其公钥与签名一致。若使用非默认签名者,需显式传递公钥,否则 Anchor 默认使用环境签名者(provider wallet)。
若签名与公钥不匹配,会触发错误:
以下程序展示 Alice 初始化账户,Bob 更新其值。
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("26Kiu5LSV5xXDN3yGwE8L6aU59kKRKdyyKtSQv5Vu5VC");
#[program]
pub mod other_write {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn update_value(ctx: Context<UpdateValue>, new_value: u64) -> Result<()> {
ctx.accounts.my_storage.x = new_value;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = fren,
space=size_of::<MyStorage>() + 8,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub fren: Signer<'info>, // A public key is passed here
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct UpdateValue<'info> {
#[account(mut, seeds = [], bump)]
pub my_storage: Account<'info, MyStorage>,
// THIS FIELD MUST BE INCLUDED
#[account(mut)]
pub fren: Signer<'info>,
}
#[account]
pub struct MyStorage {
x: u64,
}
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { OtherWrite } from "../target/types/other_write";
// this airdrops sol to an address
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("other_write", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.OtherWrite as Program<OtherWrite>;
it("Is initialized!", async () => {
const alice = anchor.web3.Keypair.generate();
const bob = anchor.web3.Keypair.generate();
const airdrop_alice_tx = await anchor.getProvider().connection.requestAirdrop(alice.publicKey, 1 * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdrop_alice_tx);
const airdrop_alice_bob = await anchor.getProvider().connection.requestAirdrop(bob.publicKey, 1 * anchor.web3.LAMPOINTS_PER_SOL);
await confirmTransaction(airdrop_alice_bob);
let seeds = [];
const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
// ALICE INITIALIZE ACCOUNT
await program.methods.initialize().accounts({
myStorage: myStorage,
fren: alice.publicKey
}).signers([alice]).rpc();
// BOB WRITE TO ACCOUNT
await program.methods.updateValue(new anchor.BN(3)).accounts({
myStorage: myStorage,
fren: bob.publicKey
}).signers([bob]).rpc();
let value = await program.account.myStorage.fetch(myStorage);
console.log(`value stored is ${value.x}`);
});
});
实际应用中,需控制谁可修改账户。以下通过积分系统示例实现权限管理。
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("At8wSp7MnVm39m1zMUNYskn4DjqwsEsxZnMXARjycKBU");
const STARTING_POINTS: u32 = 10;
#[program]
pub mod points {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.player.points = STARTING_POINTS;
ctx.accounts.player.authority = ctx.accounts.signer.key();
Ok(())
}
pub fn transfer_points(ctx: Context<TransferPoints>, amount: u32) -> Result<()> {
require!(ctx.accounts.from.authority == ctx.accounts.signer.key(), Errors::SignerIsNotAuthority);
require!(ctx.accounts.from.points >= amount, Errors::InsufficientPoints);
ctx.accounts.from.points -= amount;
ctx.accounts.to.points += amount;
Ok(())
}
}
#[error_code]
pub enum Errors {
#[msg("SignerIsNotAuthority")]
SignerIsNotAuthority,
#[msg("InsufficientPoints")]
InsufficientPoints
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space = size_of::<Player>() + 8,
seeds = [&(signer.as_ref().key().to_bytes())],
bump)]
player: Account<'info, Player>,
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct TransferPoints<'info> {
#[account(mut)]
from: Account<'info, Player>,
#[account(mut)]
to: Account<'info, Player>,
#[account(mut)]
signer: Signer<'info>,
}
#[account]
pub struct Player {
points: u32,
authority: Pubkey
}
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Points } from "../target/types/points";
// this airdrops sol to an address
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("points", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Points as Program<Points>;
it("Alice transfers points to Bob", async () => {
const alice = anchor.web3.Keypair.generate();
const bob = anchor.web3.Keypair.generate();
const airdrop_alice_tx = await anchor.getProvider().connection.requestAirdrop(alice.publicKey, 1 * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdrop_alice_tx);
const airdrop_alice_bob = await anchor.getProvider().connection.requestAirdrop(bob.publicKey, 1 * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdrop_alice_bob);
let seeds_alice = [alice.publicKey.toBytes()];
const [playerAlice, _bumpA] = anchor.web3.PublicKey.findProgramAddressSync(seeds_alice, program.programId);
let seeds_bob = [bob.publicKey.toBytes()];
const [playerBob, _bumpB] = anchor.web3.PublicKey.findProgramAddressSync(seeds_bob, program.programId);
// Alice and Bob initialize their accounts
await program.methods.initialize().accounts({
player: playerAlice,
signer: alice.publicKey,
}).signers([alice]).rpc();
await program.methods.initialize().accounts({
player: playerBob,
signer: bob.publicKey,
}).signers([bob]).rpc();
// Alice transfers 5 points to Bob. Note that this is a u32
// so we don't need a BigNum
await program.methods.transferPoints(5).accounts({
from: playerAlice,
to: playerBob,
signer: alice.publicKey,
}).signers([alice]).rpc();
console.log(`Alice has ${(await program.account.player.fetch(playerAlice)).points} points`);
console.log(`Bob has ${(await program.account.player.fetch(playerBob)).points} points`)
});
});
替代 require!(authority == signer),用 has_one 验证字段匹配:
#[derive(Accounts)]
pub struct TransferPoints<'info> {
#[account(mut, has_one = authority @ Errors::SignerIsNotAuthority)]
from: Account<'info, Player>,
#[account(mut)]
to: Account<'info, Player>,
authority: Signer<'info>,
}
#[account]
pub struct Player {
points: u32,
authority: Pubkey
}
替代积分检查:
#[derive(Accounts)]
#[instruction(amount: u32)]
pub struct TransferPoints<'info> {
#[account(mut,
has_one = authority @ Errors::SignerIsNotAuthority,
constraint = from.points >= amount @ Errors::InsufficientPoints)]
from: Account<'info, Player>,
#[account(mut)]
to: Account<'info, Player>,
authority: Signer<'info>,
}
【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!