Browse Source

add basics/cross-program-invocation/steel (#248)

Perelyn 9 months ago
parent
commit
ecf7486c8d
29 changed files with 591 additions and 0 deletions
  1. 6 0
      basics/cross-program-invocation/steel/README.md
  2. 2 0
      basics/cross-program-invocation/steel/hand/.gitignore
  3. 25 0
      basics/cross-program-invocation/steel/hand/Cargo.toml
  4. 21 0
      basics/cross-program-invocation/steel/hand/README.md
  5. 19 0
      basics/cross-program-invocation/steel/hand/api/Cargo.toml
  6. 15 0
      basics/cross-program-invocation/steel/hand/api/src/instruction.rs
  7. 12 0
      basics/cross-program-invocation/steel/hand/api/src/lib.rs
  8. 20 0
      basics/cross-program-invocation/steel/hand/api/src/sdk.rs
  9. 28 0
      basics/cross-program-invocation/steel/hand/program/Cargo.toml
  10. 22 0
      basics/cross-program-invocation/steel/hand/program/src/lib.rs
  11. 22 0
      basics/cross-program-invocation/steel/hand/program/src/pull_lever.rs
  12. 51 0
      basics/cross-program-invocation/steel/hand/program/tests/test.rs
  13. 2 0
      basics/cross-program-invocation/steel/lever/.gitignore
  14. 21 0
      basics/cross-program-invocation/steel/lever/Cargo.toml
  15. 28 0
      basics/cross-program-invocation/steel/lever/README.md
  16. 18 0
      basics/cross-program-invocation/steel/lever/api/Cargo.toml
  17. 2 0
      basics/cross-program-invocation/steel/lever/api/src/consts.rs
  18. 10 0
      basics/cross-program-invocation/steel/lever/api/src/error.rs
  19. 21 0
      basics/cross-program-invocation/steel/lever/api/src/instruction.rs
  20. 20 0
      basics/cross-program-invocation/steel/lever/api/src/lib.rs
  21. 26 0
      basics/cross-program-invocation/steel/lever/api/src/sdk.rs
  22. 16 0
      basics/cross-program-invocation/steel/lever/api/src/state/mod.rs
  23. 11 0
      basics/cross-program-invocation/steel/lever/api/src/state/power_status.rs
  24. 13 0
      basics/cross-program-invocation/steel/lever/api/src/utils.rs
  25. 30 0
      basics/cross-program-invocation/steel/lever/program/Cargo.toml
  26. 17 0
      basics/cross-program-invocation/steel/lever/program/src/initialize.rs
  27. 25 0
      basics/cross-program-invocation/steel/lever/program/src/lib.rs
  28. 36 0
      basics/cross-program-invocation/steel/lever/program/src/switch_power.rs
  29. 52 0
      basics/cross-program-invocation/steel/lever/program/tests/test.rs

+ 6 - 0
basics/cross-program-invocation/steel/README.md

@@ -0,0 +1,6 @@
+##  Cross Program Invocation steel example
+
+### Programs
+
+- [Hand](./hand/README.md)
+- [Lever](./lever/README.md)

+ 2 - 0
basics/cross-program-invocation/steel/hand/.gitignore

@@ -0,0 +1,2 @@
+target
+test-ledger

+ 25 - 0
basics/cross-program-invocation/steel/hand/Cargo.toml

@@ -0,0 +1,25 @@
+[workspace]
+resolver = "2"
+members = ["api", "program"]
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+license = "Apache-2.0"
+homepage = ""
+documentation = ""
+repository = ""
+readme = "./README.md"
+keywords = ["solana"]
+
+[workspace.dependencies]
+hand-api = { path = "./api", version = "0.1.0" }
+lever-api = { path = "../lever/api", version = "0.1.0" }
+lever-program = { path = "../lever/program", version = "0.1.0", features = [
+    "cpi",
+] }
+bytemuck = "1.14"
+num_enum = "0.7"
+solana-program = "1.18"
+steel = "2.0"
+thiserror = "1.0"

+ 21 - 0
basics/cross-program-invocation/steel/hand/README.md

@@ -0,0 +1,21 @@
+# Hand
+
+**Hand** is a ...
+
+## API
+- [`Instruction`](api/src/instruction.rs) – Declared instructions.
+
+## Instructions
+- [`PullLever`](program/src/pull_lever.rs) – Pull Lever ...
+
+## Get started
+
+Compile your program:
+```sh
+steel build
+```
+
+Run unit and integration tests:
+```sh
+steel test
+```

+ 19 - 0
basics/cross-program-invocation/steel/hand/api/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "hand-api"
+description = "API for interacting with the Hand program"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+documentation.workspace = true
+repository.workspace = true
+readme.workspace = true
+keywords.workspace = true
+
+[dependencies]
+bytemuck.workspace = true
+num_enum.workspace = true
+solana-program.workspace = true
+steel.workspace = true
+thiserror.workspace = true
+lever-api.workspace = true

+ 15 - 0
basics/cross-program-invocation/steel/hand/api/src/instruction.rs

@@ -0,0 +1,15 @@
+use steel::*;
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
+pub enum HandInstruction {
+    PullLever = 0,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+pub struct PullLever {
+    pub name: [u8; 32],
+}
+
+instruction!(HandInstruction, PullLever);

+ 12 - 0
basics/cross-program-invocation/steel/hand/api/src/lib.rs

@@ -0,0 +1,12 @@
+pub mod instruction;
+pub mod sdk;
+
+pub mod prelude {
+    pub use crate::instruction::*;
+    pub use crate::sdk::*;
+}
+
+use steel::*;
+
+// TODO Set program id
+declare_id!("Bi5N7SUQhpGknVcqPTzdFFVueQoxoUu8YTLz75J6fT8A");

+ 20 - 0
basics/cross-program-invocation/steel/hand/api/src/sdk.rs

@@ -0,0 +1,20 @@
+use lever_api::prelude::*;
+use steel::*;
+
+use crate::prelude::*;
+
+pub fn pull_lever(power_account: Pubkey, name: &str) -> Instruction {
+    // pub fn pull_lever(power_account: Pubkey, lever_program: Pubkey, name: &str) -> Instruction {
+    Instruction {
+        program_id: crate::ID,
+        accounts: vec![
+            AccountMeta::new(power_account, false),
+            // AccountMeta::new(lever_program, false),
+            AccountMeta::new_readonly(lever_api::ID, false),
+        ],
+        data: PullLever {
+            name: str_to_bytes(name),
+        }
+        .to_bytes(),
+    }
+}

+ 28 - 0
basics/cross-program-invocation/steel/hand/program/Cargo.toml

@@ -0,0 +1,28 @@
+[package]
+name = "hand-program"
+description = ""
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+documentation.workspace = true
+repository.workspace = true
+readme.workspace = true
+keywords.workspace = true
+
+[lib]
+crate-type = ["cdylib", "lib"]
+
+[dependencies]
+hand-api.workspace = true
+lever-api.workspace = true
+lever-program.workspace = true
+solana-program.workspace = true
+steel.workspace = true
+
+[dev-dependencies]
+base64 = "0.21"
+rand = "0.8.5"
+solana-program-test = "1.18"
+solana-sdk = "1.18"
+tokio = { version = "1.35", features = ["full"] }

+ 22 - 0
basics/cross-program-invocation/steel/hand/program/src/lib.rs

@@ -0,0 +1,22 @@
+mod pull_lever;
+
+use pull_lever::*;
+
+use hand_api::prelude::*;
+use steel::*;
+
+pub fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    data: &[u8],
+) -> ProgramResult {
+    let (ix, data) = parse_instruction(&hand_api::ID, program_id, data)?;
+
+    match ix {
+        HandInstruction::PullLever => process_pull_lever(accounts, data)?,
+    }
+
+    Ok(())
+}
+
+entrypoint!(process_instruction);

+ 22 - 0
basics/cross-program-invocation/steel/hand/program/src/pull_lever.rs

@@ -0,0 +1,22 @@
+use hand_api::prelude::*;
+use lever_api::prelude::*;
+use steel::*;
+
+pub fn process_pull_lever(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
+    // Parse args.
+    let args = PullLever::try_from_bytes(data)?;
+    let name = bytes_to_str(&args.name);
+
+    // Load accounts.
+    let [power_info, lever_program] = accounts else {
+        return Err(ProgramError::NotEnoughAccountKeys);
+    };
+
+    power_info.is_writable()?;
+
+    let ix = switch_power(*power_info.key, &name);
+
+    solana_program::program::invoke(&ix, &[power_info.clone(), lever_program.clone()])?;
+
+    Ok(())
+}

+ 51 - 0
basics/cross-program-invocation/steel/hand/program/tests/test.rs

@@ -0,0 +1,51 @@
+use hand_api::prelude::*;
+use lever_api::prelude::*;
+use solana_program::hash::Hash;
+use solana_program_test::{processor, BanksClient, ProgramTest};
+use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction};
+
+async fn setup() -> (BanksClient, Keypair, Hash) {
+    let mut program_test = ProgramTest::new(
+        "hand_program",
+        hand_api::ID,
+        processor!(hand_program::process_instruction),
+    );
+
+    program_test.add_program(
+        "lever_program",
+        lever_api::ID,
+        processor!(lever_program::process_instruction),
+    );
+
+    program_test.prefer_bpf(true);
+    program_test.start().await
+}
+
+#[tokio::test]
+async fn run_test() {
+    // Setup test
+    let (mut banks, payer, blockhash) = setup().await;
+    let power_account = Keypair::new();
+
+    // Submit initialize transaction.
+    let ix = initialize(payer.pubkey(), power_account.pubkey());
+    let tx = Transaction::new_signed_with_payer(
+        &[ix],
+        Some(&payer.pubkey()),
+        &[&payer, &power_account],
+        blockhash,
+    );
+    let res = banks.process_transaction(tx).await;
+    assert!(res.is_ok());
+
+    // Submit pull_lever transaction.
+    let ix = pull_lever(power_account.pubkey(), "Chris");
+    let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash);
+    let res = banks.process_transaction(tx).await;
+    assert!(res.is_ok());
+
+    let ix = pull_lever(power_account.pubkey(), "Ashley");
+    let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash);
+    let res = banks.process_transaction(tx).await;
+    assert!(res.is_ok());
+}

+ 2 - 0
basics/cross-program-invocation/steel/lever/.gitignore

@@ -0,0 +1,2 @@
+target
+test-ledger

+ 21 - 0
basics/cross-program-invocation/steel/lever/Cargo.toml

@@ -0,0 +1,21 @@
+[workspace]
+resolver = "2"
+members = ["api", "program"]
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+license = "Apache-2.0"
+homepage = ""
+documentation = ""
+repository = ""
+readme = "./README.md"
+keywords = ["solana"]
+
+[workspace.dependencies]
+lever-api = { path = "./api", version = "0.1.0" }
+bytemuck = "1.14"
+num_enum = "0.7"
+solana-program = "1.18"
+steel = "2.0"
+thiserror = "1.0"

+ 28 - 0
basics/cross-program-invocation/steel/lever/README.md

@@ -0,0 +1,28 @@
+# Lever
+
+**Lever** is a ...
+
+## API
+- [`Consts`](api/src/consts.rs) – Program constants.
+- [`Error`](api/src/error.rs) – Custom program errors.
+- [`Event`](api/src/event.rs) – Custom program events.
+- [`Instruction`](api/src/instruction.rs) – Declared instructions.
+
+## Instructions
+- [`SwitchPower`](program/src/switch_power.rs) – Switch Power ...
+- [`Initialize`](program/src/initialize.rs) – Initialize ...
+
+## State
+- [`PowerStatus`](api/src/state/power_status.rs) – Power status ...
+
+## Get started
+
+Compile your program:
+```sh
+steel build
+```
+
+Run unit and integration tests:
+```sh
+steel test
+```

+ 18 - 0
basics/cross-program-invocation/steel/lever/api/Cargo.toml

@@ -0,0 +1,18 @@
+[package]
+name = "lever-api"
+description = "API for interacting with the Lever program"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+documentation.workspace = true
+repository.workspace = true
+readme.workspace = true
+keywords.workspace = true
+
+[dependencies]
+bytemuck.workspace = true
+num_enum.workspace = true
+solana-program.workspace = true
+steel.workspace = true
+thiserror.workspace = true

+ 2 - 0
basics/cross-program-invocation/steel/lever/api/src/consts.rs

@@ -0,0 +1,2 @@
+/// Seed of the counter account PDA.
+pub const COUNTER: &[u8] = b"counter";

+ 10 - 0
basics/cross-program-invocation/steel/lever/api/src/error.rs

@@ -0,0 +1,10 @@
+use steel::*;
+
+#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)]
+#[repr(u32)]
+pub enum LeverError {
+    #[error("This is a dummy error")]
+    Dummy = 0,
+}
+
+error!(LeverError);

+ 21 - 0
basics/cross-program-invocation/steel/lever/api/src/instruction.rs

@@ -0,0 +1,21 @@
+use steel::*;
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
+pub enum LeverInstruction {
+    Initialize = 0,
+    SetPowerStatus = 1,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+pub struct Initialize {}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+pub struct SetPowerStatus {
+    pub name: [u8; 32],
+}
+
+instruction!(LeverInstruction, Initialize);
+instruction!(LeverInstruction, SetPowerStatus);

+ 20 - 0
basics/cross-program-invocation/steel/lever/api/src/lib.rs

@@ -0,0 +1,20 @@
+pub mod consts;
+pub mod error;
+pub mod instruction;
+pub mod sdk;
+pub mod state;
+pub mod utils;
+
+pub mod prelude {
+    pub use crate::consts::*;
+    pub use crate::error::*;
+    pub use crate::instruction::*;
+    pub use crate::sdk::*;
+    pub use crate::state::*;
+    pub use crate::utils::*;
+}
+
+use steel::*;
+
+// TODO Set program id
+declare_id!("E64FVeubGC4NPNF2UBJYX4AkrVowf74fRJD9q6YhwstN");

+ 26 - 0
basics/cross-program-invocation/steel/lever/api/src/sdk.rs

@@ -0,0 +1,26 @@
+use steel::*;
+
+use crate::prelude::*;
+
+pub fn initialize(user: Pubkey, power_account: Pubkey) -> Instruction {
+    Instruction {
+        program_id: crate::ID,
+        accounts: vec![
+            AccountMeta::new(user, true),
+            AccountMeta::new(power_account, true),
+            AccountMeta::new_readonly(system_program::ID, false),
+        ],
+        data: Initialize {}.to_bytes(),
+    }
+}
+
+pub fn switch_power(power_account: Pubkey, name: &str) -> Instruction {
+    Instruction {
+        program_id: crate::ID,
+        accounts: vec![AccountMeta::new(power_account, false)],
+        data: SetPowerStatus {
+            name: str_to_bytes(name),
+        }
+        .to_bytes(),
+    }
+}

+ 16 - 0
basics/cross-program-invocation/steel/lever/api/src/state/mod.rs

@@ -0,0 +1,16 @@
+mod power_status;
+
+pub use power_status::*;
+
+use steel::*;
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
+pub enum LeverAccount {
+    PowerStatus = 0,
+}
+
+// /// Fetch PDA of the counter account.
+// pub fn counter_pda() -> (Pubkey, u8) {
+//     Pubkey::find_program_address(&[COUNTER], &crate::id())
+// }

+ 11 - 0
basics/cross-program-invocation/steel/lever/api/src/state/power_status.rs

@@ -0,0 +1,11 @@
+use steel::*;
+
+use super::LeverAccount;
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
+pub struct PowerStatus {
+    pub is_on: u8,
+}
+
+account!(LeverAccount, PowerStatus);

+ 13 - 0
basics/cross-program-invocation/steel/lever/api/src/utils.rs

@@ -0,0 +1,13 @@
+pub fn str_to_bytes(name: &str) -> [u8; 32] {
+    let mut name_bytes = [0u8; 32];
+    name_bytes[..name.len()].copy_from_slice(name.as_bytes());
+    name_bytes
+}
+
+pub fn bytes_to_str(bytes: &[u8; 32]) -> String {
+    // Find the first occurrence of 0 (null terminator) or take all bytes if no null found
+    let length = bytes.iter().position(|&x| x == 0).unwrap_or(bytes.len());
+
+    // Convert the slice up to the null terminator (or full length) to a string
+    String::from_utf8_lossy(&bytes[..length]).into_owned()
+}

+ 30 - 0
basics/cross-program-invocation/steel/lever/program/Cargo.toml

@@ -0,0 +1,30 @@
+[package]
+name = "lever-program"
+description = ""
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+documentation.workspace = true
+repository.workspace = true
+readme.workspace = true
+keywords.workspace = true
+
+[lib]
+crate-type = ["cdylib", "lib"]
+
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+
+[dependencies]
+lever-api.workspace = true
+solana-program.workspace = true
+steel.workspace = true
+
+[dev-dependencies]
+base64 = "0.21"
+rand = "0.8.5"
+solana-program-test = "1.18"
+solana-sdk = "1.18"
+tokio = { version = "1.35", features = ["full"] }

+ 17 - 0
basics/cross-program-invocation/steel/lever/program/src/initialize.rs

@@ -0,0 +1,17 @@
+use lever_api::prelude::*;
+use steel::*;
+
+pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
+    // Load accounts.
+    let [signer_info, power_info, system_program] = accounts else {
+        return Err(ProgramError::NotEnoughAccountKeys);
+    };
+    signer_info.is_signer()?;
+    power_info.is_empty()?.is_writable()?;
+    system_program.is_program(&system_program::ID)?;
+
+    // Initialize power.
+    create_account::<PowerStatus>(power_info, system_program, signer_info, &lever_api::ID, &[])?;
+
+    Ok(())
+}

+ 25 - 0
basics/cross-program-invocation/steel/lever/program/src/lib.rs

@@ -0,0 +1,25 @@
+mod initialize;
+mod switch_power;
+
+use initialize::*;
+use switch_power::*;
+
+use lever_api::prelude::*;
+use steel::*;
+
+pub fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    data: &[u8],
+) -> ProgramResult {
+    let (ix, data) = parse_instruction(&lever_api::ID, program_id, data)?;
+
+    match ix {
+        LeverInstruction::Initialize => process_initialize(accounts, data)?,
+        LeverInstruction::SetPowerStatus => process_switch_power(accounts, data)?,
+    }
+
+    Ok(())
+}
+
+entrypoint!(process_instruction);

+ 36 - 0
basics/cross-program-invocation/steel/lever/program/src/switch_power.rs

@@ -0,0 +1,36 @@
+use lever_api::prelude::*;
+use solana_program::msg;
+use steel::*;
+
+pub fn process_switch_power(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
+    // Parse args.
+    let args = SetPowerStatus::try_from_bytes(data)?;
+    let name = bytes_to_str(&args.name);
+
+    // Load accounts.
+    let [power_info] = accounts else {
+        return Err(ProgramError::NotEnoughAccountKeys);
+    };
+
+    power_info.is_writable()?;
+
+    let power = power_info
+        .as_account_mut::<PowerStatus>(&lever_api::ID)?
+        .assert_mut(|c| c.is_on <= 1)?;
+
+    match power.is_on {
+        0 => power.is_on = 1,
+        1 => power.is_on = 0,
+        _ => panic!("Invalid boolean value"),
+    }
+
+    msg!("{} is pulling the power switch!", &name);
+
+    match power.is_on {
+        1 => msg!("The power is now on."),
+        0 => msg!("The power is now off!"),
+        _ => panic!("Invalid boolean value"),
+    };
+
+    Ok(())
+}

+ 52 - 0
basics/cross-program-invocation/steel/lever/program/tests/test.rs

@@ -0,0 +1,52 @@
+use lever_api::prelude::*;
+use solana_program::hash::Hash;
+use solana_program_test::{processor, BanksClient, ProgramTest};
+use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction};
+use steel::*;
+
+async fn setup() -> (BanksClient, Keypair, Hash) {
+    let mut program_test = ProgramTest::new(
+        "lever_program",
+        lever_api::ID,
+        processor!(lever_program::process_instruction),
+    );
+    program_test.prefer_bpf(true);
+    program_test.start().await
+}
+
+#[tokio::test]
+async fn run_test() {
+    let (mut banks, payer, blockhash) = setup().await;
+
+    let power_account = Keypair::new();
+
+    // Submit initialize transaction.
+    let ix = initialize(payer.pubkey(), power_account.pubkey());
+    let tx = Transaction::new_signed_with_payer(
+        &[ix],
+        Some(&payer.pubkey()),
+        &[&payer, &power_account],
+        blockhash,
+    );
+    let res = banks.process_transaction(tx).await;
+    assert!(res.is_ok());
+
+    // Verify power account was initialized.
+    let power_address = power_account.pubkey();
+    let power_account = banks.get_account(power_address).await.unwrap().unwrap();
+    let power_status = PowerStatus::try_from_bytes(&power_account.data).unwrap();
+    assert_eq!(power_account.owner, lever_api::ID);
+    assert_eq!(power_status.is_on, 0);
+
+    // Submit switch_power transaction.
+    let ix = switch_power(power_address, "Chris");
+    let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash);
+    let res = banks.process_transaction(tx).await;
+    assert!(res.is_ok());
+
+    // // Verify power_status was changed.
+    let power_account = banks.get_account(power_address).await.unwrap().unwrap();
+    let power_status = PowerStatus::try_from_bytes(&power_account.data).unwrap();
+    assert_eq!(power_account.owner, lever_api::ID);
+    assert_eq!(power_status.is_on, 1);
+}