From 85082a332b3b744625a038403f5581389ca50b85 Mon Sep 17 00:00:00 2001 From: Sabir Khan <76485932+simplysabir@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:17:28 +0530 Subject: [PATCH] add basics/rent/steel (#240) --- basics/rent/steel/.gitignore | 2 + basics/rent/steel/Cargo.toml | 21 ++++ basics/rent/steel/README.md | 28 ++++++ basics/rent/steel/api/Cargo.toml | 18 ++++ basics/rent/steel/api/src/error.rs | 10 ++ basics/rent/steel/api/src/instruction.rs | 16 ++++ basics/rent/steel/api/src/lib.rs | 21 ++++ basics/rent/steel/api/src/sdk.rs | 29 ++++++ basics/rent/steel/api/src/state/address.rs | 11 +++ basics/rent/steel/api/src/state/mod.rs | 11 +++ basics/rent/steel/package.json | 14 +++ basics/rent/steel/program/Cargo.toml | 26 +++++ .../rent/steel/program/src/create_account.rs | 36 +++++++ basics/rent/steel/program/src/lib.rs | 21 ++++ basics/rent/steel/program/tests/test.rs | 95 +++++++++++++++++++ 15 files changed, 359 insertions(+) create mode 100644 basics/rent/steel/.gitignore create mode 100644 basics/rent/steel/Cargo.toml create mode 100644 basics/rent/steel/README.md create mode 100644 basics/rent/steel/api/Cargo.toml create mode 100644 basics/rent/steel/api/src/error.rs create mode 100644 basics/rent/steel/api/src/instruction.rs create mode 100644 basics/rent/steel/api/src/lib.rs create mode 100644 basics/rent/steel/api/src/sdk.rs create mode 100644 basics/rent/steel/api/src/state/address.rs create mode 100644 basics/rent/steel/api/src/state/mod.rs create mode 100644 basics/rent/steel/package.json create mode 100644 basics/rent/steel/program/Cargo.toml create mode 100644 basics/rent/steel/program/src/create_account.rs create mode 100644 basics/rent/steel/program/src/lib.rs create mode 100644 basics/rent/steel/program/tests/test.rs diff --git a/basics/rent/steel/.gitignore b/basics/rent/steel/.gitignore new file mode 100644 index 000000000..052739dbc --- /dev/null +++ b/basics/rent/steel/.gitignore @@ -0,0 +1,2 @@ +target +test-ledger diff --git a/basics/rent/steel/Cargo.toml b/basics/rent/steel/Cargo.toml new file mode 100644 index 000000000..8d43c4138 --- /dev/null +++ b/basics/rent/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] +rent_example-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/rent/steel/README.md b/basics/rent/steel/README.md new file mode 100644 index 000000000..0bec2629c --- /dev/null +++ b/basics/rent/steel/README.md @@ -0,0 +1,28 @@ +# RentExample + +**RentExample** 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/rent/steel/api/Cargo.toml b/basics/rent/steel/api/Cargo.toml new file mode 100644 index 000000000..7121fa9c3 --- /dev/null +++ b/basics/rent/steel/api/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rent_example-api" +description = "API for interacting with the RentExample 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/rent/steel/api/src/error.rs b/basics/rent/steel/api/src/error.rs new file mode 100644 index 000000000..c8d041045 --- /dev/null +++ b/basics/rent/steel/api/src/error.rs @@ -0,0 +1,10 @@ +use steel::*; + +#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] +#[repr(u32)] +pub enum RentExampleError { + #[error("This is a dummy error")] + Dummy = 0, +} + +error!(RentExampleError); diff --git a/basics/rent/steel/api/src/instruction.rs b/basics/rent/steel/api/src/instruction.rs new file mode 100644 index 000000000..5a4289fe4 --- /dev/null +++ b/basics/rent/steel/api/src/instruction.rs @@ -0,0 +1,16 @@ +use steel::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum RentInstruction { + CreateSystemAccount = 0, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct CreateSystemAccount { + pub name: [u8; 32], + pub address: [u8; 64], +} + +instruction!(RentInstruction, CreateSystemAccount); diff --git a/basics/rent/steel/api/src/lib.rs b/basics/rent/steel/api/src/lib.rs new file mode 100644 index 000000000..3c0c8c90a --- /dev/null +++ b/basics/rent/steel/api/src/lib.rs @@ -0,0 +1,21 @@ +pub mod error; +pub mod instruction; +pub mod sdk; +pub mod state; + +pub mod prelude { + pub use crate::error::*; + pub use crate::instruction::*; + pub use crate::sdk::*; + pub use crate::state::*; + // Re-export common solana dependencies + pub use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey, rent::Rent, + system_program, + }; +} + +use steel::*; + +// TODO Set program id +declare_id!("z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35"); diff --git a/basics/rent/steel/api/src/sdk.rs b/basics/rent/steel/api/src/sdk.rs new file mode 100644 index 000000000..ddb99ac96 --- /dev/null +++ b/basics/rent/steel/api/src/sdk.rs @@ -0,0 +1,29 @@ +use crate::prelude::*; +use steel::*; + +pub fn create_system_account( + payer: Pubkey, + new_account: Pubkey, + name: String, + address: String, +) -> Instruction { + let mut name_bytes = [0u8; 32]; + let mut address_bytes = [0u8; 64]; + + name_bytes[..name.len()].copy_from_slice(name.as_bytes()); + address_bytes[..address.len()].copy_from_slice(address.as_bytes()); + + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(payer, true), + AccountMeta::new(new_account, true), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: CreateSystemAccount { + name: name_bytes, + address: address_bytes, + } + .to_bytes(), + } +} diff --git a/basics/rent/steel/api/src/state/address.rs b/basics/rent/steel/api/src/state/address.rs new file mode 100644 index 000000000..381acd526 --- /dev/null +++ b/basics/rent/steel/api/src/state/address.rs @@ -0,0 +1,11 @@ +use super::RentAccount; +use steel::*; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Address { + pub name: [u8; 32], + pub address: [u8; 64], +} + +account!(RentAccount, Address); diff --git a/basics/rent/steel/api/src/state/mod.rs b/basics/rent/steel/api/src/state/mod.rs new file mode 100644 index 000000000..fe2aca28e --- /dev/null +++ b/basics/rent/steel/api/src/state/mod.rs @@ -0,0 +1,11 @@ +mod address; + +pub use address::*; + +use steel::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +pub enum RentAccount { + Address = 0, +} diff --git a/basics/rent/steel/package.json b/basics/rent/steel/package.json new file mode 100644 index 000000000..b08f2eb02 --- /dev/null +++ b/basics/rent/steel/package.json @@ -0,0 +1,14 @@ +{ + "name": "rent-example", + "version": "1.0.0", + "description": "rent example with steel framework for solana", + "scripts": { + "test": "cargo test-sbf", + "build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures && pnpm test", + "build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./program/target/so", + "deploy": "solana program deploy ./program/target/so/rent_example_program.so" + }, + "keywords": [], + "author": "Sabir Khan", + "license": "ISC" +} diff --git a/basics/rent/steel/program/Cargo.toml b/basics/rent/steel/program/Cargo.toml new file mode 100644 index 000000000..89748845d --- /dev/null +++ b/basics/rent/steel/program/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "rent_example-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] +rent_example-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/rent/steel/program/src/create_account.rs b/basics/rent/steel/program/src/create_account.rs new file mode 100644 index 000000000..bceb37a85 --- /dev/null +++ b/basics/rent/steel/program/src/create_account.rs @@ -0,0 +1,36 @@ +use rent_example_api::prelude::*; +use solana_program::msg; +use steel::*; + +pub fn process_create_account(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + let args = CreateSystemAccount::try_from_bytes(data)?; + + let [payer_info, new_account_info, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + payer_info.is_signer()?; + new_account_info.is_signer()?; + + let account_size = std::mem::size_of::
(); + let rent = Rent::get()?; + let lamports_required = rent.minimum_balance(account_size); + + msg!("Account size: {}", account_size); + msg!("Lamports required: {}", lamports_required); + + // Create account with correct type argument + create_account::
( + new_account_info, + system_program, + payer_info, + &rent_example_api::ID, + &[], + )?; + + let address = new_account_info.as_account_mut::
(&rent_example_api::ID)?; + address.name = args.name; + address.address = args.address; + + Ok(()) +} diff --git a/basics/rent/steel/program/src/lib.rs b/basics/rent/steel/program/src/lib.rs new file mode 100644 index 000000000..e4d937af0 --- /dev/null +++ b/basics/rent/steel/program/src/lib.rs @@ -0,0 +1,21 @@ +mod create_account; + +use create_account::*; +use rent_example_api::prelude::*; +use steel::*; + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let (ix, data) = parse_instruction::(&rent_example_api::ID, program_id, data)?; + + match ix { + RentInstruction::CreateSystemAccount => process_create_account(accounts, data)?, + } + + Ok(()) +} + +entrypoint!(process_instruction); diff --git a/basics/rent/steel/program/tests/test.rs b/basics/rent/steel/program/tests/test.rs new file mode 100644 index 000000000..0d6fe627f --- /dev/null +++ b/basics/rent/steel/program/tests/test.rs @@ -0,0 +1,95 @@ +use rent_example_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( + "rent_example_program", + rent_example_api::ID, + processor!(rent_example_program::process_instruction), + ); + program_test.prefer_bpf(true); + program_test.start().await +} + +#[tokio::test] +async fn test_create_system_account() { + // Setup test environment + let (mut banks_client, payer, recent_blockhash) = setup().await; + + // Generate a new keypair for the account we'll create + let new_account = Keypair::new(); + + // Test data + let name = "John Doe"; + let address = "123 Blockchain Street"; + + // Create the instruction + let ix = create_system_account( + payer.pubkey(), + new_account.pubkey(), + name.to_string(), + address.to_string(), + ); + + // Create and send transaction + let mut transaction = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &new_account], + recent_blockhash, + ); + + // Process transaction + let result = banks_client.process_transaction(transaction).await; + assert!( + result.is_ok(), + "Failed to process transaction: {:?}", + result + ); + + // Fetch and verify the created account + let account = banks_client + .get_account(new_account.pubkey()) + .await + .expect("Failed to get account") + .expect("Account not found"); + + // Verify account owner + assert_eq!( + account.owner, + rent_example_api::ID, + "Incorrect account owner" + ); + + // Deserialize and verify account data + let address_account = + Address::try_from_bytes(&account.data).expect("Failed to deserialize account data"); + + // Convert stored bytes back to strings for comparison + let stored_name = String::from_utf8( + address_account + .name + .iter() + .take_while(|&&b| b != 0) + .cloned() + .collect::>(), + ) + .unwrap(); + + let stored_address = String::from_utf8( + address_account + .address + .iter() + .take_while(|&&b| b != 0) + .cloned() + .collect::>(), + ) + .unwrap(); + + // Verify the stored data matches what we sent + assert_eq!(stored_name, name, "Stored name doesn't match"); + assert_eq!(stored_address, address, "Stored address doesn't match"); +}