#[cfg(feature = "host")] use std::{fmt::Display, str::FromStr}; #[cfg(feature = "host")] use base58::{FromBase58, ToBase58}; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::program::ProgramId; pub type Nonce = u128; pub type Data = Vec; /// Account to be used both in public and private contexts #[derive( Serialize, Deserialize, Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, )] #[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct Account { pub program_owner: ProgramId, pub balance: u128, pub data: Data, pub nonce: Nonce, } #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, pub account_id: AccountId, } #[cfg(feature = "host")] impl AccountWithMetadata { pub fn new(account: Account, is_authorized: bool, account_id: impl Into) -> Self { Self { account, is_authorized, account_id: account_id.into(), } } } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] #[cfg_attr( any(feature = "host", test), derive(Debug, Copy, PartialOrd, Ord, Default) )] pub struct AccountId { value: [u8; 32], } impl AccountId { pub fn new(value: [u8; 32]) -> Self { Self { value } } pub fn value(&self) -> &[u8; 32] { &self.value } } impl AsRef<[u8]> for AccountId { fn as_ref(&self) -> &[u8] { &self.value } } #[cfg(feature = "host")] #[derive(Debug, thiserror::Error)] pub enum AccountIdError { #[error("invalid base58")] InvalidBase58(#[from] anyhow::Error), #[error("invalid length: expected 32 bytes, got {0}")] InvalidLength(usize), } #[cfg(feature = "host")] impl FromStr for AccountId { type Err = AccountIdError; fn from_str(s: &str) -> Result { let bytes = s .from_base58() .map_err(|err| anyhow::anyhow!("Invalid base58 err {err:?}"))?; if bytes.len() != 32 { return Err(AccountIdError::InvalidLength(bytes.len())); } let mut value = [0u8; 32]; value.copy_from_slice(&bytes); Ok(AccountId { value }) } } #[cfg(feature = "host")] impl Display for AccountId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.value.to_base58()) } } #[cfg(test)] mod tests { use super::*; use crate::program::DEFAULT_PROGRAM_ID; #[test] fn test_zero_balance_account_data_creation() { let new_acc = Account::default(); assert_eq!(new_acc.balance, 0); } #[test] fn test_zero_nonce_account_data_creation() { let new_acc = Account::default(); assert_eq!(new_acc.nonce, 0); } #[test] fn test_empty_data_account_data_creation() { let new_acc = Account::default(); assert!(new_acc.data.is_empty()); } #[test] fn test_default_program_owner_account_data_creation() { let new_acc = Account::default(); assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID); } #[cfg(feature = "host")] #[test] fn test_account_with_metadata_constructor() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, data: b"testing_account_with_metadata_constructor".to_vec(), nonce: 0xdeadbeef, }; let fingerprint = AccountId::new([8; 32]); let new_acc_with_metadata = AccountWithMetadata::new(account.clone(), true, fingerprint); assert_eq!(new_acc_with_metadata.account, account); assert!(new_acc_with_metadata.is_authorized); assert_eq!(new_acc_with_metadata.account_id, fingerprint); } #[test] fn parse_valid_account_id() { let base58_str = "11111111111111111111111111111111"; let account_id: AccountId = base58_str.parse().unwrap(); assert_eq!(account_id.value, [0u8; 32]); } #[test] fn parse_invalid_base58() { let base58_str = "00".repeat(32); // invalid base58 chars let result = base58_str.parse::().unwrap_err(); assert!(matches!(result, AccountIdError::InvalidBase58(_))); } #[test] fn parse_wrong_length_short() { let base58_str = "11".repeat(31); // 62 chars = 31 bytes let result = base58_str.parse::().unwrap_err(); assert!(matches!(result, AccountIdError::InvalidLength(_))); } #[test] fn parse_wrong_length_long() { let base58_str = "11".repeat(33); // 66 chars = 33 bytes let result = base58_str.parse::().unwrap_err(); assert!(matches!(result, AccountIdError::InvalidLength(_))); } }