Part:2 Deep Dive Into PDA

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.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *