jpcaulfi 3 years ago
parent
commit
1818715bca

+ 35 - 10
basics/transfer-sol/anchor/programs/transfer-sol/src/lib.rs

@@ -1,17 +1,18 @@
 use anchor_lang::prelude::*;
 use anchor_lang::prelude::*;
 use anchor_lang::system_program;
 use anchor_lang::system_program;
 
 
+
 declare_id!("4fQVnLWKKKYxtxgGn7Haw8v2g2Hzbu8K61JvWKvqAi7W");
 declare_id!("4fQVnLWKKKYxtxgGn7Haw8v2g2Hzbu8K61JvWKvqAi7W");
 
 
+
 #[program]
 #[program]
 pub mod transfer_sol {
 pub mod transfer_sol {
     use super::*;
     use super::*;
 
 
-    pub fn transfer_sol(ctx: Context<TransferSol>, amount: u64) -> Result<()> {
-        
-        msg!("Received request to transfer {:?} lamports from {:?} to {:?}.", 
-            amount, &ctx.accounts.payer.key(), &ctx.accounts.recipient.key());
-        msg!("  Processing transfer...");
+    pub fn transfer_sol_with_cpi(
+        ctx: Context<TransferSolWithCpi>, 
+        amount: u64
+    ) -> Result<()> {
 
 
         system_program::transfer(
         system_program::transfer(
             CpiContext::new(
             CpiContext::new(
@@ -23,18 +24,42 @@ pub mod transfer_sol {
             ),
             ),
             amount,
             amount,
         )?;
         )?;
+
+        Ok(())
+    }
+
+    pub fn transfer_sol_with_program(
+        ctx: Context<TransferSolWithProgram>, 
+        amount: u64
+    ) -> Result<()> {
+
+        **ctx.accounts.payer
+            .to_account_info()
+            .try_borrow_mut_lamports()? -= amount;
+        **ctx.accounts.recipient
+            .to_account_info()
+            .try_borrow_mut_lamports()? += amount;
         
         
-        msg!("Transfer completed successfully.");
         Ok(())
         Ok(())
     }
     }
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
-pub struct TransferSol<'info> {
-    /// CHECK: We're initializing this account via the transfer
+pub struct TransferSolWithCpi<'info> {
     #[account(mut)]
     #[account(mut)]
-    recipient: AccountInfo<'info>,
+    recipient: SystemAccount<'info>,
     #[account(mut)]
     #[account(mut)]
     payer: Signer<'info>,
     payer: Signer<'info>,
     system_program: Program<'info, System>,
     system_program: Program<'info, System>,
-}
+}
+
+#[derive(Accounts)]
+pub struct TransferSolWithProgram<'info> {
+    /// CHECK: This is just an example, not checking data
+    #[account(mut)]
+    recipient: UncheckedAccount<'info>,
+    /// CHECK: This is just an example, not checking data
+    #[account(mut)]
+    payer: UncheckedAccount<'info>,
+    system_program: Program<'info, System>,
+}

+ 65 - 20
basics/transfer-sol/anchor/tests/test.ts

@@ -5,34 +5,79 @@ describe("transfer-sol", () => {
   
   
   const provider = anchor.AnchorProvider.env();
   const provider = anchor.AnchorProvider.env();
   anchor.setProvider(provider);
   anchor.setProvider(provider);
-  const wallet = provider.wallet as anchor.Wallet;
+  const payer = provider.wallet as anchor.Wallet;
   const program = anchor.workspace.TransferSol as anchor.Program<TransferSol>;
   const program = anchor.workspace.TransferSol as anchor.Program<TransferSol>;
 
 
-  it("Transfer some SOL", async () => {
+  const transferAmount = 1 * anchor.web3.LAMPORTS_PER_SOL;
+  const test1Recipient = anchor.web3.Keypair.generate();
+  const test2Recipient1 = anchor.web3.Keypair.generate();
+  const test2Recipient2 = anchor.web3.Keypair.generate();
 
 
-    async function getBalances(payerPubkey: anchor.web3.PublicKey, recipientPubkey: anchor.web3.PublicKey, timeframe: string) {
-      let payerBalance = await provider.connection.getBalance(payerPubkey);
-      let recipientBalance = await provider.connection.getBalance(recipientPubkey);
-      console.log(`${timeframe} balances:`);
-      console.log(`   Payer: ${payerBalance}`);
-      console.log(`   Recipient: ${recipientBalance}`);
+  it("Transfer between accounts using the system program", async () => {
+
+    await getBalances(payer.publicKey, test1Recipient.publicKey, "Beginning");
+    
+    await program.methods.transferSolWithCpi(new anchor.BN(transferAmount))
+      .accounts({
+        payer: payer.publicKey,
+        recipient: test1Recipient.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .signers([payer.payer])
+      .rpc();
+
+    await getBalances(payer.publicKey, test1Recipient.publicKey, "Resulting");
+
+  });
+
+  it("Create two accounts for the following test", async () => {
+
+    const ix = (pubkey: anchor.web3.PublicKey) => {
+      return anchor.web3.SystemProgram.createAccount({
+        fromPubkey: payer.publicKey,
+        newAccountPubkey: pubkey,
+        space: 0,
+        lamports: 2 * anchor.web3.LAMPORTS_PER_SOL,
+        programId: program.programId,
+      })
     };
     };
 
 
-    const recipientKeypair = anchor.web3.Keypair.generate();
-    const transferAmount = 1 * anchor.web3.LAMPORTS_PER_SOL;
+    await anchor.web3.sendAndConfirmTransaction(
+      provider.connection,
+      new anchor.web3.Transaction()
+        .add(ix(test2Recipient1.publicKey))
+        .add(ix(test2Recipient2.publicKey))
+      ,
+      [payer.payer, test2Recipient1, test2Recipient2]
+    );
+  });
+
+  it("Transfer between accounts using our program", async () => {
 
 
-    await getBalances(wallet.publicKey, recipientKeypair.publicKey, "Beginning");
+    await getBalances(test2Recipient1.publicKey, test2Recipient2.publicKey, "Beginning");
     
     
-    await program.methods.transferSol(new anchor.BN(transferAmount))
-    .accounts({
-      payer: wallet.publicKey,
-      recipient: recipientKeypair.publicKey,
-      systemProgram: anchor.web3.SystemProgram.programId,
-    })
-    .signers([wallet.payer])
-    .rpc();
+    await program.methods.transferSolWithProgram(new anchor.BN(transferAmount))
+      .accounts({
+        payer: test2Recipient1.publicKey,
+        recipient: test2Recipient2.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .rpc();
 
 
-    await getBalances(wallet.publicKey, recipientKeypair.publicKey, "Resulting");
+    await getBalances(test2Recipient1.publicKey, test2Recipient2.publicKey, "Resulting");
 
 
   });
   });
+
+  async function getBalances(
+    payerPubkey: anchor.web3.PublicKey, 
+    recipientPubkey: anchor.web3.PublicKey, 
+    timeframe: string
+  ) {
+
+    let payerBalance = await provider.connection.getBalance(payerPubkey);
+    let recipientBalance = await provider.connection.getBalance(recipientPubkey);
+    console.log(`${timeframe} balances:`);
+    console.log(`   Payer: ${payerBalance}`);
+    console.log(`   Recipient: ${recipientBalance}`);
+  };
 });
 });

+ 2 - 0
basics/transfer-sol/native/program/Cargo.toml

@@ -4,6 +4,8 @@ version = "0.1.0"
 edition = "2021"
 edition = "2021"
 
 
 [dependencies]
 [dependencies]
+borsh = "0.9.3"
+borsh-derive = "0.9.1"
 solana-program = "1.10.12"
 solana-program = "1.10.12"
 
 
 [lib]
 [lib]

+ 42 - 0
basics/transfer-sol/native/program/src/instruction.rs

@@ -0,0 +1,42 @@
+use solana_program::{
+    account_info::{ AccountInfo, next_account_info }, 
+    entrypoint::ProgramResult, 
+    program::invoke,
+    pubkey::Pubkey,
+    system_instruction,
+};
+
+
+pub fn transfer_sol_with_cpi(
+    accounts: &[AccountInfo],
+    amount: u64,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let payer = next_account_info(accounts_iter)?;
+    let recipient = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+
+    invoke(
+        &system_instruction::transfer(payer.key, recipient.key, amount),
+        &[payer.clone(), recipient.clone(), system_program.clone()],
+    )?;
+    
+    Ok(())
+}
+
+pub fn transfer_sol_with_program(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let payer = next_account_info(accounts_iter)?;
+    let recipient = next_account_info(accounts_iter)?;
+
+    **payer.try_borrow_mut_lamports()? -= amount;
+    **recipient.try_borrow_mut_lamports()? += amount;
+    
+    Ok(())
+}

+ 7 - 45
basics/transfer-sol/native/program/src/lib.rs

@@ -1,48 +1,10 @@
-use {
-    std::convert::TryInto,
-    solana_program::{
-        account_info::{
-            next_account_info, AccountInfo
-        },
-        entrypoint,
-        entrypoint::ProgramResult,
-        msg,
-        program::invoke,
-        program_error::ProgramError,
-        pubkey::Pubkey,
-        system_instruction,
-    },
-};
-
-
-entrypoint!(process_instruction);
-
 
 
-fn process_instruction(
-    _program_id: &Pubkey,
-    accounts: &[AccountInfo],
-    instruction_data: &[u8],
-) -> ProgramResult {
+pub mod instruction;
+pub mod processor;
 
 
-    let accounts_iter = &mut accounts.iter();
-    let payer = next_account_info(accounts_iter)?;
-    let recipient = next_account_info(accounts_iter)?;
-
-    let amount = instruction_data
-        .get(..8)
-        .and_then(|slice| slice.try_into().ok())
-        .map(u64::from_le_bytes)
-        .ok_or(ProgramError::InvalidInstructionData)?;
-
-    msg!("Received request to transfer {:?} lamports from {:?} to {:?}.", 
-        amount, payer.key, recipient.key);
-    msg!("  Processing transfer...");
+use {
+    solana_program::entrypoint,
+    crate::processor::process_instruction,
+};
 
 
-    invoke(
-        &system_instruction::transfer(payer.key, recipient.key, amount),
-        &[payer.clone(), recipient.clone()],
-    )?;
-    
-    msg!("Transfer completed successfully.");
-    Ok(())
-}
+entrypoint!(process_instruction);

+ 32 - 0
basics/transfer-sol/native/program/src/processor.rs

@@ -0,0 +1,32 @@
+use borsh::{ BorshDeserialize, BorshSerialize };
+use solana_program::{
+    account_info::AccountInfo, 
+    entrypoint::ProgramResult, 
+    pubkey::Pubkey,
+};
+
+use crate::instruction::transfer_sol_with_cpi;
+use crate::instruction::transfer_sol_with_program;
+
+
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+pub enum TransferInstruction {
+    CpiTransfer(u64),
+    ProgramTransfer(u64),
+}
+
+
+pub fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    input: &[u8],
+) -> ProgramResult {
+
+    let instruction = TransferInstruction::try_from_slice(&input)?;
+    match instruction {
+        TransferInstruction::CpiTransfer(
+            args) => transfer_sol_with_cpi(accounts, args),
+            TransferInstruction::ProgramTransfer(
+            args) => transfer_sol_with_program(program_id, accounts, args),
+    }
+}

+ 72 - 0
basics/transfer-sol/native/tests/instruction.ts

@@ -0,0 +1,72 @@
+import * as borsh from "borsh";
+import { Buffer } from "buffer";
+import { 
+    PublicKey, 
+    SystemProgram,
+    TransactionInstruction 
+} from '@solana/web3.js';
+
+
+export enum InstructionType {
+    CpiTransfer,
+    ProgramTransfer,
+}
+
+
+export class TransferInstruction {
+
+    instruction: InstructionType;
+    amount: number;
+
+    constructor(props: {
+        instruction: InstructionType,
+        amount: number,
+    }) {
+        this.instruction = props.instruction;
+        this.amount = props.amount;
+    }
+
+    toBuffer() { 
+        return Buffer.from(borsh.serialize(TransferInstructionSchema, this)) 
+    };
+    
+    static fromBuffer(buffer: Buffer) {
+        return borsh.deserialize(TransferInstructionSchema, TransferInstruction, buffer);
+    };
+};
+
+export const TransferInstructionSchema = new Map([
+    [ TransferInstruction, { 
+        kind: 'struct', 
+        fields: [ 
+            ['instruction', 'u8'],
+            ['amount', 'u64'],
+        ],
+    }]
+]);
+
+export function createTransferInstruction(
+    payerPubkey: PublicKey,
+    recipientPubkey: PublicKey,
+    programId: PublicKey,
+    instruction: InstructionType,
+    amount: number,
+): TransactionInstruction {
+
+    const instructionObject = new TransferInstruction({
+        instruction,
+        amount,
+    });
+
+    const ix = new TransactionInstruction({
+        keys: [
+            {pubkey: payerPubkey, isSigner: true, isWritable: true},
+            {pubkey: recipientPubkey, isSigner: false, isWritable: true},
+            {pubkey: SystemProgram.programId, isSigner: false, isWritable: false}
+        ],
+        programId,
+        data: instructionObject.toBuffer(),
+    });
+
+    return ix;
+}

+ 65 - 26
basics/transfer-sol/native/tests/test.ts

@@ -6,9 +6,8 @@ import {
     sendAndConfirmTransaction,
     sendAndConfirmTransaction,
     SystemProgram,
     SystemProgram,
     Transaction,
     Transaction,
-    TransactionInstruction,
 } from '@solana/web3.js';
 } from '@solana/web3.js';
-import * as buffer_layout from "buffer-layout";
+import { createTransferInstruction, InstructionType } from './instruction';
 
 
 
 
 function createKeypairFromFile(path: string): Keypair {
 function createKeypairFromFile(path: string): Keypair {
@@ -20,45 +19,85 @@ function createKeypairFromFile(path: string): Keypair {
 
 
 describe("transfer-sol", () => {
 describe("transfer-sol", () => {
 
 
-    async function getBalances(payerPubkey: PublicKey, recipientPubkey: PublicKey, timeframe: string) {
-        let payerBalance = await connection.getBalance(payerPubkey);
-        let recipientBalance = await connection.getBalance(recipientPubkey);
-        console.log(`${timeframe} balances:`);
-        console.log(`   Payer: ${payerBalance}`);
-        console.log(`   Recipient: ${recipientBalance}`);
-    };
-
     const connection = new Connection(`http://localhost:8899`, 'confirmed');
     const connection = new Connection(`http://localhost:8899`, 'confirmed');
     const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
     const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
     const program = createKeypairFromFile('./program/target/so/program-keypair.json');
     const program = createKeypairFromFile('./program/target/so/program-keypair.json');
+
+    const transferAmount = 1 * LAMPORTS_PER_SOL;
+    const test1Recipient = Keypair.generate();
+    const test2Recipient1 = Keypair.generate();
+    const test2Recipient2 = Keypair.generate();
   
   
-    it("Transfer some SOL", async () => {
+    it("Transfer between accounts using the system program", async () => {
 
 
-        let recipientKeypair = Keypair.generate();
-        let transferAmount = 1 * LAMPORTS_PER_SOL;
+        await getBalances(payer.publicKey, test1Recipient.publicKey, "Beginning");
 
 
-        await getBalances(payer.publicKey, recipientKeypair.publicKey, "Beginning");
+        let ix = createTransferInstruction(
+            payer.publicKey,
+            test1Recipient.publicKey,
+            program.publicKey,
+            InstructionType.CpiTransfer,
+            transferAmount
+        );
+
+        await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer]
+        );
 
 
-        let data = Buffer.alloc(8) // 8 bytes
-        buffer_layout.ns64("value").encode(transferAmount, data);
+        await getBalances(payer.publicKey, test1Recipient.publicKey, "Resulting");
+    });
 
 
-        let ix = new TransactionInstruction({
-            keys: [
-                {pubkey: payer.publicKey, isSigner: true, isWritable: true},
-                {pubkey: recipientKeypair.publicKey, isSigner: false, isWritable: true},
-                {pubkey: SystemProgram.programId, isSigner: false, isWritable: false}
-            ],
+    it("Create two accounts for the following test", async () => {
+
+        const ix = (pubkey: PublicKey) => {
+          return SystemProgram.createAccount({
+            fromPubkey: payer.publicKey,
+            newAccountPubkey: pubkey,
+            space: 0,
+            lamports: 2 * LAMPORTS_PER_SOL,
             programId: program.publicKey,
             programId: program.publicKey,
-            data: data,
-        });
+          })
+        };
+    
+        await sendAndConfirmTransaction(
+          connection,
+          new Transaction()
+            .add(ix(test2Recipient1.publicKey))
+            .add(ix(test2Recipient2.publicKey))
+          ,
+          [payer, test2Recipient1, test2Recipient2]
+        );
+      });
+
+    it("Transfer between accounts using our program", async () => {
+
+        await getBalances(test2Recipient1.publicKey, test2Recipient2.publicKey, "Beginning");
+
+        let ix = createTransferInstruction(
+            test2Recipient1.publicKey,
+            test2Recipient2.publicKey,
+            program.publicKey,
+            InstructionType.ProgramTransfer,
+            transferAmount
+        );
 
 
         await sendAndConfirmTransaction(
         await sendAndConfirmTransaction(
             connection, 
             connection, 
             new Transaction().add(ix),
             new Transaction().add(ix),
-            [payer]
+            [payer, test2Recipient1]
         );
         );
 
 
-        await getBalances(payer.publicKey, recipientKeypair.publicKey, "Resulting");
+        await getBalances(test2Recipient1.publicKey, test2Recipient2.publicKey, "Resulting");
     });
     });
+
+    async function getBalances(payerPubkey: PublicKey, recipientPubkey: PublicKey, timeframe: string) {
+        let payerBalance = await connection.getBalance(payerPubkey);
+        let recipientBalance = await connection.getBalance(recipientPubkey);
+        console.log(`${timeframe} balances:`);
+        console.log(`   Payer: ${payerBalance}`);
+        console.log(`   Recipient: ${recipientBalance}`);
+    };
   });
   });