anchor-book/anchor_in_depth/PDAs.html

477 lines
32 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>PDAs - The Anchor Book v0.29.0</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../introduction/introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../introduction/what_is_anchor.html"><strong aria-hidden="true">1.1.</strong> What is Anchor</a></li><li class="chapter-item expanded "><a href="../introduction/anchor_documentation.html"><strong aria-hidden="true">1.2.</strong> Anchor Documentation</a></li></ol></li><li class="chapter-item expanded "><a href="../prerequisites/prerequisites.html"><strong aria-hidden="true">2.</strong> Prerequisites</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../prerequisites/useful_resources.html"><strong aria-hidden="true">2.1.</strong> Useful Resources</a></li><li class="chapter-item expanded "><a href="../prerequisites/intro_to_solana.html"><strong aria-hidden="true">2.2.</strong> Intro to Solana</a></li></ol></li><li class="chapter-item expanded "><a href="../getting_started/getting_started.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../getting_started/installation.html"><strong aria-hidden="true">3.1.</strong> Installation</a></li><li class="chapter-item expanded "><a href="../getting_started/hello_anchor.html"><strong aria-hidden="true">3.2.</strong> Hello, Anchor!</a></li></ol></li><li class="chapter-item expanded "><a href="../anchor_in_depth/anchor_programs_in-depth.html"><strong aria-hidden="true">4.</strong> Anchor Programs In-Depth</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../anchor_in_depth/essentials.html"><strong aria-hidden="true">4.1.</strong> Essentials</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../anchor_in_depth/high-level_overview.html"><strong aria-hidden="true">4.1.1.</strong> High-level Overview</a></li><li class="chapter-item expanded "><a href="../anchor_in_depth/the_accounts_struct.html"><strong aria-hidden="true">4.1.2.</strong> The Accounts Struct</a></li><li class="chapter-item expanded "><a href="../anchor_in_depth/the_program_module.html"><strong aria-hidden="true">4.1.3.</strong> The Program Module</a></li><li class="chapter-item expanded "><a href="../anchor_in_depth/errors.html"><strong aria-hidden="true">4.1.4.</strong> Errors</a></li><li class="chapter-item expanded "><a href="../anchor_in_depth/milestone_project_tic-tac-toe.html"><strong aria-hidden="true">4.1.5.</strong> Milestone Project - Tic-Tac-Toe</a></li></ol></li><li class="chapter-item expanded "><a href="../anchor_in_depth/intermediate.html"><strong aria-hidden="true">4.2.</strong> Intermediate</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../anchor_in_depth/CPIs.html"><strong aria-hidden="true">4.2.1.</strong> Cross-Program Invocations</a></li><li class="chapter-item expanded "><a href="../anchor_in_depth/PDAs.html" class="active"><strong aria-hidden="true">4.2.2.</strong> PDAs</a></li><li class="chapter-item expanded "><a href="../anchor_in_depth/events.html"><strong aria-hidden="true">4.2.3.</strong> Events</a></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.2.4.</strong> Constants</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.2.5.</strong> Zero-Copy</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.2.6.</strong> Access Control</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.2.7.</strong> Building &amp; Testing</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.2.8.</strong> Milestone Project - The Nightclub</div></li></ol></li></ol></li><li class="chapter-item expanded "><div><strong aria-hidden="true">5.</strong> Anchor BTS</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../anchor_bts/discriminator.html"><strong aria-hidden="true">5.1.</strong> The Discriminator</a></li><li class="chapter-item expanded "><div><strong aria-hidden="true">5.2.</strong> Dispatch</div></li><li class="spacer"></li></ol></li><li class="chapter-item expanded "><a href="../anchor_references/anchor_references.html"><strong aria-hidden="true">6.</strong> Anchor References</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../anchor_references/space.html"><strong aria-hidden="true">6.1.</strong> Space Reference</a></li><li class="chapter-item expanded "><a href="../anchor_references/javascript_anchor_types_reference.html"><strong aria-hidden="true">6.2.</strong> Javascript Anchor Types Reference</a></li><li class="chapter-item expanded "><a href="../anchor_references/cli.html"><strong aria-hidden="true">6.3.</strong> CLI Reference</a></li><li class="chapter-item expanded "><a href="../anchor_references/avm.html"><strong aria-hidden="true">6.4.</strong> AVM Reference</a></li><li class="chapter-item expanded "><a href="../anchor_references/anchor-toml_reference.html"><strong aria-hidden="true">6.5.</strong> Anchor.toml Reference</a></li><li class="chapter-item expanded "><a href="../anchor_references/reference_links.html"><strong aria-hidden="true">6.6.</strong> Code References</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">The Anchor Book v0.29.0</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="pdas"><a class="header" href="#pdas">PDAs</a></h1>
<p>Knowing how to use PDAs is one of the most important skills for Solana Programming.
They simplify the programming model and make programs more secure. So what are they?</p>
<p>PDAs (program derived addresses) are addresses with special properties.</p>
<p>Unlike normal addresses, PDAs are not public keys and therefore do not have an associated private key. There are two use cases for PDAs. They provide a mechanism to build hashmap-like structures on-chain and they allow programs to sign instructions.</p>
<h2 id="creation-of-a-pda"><a class="header" href="#creation-of-a-pda">Creation of a PDA</a></h2>
<p>Before we dive into how to use PDAs in anchor, here's a short explainer on what PDAs are.</p>
<p>PDAs are created by hashing a number of seeds the user can choose and the id of a program:</p>
<pre><code class="language-rust ignore">// pseudo code
let pda = hash(seeds, program_id);
</code></pre>
<p>The seeds can be anything. A pubkey, a string, an array of numbers etc.</p>
<p>There's a 50% chance that this hash function results in a public key (but PDAs are not public keys), so a bump has to be searched for so that we get a PDA:</p>
<pre><code class="language-rust ignore">// pseudo code
fn find_pda(seeds, program_id) {
for bump in 0..256 {
let potential_pda = hash(seeds, bump, program_id);
if is_pubkey(potential_pda) {
continue;
}
return (potential_pda, bump);
}
panic!(&quot;Could not find pda after 256 tries.&quot;);
}
</code></pre>
<p>It is technically possible that no bump is found within 256 tries but this probability is negligible.
If you're interested in the exact calculation of a PDA, check out the <a href="https://docs.rs/solana-program/1.17.0/solana_program/pubkey/struct.Pubkey.html#method.find_program_address"><code>solana_program</code> source code</a>.</p>
<p>The first bump that results in a PDA is commonly called the &quot;canonical bump&quot;. Other bumps may also result in a PDA but it's recommended to only use the canonical bump to avoid confusion.</p>
<h2 id="using-pdas"><a class="header" href="#using-pdas">Using PDAs</a></h2>
<p>We are now going to show you what you can do with PDAs and how to do it in Anchor!</p>
<h3 id="hashmap-like-structures-using-pdas"><a class="header" href="#hashmap-like-structures-using-pdas">Hashmap-like structures using PDAs</a></h3>
<p>Before we dive into the specifics of creating hashmaps in anchor, let's look at how to create a hashmap with PDAs in general.</p>
<h4 id="building-hashmaps-with-pdas"><a class="header" href="#building-hashmaps-with-pdas">Building hashmaps with PDAs</a></h4>
<p>PDAs are hashed from the bump, a program id, but also a number of seeds which can be freely chosen by the user.
These seeds can be used to build hashmap-like structures on-chain.</p>
<p>For instance, imagine you're building an in-browser game and want to store some user stats. Maybe their level and their in-game name. You could create an account with a layout that looks like this:</p>
<pre><code class="language-rust ignore">pub struct UserStats {
level: u16,
name: String,
authority: Pubkey
}
</code></pre>
<p>The <code>authority</code> would be the user the accounts belongs to.</p>
<p>This approach creates the following problem. It's easy to go from the user stats account to the user account address (just read the <code>authority</code> field) but if you just have the user account address (which is more likely), how do you find the user stats account? You can't. This is a problem because your game probably has instructions that require both the user stats account and its authority which means the client needs to pass those accounts into the instruction (for example, a <code>ChangeName</code> instruction). So maybe the frontend could store a mapping between a user's account address and a user's info address in local storage. This works until the user accidentally wipes their local storage.</p>
<p>With PDAs you can have a layout like this:</p>
<pre><code class="language-rust ignore">pub struct UserStats {
level: u16,
name: String,
bump: u8
}
</code></pre>
<p>and encode the information about the relationship between the user and the user stats account in the address of the user stats account itself.</p>
<p>Reusing the pseudo code from above:</p>
<pre><code class="language-rust ignore">// pseudo code
let seeds = [b&quot;user-stats&quot;, authority];
let (pda, bump) = find_pda(seeds, game_program_id);
</code></pre>
<p>When a user connects to your website, this pda calculation can be done client-side using their user account address as the <code>authority</code>. The resulting pda then serves as the address of the user's stats account. The <code>b&quot;user-stats&quot;</code> is added in case there are other account types that are also PDAs. If there were an inventory account, it could be inferred using these seeds:</p>
<pre><code class="language-rust ignore">let seeds = [b&quot;inventory&quot;, authority];
</code></pre>
<p>To summarize, we have used PDAs to create a mapping between a user and their user stats account. There is no single hashmap object that exposes a <code>get</code> function. Instead, each value (the user stats address) can be found by using certain seeds (&quot;user-stats&quot; and the user account address) as inputs to the <code>find_pda</code> function.</p>
<h4 id="how-to-build-pda-hashmaps-in-anchor"><a class="header" href="#how-to-build-pda-hashmaps-in-anchor">How to build PDA hashmaps in Anchor</a></h4>
<p>Continuing with the example from the previous sections, create a new workspace</p>
<pre><code>anchor init game
</code></pre>
<p>and copy the following code</p>
<pre><code class="language-rust ignore">use anchor_lang::prelude::*;
declare_id!(&quot;Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS&quot;);
#[program]
pub mod game {
use super::*;
// handler function
pub fn create_user_stats(ctx: Context&lt;CreateUserStats&gt;, name: String) -&gt; Result&lt;()&gt; {
let user_stats = &amp;mut ctx.accounts.user_stats;
user_stats.level = 0;
if name.as_bytes().len() &gt; 200 {
// proper error handling omitted for brevity
panic!();
}
user_stats.name = name;
user_stats.bump = ctx.bumps.user_stats;
Ok(())
}
}
#[account]
pub struct UserStats {
level: u16,
name: String,
bump: u8,
}
// validation struct
#[derive(Accounts)]
pub struct CreateUserStats&lt;'info&gt; {
#[account(mut)]
pub user: Signer&lt;'info&gt;,
// space: 8 discriminator + 2 level + 4 name length + 200 name + 1 bump
#[account(
init,
payer = user,
space = 8 + 2 + 4 + 200 + 1, seeds = [b&quot;user-stats&quot;, user.key().as_ref()], bump
)]
pub user_stats: Account&lt;'info, UserStats&gt;,
pub system_program: Program&lt;'info, System&gt;,
}
</code></pre>
<p>In the account validation struct we use <code>seeds</code> together with <code>init</code> to create a PDA with the desired seeds.
Additionally, we add an empty <code>bump</code> constraint to signal to anchor that it should find the canonical bump itself.
Then, in the handler, we access <code>ctx.bumps.user_stats</code> to get the bump anchor found and save it to the user stats
account as an extra property.</p>
<p>If we then want to use the created pda in a different instruction, we can add a new validation struct (This will check that the <code>user_stats</code> account is the pda created by running <code>hash(seeds, user_stats.bump, game_program_id)</code>):</p>
<pre><code class="language-rust ignore">// validation struct
#[derive(Accounts)]
pub struct ChangeUserName&lt;'info&gt; {
pub user: Signer&lt;'info&gt;,
#[account(mut, seeds = [b&quot;user-stats&quot;, user.key().as_ref()], bump = user_stats.bump)]
pub user_stats: Account&lt;'info, UserStats&gt;,
}
</code></pre>
<p>and another handler function:</p>
<pre><code class="language-rust ignore">// handler function (add this next to the create_user_stats function in the game module)
pub fn change_user_name(ctx: Context&lt;ChangeUserName&gt;, new_name: String) -&gt; Result&lt;()&gt; {
if new_name.as_bytes().len() &gt; 200 {
// proper error handling omitted for brevity
panic!();
}
ctx.accounts.user_stats.name = new_name;
Ok(())
}
</code></pre>
<p>Finally, let's add a test. Copy this into <code>game.ts</code></p>
<pre><code class="language-ts">import * as anchor from &quot;@coral-xyz/anchor&quot;;
import { Program } from &quot;@coral-xyz/anchor&quot;;
import { PublicKey } from &quot;@solana/web3.js&quot;;
import { Game } from &quot;../target/types/game&quot;;
import { expect } from &quot;chai&quot;;
describe(&quot;game&quot;, async () =&gt; {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Game as Program&lt;Game&gt;;
it(&quot;Sets and changes name!&quot;, async () =&gt; {
const [userStatsPDA, _] = await PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode(&quot;user-stats&quot;),
provider.wallet.publicKey.toBuffer(),
],
program.programId
);
await program.methods
.createUserStats(&quot;brian&quot;)
.accounts({
user: provider.wallet.publicKey,
userStats: userStatsPDA,
})
.rpc();
expect((await program.account.userStats.fetch(userStatsPDA)).name).to.equal(
&quot;brian&quot;
);
await program.methods
.changeUserName(&quot;tom&quot;)
.accounts({
user: provider.wallet.publicKey,
userStats: userStatsPDA,
})
.rpc();
expect((await program.account.userStats.fetch(userStatsPDA)).name).to.equal(
&quot;tom&quot;
);
});
});
</code></pre>
<p>Exactly as described in the subchapter before this one, we use a <code>find</code> function to find the PDA. We can then use it just like a normal address. Well, almost. When we call <code>createUserStats</code>, we don't have to add the PDA to the <code>[signers]</code> array even though account creation requires a signature. This is because it is impossible to sign the transaction from outside the program as the PDA (it's not a public key so there is no private key to sign with). Instead, the signature is added when the CPI to the system program is made. We're going to explain how this works in the <a href="#programs-as-signers">Programs as Signers</a> section.</p>
<h4 id="enforcing-uniqueness"><a class="header" href="#enforcing-uniqueness">Enforcing uniqueness</a></h4>
<p>A subtle result of this hashmap structure is enforced uniqueness. When <code>init</code> is used with <code>seeds</code> and <code>bump</code>, it will always search for the canonical bump. This means that it can only be called once (because the 2nd time it's called the PDA will already be initialized). To illustrate how powerful enforced uniqueness is, consider a decentralized exchange program. In this program, anyone can create a new market for two assets. However, the program creators want liquidity to be concentrated so there should only be one market for every combination of two assets. This could be done without PDAs but would require a global account that saves all the different markets. Then upon market creation, the program would check whether the asset combination exists in the global market list. With PDAs this can be done in a much more straightforward way. Any market would simply be the PDA of the mint addresses of the two assets. The program would then check whether either of the two possible PDAs (because the market could've been created with the assets in reverse order) already exists.</p>
<h3 id="programs-as-signers"><a class="header" href="#programs-as-signers">Programs as Signers</a></h3>
<p>Creating PDAs requires them to sign the <code>createAccount</code> CPI of the system program. How does that work?</p>
<p>PDAs are not public keys so it's impossible for them to sign anything. However, PDAs can still pseudo sign CPIs.
In anchor, to sign with a pda you have to change <code>CpiContext::new(cpi_program, cpi_accounts)</code> to <code>CpiContext::new_with_signer(cpi_program, cpi_accounts, seeds)</code> where the <code>seeds</code> argument are the seeds <em>and</em> the bump the PDA was created with.
When the CPI is invoked, for each account in <code>cpi_accounts</code> the Solana runtime will check whether<code>hash(seeds, current_program_id) == account address</code> is true. If yes, that account's <code>is_signer</code> flag will be turned to true.
This means a PDA derived from some program X, may only be used to sign CPIs that originate from that program X. This means that on a high level, PDA signatures can be considered program signatures.</p>
<p>This is great news because for many programs it is necessary that the program itself takes the authority over some assets.
For instance, lending protocol programs need to manage deposited collateral and automated market maker programs need to manage the tokens put into their liquidity pools.</p>
<p>Let's revisit the puppet workspace and add a PDA signature.</p>
<p>First, adjust the puppet-master code:</p>
<pre><code class="language-rust ignore">use anchor_lang::prelude::*;
use puppet::cpi::accounts::SetData;
use puppet::program::Puppet;
use puppet::{self, Data};
declare_id!(&quot;HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L&quot;);
#[program]
mod puppet_master {
use super::*;
pub fn pull_strings(ctx: Context&lt;PullStrings&gt;, bump: u8, data: u64) -&gt; Result&lt;()&gt; {
let bump = &amp;[bump][..];
puppet::cpi::set_data(
ctx.accounts.set_data_ctx().with_signer(&amp;[&amp;[bump][..]]),
data,
)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct PullStrings&lt;'info&gt; {
#[account(mut)]
pub puppet: Account&lt;'info, Data&gt;,
pub puppet_program: Program&lt;'info, Puppet&gt;,
/// CHECK: only used as a signing PDA
pub authority: UncheckedAccount&lt;'info&gt;,
}
impl&lt;'info&gt; PullStrings&lt;'info&gt; {
pub fn set_data_ctx(&amp;self) -&gt; CpiContext&lt;'_, '_, '_, 'info, SetData&lt;'info&gt;&gt; {
let cpi_program = self.puppet_program.to_account_info();
let cpi_accounts = SetData {
puppet: self.puppet.to_account_info(),
authority: self.authority.to_account_info(),
};
CpiContext::new(cpi_program, cpi_accounts)
}
}
</code></pre>
<p>The <code>authority</code> account is now an <code>UncheckedAccount</code> instead of a <code>Signer</code>. When the puppet-master is invoked, the <code>authority</code> pda is not a signer yet so we mustn't add a check for it. We just care about the puppet-master being able to sign so we don't add any additional seeds. Just a bump that is calculated off-chain and then passed to the function.</p>
<p>Finally, this is the new <code>puppet.ts</code>:</p>
<pre><code class="language-ts">import * as anchor from &quot;@coral-xyz/anchor&quot;;
import { Program } from &quot;@coral-xyz/anchor&quot;;
import { Keypair, PublicKey } from &quot;@solana/web3.js&quot;;
import { Puppet } from &quot;../target/types/puppet&quot;;
import { PuppetMaster } from &quot;../target/types/puppet_master&quot;;
import { expect } from &quot;chai&quot;;
describe(&quot;puppet&quot;, () =&gt; {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const puppetProgram = anchor.workspace.Puppet as Program&lt;Puppet&gt;;
const puppetMasterProgram = anchor.workspace
.PuppetMaster as Program&lt;PuppetMaster&gt;;
const puppetKeypair = Keypair.generate();
it(&quot;Does CPI!&quot;, async () =&gt; {
const [puppetMasterPDA, puppetMasterBump] =
await PublicKey.findProgramAddress([], puppetMasterProgram.programId);
await puppetProgram.methods
.initialize(puppetMasterPDA)
.accounts({
puppet: puppetKeypair.publicKey,
user: provider.wallet.publicKey,
})
.signers([puppetKeypair])
.rpc();
await puppetMasterProgram.methods
.pullStrings(puppetMasterBump, new anchor.BN(42))
.accounts({
puppetProgram: puppetProgram.programId,
puppet: puppetKeypair.publicKey,
authority: puppetMasterPDA,
})
.rpc();
expect(
(
await puppetProgram.account.data.fetch(puppetKeypair.publicKey)
).data.toNumber()
).to.equal(42);
});
});
</code></pre>
<p>The <code>authority</code> is no longer a randomly generated keypair but a PDA derived from the puppet-master program. This means the puppet-master can sign with it which it does inside <code>pullStrings</code>. It's worth noting that our implementation also allows non-canonical bumps but again because we are only interested in being able to sign we don't care which bump is used.</p>
<blockquote>
<p>In some cases it's possible to reduce the number of accounts you need by making a PDA storing state also sign a CPI instead of defining a separate PDA to do that.</p>
</blockquote>
<h2 id="pdas-conclusion"><a class="header" href="#pdas-conclusion">PDAs: Conclusion</a></h2>
<p>This section serves as a brief recap of the different things you can do with PDAs.</p>
<p>First, you can create hashmaps with them. We created a user stats PDA which was derived from the user address. This derivation linked the user address and the user stats account, allowing the latter to be easily found given the former.
Hashmaps also result in enforced uniqueness which can be used in many different ways, e.g. for only allowing one market per two assets in a decentralized exchange.</p>
<p>Secondly, PDAs can be used to allow programs to sign CPIs. This means that programs can be given control over assets which they then manage according to the rules defined in their code.</p>
<p>You can even combine these two use cases and use a PDA that's used in an instruction as a state account to also sign a CPI.</p>
<p>Admittedly, working with PDAs is one of the most challenging parts of working with Solana.
This is why in addition to our explanations here, we want to provide you with some further resources.</p>
<ul>
<li><a href="https://twitter.com/pencilflip/status/1455948263853600768?s=20&amp;t=J2JXCwv395D7MNkX7a9LGw">Pencilflips's twitter thread on PDAs</a></li>
<li><a href="https://www.youtube.com/watch?v=iMWaQRyjpl4">jarry xiao's talk on PDAs and CPIs</a></li>
<li><a href="https://paulx.dev/blog/2021/01/14/programming-on-solana-an-introduction/">paulx's guide on everything Solana (covers much more than PDAs)</a></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../anchor_in_depth/CPIs.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../anchor_in_depth/events.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../anchor_in_depth/CPIs.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../anchor_in_depth/events.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>