n part: 1, We have explained PDA fundamentals and its advantages.
In this article, we will explain practical knowledge of PDA along with example.
How to derive a PDA?
There is 3 main steps to derive a PDA:
– Program ID
– Seed
– Bump seed
1. Program ID
Program ID is the account address of the program. It’s a 32 bytes array. It’s unique to each program.
2. Seed
Seed is a string of bytes that is passed to the program when it is invoked. seed can be any bytes array. It’s not unique to each program.
3. Bump seed
Bump seed is a number that is used to derive a PDA. It’s a number between 0 and 255. To prevent PDAs from having private keys, a special bump seed is used to “bump” the hash result of the curve
What problems do PDAs solve?
A Solana program cannot use its private key to sign a transaction on its own behalf, because the key itself would be stored on-chain, making it visible to everyone. If this happens, the private key could be used to sign transactions on behalf of the program and change the profile picture of any user.
Imagine that a program was responsible for handling millions of SOL tokens. Such an exploit would become a major hack. Program Derived Addresses solve this.
Creating a PDA example Program.
use anchor_lang::prelude::*;
#[constant]
pub const VOTE_TAG: &[u8] = b"VOTE_STATE";
#[constant]
pub const USER_TAG: &[u8] = b"USER_STATE";
#[account]
#[derive(Default)]
pub struct UserProfile {
pub authority: Pubkey,
pub last_ref: u8,
}
#[account]
#[derive(Default)]
pub struct VoteAccount {
pub authority: Pubkey,
pub question: String,
pub options: Vec<String>,
}
declare_id!("7C7P5ahRMegnPa3ruNeqRyUi3eFPhf5EDyQkA6qiL5FC");
#[program]
mod voting {
use super::*;
pub fn initialize_user(ctx: Context<InitializeUser>) -> Result<()> {
let user_profile = &mut ctx.accounts.user_profile;
user_profile.authority = ctx.accounts.authority.key();
user_profile.last_ref = 0;
Ok(())
}
pub fn create_vote_topic(
ctx: Context<AddVote>,
question: String,
options: Vec<String>,
) -> Result<()> {
let vote_account = &mut ctx.accounts.vote_account;
let user_profile = &mut ctx.accounts.user_profile;
vote_account.authority = ctx.accounts.authority.key();
vote_account.question = question;
vote_account.options = options;
user_profile.last_ref = user_profile.last_ref.wrapping_add(1);
Ok(())
}
}
#[derive(Accounts)]
#[instruction()]
pub struct InitializeUser<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(init, seeds = [USER_TAG, authority.key().as_ref()],
bump, payer = authority,
space = 8 + std::mem::size_of::<UserProfile>(),
)]
pub user_profile: Box<Account<'info, UserProfile>>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
#[instruction()]
pub struct AddVote<'info> {
#[account( mut, seeds = [USER_TAG, authority.key().as_ref()],
bump, has_one = authority,
)]
pub user_profile: Box<Account<'info, UserProfile>>,
#[account( init, seeds = [VOTE_TAG, authority.key().as_ref(), &user_profile.last_ref.to_le_bytes()], bump,
payer = authority,
space = std::mem::size_of::<VoteAccount>() + 8,
)]
pub vote_account: Box<Account<'info, VoteAccount>>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
1). Initialize the user account
To initialize the user account, we need to pass the InitializeUser instructions to the program. The InitializeUser instruction requires the following accounts:
– authority – authority refers to the user’s wallet address
– user_profile – The user account
2). Create a vote
To create a vote, we need to pass the AddVote instruction to the program. The AddVote instruction requires the following accounts:
– authority – authority refers to the user’s wallet address
– user_profile – The user account
– vote_account – The vote account
we are using the user_profile.last_ref as a seed to create the vote account. This is to ensure that the vote account is unique for each user.
The PDA is created using the following code:
#[account( init,
seeds = [VOTE_TAG, authority.key().as_ref(), &user_profile.last_ref.to_le_bytes()],
bump,
payer = authority,
space = std::mem::size_of::<VoteAccount>() + 8,
)]
pub vote_account: Box<Account<'info, VoteAccount>>,
The unique PDA is created using the following seeds:
– VOTE_TAG – The tag to identify the PDA
– authority.key().as_ref() – The authority address
– user_profile.last_ref.to_le_bytes() – The last_ref value of the user account
Conclusion
PDAs are addresses which don’t have any private key and the program can sign for it. It helps to create on-chain hash-maps like structures without maintaining any mapping. PDAs are unique to solana and it’s very important to understand them to create complex programs on solana.
Leave a Reply