diff --git a/basics/cross-program-invocation/steel/README.md b/basics/cross-program-invocation/steel/README.md new file mode 100644 index 000000000..56465a6e4 --- /dev/null +++ b/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) diff --git a/basics/cross-program-invocation/steel/hand/.gitignore b/basics/cross-program-invocation/steel/hand/.gitignore new file mode 100644 index 000000000..052739dbc --- /dev/null +++ b/basics/cross-program-invocation/steel/hand/.gitignore @@ -0,0 +1,2 @@ +target +test-ledger diff --git a/basics/cross-program-invocation/steel/hand/Cargo.toml b/basics/cross-program-invocation/steel/hand/Cargo.toml new file mode 100644 index 000000000..09abdd6b3 --- /dev/null +++ b/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" diff --git a/basics/cross-program-invocation/steel/hand/README.md b/basics/cross-program-invocation/steel/hand/README.md new file mode 100644 index 000000000..6ef2fade1 --- /dev/null +++ b/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 +``` diff --git a/basics/cross-program-invocation/steel/hand/api/Cargo.toml b/basics/cross-program-invocation/steel/hand/api/Cargo.toml new file mode 100644 index 000000000..10404f391 --- /dev/null +++ b/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 diff --git a/basics/cross-program-invocation/steel/hand/api/src/instruction.rs b/basics/cross-program-invocation/steel/hand/api/src/instruction.rs new file mode 100644 index 000000000..1bc8f9d96 --- /dev/null +++ b/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); diff --git a/basics/cross-program-invocation/steel/hand/api/src/lib.rs b/basics/cross-program-invocation/steel/hand/api/src/lib.rs new file mode 100644 index 000000000..ac968003f --- /dev/null +++ b/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"); diff --git a/basics/cross-program-invocation/steel/hand/api/src/sdk.rs b/basics/cross-program-invocation/steel/hand/api/src/sdk.rs new file mode 100644 index 000000000..dd0bd361e --- /dev/null +++ b/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(), + } +} diff --git a/basics/cross-program-invocation/steel/hand/program/Cargo.toml b/basics/cross-program-invocation/steel/hand/program/Cargo.toml new file mode 100644 index 000000000..957f017d3 --- /dev/null +++ b/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"] } diff --git a/basics/cross-program-invocation/steel/hand/program/src/lib.rs b/basics/cross-program-invocation/steel/hand/program/src/lib.rs new file mode 100644 index 000000000..7251912e2 --- /dev/null +++ b/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); diff --git a/basics/cross-program-invocation/steel/hand/program/src/pull_lever.rs b/basics/cross-program-invocation/steel/hand/program/src/pull_lever.rs new file mode 100644 index 000000000..2668f2d6e --- /dev/null +++ b/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(()) +} diff --git a/basics/cross-program-invocation/steel/hand/program/tests/test.rs b/basics/cross-program-invocation/steel/hand/program/tests/test.rs new file mode 100644 index 000000000..fc6b34d4e --- /dev/null +++ b/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()); +} diff --git a/basics/cross-program-invocation/steel/lever/.gitignore b/basics/cross-program-invocation/steel/lever/.gitignore new file mode 100644 index 000000000..052739dbc --- /dev/null +++ b/basics/cross-program-invocation/steel/lever/.gitignore @@ -0,0 +1,2 @@ +target +test-ledger diff --git a/basics/cross-program-invocation/steel/lever/Cargo.toml b/basics/cross-program-invocation/steel/lever/Cargo.toml new file mode 100644 index 000000000..6231f1076 --- /dev/null +++ b/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" diff --git a/basics/cross-program-invocation/steel/lever/README.md b/basics/cross-program-invocation/steel/lever/README.md new file mode 100644 index 000000000..4b01b5852 --- /dev/null +++ b/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 +``` diff --git a/basics/cross-program-invocation/steel/lever/api/Cargo.toml b/basics/cross-program-invocation/steel/lever/api/Cargo.toml new file mode 100644 index 000000000..159b3dedf --- /dev/null +++ b/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 diff --git a/basics/cross-program-invocation/steel/lever/api/src/consts.rs b/basics/cross-program-invocation/steel/lever/api/src/consts.rs new file mode 100644 index 000000000..af20c1a11 --- /dev/null +++ b/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"; diff --git a/basics/cross-program-invocation/steel/lever/api/src/error.rs b/basics/cross-program-invocation/steel/lever/api/src/error.rs new file mode 100644 index 000000000..a6b46c777 --- /dev/null +++ b/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); diff --git a/basics/cross-program-invocation/steel/lever/api/src/instruction.rs b/basics/cross-program-invocation/steel/lever/api/src/instruction.rs new file mode 100644 index 000000000..45bd9dad3 --- /dev/null +++ b/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); diff --git a/basics/cross-program-invocation/steel/lever/api/src/lib.rs b/basics/cross-program-invocation/steel/lever/api/src/lib.rs new file mode 100644 index 000000000..1974d1b96 --- /dev/null +++ b/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"); diff --git a/basics/cross-program-invocation/steel/lever/api/src/sdk.rs b/basics/cross-program-invocation/steel/lever/api/src/sdk.rs new file mode 100644 index 000000000..ce5ab8fb3 --- /dev/null +++ b/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(), + } +} diff --git a/basics/cross-program-invocation/steel/lever/api/src/state/mod.rs b/basics/cross-program-invocation/steel/lever/api/src/state/mod.rs new file mode 100644 index 000000000..ae4d12439 --- /dev/null +++ b/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()) +// } diff --git a/basics/cross-program-invocation/steel/lever/api/src/state/power_status.rs b/basics/cross-program-invocation/steel/lever/api/src/state/power_status.rs new file mode 100644 index 000000000..b30fc5c69 --- /dev/null +++ b/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); diff --git a/basics/cross-program-invocation/steel/lever/api/src/utils.rs b/basics/cross-program-invocation/steel/lever/api/src/utils.rs new file mode 100644 index 000000000..d39d4b0a8 --- /dev/null +++ b/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() +} diff --git a/basics/cross-program-invocation/steel/lever/program/Cargo.toml b/basics/cross-program-invocation/steel/lever/program/Cargo.toml new file mode 100644 index 000000000..08fdfd845 --- /dev/null +++ b/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"] } diff --git a/basics/cross-program-invocation/steel/lever/program/src/initialize.rs b/basics/cross-program-invocation/steel/lever/program/src/initialize.rs new file mode 100644 index 000000000..76f4874df --- /dev/null +++ b/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::(power_info, system_program, signer_info, &lever_api::ID, &[])?; + + Ok(()) +} diff --git a/basics/cross-program-invocation/steel/lever/program/src/lib.rs b/basics/cross-program-invocation/steel/lever/program/src/lib.rs new file mode 100644 index 000000000..0aa3ab168 --- /dev/null +++ b/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); diff --git a/basics/cross-program-invocation/steel/lever/program/src/switch_power.rs b/basics/cross-program-invocation/steel/lever/program/src/switch_power.rs new file mode 100644 index 000000000..d3f1869f9 --- /dev/null +++ b/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::(&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(()) +} diff --git a/basics/cross-program-invocation/steel/lever/program/tests/test.rs b/basics/cross-program-invocation/steel/lever/program/tests/test.rs new file mode 100644 index 000000000..2756b2644 --- /dev/null +++ b/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); +}