diff --git a/basics/realloc/steel/.gitignore b/basics/realloc/steel/.gitignore new file mode 100644 index 000000000..052739dbc --- /dev/null +++ b/basics/realloc/steel/.gitignore @@ -0,0 +1,2 @@ +target +test-ledger diff --git a/basics/realloc/steel/Cargo.toml b/basics/realloc/steel/Cargo.toml new file mode 100644 index 000000000..72eddd3c3 --- /dev/null +++ b/basics/realloc/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 = "" +repository = "" +readme = "./README.md" +keywords = ["solana"] + +[workspace.dependencies] +realloc-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/realloc/steel/README.md b/basics/realloc/steel/README.md new file mode 100644 index 000000000..769660f4d --- /dev/null +++ b/basics/realloc/steel/README.md @@ -0,0 +1,28 @@ +# Realloc + +**Realloc** 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 +- [`Add`](program/src/add.rs) – Add ... +- [`Initialize`](program/src/initialize.rs) – Initialize ... + +## State +- [`Counter`](api/src/state/counter.rs) – Counter ... + +## Get started + +Compile your program: +```sh +steel build +``` + +Run unit and integration tests: +```sh +steel test +``` diff --git a/basics/realloc/steel/api/Cargo.toml b/basics/realloc/steel/api/Cargo.toml new file mode 100644 index 000000000..1e0002fb7 --- /dev/null +++ b/basics/realloc/steel/api/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "realloc-api" +description = "API for interacting with the Realloc 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/realloc/steel/api/src/consts.rs b/basics/realloc/steel/api/src/consts.rs new file mode 100644 index 000000000..115e5abe9 --- /dev/null +++ b/basics/realloc/steel/api/src/consts.rs @@ -0,0 +1,4 @@ +/// Seed of the address_info account PDA. +pub const ADDRESS_INFO: &[u8] = b"address_info"; + +pub const MAX_STR_LEN: usize = 32; diff --git a/basics/realloc/steel/api/src/error.rs b/basics/realloc/steel/api/src/error.rs new file mode 100644 index 000000000..edfa7574e --- /dev/null +++ b/basics/realloc/steel/api/src/error.rs @@ -0,0 +1,10 @@ +use steel::*; + +#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] +#[repr(u32)] +pub enum ReallocError { + #[error("This is a dummy error")] + Dummy = 0, +} + +error!(ReallocError); diff --git a/basics/realloc/steel/api/src/instruction.rs b/basics/realloc/steel/api/src/instruction.rs new file mode 100644 index 000000000..d4e000103 --- /dev/null +++ b/basics/realloc/steel/api/src/instruction.rs @@ -0,0 +1,21 @@ +use steel::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum ReallocInstruction { + Initialize = 0, + Add = 1 +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Initialize {} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Add { + pub amount: [u8; 8] +} + +instruction!(ReallocInstruction, Initialize); +instruction!(ReallocInstruction, Add); diff --git a/basics/realloc/steel/api/src/lib.rs b/basics/realloc/steel/api/src/lib.rs new file mode 100644 index 000000000..76867dc34 --- /dev/null +++ b/basics/realloc/steel/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!("z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35"); diff --git a/basics/realloc/steel/api/src/sdk.rs b/basics/realloc/steel/api/src/sdk.rs new file mode 100644 index 000000000..c1f0ff619 --- /dev/null +++ b/basics/realloc/steel/api/src/sdk.rs @@ -0,0 +1,29 @@ +use steel::*; + +use crate::prelude::*; + +pub fn initialize(signer: Pubkey) -> Instruction { + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(counter_pda().0, false), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: Initialize {}.to_bytes() + } +} + +pub fn add(signer: Pubkey, amount: u64) -> Instruction { + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(counter_pda().0, false), + ], + data: Add { + amount: amount.to_le_bytes(), + } + .to_bytes(), + } +} diff --git a/basics/realloc/steel/api/src/state/address_info.rs b/basics/realloc/steel/api/src/state/address_info.rs new file mode 100644 index 000000000..fd1680b34 --- /dev/null +++ b/basics/realloc/steel/api/src/state/address_info.rs @@ -0,0 +1,27 @@ +use crate::consts::*; +use crate::utils::*; +use steel::*; + +use super::ReallocAccount; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct AddressInfo { + pub name: [u8; MAX_STR_LEN], + pub house_number: u8, + pub street: [u8; MAX_STR_LEN], + pub city: [u8; MAX_STR_LEN], +} + +impl AddressInfo { + pub fn new(name: &str, house_number: u8, street: &str, city: &str) -> Self { + AddressInfo { + name: str_to_bytes(name), + house_number, + street: str_to_bytes(street), + city: str_to_bytes(city), + } + } +} + +account!(ReallocAccount, AddressInfo); diff --git a/basics/realloc/steel/api/src/state/enchanced_address_info.rs b/basics/realloc/steel/api/src/state/enchanced_address_info.rs new file mode 100644 index 000000000..95dbd364c --- /dev/null +++ b/basics/realloc/steel/api/src/state/enchanced_address_info.rs @@ -0,0 +1,43 @@ +use crate::consts::*; +use crate::state::AddressInfo; +use crate::utils::*; +use steel::*; + +use super::ReallocAccount; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct EnhancedAddressInfoExtender { + pub state: [u8; MAX_STR_LEN], + pub zip: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct EnhancedAddressInfo { + pub zip: u32, + pub house_number: u8, + + pub name: [u8; MAX_STR_LEN], + pub street: [u8; MAX_STR_LEN], + pub city: [u8; MAX_STR_LEN], + pub state: [u8; MAX_STR_LEN], + + pub _padding: [u8; 3], +} + +impl EnhancedAddressInfo { + pub fn from_address_info(address_info: AddressInfo, state: &str, zip: u32) -> Self { + EnhancedAddressInfo { + name: address_info.name, + house_number: address_info.house_number, + street: address_info.street, + city: address_info.city, + state: str_to_bytes(state), + zip, + _padding: [0u8; 3], + } + } +} + +account!(ReallocAccount, EnhancedAddressInfo); diff --git a/basics/realloc/steel/api/src/state/mod.rs b/basics/realloc/steel/api/src/state/mod.rs new file mode 100644 index 000000000..8576d0ed1 --- /dev/null +++ b/basics/realloc/steel/api/src/state/mod.rs @@ -0,0 +1,25 @@ +mod address_info; +mod enchanced_address_info; +mod work_info; + +pub use address_info::*; +pub use enchanced_address_info::*; +pub use work_info::*; + +use steel::*; + +use crate::consts::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +pub enum ReallocAccount { + AddressInfo = 0, + EnhancedAddressInfo = 1, + EnhancedAddressInfoExtender = 2, + WorkInfo = 3, +} + +/// Fetch PDA of the counter account. +pub fn counter_pda() -> (Pubkey, u8) { + Pubkey::find_program_address(&[COUNTER], &crate::id()) +} diff --git a/basics/realloc/steel/api/src/state/work_info.rs b/basics/realloc/steel/api/src/state/work_info.rs new file mode 100644 index 000000000..43f3cbda0 --- /dev/null +++ b/basics/realloc/steel/api/src/state/work_info.rs @@ -0,0 +1,27 @@ +use crate::consts::*; +use crate::utils::*; +use steel::*; + +use super::ReallocAccount; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct WorkInfo { + pub name: [u8; MAX_STR_LEN], + pub position: [u8; MAX_STR_LEN], + pub company: [u8; MAX_STR_LEN], + pub years_employed: u8, +} + +impl WorkInfo { + pub fn new(name: &str, position: &str, company: &str, years_employed: u8) -> Self { + WorkInfo { + name: str_to_bytes(name), + position: str_to_bytes(position), + company: str_to_bytes(company), + years_employed, + } + } +} + +account!(ReallocAccount, WorkInfo); diff --git a/basics/realloc/steel/api/src/utils.rs b/basics/realloc/steel/api/src/utils.rs new file mode 100644 index 000000000..900ed0062 --- /dev/null +++ b/basics/realloc/steel/api/src/utils.rs @@ -0,0 +1,5 @@ +pub fn str_to_bytes(name: &str) -> [u8; MAX_STR_LEN] { + let mut name_bytes = [0u8; MAX_STR_LEN]; + name_bytes[..name.len()].copy_from_slice(name.as_bytes()); + name_bytes +} diff --git a/basics/realloc/steel/program/Cargo.toml b/basics/realloc/steel/program/Cargo.toml new file mode 100644 index 000000000..ac2bae813 --- /dev/null +++ b/basics/realloc/steel/program/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "realloc-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] +realloc-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/realloc/steel/program/src/add.rs b/basics/realloc/steel/program/src/add.rs new file mode 100644 index 000000000..e2aafa05b --- /dev/null +++ b/basics/realloc/steel/program/src/add.rs @@ -0,0 +1,22 @@ +use realloc_api::prelude::*; +use steel::*; + +pub fn process_add(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse args. + let args = Add::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts. + let [signer_info, counter_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + let counter = counter_info + .as_account_mut::(&realloc_api::ID)? + .assert_mut(|c| c.value < 100)?; + + // Update state + counter.value += amount; + + Ok(()) +} diff --git a/basics/realloc/steel/program/src/initialize.rs b/basics/realloc/steel/program/src/initialize.rs new file mode 100644 index 000000000..f579bd9a2 --- /dev/null +++ b/basics/realloc/steel/program/src/initialize.rs @@ -0,0 +1,28 @@ +use realloc_api::prelude::*; +use steel::*; + +pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { + // Load accounts. + let [signer_info, counter_info, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + counter_info.is_empty()?.is_writable()?.has_seeds( + &[COUNTER], + &realloc_api::ID + )?; + system_program.is_program(&system_program::ID)?; + + // Initialize counter. + create_account::( + counter_info, + system_program, + signer_info, + &realloc_api::ID, + &[COUNTER], + )?; + let counter = counter_info.as_account_mut::(&realloc_api::ID)?; + counter.value = 0; + + Ok(()) +} diff --git a/basics/realloc/steel/program/src/lib.rs b/basics/realloc/steel/program/src/lib.rs new file mode 100644 index 000000000..8c0f2d75e --- /dev/null +++ b/basics/realloc/steel/program/src/lib.rs @@ -0,0 +1,25 @@ +mod add; +mod initialize; + +use add::*; +use initialize::*; + +use realloc_api::prelude::*; +use steel::*; + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let (ix, data) = parse_instruction(&realloc_api::ID, program_id, data)?; + + match ix { + ReallocInstruction::Initialize => process_initialize(accounts, data)?, + ReallocInstruction::Add => process_add(accounts, data)?, + } + + Ok(()) +} + +entrypoint!(process_instruction); diff --git a/basics/realloc/steel/program/tests/test.rs b/basics/realloc/steel/program/tests/test.rs new file mode 100644 index 000000000..e75c75637 --- /dev/null +++ b/basics/realloc/steel/program/tests/test.rs @@ -0,0 +1,46 @@ +use realloc_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( + "realloc_program", + realloc_api::ID, + processor!(realloc_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; + + // Submit initialize transaction. + let ix = initialize(payer.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()); + + // Verify counter was initialized. + let counter_address = counter_pda().0; + let counter_account = banks.get_account(counter_address).await.unwrap().unwrap(); + let counter = Counter::try_from_bytes(&counter_account.data).unwrap(); + assert_eq!(counter_account.owner, realloc_api::ID); + assert_eq!(counter.value, 0); + + // Submit add transaction. + let ix = add(payer.pubkey(), 42); + 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 counter was incremented. + let counter_account = banks.get_account(counter_address).await.unwrap().unwrap(); + let counter = Counter::try_from_bytes(&counter_account.data).unwrap(); + assert_eq!(counter.value, 42); +} +