diff --git a/basics/checking-accounts/steel/.gitignore b/basics/checking-accounts/steel/.gitignore new file mode 100644 index 000000000..052739dbc --- /dev/null +++ b/basics/checking-accounts/steel/.gitignore @@ -0,0 +1,2 @@ +target +test-ledger diff --git a/basics/checking-accounts/steel/Cargo.toml b/basics/checking-accounts/steel/Cargo.toml new file mode 100644 index 000000000..203056488 --- /dev/null +++ b/basics/checking-accounts/steel/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 = "" +respository = "" +readme = "./README.md" +keywords = ["solana"] + +[workspace.dependencies] +steel-api = { path = "./api", version = "0.1.0" } +bytemuck = "1.14" +num_enum = "0.7" +solana-program = "1.18" +steel = "1.3" +thiserror = "1.0" diff --git a/basics/checking-accounts/steel/README.md b/basics/checking-accounts/steel/README.md new file mode 100644 index 000000000..4f4fe2a54 --- /dev/null +++ b/basics/checking-accounts/steel/README.md @@ -0,0 +1,22 @@ +# Steel + +**Steel** 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 +- [`Hello`](program/src/hello.rs) – Hello ... + +## State +- [`User`](api/src/state/user.rs) – User ... + +## Tests + +To run the test suit, use the Solana toolchain: +``` +cargo test-sbf +``` diff --git a/basics/checking-accounts/steel/api/Cargo.toml b/basics/checking-accounts/steel/api/Cargo.toml new file mode 100644 index 000000000..34b563782 --- /dev/null +++ b/basics/checking-accounts/steel/api/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "steel-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +bytemuck.workspace = true +num_enum.workspace = true +solana-program.workspace = true +steel.workspace = true +thiserror.workspace = true diff --git a/basics/checking-accounts/steel/api/src/consts.rs b/basics/checking-accounts/steel/api/src/consts.rs new file mode 100644 index 000000000..97104be5e --- /dev/null +++ b/basics/checking-accounts/steel/api/src/consts.rs @@ -0,0 +1,2 @@ +/// Seed of the account_to_change account PDA. +pub const ACCOUNT_TO_CHANGE: &[u8] = b"account_to_change"; diff --git a/basics/checking-accounts/steel/api/src/instruction.rs b/basics/checking-accounts/steel/api/src/instruction.rs new file mode 100644 index 000000000..f6225266c --- /dev/null +++ b/basics/checking-accounts/steel/api/src/instruction.rs @@ -0,0 +1,13 @@ +use steel::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum SteelInstruction { + CheckAccounts = 0, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct CheckAccounts {} + +instruction!(SteelInstruction, CheckAccounts); diff --git a/basics/checking-accounts/steel/api/src/lib.rs b/basics/checking-accounts/steel/api/src/lib.rs new file mode 100644 index 000000000..6db8ac73d --- /dev/null +++ b/basics/checking-accounts/steel/api/src/lib.rs @@ -0,0 +1,16 @@ +pub mod consts; +pub mod instruction; +pub mod sdk; +pub mod state; + +pub mod prelude { + pub use crate::consts::*; + pub use crate::instruction::*; + pub use crate::sdk::*; + pub use crate::state::*; +} + +use steel::*; + +// TODO Set program id +declare_id!("z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35"); diff --git a/basics/checking-accounts/steel/api/src/sdk.rs b/basics/checking-accounts/steel/api/src/sdk.rs new file mode 100644 index 000000000..60df2db5f --- /dev/null +++ b/basics/checking-accounts/steel/api/src/sdk.rs @@ -0,0 +1,20 @@ +use steel::*; + +use crate::prelude::*; + +pub fn check_accounts( + signer: Pubkey, + account_to_create: Pubkey, + account_to_change: Pubkey, +) -> Instruction { + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(account_to_create, false), + AccountMeta::new(account_to_change, false), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: CheckAccounts {}.to_bytes(), + } +} diff --git a/basics/checking-accounts/steel/api/src/state/account_to_change.rs b/basics/checking-accounts/steel/api/src/state/account_to_change.rs new file mode 100644 index 000000000..674d9f7bf --- /dev/null +++ b/basics/checking-accounts/steel/api/src/state/account_to_change.rs @@ -0,0 +1,9 @@ +use steel::*; + +use super::SteelAccount; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct AccountToChange {} + +account!(SteelAccount, AccountToChange); diff --git a/basics/checking-accounts/steel/api/src/state/mod.rs b/basics/checking-accounts/steel/api/src/state/mod.rs new file mode 100644 index 000000000..e44c1daa3 --- /dev/null +++ b/basics/checking-accounts/steel/api/src/state/mod.rs @@ -0,0 +1,18 @@ +mod account_to_change; + +pub use account_to_change::*; + +use steel::*; + +use crate::consts::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +pub enum SteelAccount { + AccountToChange = 0, +} + +/// Fetch PDA of the account_to_change account. +pub fn account_to_change_pda() -> (Pubkey, u8) { + Pubkey::find_program_address(&[ACCOUNT_TO_CHANGE], &crate::id()) +} diff --git a/basics/checking-accounts/steel/program/Cargo.toml b/basics/checking-accounts/steel/program/Cargo.toml new file mode 100644 index 000000000..db99b68cc --- /dev/null +++ b/basics/checking-accounts/steel/program/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "steel-program" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +steel-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/checking-accounts/steel/program/src/check_accounts.rs b/basics/checking-accounts/steel/program/src/check_accounts.rs new file mode 100644 index 000000000..67f70fc6f --- /dev/null +++ b/basics/checking-accounts/steel/program/src/check_accounts.rs @@ -0,0 +1,49 @@ +use solana_program::msg; +use steel::*; + +pub fn process_check_accounts(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { + // Load accounts. + // You can verify the list has the correct number of accounts. + let [signer_info, account_to_create_info, account_to_change_info, system_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // You can verify if an account is a signer + signer_info.is_signer()?; + + // You can verify the program ID from the instruction is in fact + // the program ID of your program. + if system_program.is_program(&system_program::ID).is_err() { + return Err(ProgramError::IncorrectProgramId); + }; + + // You can make sure an account has NOT been initialized. + + msg!("New account: {}", account_to_create_info.key); + if account_to_create_info.lamports() != 0 { + msg!("The program expected the account to create to not yet be initialized."); + return Err(ProgramError::AccountAlreadyInitialized); + }; + // (Create account...) + + // You can also make sure an account has been initialized. + msg!("Account to change: {}", account_to_change_info.key); + if account_to_change_info.lamports() == 0 { + msg!("The program expected the account to change to be initialized."); + return Err(ProgramError::UninitializedAccount); + }; + + // If we want to modify an account's data, it must be owned by our program. + if account_to_change_info.owner != &steel_api::ID { + msg!("Account to change does not have the correct program id."); + return Err(ProgramError::IncorrectProgramId); + }; + + // You can also check pubkeys against constants. + if system_program.key != &system_program::ID { + return Err(ProgramError::IncorrectProgramId); + }; + + Ok(()) +} diff --git a/basics/checking-accounts/steel/program/src/lib.rs b/basics/checking-accounts/steel/program/src/lib.rs new file mode 100644 index 000000000..421e9e3b3 --- /dev/null +++ b/basics/checking-accounts/steel/program/src/lib.rs @@ -0,0 +1,22 @@ +mod check_accounts; + +use check_accounts::*; + +use steel::*; +use steel_api::prelude::*; + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let (ix, data) = parse_instruction(&steel_api::ID, program_id, data)?; + + match ix { + SteelInstruction::CheckAccounts => process_check_accounts(accounts, data)?, + } + + Ok(()) +} + +entrypoint!(process_instruction); diff --git a/basics/checking-accounts/steel/program/tests/test.rs b/basics/checking-accounts/steel/program/tests/test.rs new file mode 100644 index 000000000..bc8f607c0 --- /dev/null +++ b/basics/checking-accounts/steel/program/tests/test.rs @@ -0,0 +1,51 @@ +use solana_program::hash::Hash; +use solana_program_test::{processor, BanksClient, ProgramTest}; +use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction}; +use steel_api::prelude::*; + +async fn setup() -> (BanksClient, Keypair, Hash) { + let mut program_test = ProgramTest::new( + "steel_program", + steel_api::ID, + processor!(steel_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 account_to_create = Keypair::new(); + let account_to_change = Keypair::new(); + + let account_to_change_ix = solana_sdk::system_instruction::create_account( + &payer.pubkey(), + &account_to_change.pubkey(), + solana_sdk::native_token::LAMPORTS_PER_SOL, + 0, + &steel_api::ID, + ); + + let tx = Transaction::new_signed_with_payer( + &[account_to_change_ix], + Some(&payer.pubkey()), + &[&payer, &account_to_change], + blockhash, + ); + + let res = banks.process_transaction(tx).await; + assert!(res.is_ok()); + + // Submit check_accounts transaction. + let ix = check_accounts( + payer.pubkey(), + account_to_create.pubkey(), + account_to_change.pubkey(), + ); + let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); + let res = banks.process_transaction(tx).await; + assert!(res.is_ok()); +}