Skip to content

Commit

Permalink
add basics/favorites/steel (#192)
Browse files Browse the repository at this point in the history
  • Loading branch information
Perelyn-sama authored Jan 2, 2025
1 parent ecf7486 commit 81d5424
Show file tree
Hide file tree
Showing 17 changed files with 529 additions and 0 deletions.
2 changes: 2 additions & 0 deletions basics/favorites/steel/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target
test-ledger
21 changes: 21 additions & 0 deletions basics/favorites/steel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
22 changes: 22 additions & 0 deletions basics/favorites/steel/README.md
Original file line number Diff line number Diff line change
@@ -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
```
11 changes: 11 additions & 0 deletions basics/favorites/steel/api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions basics/favorites/steel/api/src/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// Seed of the favorites account PDA.
pub const FAVORITES: &[u8] = b"favorites";
10 changes: 10 additions & 0 deletions basics/favorites/steel/api/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use steel::*;

#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)]
#[repr(u32)]
pub enum SteelError {
#[error("This is a dummy error")]
Dummy = 0,
}

error!(SteelError);
19 changes: 19 additions & 0 deletions basics/favorites/steel/api/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use steel::*;

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
pub enum SteelInstruction {
SetFavorites = 0,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct SetFavorites {
pub number: [u8; 8],

pub color: [u8; 32],

pub hobbies: [[u8; 32]; 3],
}

instruction!(SteelInstruction, SetFavorites);
20 changes: 20 additions & 0 deletions basics/favorites/steel/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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");
24 changes: 24 additions & 0 deletions basics/favorites/steel/api/src/sdk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use steel::*;

use crate::prelude::*;

pub fn set_favorites(signer: Pubkey, number: u64, color: &str, hobbies: Vec<&str>) -> Instruction {
let color_bytes: [u8; 32] = string_to_bytes32_padded(color).unwrap();

let hobbies_bytes = strings_to_bytes32_array_padded(hobbies).unwrap();

Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(favorites_pda().0, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: SetFavorites {
number: number.to_le_bytes(),
color: color_bytes,
hobbies: hobbies_bytes,
}
.to_bytes(),
}
}
15 changes: 15 additions & 0 deletions basics/favorites/steel/api/src/state/favorites.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use steel::*;

use super::SteelAccount;

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Favorites {
pub number: u64,

pub color: [u8; 32],

pub hobbies: [[u8; 32]; 3],
}

account!(SteelAccount, Favorites);
18 changes: 18 additions & 0 deletions basics/favorites/steel/api/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
mod favorites;

pub use favorites::*;

use steel::*;

use crate::consts::*;

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum SteelAccount {
Favorites = 0,
}

/// Fetch PDA of the favorites account.
pub fn favorites_pda() -> (Pubkey, u8) {
Pubkey::find_program_address(&[FAVORITES], &crate::id())
}
85 changes: 85 additions & 0 deletions basics/favorites/steel/api/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::error::Error;
use std::fmt;

#[derive(Debug)]
pub enum ConversionError {
StringTooLong(usize),
StringTooShort(usize),
VecLengthMismatch { expected: usize, actual: usize },
InvalidUtf8,
}

impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ConversionError::StringTooLong(len) => {
write!(f, "String length {} exceeds 32 bytes", len)
}
ConversionError::StringTooShort(len) => {
write!(f, "String length {} is less than 32 bytes", len)
}
ConversionError::VecLengthMismatch { expected, actual } => {
write!(
f,
"Vector length mismatch: expected {}, got {}",
expected, actual
)
}
ConversionError::InvalidUtf8 => write!(f, "Invalid UTF-8 sequence in bytes"),
}
}
}

impl Error for ConversionError {}

// Convert string to bytes with padding
pub fn string_to_bytes32_padded(input: &str) -> Result<[u8; 32], ConversionError> {
let bytes = input.as_bytes();
let len = bytes.len();

if len > 32 {
return Err(ConversionError::StringTooLong(len));
}

let mut result = [0u8; 32];
result[..len].copy_from_slice(bytes);
Ok(result)
}

// Convert bytes back to string, trimming trailing zeros
pub fn bytes32_to_string(bytes: &[u8; 32]) -> Result<String, ConversionError> {
// Find the actual length by looking for the first zero or taking full length
let actual_len = bytes.iter().position(|&b| b == 0).unwrap_or(32);

// Convert the slice up to actual_len to a string
String::from_utf8(bytes[..actual_len].to_vec()).map_err(|_| ConversionError::InvalidUtf8)
}

// Convert vec of strings to byte arrays with padding
pub fn strings_to_bytes32_array_padded<const N: usize>(
inputs: Vec<&str>,
) -> Result<[[u8; 32]; N], ConversionError> {
if inputs.len() != N {
return Err(ConversionError::VecLengthMismatch {
expected: N,
actual: inputs.len(),
});
}

let mut result = [[0u8; 32]; N];
for (i, input) in inputs.iter().enumerate() {
result[i] = string_to_bytes32_padded(input)?;
}
Ok(result)
}

// Convert array of byte arrays back to vec of strings
pub fn bytes32_array_to_strings<const N: usize>(
bytes_array: &[[u8; 32]; N],
) -> Result<Vec<String>, ConversionError> {
let mut result = Vec::with_capacity(N);
for bytes in bytes_array.iter() {
result.push(bytes32_to_string(bytes)?);
}
Ok(result)
}
19 changes: 19 additions & 0 deletions basics/favorites/steel/program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"] }
22 changes: 22 additions & 0 deletions basics/favorites/steel/program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
mod set_favorites;

pub use set_favorites::*;

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::SetFavorites => process_set_favorites(accounts, data)?,
}

Ok(())
}

entrypoint!(process_instruction);
49 changes: 49 additions & 0 deletions basics/favorites/steel/program/src/set_favorites.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use solana_program::msg;
use steel::*;
use steel_api::prelude::*;

pub fn process_set_favorites(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
// Parse args.
let args = SetFavorites::try_from_bytes(data)?;
let number = u64::from_le_bytes(args.number);
let color = bytes32_to_string(&args.color).unwrap();
let hobbies = bytes32_array_to_strings(&args.hobbies).unwrap();

// Get expected pda bump.
let favorites_bump = favorites_pda().1;

// Load accounts.
let [user_info, favorites_info, system_program] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
user_info.is_signer()?;
favorites_info.is_empty()?.is_writable()?.has_seeds(
&[FAVORITES],
favorites_bump,
&steel_api::ID,
)?;
system_program.is_program(&system_program::ID)?;

// Initialize favorites.
create_account::<Favorites>(
favorites_info,
&steel_api::ID,
&[FAVORITES, &[favorites_bump]],
system_program,
user_info,
)?;

msg!("Greetings from {}", &steel_api::ID);
let user_public_key = user_info.key;

msg!(
"User {user_public_key}'s favorite number is {number}, favorite color is: {color}, and their hobbies are {hobbies:?}",
);

let favorites = favorites_info.to_account_mut::<Favorites>(&steel_api::ID)?;
favorites.number = number;
favorites.color = args.color;
favorites.hobbies = args.hobbies;

Ok(())
}
Loading

0 comments on commit 81d5424

Please sign in to comment.