jpcaulfi 3 years ago
parent
commit
291c4878b3

+ 22 - 0
basics/cross-program-invocation/README.md

@@ -16,6 +16,28 @@ In the above steps, we can't create a metadata account without first creating a
 
 Let's say we decided it was essential to have our mint (operation 1) and our "mint to user" (operation 4) tasks on-chain. We would have no choice but to also include the other two operations, since we can't do operation #1, pause the program while we do #2 & #3 from the client, and then resume the program for #4.
 
+#### Notes on Native setup:
+
+With the `native` implementation, you have to do a little bit of lifting to import one crate into another within your Cargo workspace.   
+
+Add the `no-entrypoint` feature to Cargo.toml:
+```toml
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+```
+Then use the import just like we did in the `anchor` example:
+```toml
+[dependencies]
+...
+lever = { path = "../lever", features = [ "cpi" ] }
+```
+Lastly, add this annotation over the `entrypoint!` macro that you wish to disable on import (the child program):
+```rust
+#[cfg(not(feature = "no-entrypoint"))]
+entrypoint!(process_instruction);
+```
+
 ### Let's switch the power on and off using a CPI!   
 
 <img src="istockphoto-1303616086-612x612.jpeg" alt="lever" width="128" align="center"/>

+ 16 - 6
basics/cross-program-invocation/anchor/programs/hand/src/lib.rs

@@ -11,12 +11,22 @@ declare_id!("ABoYG2GWbzLgnnGhK2pUGNupzKoYe7UGk2idrAXbstAS");
 mod hand {
     use super::*;
     pub fn pull_lever(ctx: Context<PullLever>, name: String) -> anchor_lang::Result<()> {
-        let cpi_program = ctx.accounts.lever_program.to_account_info();
-        let cpi_accounts = SetPowerStatus {
-            power: ctx.accounts.power.to_account_info(),
-        };
-        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
-        lever::cpi::switch_power(cpi_ctx, name)
+        
+        // Hitting the switch_power method on the lever program
+        //
+        lever::cpi::switch_power(
+            CpiContext::new(
+                
+                ctx.accounts.lever_program.to_account_info(), 
+
+                // Using the accounts context struct from the lever program
+                //
+                let cpi_accounts = SetPowerStatus {
+                    power: ctx.accounts.power.to_account_info(),
+                };
+            ), 
+            name
+        )
     }
 }
 

+ 1 - 1
basics/cross-program-invocation/anchor/tests/test.ts

@@ -2,7 +2,7 @@ import * as anchor from "@project-serum/anchor";
 import { Hand } from "../target/types/hand";
 import { Lever } from "../target/types/lever";
 
-describe("hello-solana", () => {
+describe("CPI Example", () => {
   
   const provider = anchor.AnchorProvider.env();
   anchor.setProvider(provider);

+ 4 - 0
basics/cross-program-invocation/native/Cargo.toml

@@ -0,0 +1,4 @@
+[workspace]
+members = [
+    "programs/*"
+]

+ 8 - 2
basics/cross-program-invocation/native/cicd.sh

@@ -4,5 +4,11 @@
 # 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
+cargo build-bpf --bpf-out-dir=./target/so
+echo "Hand:"
+solana program deploy ./target/so/hand.so | grep "Program Id:"
+echo "Lever:"
+solana program deploy ./target/so/lever.so | grep "Program Id:"
+
+# D2E39tDWxnndmW3QvYVzmm2gU2kvr9Zv2ywBLAmq8Fw3
+# 9qprjFZKZbBeRGNT3vgPW5xpPFqXjNxKZWybUy7mYNBw

+ 2 - 0
basics/cross-program-invocation/native/package.json

@@ -4,6 +4,8 @@
   },
   "dependencies": {
     "@solana/web3.js": "^1.47.3",
+    "borsh": "^0.7.0",
+    "buffer": "^6.0.3",
     "fs": "^0.0.1-security"
   },
   "devDependencies": {

+ 0 - 29
basics/cross-program-invocation/native/program/src/lib.rs

@@ -1,29 +0,0 @@
-use solana_program::{
-    account_info::AccountInfo, 
-    entrypoint, 
-    entrypoint::ProgramResult, 
-    msg, 
-    pubkey::Pubkey,
-};
-
-
-// Tells Solana that the entrypoint to this program
-//  is the "process_instruction" function.
-//
-entrypoint!(process_instruction);
-
-
-// Our entrypoint's parameters have to match the
-//  anatomy of a transaction instruction (see README).
-//
-fn process_instruction(
-    program_id: &Pubkey,
-    accounts: &[AccountInfo],
-    instruction_data: &[u8],
-) -> ProgramResult {
-
-    
-    msg!("Hello, Solana!");
-
-    Ok(())
-}

+ 17 - 0
basics/cross-program-invocation/native/programs/hand/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "hand"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+
+[dependencies]
+borsh = "0.9.3"
+borsh-derive = "0.9.1"
+solana-program = "1.10.12"
+lever = { path = "../lever", features = [ "cpi" ] }
+
+[lib]
+crate-type = ["cdylib", "lib"]

+ 2 - 0
basics/cross-program-invocation/native/programs/hand/Xargo.toml

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

+ 37 - 0
basics/cross-program-invocation/native/programs/hand/src/lib.rs

@@ -0,0 +1,37 @@
+use borsh::BorshDeserialize;
+use lever::SetPowerStatus;
+use solana_program::{
+    account_info::{
+        next_account_info, AccountInfo
+    },
+    entrypoint, 
+    entrypoint::ProgramResult, 
+    instruction::{ AccountMeta, Instruction },
+    pubkey::Pubkey,
+    program::invoke,
+};
+
+
+entrypoint!(pull_lever);
+
+
+fn pull_lever(
+    _program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let power = next_account_info(accounts_iter)?;
+    let lever_program = next_account_info(accounts_iter)?;
+
+    let set_power_status_instruction = SetPowerStatus::try_from_slice(instruction_data)?;
+
+    let ix = Instruction::new_with_borsh(
+        lever_program.key.clone(),                          // Our lever program's ID
+        &set_power_status_instruction,                      // Passing same instructions through
+        vec![AccountMeta::new(power.key.clone(), false)],   // Passing same accounts through
+    );
+
+    invoke(&ix, &[power.clone()])
+}

+ 7 - 1
basics/cross-program-invocation/native/program/Cargo.toml → basics/cross-program-invocation/native/programs/lever/Cargo.toml

@@ -1,9 +1,15 @@
 [package]
-name = "program"
+name = "lever"
 version = "0.1.0"
 edition = "2021"
 
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+
 [dependencies]
+borsh = "0.9.3"
+borsh-derive = "0.9.1"
 solana-program = "1.10.12"
 
 [lib]

+ 2 - 0
basics/cross-program-invocation/native/programs/lever/Xargo.toml

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

+ 106 - 0
basics/cross-program-invocation/native/programs/lever/src/lib.rs

@@ -0,0 +1,106 @@
+use borsh::{ BorshDeserialize, BorshSerialize };
+use solana_program::{
+    account_info::{
+        next_account_info, AccountInfo
+    },
+    entrypoint, 
+    entrypoint::ProgramResult, 
+    msg, 
+    program::invoke,
+    program_error::ProgramError,
+    pubkey::Pubkey,
+    rent::Rent,
+    system_instruction,
+    sysvar::Sysvar,
+};
+
+
+#[cfg(not(feature = "no-entrypoint"))]
+entrypoint!(process_instruction);
+
+
+pub fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+
+    match PowerStatus::try_from_slice(&instruction_data) {
+        Ok(power_status) => return initialize(program_id, accounts, power_status),
+        Err(_) => {},
+    }
+
+    match SetPowerStatus::try_from_slice(&instruction_data) {
+        Ok(set_power_status) => return switch_power(accounts, set_power_status.name),
+        Err(_) => {},
+    }
+
+    Err(ProgramError::InvalidInstructionData)
+}
+
+
+
+pub fn initialize(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    power_status: PowerStatus,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let power = next_account_info(accounts_iter)?;
+    let user = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+
+    let account_span = (power_status.try_to_vec()?).len();
+    let lamports_required = (Rent::get()?).minimum_balance(account_span);
+
+    invoke(
+        &system_instruction::create_account(
+            &user.key,
+            &power.key,
+            lamports_required,
+            account_span as u64,
+            program_id,
+        ),
+        &[
+            user.clone(), power.clone(), system_program.clone()
+        ]
+    )?;
+
+    power_status.serialize(&mut &mut power.data.borrow_mut()[..])?;
+
+    Ok(())
+}
+
+pub fn switch_power(
+    accounts: &[AccountInfo],
+    name: String,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+    let power = next_account_info(accounts_iter)?;
+    
+    let mut power_status = PowerStatus::try_from_slice(&power.data.borrow())?;
+    power_status.is_on = !power_status.is_on;
+    power_status.serialize(&mut &mut power.data.borrow_mut()[..])?;
+
+    msg!("{} is pulling the power switch!", &name);
+
+    match power_status.is_on {
+        true => msg!("The power is now on."),
+        false => msg!("The power is now off!"),
+    };
+
+    Ok(())
+}
+
+
+#[derive(BorshDeserialize, BorshSerialize, Debug)]
+pub struct SetPowerStatus {
+    pub name: String,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Debug)]
+pub struct PowerStatus {
+    pub is_on: bool,
+}

+ 75 - 15
basics/cross-program-invocation/native/tests/test.ts

@@ -2,9 +2,12 @@ import {
     Connection,
     Keypair,
     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(
@@ -13,33 +16,90 @@ function createKeypairFromFile(path: string): Keypair {
 };
 
 
-describe("hello-solana", () => {
-
-    // Loading these from local files for development
-    //
+describe("CPI Example", () => {
+  
     const connection = new Connection(`http://localhost:8899`, 'confirmed');
     const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
-    const program = createKeypairFromFile('./program/target/so/program-keypair.json');
+    const hand = createKeypairFromFile('./target/so/hand-keypair.json');
+    const lever = createKeypairFromFile('./target/so/lever-keypair.json');
+
+    class Assignable {
+        constructor(properties) {
+            Object.keys(properties).map((key) => {
+                return (this[key] = properties[key]);
+            });
+        };
+    };
+
+    class PowerStatus extends Assignable {
+        toBuffer() { return Buffer.from(borsh.serialize(PowerStatusSchema, this)) }
+    };
+    const PowerStatusSchema = new Map([[ PowerStatus, { kind: 'struct', fields: [ ['is_on', 'u8'] ]} ]]);
+    
+    class SetPowerStatus extends Assignable {
+        toBuffer() { return Buffer.from(borsh.serialize(SetPowerStatusSchema, this)) }
+    };
+    const SetPowerStatusSchema = new Map([[ SetPowerStatus, { kind: 'struct', fields: [ ['name', 'string'] ]} ]]);
+  
+    const powerAccount = Keypair.generate();
+  
+    it("Initialize the lever!", async () => {
+
+        let ix = new TransactionInstruction({
+            keys: [
+                {pubkey: powerAccount.publicKey, isSigner: true, isWritable: true},
+                {pubkey: payer.publicKey, isSigner: true, isWritable: true},
+                {pubkey: SystemProgram.programId, isSigner: false, isWritable: false}
+            ],
+            programId: lever.publicKey,
+            data: (new PowerStatus({is_on: true})).toBuffer(),
+        });
+
+        await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer, powerAccount]
+        );
+  
+    });
   
-    it("Say hello!", async () => {
+    it("Pull the lever!", async () => {
+
+        let ix = new TransactionInstruction({
+            keys: [
+                {pubkey: powerAccount.publicKey, isSigner: false, isWritable: true},
+                {pubkey: lever.publicKey, isSigner: false, isWritable: false},
+            ],
+            programId: hand.publicKey,
+            data: new SetPowerStatus({name: "Chris"}).toBuffer(),
+        });
 
-        // We set up our instruction first.
-        //
+        await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer]
+        );
+  
+    });
+  
+    it("Pull it again!", async () => {
+  
         let ix = new TransactionInstruction({
             keys: [
-                {pubkey: payer.publicKey, isSigner: true, isWritable: true}
+                {pubkey: powerAccount.publicKey, isSigner: false, isWritable: true},
+                {pubkey: lever.publicKey, isSigner: false, isWritable: false},
             ],
-            programId: program.publicKey,
-            data: Buffer.alloc(0), // No data
+            programId: hand.publicKey,
+            data: new SetPowerStatus({name: "Ashley"}).toBuffer(),
         });
 
-        // Now we send the transaction over RPC
-        //
         await sendAndConfirmTransaction(
             connection, 
-            new Transaction().add(ix), // Add our instruction (you can add more than one)
+            new Transaction().add(ix),
             [payer]
         );
+  
     });
-  });
+});
+