ソースを参照

PDA rent-payer example

jpcaulfi 2 年 前
コミット
3bd26a073a
24 ファイル変更519 行追加0 行削除
  1. 5 0
      basics/pda-rent-payer/README.md
  2. 14 0
      basics/pda-rent-payer/anchor/Anchor.toml
  3. 13 0
      basics/pda-rent-payer/anchor/Cargo.toml
  4. 14 0
      basics/pda-rent-payer/anchor/package.json
  5. 19 0
      basics/pda-rent-payer/anchor/programs/anchor-program-example/Cargo.toml
  6. 2 0
      basics/pda-rent-payer/anchor/programs/anchor-program-example/Xargo.toml
  7. 40 0
      basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/create_new_account.rs
  8. 38 0
      basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/init_rent_vault.rs
  9. 5 0
      basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/mod.rs
  10. 21 0
      basics/pda-rent-payer/anchor/programs/anchor-program-example/src/lib.rs
  11. 11 0
      basics/pda-rent-payer/anchor/programs/anchor-program-example/src/state/mod.rs
  12. 42 0
      basics/pda-rent-payer/anchor/tests/test.ts
  13. 10 0
      basics/pda-rent-payer/anchor/tsconfig.json
  14. 8 0
      basics/pda-rent-payer/native/cicd.sh
  15. 18 0
      basics/pda-rent-payer/native/package.json
  16. 12 0
      basics/pda-rent-payer/native/program/Cargo.toml
  17. 31 0
      basics/pda-rent-payer/native/program/src/instructions/create_new_account.rs
  18. 53 0
      basics/pda-rent-payer/native/program/src/instructions/init_rent_vault.rs
  19. 5 0
      basics/pda-rent-payer/native/program/src/instructions/mod.rs
  20. 10 0
      basics/pda-rent-payer/native/program/src/lib.rs
  21. 25 0
      basics/pda-rent-payer/native/program/src/processor.rs
  22. 8 0
      basics/pda-rent-payer/native/program/src/state/mod.rs
  23. 105 0
      basics/pda-rent-payer/native/tests/test.ts
  24. 10 0
      basics/pda-rent-payer/native/tsconfig.json

+ 5 - 0
basics/pda-rent-payer/README.md

@@ -0,0 +1,5 @@
+# PDA Rent-Payer
+
+This examples demonstrates how to use a PDA to pay the rent for the creation of a new account.   
+   
+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!

+ 14 - 0
basics/pda-rent-payer/anchor/Anchor.toml

@@ -0,0 +1,14 @@
+[features]
+seeds = false
+[programs.localnet]
+pda_rent_payer = "7Hm9nsYVuBZ9rf8z9AMUHreZRv8Q4vLhqwdVTCawRZtA"
+
+[registry]
+url = "https://anchor.projectserum.com"
+
+[provider]
+cluster = "localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 13 - 0
basics/pda-rent-payer/anchor/Cargo.toml

@@ -0,0 +1,13 @@
+[workspace]
+members = [
+    "programs/*"
+]
+
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+[profile.release.build-override]
+opt-level = 3
+incremental = false
+codegen-units = 1

+ 14 - 0
basics/pda-rent-payer/anchor/package.json

@@ -0,0 +1,14 @@
+{
+    "dependencies": {
+        "@project-serum/anchor": "^0.24.2"
+    },
+    "devDependencies": {
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
+    }
+}

+ 19 - 0
basics/pda-rent-payer/anchor/programs/anchor-program-example/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "pda-rent-payer"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "pda_rent_payer"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.24.2"

+ 2 - 0
basics/pda-rent-payer/anchor/programs/anchor-program-example/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 40 - 0
basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/create_new_account.rs

@@ -0,0 +1,40 @@
+use anchor_lang::prelude::*;
+
+use crate::state::RentVault;
+
+pub fn create_new_account(ctx: Context<CreateNewAccount>) -> Result<()> {
+    // Assuming this account has no inner data (size 0)
+    //
+    let lamports_required_for_rent = (Rent::get()?).minimum_balance(0);
+
+    **ctx
+        .accounts
+        .rent_vault
+        .to_account_info()
+        .lamports
+        .borrow_mut() -= lamports_required_for_rent;
+    **ctx
+        .accounts
+        .new_account
+        .to_account_info()
+        .lamports
+        .borrow_mut() += lamports_required_for_rent;
+
+    Ok(())
+}
+
+#[derive(Accounts)]
+pub struct CreateNewAccount<'info> {
+    /// CHECK: Unchecked
+    #[account(mut)]
+    new_account: SystemAccount<'info>,
+    #[account(
+        mut,
+        seeds = [
+            RentVault::SEED_PREFIX.as_bytes().as_ref(),
+        ],
+        bump = rent_vault.bump,
+    )]
+    rent_vault: Account<'info, RentVault>,
+    system_program: Program<'info, System>,
+}

+ 38 - 0
basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/init_rent_vault.rs

@@ -0,0 +1,38 @@
+use anchor_lang::prelude::*;
+use anchor_lang::system_program;
+
+use crate::state::RentVault;
+
+pub fn init_rent_vault(ctx: Context<InitRentVault>, fund_lamports: u64) -> Result<()> {
+    ctx.accounts.rent_vault.set_inner(RentVault {
+        bump: *ctx.bumps.get(RentVault::SEED_PREFIX).unwrap(),
+    });
+    system_program::transfer(
+        CpiContext::new(
+            ctx.accounts.system_program.to_account_info(),
+            system_program::Transfer {
+                from: ctx.accounts.payer.to_account_info(),
+                to: ctx.accounts.rent_vault.to_account_info(),
+            },
+        ),
+        fund_lamports,
+    )?;
+    Ok(())
+}
+
+#[derive(Accounts)]
+pub struct InitRentVault<'info> {
+    #[account(
+        init,
+        space = RentVault::ACCOUNT_SPACE,
+        payer = payer,
+        seeds = [
+            RentVault::SEED_PREFIX.as_bytes().as_ref(),
+        ],
+        bump,
+    )]
+    rent_vault: Account<'info, RentVault>,
+    #[account(mut)]
+    payer: Signer<'info>,
+    system_program: Program<'info, System>,
+}

+ 5 - 0
basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/mod.rs

@@ -0,0 +1,5 @@
+pub mod create_new_account;
+pub mod init_rent_vault;
+
+pub use create_new_account::*;
+pub use init_rent_vault::*;

+ 21 - 0
basics/pda-rent-payer/anchor/programs/anchor-program-example/src/lib.rs

@@ -0,0 +1,21 @@
+use anchor_lang::prelude::*;
+
+use instructions::*;
+
+pub mod instructions;
+pub mod state;
+
+declare_id!("7Hm9nsYVuBZ9rf8z9AMUHreZRv8Q4vLhqwdVTCawRZtA");
+
+#[program]
+pub mod pda_rent_payer {
+    use super::*;
+
+    pub fn init_rent_vault(ctx: Context<InitRentVault>, fund_lamports: u64) -> Result<()> {
+        instructions::init_rent_vault::init_rent_vault(ctx, fund_lamports)
+    }
+
+    pub fn create_new_account(ctx: Context<CreateNewAccount>) -> Result<()> {
+        instructions::create_new_account::create_new_account(ctx)
+    }
+}

+ 11 - 0
basics/pda-rent-payer/anchor/programs/anchor-program-example/src/state/mod.rs

@@ -0,0 +1,11 @@
+use anchor_lang::prelude::*;
+
+#[account]
+pub struct RentVault {
+    pub bump: u8,
+}
+
+impl RentVault {
+    pub const SEED_PREFIX: &'static str = "rent_vault";
+    pub const ACCOUNT_SPACE: usize = 8 + 8;
+}

+ 42 - 0
basics/pda-rent-payer/anchor/tests/test.ts

@@ -0,0 +1,42 @@
+import * as anchor from "@project-serum/anchor";
+import { PdaRentPayer } from "../target/types/pda_rent_payer";
+
+describe("PDA Rent-Payer", () => {
+
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+  const wallet = provider.wallet as anchor.Wallet;
+  const program = anchor.workspace.PdaRentPayer as anchor.Program<PdaRentPayer>;
+
+  function deriveRentVaultPda() {
+      const pda = anchor.web3.PublicKey.findProgramAddressSync(
+          [Buffer.from("rent_vault")],
+          program.programId,
+      )
+      console.log(`PDA: ${pda[0].toBase58()}`)
+      return pda
+  }
+
+  it("Initialize the Rent Vault", async () => {
+      const [rentVaultPda, _] = deriveRentVaultPda();
+      await program.methods.initRentVault(new anchor.BN(1000000000))
+        .accounts({
+          rentVault: rentVaultPda,
+          payer: wallet.publicKey,
+        })
+        .signers([wallet.payer])
+        .rpc()
+  });
+
+  it("Create a new account using the Rent Vault", async () => {
+      const newAccount = anchor.web3.Keypair.generate();
+      const [rentVaultPda, _] = deriveRentVaultPda();
+      await program.methods.createNewAccount()
+        .accounts({
+          newAccount: newAccount.publicKey,
+          rentVault: rentVaultPda,
+        })
+        .signers([wallet.payer])
+        .rpc()
+  });
+});

+ 10 - 0
basics/pda-rent-payer/anchor/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true
+  }
+}

+ 8 - 0
basics/pda-rent-payer/native/cicd.sh

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

+ 18 - 0
basics/pda-rent-payer/native/package.json

@@ -0,0 +1,18 @@
+{
+  "scripts": {
+    "test": "yarn run ts-mocha -p ./tsconfig.json -t 1000000 ./tests/test.ts"
+  },
+  "dependencies": {
+    "@solana/web3.js": "^1.47.3",
+    "fs": "^0.0.1-security"
+  },
+  "devDependencies": {
+    "@types/bn.js": "^5.1.0",
+    "@types/chai": "^4.3.1",
+    "@types/mocha": "^9.1.1",
+    "chai": "^4.3.4",
+    "mocha": "^9.0.3",
+    "ts-mocha": "^10.0.0",
+    "typescript": "^4.3.5"
+  }
+}

+ 12 - 0
basics/pda-rent-payer/native/program/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "program"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+solana-program = "1.10.12"
+borsh = "0.9.3"
+borsh-derive = "0.9.1"
+
+[lib]
+crate-type = ["cdylib", "lib"]

+ 31 - 0
basics/pda-rent-payer/native/program/src/instructions/create_new_account.rs

@@ -0,0 +1,31 @@
+use solana_program::{
+    account_info::{next_account_info, AccountInfo},
+    entrypoint::ProgramResult,
+    program::invoke_signed,
+    pubkey::Pubkey,
+    rent::Rent,
+    system_instruction,
+    sysvar::Sysvar,
+};
+
+use crate::state::RentVault;
+
+pub fn create_new_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
+    let accounts_iter = &mut accounts.iter();
+    let new_account = next_account_info(accounts_iter)?;
+    let rent_vault = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+
+    let (rent_vault_pda, rent_vault_bump) =
+        Pubkey::find_program_address(&[RentVault::SEED_PREFIX.as_bytes().as_ref()], program_id);
+    assert!(rent_vault.key.eq(&rent_vault_pda));
+
+    // Assuming this account has no inner data (size 0)
+    //
+    let lamports_required_for_rent = (Rent::get()?).minimum_balance(0);
+
+    **rent_vault.lamports.borrow_mut() -= lamports_required_for_rent;
+    **new_account.lamports.borrow_mut() += lamports_required_for_rent;
+
+    Ok(())
+}

+ 53 - 0
basics/pda-rent-payer/native/program/src/instructions/init_rent_vault.rs

@@ -0,0 +1,53 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+use solana_program::{
+    account_info::{next_account_info, AccountInfo},
+    entrypoint::ProgramResult,
+    program::invoke_signed,
+    pubkey::Pubkey,
+    rent::Rent,
+    system_instruction,
+    sysvar::Sysvar,
+};
+
+use crate::state::RentVault;
+
+#[derive(BorshDeserialize, BorshSerialize)]
+pub struct InitRentVaultArgs {
+    fund_lamports: u64,
+}
+
+pub fn init_rent_vault(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    args: InitRentVaultArgs,
+) -> ProgramResult {
+    let accounts_iter = &mut accounts.iter();
+    let rent_vault = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+
+    let (rent_vault_pda, rent_vault_bump) =
+        Pubkey::find_program_address(&[RentVault::SEED_PREFIX.as_bytes().as_ref()], program_id);
+    assert!(rent_vault.key.eq(&rent_vault_pda));
+
+    // Lamports for rent on the vault, plus the desired additional funding
+    //
+    let lamports_required = (Rent::get()?).minimum_balance(0) + args.fund_lamports;
+
+    invoke_signed(
+        &system_instruction::create_account(
+            &payer.key,
+            &rent_vault.key,
+            lamports_required,
+            0,
+            program_id,
+        ),
+        &[payer.clone(), rent_vault.clone(), system_program.clone()],
+        &[&[
+            RentVault::SEED_PREFIX.as_bytes().as_ref(),
+            &[rent_vault_bump],
+        ]],
+    )?;
+
+    Ok(())
+}

+ 5 - 0
basics/pda-rent-payer/native/program/src/instructions/mod.rs

@@ -0,0 +1,5 @@
+pub mod create_new_account;
+pub mod init_rent_vault;
+
+pub use create_new_account::*;
+pub use init_rent_vault::*;

+ 10 - 0
basics/pda-rent-payer/native/program/src/lib.rs

@@ -0,0 +1,10 @@
+use solana_program::entrypoint;
+
+use processor::process_instruction;
+
+pub mod instructions;
+pub mod processor;
+pub mod state;
+
+
+entrypoint!(process_instruction);

+ 25 - 0
basics/pda-rent-payer/native/program/src/processor.rs

@@ -0,0 +1,25 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
+
+use crate::instructions::{
+    create_new_account::create_new_account,
+    init_rent_vault::{init_rent_vault, InitRentVaultArgs},
+};
+
+#[derive(BorshSerialize, BorshDeserialize)]
+pub enum MyInstruction {
+    InitRentVault(InitRentVaultArgs),
+    CreateNewAccount,
+}
+
+pub fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    input: &[u8],
+) -> ProgramResult {
+    let instruction = MyInstruction::try_from_slice(&input)?;
+    match instruction {
+        MyInstruction::InitRentVault(args) => init_rent_vault(program_id, accounts, args),
+        MyInstruction::CreateNewAccount => create_new_account(program_id, accounts),
+    }
+}

+ 8 - 0
basics/pda-rent-payer/native/program/src/state/mod.rs

@@ -0,0 +1,8 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+
+#[derive(BorshDeserialize, BorshSerialize, Debug)]
+pub struct RentVault {}
+
+impl RentVault {
+    pub const SEED_PREFIX: &'static str = "rent_vault";
+}

+ 105 - 0
basics/pda-rent-payer/native/tests/test.ts

@@ -0,0 +1,105 @@
+import {
+    Connection,
+    Keypair,
+    PublicKey,
+    sendAndConfirmTransaction,
+    SystemProgram,
+    Transaction,
+    TransactionInstruction,
+} from '@solana/web3.js';
+import * as borsh from "borsh";
+import { Buffer } from "buffer";
+
+
+function createKeypairFromFile(path: string): Keypair {
+    return Keypair.fromSecretKey(
+        Buffer.from(JSON.parse(require('fs').readFileSync(path, "utf-8")))
+    )
+};
+
+
+describe("PDA Rent-Payer", () => {
+
+    const connection = new Connection(`http://localhost:8899`, 'confirmed');
+    const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
+    const PROGRAM_ID: PublicKey = createKeypairFromFile('./program/target/deploy/program-keypair.json').publicKey;
+
+    class Assignable {
+        constructor(properties) {
+            Object.keys(properties).map((key) => {
+                return (this[key] = properties[key]);
+            });
+        };
+    };
+
+    enum MyInstruction {
+        InitRentVault,
+        CreateNewAccount,
+    }
+
+    class InitRentVault extends Assignable {
+        toBuffer() { return Buffer.from(borsh.serialize(InitRentVaultSchema, this)) }
+    };
+    const InitRentVaultSchema = new Map([
+        [ InitRentVault, { 
+            kind: 'struct', 
+            fields: [ ['instruction', 'u8'], ['fund_lamports', 'u64'] ],
+        }]
+    ]);
+
+    class CreateNewAccount extends Assignable {
+        toBuffer() { return Buffer.from(borsh.serialize(CreateNewAccountSchema, this)) }
+    };
+    const CreateNewAccountSchema = new Map([
+        [ CreateNewAccount, { 
+            kind: 'struct', 
+            fields: [ ['instruction', 'u8'] ],
+        }]
+    ]);
+
+    function deriveRentVaultPda() {
+        const pda = PublicKey.findProgramAddressSync(
+            [Buffer.from("rent_vault")],
+            PROGRAM_ID,
+        )
+        console.log(`PDA: ${pda[0].toBase58()}`)
+        return pda
+    }
+
+    it("Initialize the Rent Vault", async () => {
+        const [rentVaultPda, _] = deriveRentVaultPda();
+        let ix = new TransactionInstruction({
+            keys: [
+                {pubkey: rentVaultPda, isSigner: false, isWritable: true},
+                {pubkey: payer.publicKey, isSigner: true, isWritable: true},
+                {pubkey: SystemProgram.programId, isSigner: false, isWritable: false}
+            ],
+            programId: PROGRAM_ID,
+            data: (new InitRentVault({ instruction: MyInstruction.InitRentVault, fund_lamports: 1000000000 })).toBuffer(),
+        });
+        await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer]
+        );
+    });
+
+    it("Create a new account using the Rent Vault", async () => {
+        const newAccount = Keypair.generate();
+        const [rentVaultPda, _] = deriveRentVaultPda();
+        let ix = new TransactionInstruction({
+            keys: [
+                {pubkey: newAccount.publicKey, isSigner: true, isWritable: true},
+                {pubkey: rentVaultPda, isSigner: false, isWritable: true},
+                {pubkey: SystemProgram.programId, isSigner: false, isWritable: false}
+            ],
+            programId: PROGRAM_ID,
+            data: new CreateNewAccount({ instruction: MyInstruction.CreateNewAccount }).toBuffer(),
+        });
+        await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer, newAccount]
+        );
+    });
+});

+ 10 - 0
basics/pda-rent-payer/native/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true
+  }
+}