Skip to content

Commit 3bd26a0

Browse files
author
jpcaulfi
committed
PDA rent-payer example
1 parent 90b446a commit 3bd26a0

File tree

24 files changed

+519
-0
lines changed

24 files changed

+519
-0
lines changed

Diff for: basics/pda-rent-payer/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# PDA Rent-Payer
2+
3+
This examples demonstrates how to use a PDA to pay the rent for the creation of a new account.
4+
5+
The key here is accounts on Solana are automatically created under ownership of the System Program when you transfer lamports to them. So, you can just transfer lamports from your PDA to the new account's public key!

Diff for: basics/pda-rent-payer/anchor/Anchor.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[features]
2+
seeds = false
3+
[programs.localnet]
4+
pda_rent_payer = "7Hm9nsYVuBZ9rf8z9AMUHreZRv8Q4vLhqwdVTCawRZtA"
5+
6+
[registry]
7+
url = "https://anchor.projectserum.com"
8+
9+
[provider]
10+
cluster = "localnet"
11+
wallet = "~/.config/solana/id.json"
12+
13+
[scripts]
14+
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

Diff for: basics/pda-rent-payer/anchor/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[workspace]
2+
members = [
3+
"programs/*"
4+
]
5+
6+
[profile.release]
7+
overflow-checks = true
8+
lto = "fat"
9+
codegen-units = 1
10+
[profile.release.build-override]
11+
opt-level = 3
12+
incremental = false
13+
codegen-units = 1

Diff for: basics/pda-rent-payer/anchor/package.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"dependencies": {
3+
"@project-serum/anchor": "^0.24.2"
4+
},
5+
"devDependencies": {
6+
"@types/bn.js": "^5.1.0",
7+
"@types/chai": "^4.3.0",
8+
"@types/mocha": "^9.0.0",
9+
"chai": "^4.3.4",
10+
"mocha": "^9.0.3",
11+
"ts-mocha": "^10.0.0",
12+
"typescript": "^4.3.5"
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "pda-rent-payer"
3+
version = "0.1.0"
4+
description = "Created with Anchor"
5+
edition = "2021"
6+
7+
[lib]
8+
crate-type = ["cdylib", "lib"]
9+
name = "pda_rent_payer"
10+
11+
[features]
12+
no-entrypoint = []
13+
no-idl = []
14+
no-log-ix-name = []
15+
cpi = ["no-entrypoint"]
16+
default = []
17+
18+
[dependencies]
19+
anchor-lang = "0.24.2"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use anchor_lang::prelude::*;
2+
3+
use crate::state::RentVault;
4+
5+
pub fn create_new_account(ctx: Context<CreateNewAccount>) -> Result<()> {
6+
// Assuming this account has no inner data (size 0)
7+
//
8+
let lamports_required_for_rent = (Rent::get()?).minimum_balance(0);
9+
10+
**ctx
11+
.accounts
12+
.rent_vault
13+
.to_account_info()
14+
.lamports
15+
.borrow_mut() -= lamports_required_for_rent;
16+
**ctx
17+
.accounts
18+
.new_account
19+
.to_account_info()
20+
.lamports
21+
.borrow_mut() += lamports_required_for_rent;
22+
23+
Ok(())
24+
}
25+
26+
#[derive(Accounts)]
27+
pub struct CreateNewAccount<'info> {
28+
/// CHECK: Unchecked
29+
#[account(mut)]
30+
new_account: SystemAccount<'info>,
31+
#[account(
32+
mut,
33+
seeds = [
34+
RentVault::SEED_PREFIX.as_bytes().as_ref(),
35+
],
36+
bump = rent_vault.bump,
37+
)]
38+
rent_vault: Account<'info, RentVault>,
39+
system_program: Program<'info, System>,
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use anchor_lang::prelude::*;
2+
use anchor_lang::system_program;
3+
4+
use crate::state::RentVault;
5+
6+
pub fn init_rent_vault(ctx: Context<InitRentVault>, fund_lamports: u64) -> Result<()> {
7+
ctx.accounts.rent_vault.set_inner(RentVault {
8+
bump: *ctx.bumps.get(RentVault::SEED_PREFIX).unwrap(),
9+
});
10+
system_program::transfer(
11+
CpiContext::new(
12+
ctx.accounts.system_program.to_account_info(),
13+
system_program::Transfer {
14+
from: ctx.accounts.payer.to_account_info(),
15+
to: ctx.accounts.rent_vault.to_account_info(),
16+
},
17+
),
18+
fund_lamports,
19+
)?;
20+
Ok(())
21+
}
22+
23+
#[derive(Accounts)]
24+
pub struct InitRentVault<'info> {
25+
#[account(
26+
init,
27+
space = RentVault::ACCOUNT_SPACE,
28+
payer = payer,
29+
seeds = [
30+
RentVault::SEED_PREFIX.as_bytes().as_ref(),
31+
],
32+
bump,
33+
)]
34+
rent_vault: Account<'info, RentVault>,
35+
#[account(mut)]
36+
payer: Signer<'info>,
37+
system_program: Program<'info, System>,
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod create_new_account;
2+
pub mod init_rent_vault;
3+
4+
pub use create_new_account::*;
5+
pub use init_rent_vault::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use anchor_lang::prelude::*;
2+
3+
use instructions::*;
4+
5+
pub mod instructions;
6+
pub mod state;
7+
8+
declare_id!("7Hm9nsYVuBZ9rf8z9AMUHreZRv8Q4vLhqwdVTCawRZtA");
9+
10+
#[program]
11+
pub mod pda_rent_payer {
12+
use super::*;
13+
14+
pub fn init_rent_vault(ctx: Context<InitRentVault>, fund_lamports: u64) -> Result<()> {
15+
instructions::init_rent_vault::init_rent_vault(ctx, fund_lamports)
16+
}
17+
18+
pub fn create_new_account(ctx: Context<CreateNewAccount>) -> Result<()> {
19+
instructions::create_new_account::create_new_account(ctx)
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use anchor_lang::prelude::*;
2+
3+
#[account]
4+
pub struct RentVault {
5+
pub bump: u8,
6+
}
7+
8+
impl RentVault {
9+
pub const SEED_PREFIX: &'static str = "rent_vault";
10+
pub const ACCOUNT_SPACE: usize = 8 + 8;
11+
}

Diff for: basics/pda-rent-payer/anchor/tests/test.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as anchor from "@project-serum/anchor";
2+
import { PdaRentPayer } from "../target/types/pda_rent_payer";
3+
4+
describe("PDA Rent-Payer", () => {
5+
6+
const provider = anchor.AnchorProvider.env();
7+
anchor.setProvider(provider);
8+
const wallet = provider.wallet as anchor.Wallet;
9+
const program = anchor.workspace.PdaRentPayer as anchor.Program<PdaRentPayer>;
10+
11+
function deriveRentVaultPda() {
12+
const pda = anchor.web3.PublicKey.findProgramAddressSync(
13+
[Buffer.from("rent_vault")],
14+
program.programId,
15+
)
16+
console.log(`PDA: ${pda[0].toBase58()}`)
17+
return pda
18+
}
19+
20+
it("Initialize the Rent Vault", async () => {
21+
const [rentVaultPda, _] = deriveRentVaultPda();
22+
await program.methods.initRentVault(new anchor.BN(1000000000))
23+
.accounts({
24+
rentVault: rentVaultPda,
25+
payer: wallet.publicKey,
26+
})
27+
.signers([wallet.payer])
28+
.rpc()
29+
});
30+
31+
it("Create a new account using the Rent Vault", async () => {
32+
const newAccount = anchor.web3.Keypair.generate();
33+
const [rentVaultPda, _] = deriveRentVaultPda();
34+
await program.methods.createNewAccount()
35+
.accounts({
36+
newAccount: newAccount.publicKey,
37+
rentVault: rentVaultPda,
38+
})
39+
.signers([wallet.payer])
40+
.rpc()
41+
});
42+
});

Diff for: basics/pda-rent-payer/anchor/tsconfig.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"types": ["mocha", "chai"],
4+
"typeRoots": ["./node_modules/@types"],
5+
"lib": ["es2015"],
6+
"module": "commonjs",
7+
"target": "es6",
8+
"esModuleInterop": true
9+
}
10+
}

Diff for: basics/pda-rent-payer/native/cicd.sh

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
# This script is for quick building & deploying of the program.
4+
# It also serves as a reference for the commands used for building & deploying Solana programs.
5+
# Run this bad boy with "bash cicd.sh" or "./cicd.sh"
6+
7+
cargo build-bpf --manifest-path=./program/Cargo.toml --bpf-out-dir=./program/target/so
8+
solana program deploy ./program/target/so/program.so

Diff for: basics/pda-rent-payer/native/package.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"scripts": {
3+
"test": "yarn run ts-mocha -p ./tsconfig.json -t 1000000 ./tests/test.ts"
4+
},
5+
"dependencies": {
6+
"@solana/web3.js": "^1.47.3",
7+
"fs": "^0.0.1-security"
8+
},
9+
"devDependencies": {
10+
"@types/bn.js": "^5.1.0",
11+
"@types/chai": "^4.3.1",
12+
"@types/mocha": "^9.1.1",
13+
"chai": "^4.3.4",
14+
"mocha": "^9.0.3",
15+
"ts-mocha": "^10.0.0",
16+
"typescript": "^4.3.5"
17+
}
18+
}

Diff for: basics/pda-rent-payer/native/program/Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "program"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
solana-program = "1.10.12"
8+
borsh = "0.9.3"
9+
borsh-derive = "0.9.1"
10+
11+
[lib]
12+
crate-type = ["cdylib", "lib"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use solana_program::{
2+
account_info::{next_account_info, AccountInfo},
3+
entrypoint::ProgramResult,
4+
program::invoke_signed,
5+
pubkey::Pubkey,
6+
rent::Rent,
7+
system_instruction,
8+
sysvar::Sysvar,
9+
};
10+
11+
use crate::state::RentVault;
12+
13+
pub fn create_new_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
14+
let accounts_iter = &mut accounts.iter();
15+
let new_account = next_account_info(accounts_iter)?;
16+
let rent_vault = next_account_info(accounts_iter)?;
17+
let system_program = next_account_info(accounts_iter)?;
18+
19+
let (rent_vault_pda, rent_vault_bump) =
20+
Pubkey::find_program_address(&[RentVault::SEED_PREFIX.as_bytes().as_ref()], program_id);
21+
assert!(rent_vault.key.eq(&rent_vault_pda));
22+
23+
// Assuming this account has no inner data (size 0)
24+
//
25+
let lamports_required_for_rent = (Rent::get()?).minimum_balance(0);
26+
27+
**rent_vault.lamports.borrow_mut() -= lamports_required_for_rent;
28+
**new_account.lamports.borrow_mut() += lamports_required_for_rent;
29+
30+
Ok(())
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use borsh::{BorshDeserialize, BorshSerialize};
2+
use solana_program::{
3+
account_info::{next_account_info, AccountInfo},
4+
entrypoint::ProgramResult,
5+
program::invoke_signed,
6+
pubkey::Pubkey,
7+
rent::Rent,
8+
system_instruction,
9+
sysvar::Sysvar,
10+
};
11+
12+
use crate::state::RentVault;
13+
14+
#[derive(BorshDeserialize, BorshSerialize)]
15+
pub struct InitRentVaultArgs {
16+
fund_lamports: u64,
17+
}
18+
19+
pub fn init_rent_vault(
20+
program_id: &Pubkey,
21+
accounts: &[AccountInfo],
22+
args: InitRentVaultArgs,
23+
) -> ProgramResult {
24+
let accounts_iter = &mut accounts.iter();
25+
let rent_vault = next_account_info(accounts_iter)?;
26+
let payer = next_account_info(accounts_iter)?;
27+
let system_program = next_account_info(accounts_iter)?;
28+
29+
let (rent_vault_pda, rent_vault_bump) =
30+
Pubkey::find_program_address(&[RentVault::SEED_PREFIX.as_bytes().as_ref()], program_id);
31+
assert!(rent_vault.key.eq(&rent_vault_pda));
32+
33+
// Lamports for rent on the vault, plus the desired additional funding
34+
//
35+
let lamports_required = (Rent::get()?).minimum_balance(0) + args.fund_lamports;
36+
37+
invoke_signed(
38+
&system_instruction::create_account(
39+
&payer.key,
40+
&rent_vault.key,
41+
lamports_required,
42+
0,
43+
program_id,
44+
),
45+
&[payer.clone(), rent_vault.clone(), system_program.clone()],
46+
&[&[
47+
RentVault::SEED_PREFIX.as_bytes().as_ref(),
48+
&[rent_vault_bump],
49+
]],
50+
)?;
51+
52+
Ok(())
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod create_new_account;
2+
pub mod init_rent_vault;
3+
4+
pub use create_new_account::*;
5+
pub use init_rent_vault::*;

Diff for: basics/pda-rent-payer/native/program/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use solana_program::entrypoint;
2+
3+
use processor::process_instruction;
4+
5+
pub mod instructions;
6+
pub mod processor;
7+
pub mod state;
8+
9+
10+
entrypoint!(process_instruction);

0 commit comments

Comments
 (0)