From 22440cf95dbb62c554c9c4d8df14846c778f5d5c Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:38:02 -0500 Subject: [PATCH 01/16] initialize nft branch --- nssa/program_methods/guest/src/bin/token.rs | 833 ++++++++++++++++++-- 1 file changed, 753 insertions(+), 80 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 614b5d9..a8db33f 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -1,40 +1,74 @@ use nssa_core::{ - account::{Account, AccountId, AccountWithMetadata, Data, data::DATA_MAX_LENGTH_IN_BYTES}, + account::{Account, AccountId, AccountWithMetadata, Data}, program::{ AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, }, }; // The token program has three functions: -// 1. New token definition. Arguments to this function are: -// * Two **default** accounts: [definition_account, holding_account]. The first default account -// will be initialized with the token definition account values. The second account will be -// initialized to a token holding account for the new token, holding the entire total supply. -// * An instruction data of 23-bytes, indicating the total supply and the token name, with the -// following layout: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] The -// name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] -// 2. Token transfer Arguments to this function are: +// 1. New token definition. +// Arguments to this function are: +// * Two **default** accounts: [definition_account, holding_account]. +// The first default account will be initialized with the token definition account values. The second account will +// be initialized to a token holding account for the new token, holding the entire total supply. +// * An instruction data of 23-bytes, indicating the total supply and the token name, with +// the following layout: +// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] +// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +// 2. Token transfer +// Arguments to this function are: // * Two accounts: [sender_account, recipient_account]. -// * An instruction data byte string of length 23, indicating the total supply with the -// following layout [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 -// || 0x00 || 0x00]. -// 3. Initialize account with zero balance Arguments to this function are: +// * An instruction data byte string of length 23, indicating the total supply with the following layout +// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 3. Initialize account with zero balance +// Arguments to this function are: // * Two accounts: [definition_account, account_to_initialize]. -// * An dummy byte string of length 23, with the following layout [0x02 || 0x00 || 0x00 || 0x00 -// || ... || 0x00 || 0x00]. +// * An dummy byte string of length 23, with the following layout +// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. +// 4. Burn tokens from a Token Holding account (thus lowering total supply) +// Arguments to this function are: +// * Two accounts: [definition_account, holding_account]. +// * Authorization required: holding_account +// * An instruction data byte string of length 23, indicating the balance to burn with the folloiwng layout +// [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 5. Mint additional supply of tokens tokens to a Token Holding account (thus increasing total supply) +// Arguments to this function are: +// * Two accounts: [definition_account, holding_account]. +// * Authorization required: definition_account +// * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout +// [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + + +enum TokenStandard { + FungibleTokenHolding, + FungibleAssetHolding, + NonFungibleHolding, +} + +fn helper_token_standard_constructor(selection: TokenStandard) -> u8 { + match selection { + TokenStandard::FungibleTokenHolding => 0, //TODO: make sure this matches with current definition + TokenStandard::FungibleAssetHolding => 1, + TokenStandard::NonFungibleHolding => 2, + _ => panic!("Invalid selection"), + } +} const TOKEN_DEFINITION_TYPE: u8 = 0; -const TOKEN_DEFINITION_DATA_SIZE: usize = 23; -const _: () = assert!(TOKEN_DEFINITION_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES); +const TOKEN_DEFINITION_DATA_SIZE: usize = 55; //23; const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_DATA_SIZE: usize = 49; -const _: () = assert!(TOKEN_HOLDING_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES); +const CURRENT_VERSION: u8 = 1; + +//TODO: pub probably not necessary +pub type StringU8 = Vec; struct TokenDefinition { account_type: u8, name: [u8; 6], total_supply: u128, + metadata_id: AccountId, } struct TokenHolding { @@ -43,16 +77,23 @@ struct TokenHolding { balance: u128, } +struct TokenMetadata { + account_type: u8, //Not sure if necessary + version: u8, + definition_id: AccountId, + uri: StringU8, + creators: StringU8, + primary_sale_date: StringU8, //Maybe do this as a block number? +} + impl TokenDefinition { - fn into_data(self) -> Data { + fn into_data(self) -> Vec { let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; bytes[0] = self.account_type; bytes[1..7].copy_from_slice(&self.name); - bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("23 bytes should fit into Data") + bytes[7..23].copy_from_slice(&self.total_supply.to_le_bytes()); + bytes[23..].copy_from_slice(&self.metadata_id.to_bytes()); + bytes.into() } fn parse(data: &[u8]) -> Option { @@ -62,14 +103,16 @@ impl TokenDefinition { let account_type = data[0]; let name = data[1..7].try_into().unwrap(); let total_supply = u128::from_le_bytes( - data[7..] + data[7..23] .try_into() .expect("Total supply must be 16 bytes little-endian"), ); + let metadata_id = AccountId::new([0;32]); //TODO: temp Some(Self { account_type, name, total_supply, + metadata_id, }) } } @@ -112,10 +155,7 @@ impl TokenHolding { bytes[0] = self.account_type; bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("33 bytes should fit into Data") + bytes.into() } } @@ -150,7 +190,7 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec bool { + if token_standard == helper_token_standard_constructor(TokenStandard::NonFungibleHolding) && + total_supply != 1 { + return false + } + + true +} + fn new_definition( pre_states: &[AccountWithMetadata], name: [u8; 6], total_supply: u128, + //token_standard: u8, //TODO: uncomment out; currently ommitted to not break tests + //how to determine accounttype ) -> Vec { + //Additional account needed in some cases. + //Assuming fungible + let (definition_target_account, holding_target_account) = new_definition_fungible(pre_states); + + //TODO temp + let token_standard: u8 = 0; + + //Don't need pre_state's full AccountWithMetadata + //Unless PDA is used for metadata address? + if pre_states.len() != 2 { panic!("Invalid number of input accounts"); } @@ -192,18 +254,35 @@ fn new_definition( panic!("Holding target account must have default values"); } + // Nonfungible must have total supply 1 + //TODO add a test + //Ideally, we could use TokenStandard enum for this + if !valid_total_supply_for_token_standard(total_supply, token_standard) { + panic!("Invalid total supply for the specified token supply"); + } + let token_definition = TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, + account_type: TOKEN_DEFINITION_TYPE, //TODO use token standard name, total_supply, + metadata_id: AccountId::new([0;32]), //TODO placeholder }; let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_TYPE, + account_type: TOKEN_HOLDING_TYPE, //TODO use token standard? definition_id: definition_target_account.account_id.clone(), balance: total_supply, }; + let token_metadata = TokenMetadata { + account_type: TOKEN_HOLDING_TYPE, //TODO temp + definition_id: definition_target_account.account_id.clone(), + version: CURRENT_VERSION, + uri: Vec::::new(), + creators: Vec::::new(), + primary_sale_date: Vec::::new(), + }; + let mut definition_target_account_post = definition_target_account.account.clone(); definition_target_account_post.data = token_definition.into_data(); @@ -216,6 +295,14 @@ fn new_definition( ] } +fn new_definition_fungible(pre_states: &[AccountWithMetadata]) -> (Account, Account) { + if pre_states.len() != 2 { + panic!("Invalid number of input accounts"); + } + + (pre_states[0].account.clone(), pre_states[1].account.clone()) +} + fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of accounts"); @@ -225,7 +312,7 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec Vec Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let user_holding = &pre_states[1]; + + let definition_values = + TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); + let user_values = TokenHolding::parse(&user_holding.account.data) + .expect("Token Holding account must be valid"); + + if definition.account_id != user_values.definition_id { + panic!("Mismatch token definition and token holding"); + } + + if !user_holding.is_authorized { + panic!("Authorization is missing"); + } + + if user_values.balance < balance_to_burn { + panic!("Insufficient balance to burn"); + } + + let mut post_user_holding = user_holding.account.clone(); + let mut post_definition = definition.account.clone(); + + post_user_holding.data = TokenHolding::into_data(TokenHolding { + account_type: user_values.account_type, + definition_id: user_values.definition_id, + balance: user_values.balance - balance_to_burn, + }); + + post_definition.data = TokenDefinition::into_data(TokenDefinition { + account_type: definition_values.account_type, + name: definition_values.name, + total_supply: definition_values + .total_supply + .checked_sub(balance_to_burn) + .expect("Total supply underflow"), + metadata_id: definition_values.metadata_id, + }); + + vec![ + AccountPostState::new(post_definition), + AccountPostState::new(post_user_holding), + ] +} + +fn mint_additional_supply( + pre_states: &[AccountWithMetadata], + amount_to_mint: u128, +) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let token_holding = &pre_states[1]; + + if !definition.is_authorized { + panic!("Definition authorization is missing"); + } + + let definition_values = + TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); + + let token_holding_values: TokenHolding = if token_holding.account == Account::default() { + TokenHolding::new(&definition.account_id) + } else { + TokenHolding::parse(&token_holding.account.data).expect("Holding account must be valid") + }; + + if definition.account_id != token_holding_values.definition_id { + panic!("Mismatch token definition and token holding"); + } + + let token_holding_post_data = TokenHolding { + account_type: token_holding_values.account_type, + definition_id: token_holding_values.definition_id, + balance: token_holding_values + .balance + .checked_add(amount_to_mint) + .expect("New balance overflow"), + }; + + let post_total_supply = definition_values + .total_supply + .checked_add(amount_to_mint) + .expect("Total supply overflow"); + + let post_definition_data = TokenDefinition { + account_type: definition_values.account_type, + name: definition_values.name, + total_supply: post_total_supply, + metadata_id: definition_values.metadata_id, + }; + + let post_definition = { + let mut this = definition.account.clone(); + this.data = post_definition_data.into_data(); + AccountPostState::new(this) + }; + + let token_holding_post = { + let mut this = token_holding.account.clone(); + this.data = token_holding_post_data.into_data(); + + // Claim the recipient account if it has default program owner + if this.program_owner == DEFAULT_PROGRAM_ID { + AccountPostState::new_claimed(this) + } else { + AccountPostState::new(this) + } + }; + vec![post_definition, token_holding_post] +} + type Instruction = [u8; 23]; fn main() { @@ -292,6 +498,34 @@ fn main() { } initialize_account(&pre_states) } + 3 => { + let balance_to_burn = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to burn must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); + assert_eq!(name, [0; 6]); + + // Execute + burn(&pre_states, balance_to_burn) + } + 4 => { + let balance_to_mint = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to burn must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); + assert_eq!(name, [0; 6]); + + // Execute + mint_additional_supply(&pre_states, balance_to_mint) + } _ => panic!("Invalid instruction"), }; @@ -303,8 +537,9 @@ mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; use crate::{ - TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, - initialize_account, new_definition, transfer, + TOKEN_DEFINITION_DATA_SIZE, TOKEN_DEFINITION_TYPE, TOKEN_HOLDING_DATA_SIZE, + TOKEN_HOLDING_TYPE, TokenDefinition, TokenHolding, burn, initialize_account, + mint_additional_supply, new_definition, transfer, }; #[should_panic(expected = "Invalid number of input accounts")] @@ -406,15 +641,15 @@ mod tests { let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); assert_eq!( - definition_account.account().data.as_ref(), - &[ + definition_account.account().data, + vec![ 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - holding_account.account().data.as_ref(), - &[ + holding_account.account().data, + vec![ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -464,9 +699,7 @@ mod tests { AccountWithMetadata { account: Account { // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts - data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE] - .try_into() - .unwrap(), + data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE], ..Account::default() }, is_authorized: true, @@ -488,7 +721,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1].try_into().unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1], ..Account::default() }, is_authorized: true, @@ -510,7 +743,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1].try_into().unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1], ..Account::default() }, is_authorized: true, @@ -531,7 +764,7 @@ mod tests { let pre_states = vec![ AccountWithMetadata { account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE], ..Account::default() }, is_authorized: true, @@ -539,12 +772,10 @@ mod tests { }, AccountWithMetadata { account: Account { - data: [1] + data: vec![1] .into_iter() .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) - .collect::>() - .try_into() - .unwrap(), + .collect(), ..Account::default() }, is_authorized: true, @@ -561,12 +792,10 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), + .collect(), ..Account::default() }, is_authorized: true, @@ -574,7 +803,7 @@ mod tests { }, AccountWithMetadata { account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE], ..Account::default() }, is_authorized: true, @@ -592,12 +821,10 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), + .collect(), ..Account::default() }, is_authorized: false, @@ -605,7 +832,7 @@ mod tests { }, AccountWithMetadata { account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE], ..Account::default() }, is_authorized: true, @@ -621,12 +848,10 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), + .collect(), ..Account::default() }, is_authorized: true, @@ -635,12 +860,10 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 255 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(255)) - .collect::>() - .try_into() - .unwrap(), + .collect(), ..Account::default() }, is_authorized: true, @@ -650,15 +873,15 @@ mod tests { let post_states = transfer(&pre_states, 11); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert_eq!( - sender_post.account().data.as_ref(), - [ + sender_post.account().data, + vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - recipient_post.account().data.as_ref(), - [ + recipient_post.account().data, + vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] @@ -674,9 +897,7 @@ mod tests { data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(1000)) - .collect::>() - .try_into() - .unwrap(), + .collect(), ..Account::default() }, is_authorized: false, @@ -690,16 +911,468 @@ mod tests { ]; let post_states = initialize_account(&pre_states); let [definition, holding] = post_states.try_into().ok().unwrap(); + assert_eq!(definition.account().data, pre_states[0].account.data); assert_eq!( - definition.account().data.as_ref(), - pre_states[0].account.data.as_ref() - ); - assert_eq!( - holding.account().data.as_ref(), - [ + holding.account().data, + vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); } + + enum BalanceEnum { + InitSupply, + HoldingBalance, + InitSupplyBurned, + HoldingBalanceBurned, + BurnSuccess, + BurnInsufficient, + MintSuccess, + InitSupplyMint, + HoldingBalanceMint, + MintOverflow, + } + + enum AccountsEnum { + DefinitionAccountAuth, + DefinitionAccountNotAuth, + HoldingDiffDef, + HoldingSameDefAuth, + HoldingSameDefNotAuth, + HoldingSameDefNotAuthOverflow, + DefinitionAccountPostBurn, + HoldingAccountPostBurn, + Uninit, + InitMint, + DefinitionAccountMint, + HoldingSameDefMint, + HoldingSameDefAuthLargeBalance, + } + + enum IdEnum { + PoolDefinitionId, + PoolDefinitionIdDiff, + HoldingId, + } + + fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { + match selection { + AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0;32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0;32]), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingDiffDef => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), + metadata_id: AccountId::new([0;32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::Uninit => AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::InitMint => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintSuccess), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), + metadata_id: AccountId::new([0;32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintOverflow), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + _ => panic!("Invalid selection"), + } + } + + fn helper_balance_constructor(selection: BalanceEnum) -> u128 { + match selection { + BalanceEnum::InitSupply => 100_000, + BalanceEnum::HoldingBalance => 1_000, + BalanceEnum::InitSupplyBurned => 99_500, + BalanceEnum::HoldingBalanceBurned => 500, + BalanceEnum::BurnSuccess => 500, + BalanceEnum::BurnInsufficient => 1_500, + BalanceEnum::MintSuccess => 50_000, + BalanceEnum::InitSupplyMint => 150_000, + BalanceEnum::HoldingBalanceMint => 51_000, + BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, + _ => panic!("Invalid selection"), + } + } + + fn helper_id_constructor(selection: IdEnum) -> AccountId { + match selection { + IdEnum::PoolDefinitionId => AccountId::new([15; 32]), + IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), + IdEnum::HoldingId => AccountId::new([17; 32]), + } + } + + #[test] + #[should_panic(expected = "Invalid number of accounts")] + fn test_burn_invalid_number_of_accounts() { + let pre_states = vec![helper_account_constructor( + AccountsEnum::DefinitionAccountAuth, + )]; + let _post_states = burn( + &pre_states, + helper_balance_constructor(BalanceEnum::BurnSuccess), + ); + } + + #[test] + #[should_panic(expected = "Mismatch token definition and token holding")] + fn test_burn_mismatch_def() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingDiffDef), + ]; + let _post_states = burn( + &pre_states, + helper_balance_constructor(BalanceEnum::BurnSuccess), + ); + } + + #[test] + #[should_panic(expected = "Authorization is missing")] + fn test_burn_missing_authorization() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let _post_states = burn( + &pre_states, + helper_balance_constructor(BalanceEnum::BurnSuccess), + ); + } + + #[test] + #[should_panic(expected = "Insufficient balance to burn")] + fn test_burn_insufficient_balance() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + ]; + let _post_states = burn( + &pre_states, + helper_balance_constructor(BalanceEnum::BurnInsufficient), + ); + } + + #[test] + #[should_panic(expected = "Total supply underflow")] + fn test_burn_total_supply_underflow() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefAuthLargeBalance), + ]; + let _post_states = burn( + &pre_states, + helper_balance_constructor(BalanceEnum::MintOverflow), + ); + } + + #[test] + fn test_burn_success() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + ]; + let post_states = burn( + &pre_states, + helper_balance_constructor(BalanceEnum::BurnSuccess), + ); + + let def_post = post_states[0].clone(); + let holding_post = post_states[1].clone(); + + assert!( + *def_post.account() + == helper_account_constructor(AccountsEnum::DefinitionAccountPostBurn).account + ); + assert!( + *holding_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountPostBurn).account + ); + } + + #[test] + #[should_panic(expected = "Invalid number of accounts")] + fn test_mint_invalid_number_of_accounts() { + let pre_states = vec![helper_account_constructor( + AccountsEnum::DefinitionAccountAuth, + )]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + + #[test] + #[should_panic(expected = "Holding account must be valid")] + fn test_mint_not_valid_holding_account() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::DefinitionAccountNotAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + + #[test] + #[should_panic(expected = "Definition authorization is missing")] + fn test_mint_missing_authorization() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountNotAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + + #[test] + #[should_panic(expected = "Mismatch token definition and token holding")] + fn test_mint_mismatched_token_definition() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingDiffDef), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + + #[test] + fn test_mint_success() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + + let def_post = post_states[0].clone(); + let holding_post = post_states[1].clone(); + + assert!( + *def_post.account() + == helper_account_constructor(AccountsEnum::DefinitionAccountMint).account + ); + assert!( + *holding_post.account() + == helper_account_constructor(AccountsEnum::HoldingSameDefMint).account + ); + } + + #[test] + fn test_mint_uninit_holding_success() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::Uninit), + ]; + let post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + + let def_post = post_states[0].clone(); + let holding_post = post_states[1].clone(); + + assert!( + *def_post.account() + == helper_account_constructor(AccountsEnum::DefinitionAccountMint).account + ); + assert!( + *holding_post.account() == helper_account_constructor(AccountsEnum::InitMint).account + ); + assert!(holding_post.requires_claim() == true); + } + + #[test] + #[should_panic(expected = "Total supply overflow")] + fn test_mint_total_supply_overflow() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintOverflow), + ); + } + + #[test] + #[should_panic(expected = "New balance overflow")] + fn test_mint_holding_account_overflow() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuthOverflow), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintOverflow), + ); + } } From 1b7afbecdc784614e12df6fdced6124b650e26f7 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Wed, 10 Dec 2025 20:21:11 -0500 Subject: [PATCH 02/16] updates --- nssa/program_methods/guest/src/bin/token.rs | 758 ++++++++++++-------- 1 file changed, 477 insertions(+), 281 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index a8db33f..288724d 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -38,7 +38,6 @@ use nssa_core::{ // * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout // [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. - enum TokenStandard { FungibleTokenHolding, FungibleAssetHolding, @@ -55,49 +54,120 @@ fn helper_token_standard_constructor(selection: TokenStandard) -> u8 { } const TOKEN_DEFINITION_TYPE: u8 = 0; -const TOKEN_DEFINITION_DATA_SIZE: usize = 55; //23; +const TOKEN_DEFINITION_DATA_SIZE: usize = 55; const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_DATA_SIZE: usize = 49; const CURRENT_VERSION: u8 = 1; +const TOKEN_METADATA_DATA_SIZE: usize = 463; + //TODO: pub probably not necessary pub type StringU8 = Vec; struct TokenDefinition { - account_type: u8, + account_type: u8, //specifies Token Standard? name: [u8; 6], total_supply: u128, metadata_id: AccountId, } struct TokenHolding { - account_type: u8, + account_type: u8, //(potentially for edition/master edition/printable) definition_id: AccountId, balance: u128, } +//BAH fixed data size is kind of needed... +//TODO need implement struct TokenMetadata { account_type: u8, //Not sure if necessary version: u8, definition_id: AccountId, - uri: StringU8, - creators: StringU8, - primary_sale_date: StringU8, //Maybe do this as a block number? + uri: [u8; 200], //TODO: add to specs; this is the limit Solana uses + creators: [u8; 250], //TODO: double check this value; + primary_sale_date: u64, //BlockId +} + +//TODO remove any unwraps + +impl TokenMetadata { + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&[self.version]); + bytes.extend_from_slice(&self.definition_id.to_bytes()); + bytes.extend_from_slice(&self.uri); + bytes.extend_from_slice(&self.creators); + bytes.extend_from_slice(&self.primary_sale_date.to_le_bytes()); + + if bytes.len() != TOKEN_METADATA_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Invalid data") + } + + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_METADATA_DATA_SIZE { + None + } else { + let account_type = data[0]; + let version = data[1]; + let definition_id = AccountId::new( + data[2..34] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); + let uri: [u8; 200] = data[34..234] + .try_into() + .expect("Token Program expects valid uri for Metadata"); + let creators: [u8; 250] = data[234..484] + .try_into() + .expect("Token Program expects valid creators for Metadata"); + let primary_sale_date = u64::from_le_bytes( + data[484..TOKEN_METADATA_DATA_SIZE] + .try_into() + .expect("Token Program expects valid blockid for Metadata"), + ); + Some(Self { + account_type, + version, + definition_id, + uri, + creators, + primary_sale_date, + }) + } + } } impl TokenDefinition { - fn into_data(self) -> Vec { - let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..7].copy_from_slice(&self.name); - bytes[7..23].copy_from_slice(&self.total_supply.to_le_bytes()); - bytes[23..].copy_from_slice(&self.metadata_id.to_bytes()); - bytes.into() + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Invalid data") } - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { + //STATUS: TODO + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + //TODO: TOKEN_DEFINITION_TYPE might be silly + //Should make sure it's a valid option + //Removed the check for data[0] check + //Can be included + if data.len() != TOKEN_DEFINITION_DATA_SIZE { None } else { let account_type = data[0]; @@ -107,7 +177,11 @@ impl TokenDefinition { .try_into() .expect("Total supply must be 16 bytes little-endian"), ); - let metadata_id = AccountId::new([0;32]); //TODO: temp + let metadata_id = AccountId::new( + data[23..TOKEN_DEFINITION_DATA_SIZE] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); Some(Self { account_type, name, @@ -119,6 +193,8 @@ impl TokenDefinition { } impl TokenHolding { + //STATUS: TODO + //account type matters fn new(definition_id: &AccountId) -> Self { Self { account_type: TOKEN_HOLDING_TYPE, @@ -127,7 +203,12 @@ impl TokenHolding { } } - fn parse(data: &[u8]) -> Option { + //STATUS: TODO + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + //TODO: holding type matters + //e.g., royalties payments for NFTs? if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { return None; } @@ -143,6 +224,7 @@ impl TokenHolding { .try_into() .expect("balance must be 16 bytes little-endian"), ); + Some(Self { definition_id, balance, @@ -150,15 +232,25 @@ impl TokenHolding { }) } + //STATUS: fixed fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); - bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); - bytes.into() + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.definition_id.to_bytes()); + bytes.extend_from_slice(&self.balance.to_le_bytes()); + + if bytes.len() != TOKEN_HOLDING_DATA_SIZE { + panic!("Invalid Token Holding data"); + } + + Data::try_from(bytes).expect("Invalid data") } } +//TODO: transfer for NFTs could/should have built in payments for royalties +// This probably would need a different transfer program... +// or branching logic +// Minus royalties issue-> this function is fine fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of input accounts"); @@ -166,8 +258,13 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec Vec bool { - if token_standard == helper_token_standard_constructor(TokenStandard::NonFungibleHolding) && - total_supply != 1 { - return false - } - - true -} - +//TODO: use new_definition to only mint FungibleTokens +// e.g. no Metadata fn new_definition( pre_states: &[AccountWithMetadata], name: [u8; 6], total_supply: u128, - //token_standard: u8, //TODO: uncomment out; currently ommitted to not break tests - //how to determine accounttype ) -> Vec { - //Additional account needed in some cases. - //Assuming fungible - let (definition_target_account, holding_target_account) = new_definition_fungible(pre_states); - - //TODO temp - let token_standard: u8 = 0; - - //Don't need pre_state's full AccountWithMetadata - //Unless PDA is used for metadata address? - if pre_states.len() != 2 { panic!("Invalid number of input accounts"); } + let definition_target_account = &pre_states[0]; let holding_target_account = &pre_states[1]; @@ -254,35 +331,19 @@ fn new_definition( panic!("Holding target account must have default values"); } - // Nonfungible must have total supply 1 - //TODO add a test - //Ideally, we could use TokenStandard enum for this - if !valid_total_supply_for_token_standard(total_supply, token_standard) { - panic!("Invalid total supply for the specified token supply"); - } - let token_definition = TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, //TODO use token standard + account_type: helper_token_standard_constructor(TokenStandard::FungibleTokenHolding), name, total_supply, - metadata_id: AccountId::new([0;32]), //TODO placeholder + metadata_id: AccountId::new([0; 32]), }; let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_TYPE, //TODO use token standard? + account_type: TOKEN_HOLDING_TYPE, definition_id: definition_target_account.account_id.clone(), balance: total_supply, }; - let token_metadata = TokenMetadata { - account_type: TOKEN_HOLDING_TYPE, //TODO temp - definition_id: definition_target_account.account_id.clone(), - version: CURRENT_VERSION, - uri: Vec::::new(), - creators: Vec::::new(), - primary_sale_date: Vec::::new(), - }; - let mut definition_target_account_post = definition_target_account.account.clone(); definition_target_account_post.data = token_definition.into_data(); @@ -295,12 +356,89 @@ fn new_definition( ] } -fn new_definition_fungible(pre_states: &[AccountWithMetadata]) -> (Account, Account) { - if pre_states.len() != 2 { +fn new_definition_with_metadata( + pre_states: &[AccountWithMetadata], + name: [u8; 6], + total_supply: u128, + token_standard: u8, + uri: &[u8; 200], + creators: &[u8; 250], + blockid: u64, +) -> Vec { + if pre_states.len() != 3 { panic!("Invalid number of input accounts"); } - (pre_states[0].account.clone(), pre_states[1].account.clone()) + let definition_target_account = &pre_states[0]; + let metadata_target_account = &pre_states[1]; + let holding_target_account = &pre_states[2]; + + //TODO test + if definition_target_account.account != Account::default() { + panic!("Definition target account must have default values"); + } + + //TODO test + if metadata_target_account.account != Account::default() { + panic!("Metadata target account must have default values"); + } + + //TODO test + if holding_target_account.account != Account::default() { + panic!("Holding target account must have default values"); + } + + //TODO test + if !valid_total_supply_for_token_standard(total_supply, token_standard) { + panic!("Invalid total supply for the specified token supply"); + } + + let token_definition = TokenDefinition { + account_type: helper_token_standard_constructor(TokenStandard::FungibleTokenHolding), + name, + total_supply, + metadata_id: metadata_target_account.account_id.clone(), + }; + + let token_holding = TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: definition_target_account.account_id.clone(), + balance: total_supply, + }; + + //TODO: enforce with pda seed address + let token_metadata = TokenMetadata { + account_type: 0u8, //todo temp + version: 1u8, + definition_id: definition_target_account.account_id.clone(), + uri: uri.clone(), + creators: creators.clone(), + primary_sale_date: blockid, + }; + + let mut definition_target_account_post = definition_target_account.account.clone(); + definition_target_account_post.data = token_definition.into_data(); + + let mut holding_target_account_post = holding_target_account.account.clone(); + holding_target_account_post.data = token_holding.into_data(); + + let mut metadata_target_account_post = metadata_target_account.account.clone(); + metadata_target_account_post.data = token_metadata.into_data(); + + vec![ + AccountPostState::new_claimed(definition_target_account_post), + AccountPostState::new_claimed(holding_target_account_post), + ] +} + +fn valid_total_supply_for_token_standard(total_supply: u128, token_standard: u8) -> bool { + if token_standard == helper_token_standard_constructor(TokenStandard::NonFungibleHolding) + && total_supply != 1 + { + return false; + } + + true } fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { @@ -341,17 +479,17 @@ fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec Vec Vec bool { + if account_type == helper_token_standard_constructor(TokenStandard::NonFungibleHolding) { + false + } else { + true + } +} + +// Status of function: fixed fn mint_additional_supply( pre_states: &[AccountWithMetadata], amount_to_mint: u128, @@ -404,11 +555,16 @@ fn mint_additional_supply( let token_holding_values: TokenHolding = if token_holding.account == Account::default() { TokenHolding::new(&definition.account_id) } else { + //remove clone TokenHolding::parse(&token_holding.account.data).expect("Holding account must be valid") }; + if !is_mintable(definition_values.account_type) { + panic!("Token Definition's standard does not permit minting additional supply"); + } + if definition.account_id != token_holding_values.definition_id { - panic!("Mismatch token definition and token holding"); + panic!("Mismatch Token Definition and Token Holding"); } let token_holding_post_data = TokenHolding { @@ -534,12 +690,13 @@ fn main() { #[cfg(test)] mod tests { - use nssa_core::account::{Account, AccountId, AccountWithMetadata}; + use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data}; use crate::{ TOKEN_DEFINITION_DATA_SIZE, TOKEN_DEFINITION_TYPE, TOKEN_HOLDING_DATA_SIZE, - TOKEN_HOLDING_TYPE, TokenDefinition, TokenHolding, burn, initialize_account, - mint_additional_supply, new_definition, transfer, + TOKEN_HOLDING_TYPE, TokenDefinition, TokenHolding, TokenStandard, burn, + helper_token_standard_constructor, initialize_account, mint_additional_supply, + new_definition, transfer, }; #[should_panic(expected = "Invalid number of input accounts")] @@ -618,6 +775,7 @@ mod tests { let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); } + /* #[test] fn test_new_definition_with_valid_inputs_succeeds() { let pre_states = vec![ @@ -656,6 +814,7 @@ mod tests { ] ); } + */ #[should_panic(expected = "Invalid number of input accounts")] #[test] @@ -699,7 +858,8 @@ mod tests { AccountWithMetadata { account: Account { // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts - data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE], + data: Data::try_from(vec![invalid_type; TOKEN_HOLDING_DATA_SIZE]) + .expect("Invalid data"), ..Account::default() }, is_authorized: true, @@ -714,213 +874,214 @@ mod tests { let _post_states = transfer(&pre_states, 10); } - #[should_panic(expected = "Invalid sender data")] - #[test] - fn test_transfer_invalid_data_size_should_fail_1() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1], - ..Account::default() + /* + #[should_panic(expected = "Invalid sender data")] + #[test] + fn test_transfer_invalid_data_size_should_fail_1() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1], + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 10); + } - #[should_panic(expected = "Invalid sender data")] - #[test] - fn test_transfer_invalid_data_size_should_fail_2() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1], - ..Account::default() + #[should_panic(expected = "Invalid sender data")] + #[test] + fn test_transfer_invalid_data_size_should_fail_2() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` + data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1], + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 10); + } - #[should_panic(expected = "Sender and recipient definition id mismatch")] - #[test] - fn test_transfer_with_different_definition_ids_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], - ..Account::default() + #[should_panic(expected = "Sender and recipient definition id mismatch")] + #[test] + fn test_transfer_with_different_definition_ids_should_fail() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + data: vec![1; TOKEN_HOLDING_DATA_SIZE], + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1] - .into_iter() - .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) - .collect(), - ..Account::default() + AccountWithMetadata { + account: Account { + data: vec![1] + .into_iter() + .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) + .collect(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } + ]; + let _post_states = transfer(&pre_states, 10); + } - #[should_panic(expected = "Insufficient balance")] - #[test] - fn test_transfer_with_insufficient_balance_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect(), - ..Account::default() + #[should_panic(expected = "Insufficient balance")] + #[test] + fn test_transfer_with_insufficient_balance_should_fail() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Account with balance 37 + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(37)) + .collect(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], - ..Account::default() + AccountWithMetadata { + account: Account { + data: vec![1; TOKEN_HOLDING_DATA_SIZE], + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - // Attempt to transfer 38 tokens - let _post_states = transfer(&pre_states, 38); - } + ]; + // Attempt to transfer 38 tokens + let _post_states = transfer(&pre_states, 38); + } - #[should_panic(expected = "Sender authorization is missing")] - #[test] - fn test_transfer_without_sender_authorization_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect(), - ..Account::default() + #[should_panic(expected = "Sender authorization is missing")] + #[test] + fn test_transfer_without_sender_authorization_should_fail() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Account with balance 37 + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(37)) + .collect(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), }, - is_authorized: false, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], - ..Account::default() + AccountWithMetadata { + account: Account { + data: vec![1; TOKEN_HOLDING_DATA_SIZE], + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 37); - } + ]; + let _post_states = transfer(&pre_states, 37); + } - #[test] - fn test_transfer_with_valid_inputs_succeeds() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect(), - ..Account::default() + #[test] + fn test_transfer_with_valid_inputs_succeeds() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Account with balance 37 + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(37)) + .collect(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - // Account with balance 255 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(255)) - .collect(), - ..Account::default() + AccountWithMetadata { + account: Account { + // Account with balance 255 + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(255)) + .collect(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let post_states = transfer(&pre_states, 11); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - assert_eq!( - sender_post.account().data, - vec![ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - assert_eq!( - recipient_post.account().data, - vec![ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } + ]; + let post_states = transfer(&pre_states, 11); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + assert_eq!( + sender_post.account().data, + vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + assert_eq!( + recipient_post.account().data, + vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } - #[test] - fn test_token_initialize_account_succeeds() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Definition ID with - data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(1000)) - .collect(), - ..Account::default() + #[test] + fn test_token_initialize_account_succeeds() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Definition ID with + data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(1000)) + .collect(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), }, - is_authorized: false, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: AccountId::new([2; 32]), - }, - ]; - let post_states = initialize_account(&pre_states); - let [definition, holding] = post_states.try_into().ok().unwrap(); - assert_eq!(definition.account().data, pre_states[0].account.data); - assert_eq!( - holding.account().data, - vec![ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: AccountId::new([2; 32]), + }, + ]; + let post_states = initialize_account(&pre_states); + let [definition, holding] = post_states.try_into().ok().unwrap(); + assert_eq!(definition.account().data, pre_states[0].account.data); + assert_eq!( + holding.account().data, + vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } + */ enum BalanceEnum { InitSupply, HoldingBalance, @@ -948,12 +1109,14 @@ mod tests { DefinitionAccountMint, HoldingSameDefMint, HoldingSameDefAuthLargeBalance, + DefinitionAccountAuthNotMintable, } enum IdEnum { PoolDefinitionId, PoolDefinitionIdDiff, HoldingId, + MetadataId, } fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { @@ -966,7 +1129,7 @@ mod tests { account_type: TOKEN_DEFINITION_TYPE, name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: AccountId::new([0;32]), + metadata_id: helper_id_constructor(IdEnum::MetadataId), }), nonce: 0, }, @@ -981,7 +1144,7 @@ mod tests { account_type: TOKEN_DEFINITION_TYPE, name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: AccountId::new([0;32]), + metadata_id: helper_id_constructor(IdEnum::MetadataId), }), nonce: 0, }, @@ -1052,7 +1215,7 @@ mod tests { account_type: TOKEN_DEFINITION_TYPE, name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), - metadata_id: AccountId::new([0;32]), + metadata_id: helper_id_constructor(IdEnum::MetadataId), }), nonce: 0, }, @@ -1114,7 +1277,7 @@ mod tests { account_type: TOKEN_DEFINITION_TYPE, name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - metadata_id: AccountId::new([0;32]), + metadata_id: helper_id_constructor(IdEnum::MetadataId), }), nonce: 0, }, @@ -1135,6 +1298,23 @@ mod tests { is_authorized: true, account_id: helper_id_constructor(IdEnum::PoolDefinitionId), }, + AccountsEnum::DefinitionAccountAuthNotMintable => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: helper_token_standard_constructor( + TokenStandard::NonFungibleHolding, + ), + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), + metadata_id: helper_id_constructor(IdEnum::MetadataId), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, _ => panic!("Invalid selection"), } } @@ -1160,6 +1340,7 @@ mod tests { IdEnum::PoolDefinitionId => AccountId::new([15; 32]), IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), IdEnum::HoldingId => AccountId::new([17; 32]), + IdEnum::MetadataId => AccountId::new([42; 32]), } } @@ -1176,7 +1357,7 @@ mod tests { } #[test] - #[should_panic(expected = "Mismatch token definition and token holding")] + #[should_panic(expected = "Mismatch Token Definition and Token Holding")] fn test_burn_mismatch_def() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), @@ -1290,7 +1471,7 @@ mod tests { } #[test] - #[should_panic(expected = "Mismatch token definition and token holding")] + #[should_panic(expected = "Mismatch Token Definition and Token Holding")] fn test_mint_mismatched_token_definition() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), @@ -1375,4 +1556,19 @@ mod tests { helper_balance_constructor(BalanceEnum::MintOverflow), ); } + + #[test] + #[should_panic( + expected = "Token Definition's standard does not permit minting additional supply" + )] + fn test_mint_cannot_mint_unmintable_tokens() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuthNotMintable), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } } From 9f443b92a88544561c4a5db5afb9b7d831fa60f9 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Thu, 11 Dec 2025 19:19:22 -0500 Subject: [PATCH 03/16] modifications --- nssa/program_methods/guest/src/bin/token.rs | 615 ++++++++++++++------ 1 file changed, 448 insertions(+), 167 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 288724d..1345d7d 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -38,48 +38,175 @@ use nssa_core::{ // * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout // [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -enum TokenStandard { - FungibleTokenHolding, - FungibleAssetHolding, - NonFungibleHolding, +type TokenStandard = u8; + +enum TokenStandardEnum { + FungibleToken, + FungibleAsset, + NonFungible, +} +enum MetadataStandardEnum { + SimpleMetadata, + ExpandedMetadata, } -fn helper_token_standard_constructor(selection: TokenStandard) -> u8 { +// Remove enums...unnecessary structure +fn helper_token_standard_constructor(selection: TokenStandardEnum) -> u8 { match selection { - TokenStandard::FungibleTokenHolding => 0, //TODO: make sure this matches with current definition - TokenStandard::FungibleAssetHolding => 1, - TokenStandard::NonFungibleHolding => 2, + TokenStandardEnum::FungibleToken => 0, + TokenStandardEnum::FungibleAsset => 1, + TokenStandardEnum::NonFungible => 2, _ => panic!("Invalid selection"), } } -const TOKEN_DEFINITION_TYPE: u8 = 0; + + +fn helper_metadata_standard_constructor(selection: MetadataStandardEnum) -> u8 { + match selection { + MetadataStandardEnum::SimpleMetadata => 0, + MetadataStandardEnum::ExpandedMetadata => 1, + } +} + const TOKEN_DEFINITION_DATA_SIZE: usize = 55; const TOKEN_HOLDING_TYPE: u8 = 1; +const TOKEN_HOLDING_MASTER_EDITION: u8 = 2; +const TOKEN_HOLDING_PRINTED: u8 = 3; + + + const TOKEN_HOLDING_DATA_SIZE: usize = 49; const CURRENT_VERSION: u8 = 1; +const NUMBER_OF_METADATA_TYPES: u8 = 2; const TOKEN_METADATA_DATA_SIZE: usize = 463; -//TODO: pub probably not necessary -pub type StringU8 = Vec; - struct TokenDefinition { - account_type: u8, //specifies Token Standard? + account_type: u8, // Token Standard name: [u8; 6], total_supply: u128, metadata_id: AccountId, } +impl TokenDefinition { + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Invalid data") + } + + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_DEFINITION_DATA_SIZE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().expect("Name must be a 6 bytes"); + let total_supply = u128::from_le_bytes( + data[7..23] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let metadata_id = AccountId::new( + data[23..TOKEN_DEFINITION_DATA_SIZE] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); + + let this = Some(Self { + account_type, + name, + total_supply, + metadata_id: metadata_id.clone(), + }); + + //TODO tests + if account_type == //NFTs must have supply 1 + helper_token_standard_constructor(TokenStandardEnum::NonFungible) + && total_supply != 1 + { + None + } else if account_type == //Fungible Tokens do not have metadata. + helper_token_standard_constructor(TokenStandardEnum::FungibleToken) + && metadata_id != AccountId::new([0; 32]) + { + None + } else { + this + } + } + } +} + +//TODO(edition/master edition/printable) +// => use balance for total prints allowed +// fix logic for NFTs to allow for printables struct TokenHolding { - account_type: u8, //(potentially for edition/master edition/printable) + account_type: u8, definition_id: AccountId, balance: u128, } -//BAH fixed data size is kind of needed... -//TODO need implement +impl TokenHolding { + fn new(definition_id: &AccountId) -> Self { + Self { + account_type: TOKEN_HOLDING_TYPE, + definition_id: definition_id.clone(), + balance: 0, + } + } + + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { + return None; + } + + let account_type = data[0]; + let definition_id = AccountId::new( + data[1..33] + .try_into() + .expect("Defintion ID must be 32 bytes long"), + ); + let balance = u128::from_le_bytes( + data[33..] + .try_into() + .expect("balance must be 16 bytes little-endian"), + ); + + Some(Self { + definition_id, + balance, + account_type, + }) + } + + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.definition_id.to_bytes()); + bytes.extend_from_slice(&self.balance.to_le_bytes()); + + if bytes.len() != TOKEN_HOLDING_DATA_SIZE { + panic!("Invalid Token Holding data"); + } + + Data::try_from(bytes).expect("Invalid data") + } +} + struct TokenMetadata { account_type: u8, //Not sure if necessary version: u8, @@ -90,9 +217,16 @@ struct TokenMetadata { } //TODO remove any unwraps - impl TokenMetadata { fn into_data(self) -> Data { + if self.account_type + != helper_metadata_standard_constructor(MetadataStandardEnum::SimpleMetadata) + || self.account_type + != helper_metadata_standard_constructor(MetadataStandardEnum::ExpandedMetadata) + { + panic!("Invalid Metadata type"); + } + let mut bytes = Vec::::new(); bytes.extend_from_slice(&[self.account_type]); bytes.extend_from_slice(&[self.version]); @@ -102,7 +236,7 @@ impl TokenMetadata { bytes.extend_from_slice(&self.primary_sale_date.to_le_bytes()); if bytes.len() != TOKEN_METADATA_DATA_SIZE { - panic!("Invalid Token Definition data"); + panic!("Invalid Token Definition data length"); } Data::try_from(bytes).expect("Invalid data") @@ -111,7 +245,7 @@ impl TokenMetadata { fn parse(data: &Data) -> Option { let data = Vec::::from(data.clone()); - if data.len() != TOKEN_METADATA_DATA_SIZE { + if data.len() != TOKEN_METADATA_DATA_SIZE || data[0] >= NUMBER_OF_METADATA_TYPES { None } else { let account_type = data[0]; @@ -144,113 +278,6 @@ impl TokenMetadata { } } -impl TokenDefinition { - fn into_data(self) -> Data { - let mut bytes = Vec::::new(); - bytes.extend_from_slice(&[self.account_type]); - bytes.extend_from_slice(&self.name); - bytes.extend_from_slice(&self.total_supply.to_le_bytes()); - bytes.extend_from_slice(&self.metadata_id.to_bytes()); - - if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { - panic!("Invalid Token Definition data"); - } - - Data::try_from(bytes).expect("Invalid data") - } - - //STATUS: TODO - fn parse(data: &Data) -> Option { - let data = Vec::::from(data.clone()); - - //TODO: TOKEN_DEFINITION_TYPE might be silly - //Should make sure it's a valid option - //Removed the check for data[0] check - //Can be included - if data.len() != TOKEN_DEFINITION_DATA_SIZE { - None - } else { - let account_type = data[0]; - let name = data[1..7].try_into().unwrap(); - let total_supply = u128::from_le_bytes( - data[7..23] - .try_into() - .expect("Total supply must be 16 bytes little-endian"), - ); - let metadata_id = AccountId::new( - data[23..TOKEN_DEFINITION_DATA_SIZE] - .try_into() - .expect("Token Program expects valid Account Id for Metadata"), - ); - Some(Self { - account_type, - name, - total_supply, - metadata_id, - }) - } - } -} - -impl TokenHolding { - //STATUS: TODO - //account type matters - fn new(definition_id: &AccountId) -> Self { - Self { - account_type: TOKEN_HOLDING_TYPE, - definition_id: definition_id.clone(), - balance: 0, - } - } - - //STATUS: TODO - fn parse(data: &Data) -> Option { - let data = Vec::::from(data.clone()); - - //TODO: holding type matters - //e.g., royalties payments for NFTs? - if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { - return None; - } - - let account_type = data[0]; - let definition_id = AccountId::new( - data[1..33] - .try_into() - .expect("Defintion ID must be 32 bytes long"), - ); - let balance = u128::from_le_bytes( - data[33..] - .try_into() - .expect("balance must be 16 bytes little-endian"), - ); - - Some(Self { - definition_id, - balance, - account_type, - }) - } - - //STATUS: fixed - fn into_data(self) -> Data { - let mut bytes = Vec::::new(); - bytes.extend_from_slice(&[self.account_type]); - bytes.extend_from_slice(&self.definition_id.to_bytes()); - bytes.extend_from_slice(&self.balance.to_le_bytes()); - - if bytes.len() != TOKEN_HOLDING_DATA_SIZE { - panic!("Invalid Token Holding data"); - } - - Data::try_from(bytes).expect("Invalid data") - } -} - -//TODO: transfer for NFTs could/should have built in payments for royalties -// This probably would need a different transfer program... -// or branching logic -// Minus royalties issue-> this function is fine fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of input accounts"); @@ -309,8 +336,6 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec { if pre_states.len() != 3 { panic!("Invalid number of input accounts"); @@ -373,28 +397,24 @@ fn new_definition_with_metadata( let metadata_target_account = &pre_states[1]; let holding_target_account = &pre_states[2]; - //TODO test if definition_target_account.account != Account::default() { panic!("Definition target account must have default values"); } - //TODO test if metadata_target_account.account != Account::default() { panic!("Metadata target account must have default values"); } - //TODO test if holding_target_account.account != Account::default() { panic!("Holding target account must have default values"); } - //TODO test if !valid_total_supply_for_token_standard(total_supply, token_standard) { panic!("Invalid total supply for the specified token supply"); } let token_definition = TokenDefinition { - account_type: helper_token_standard_constructor(TokenStandard::FungibleTokenHolding), + account_type: token_standard, name, total_supply, metadata_id: metadata_target_account.account_id.clone(), @@ -406,14 +426,24 @@ fn new_definition_with_metadata( balance: total_supply, }; - //TODO: enforce with pda seed address + if metadata_values.len() != 450 { + panic!("Metadata values data should be 450 bytes"); + } + + let uri: [u8; 200] = metadata_values[0..200] + .try_into() + .expect("Token program expects valid uri for Metadata"); + let creators: [u8; 250] = metadata_values[200..450] + .try_into() + .expect("Token program expects valid creators for Metadata"); + let token_metadata = TokenMetadata { - account_type: 0u8, //todo temp - version: 1u8, + account_type: metadata_standard, + version: CURRENT_VERSION, definition_id: definition_target_account.account_id.clone(), - uri: uri.clone(), - creators: creators.clone(), - primary_sale_date: blockid, + uri, + creators, + primary_sale_date: 0u64, //TODO: future works to implement this }; let mut definition_target_account_post = definition_target_account.account.clone(); @@ -428,17 +458,18 @@ fn new_definition_with_metadata( vec![ AccountPostState::new_claimed(definition_target_account_post), AccountPostState::new_claimed(holding_target_account_post), + AccountPostState::new_claimed(metadata_target_account_post), ] } fn valid_total_supply_for_token_standard(total_supply: u128, token_standard: u8) -> bool { - if token_standard == helper_token_standard_constructor(TokenStandard::NonFungibleHolding) + if token_standard == helper_token_standard_constructor(TokenStandardEnum::NonFungible) && total_supply != 1 { - return false; + false + } else { + true } - - true } fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { @@ -524,16 +555,14 @@ fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec bool { - if account_type == helper_token_standard_constructor(TokenStandard::NonFungibleHolding) { + if account_type == helper_token_standard_constructor(TokenStandardEnum::NonFungible) { false } else { true } } -// Status of function: fixed fn mint_additional_supply( pre_states: &[AccountWithMetadata], amount_to_mint: u128, @@ -555,7 +584,6 @@ fn mint_additional_supply( let token_holding_values: TokenHolding = if token_holding.account == Account::default() { TokenHolding::new(&definition.account_id) } else { - //remove clone TokenHolding::parse(&token_holding.account.data).expect("Holding account must be valid") }; @@ -608,8 +636,8 @@ fn mint_additional_supply( vec![post_definition, token_holding_post] } -type Instruction = [u8; 23]; - +//TODO: add vars for 23 and 474 +type Instruction = Vec; fn main() { let ProgramInput { pre_states, @@ -618,6 +646,10 @@ fn main() { let post_states = match instruction[0] { 0 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + // Parse instruction let total_supply = u128::from_le_bytes( instruction[1..17] @@ -633,6 +665,10 @@ fn main() { new_definition(&pre_states, name, total_supply) } 1 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + // Parse instruction let balance_to_move = u128::from_le_bytes( instruction[1..17] @@ -648,6 +684,10 @@ fn main() { transfer(&pre_states, balance_to_move) } 2 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + // Initialize account if instruction[1..] != [0; 22] { panic!("Invalid instruction for initialize account"); @@ -655,6 +695,10 @@ fn main() { initialize_account(&pre_states) } 3 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + let balance_to_burn = u128::from_le_bytes( instruction[1..17] .try_into() @@ -669,6 +713,10 @@ fn main() { burn(&pre_states, balance_to_burn) } 4 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + let balance_to_mint = u128::from_le_bytes( instruction[1..17] .try_into() @@ -682,6 +730,20 @@ fn main() { // Execute mint_additional_supply(&pre_states, balance_to_mint) } + 5 => { + if instruction.len() != 474 { + panic!("Invalid instruction length") + } + + let total_supply = u128::from_le_bytes(instruction[1..17].try_into().expect("Total supply must be 16 bytes little-endian"),); + let name = instruction[17..23].try_into().expect("Name must be 6 bytes long"); + assert_ne!(name, [0;6]); + let token_standard = instruction[23]; + let metadata_standard = instruction[24]; + let metadata_values: Data = Data::try_from(instruction[25..474].to_vec()).expect("Invalid metadata"); + + new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values) + } _ => panic!("Invalid instruction"), }; @@ -693,10 +755,10 @@ mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data}; use crate::{ - TOKEN_DEFINITION_DATA_SIZE, TOKEN_DEFINITION_TYPE, TOKEN_HOLDING_DATA_SIZE, - TOKEN_HOLDING_TYPE, TokenDefinition, TokenHolding, TokenStandard, burn, - helper_token_standard_constructor, initialize_account, mint_additional_supply, - new_definition, transfer, + TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, TokenDefinition, + TokenHolding, TokenStandardEnum, burn, helper_token_standard_constructor, + initialize_account, mint_additional_supply, new_definition, new_definition_with_metadata, + transfer, }; #[should_panic(expected = "Invalid number of input accounts")] @@ -1126,10 +1188,10 @@ mod tests { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, + account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: helper_id_constructor(IdEnum::MetadataId), + metadata_id: AccountId::new([0;32]), }), nonce: 0, }, @@ -1141,10 +1203,10 @@ mod tests { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, + account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: helper_id_constructor(IdEnum::MetadataId), + metadata_id: AccountId::new([0;32]), }), nonce: 0, }, @@ -1212,10 +1274,10 @@ mod tests { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, + account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), - metadata_id: helper_id_constructor(IdEnum::MetadataId), + metadata_id: AccountId::new([0;32]), }), nonce: 0, }, @@ -1274,10 +1336,10 @@ mod tests { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, + account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - metadata_id: helper_id_constructor(IdEnum::MetadataId), + metadata_id: AccountId::new([0;32]), }), nonce: 0, }, @@ -1304,11 +1366,11 @@ mod tests { balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: helper_token_standard_constructor( - TokenStandard::NonFungibleHolding, + TokenStandardEnum::NonFungible, ), name: [2; 6], total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - metadata_id: helper_id_constructor(IdEnum::MetadataId), + metadata_id: AccountId::new([0;32]), }), nonce: 0, }, @@ -1571,4 +1633,223 @@ mod tests { helper_balance_constructor(BalanceEnum::MintSuccess), ); } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_1() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + + let pre_states = vec![AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_2() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_3() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([4; 32]), + }, + ]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + + #[should_panic(expected = "Definition target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_definition() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + + #[should_panic(expected ="Metadata target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_metadata() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + helper_account_constructor(AccountsEnum::HoldingSameDefMint), //TODO: change to a metadata account + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + + #[should_panic(expected ="Holding target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_holding() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + helper_account_constructor(AccountsEnum::HoldingSameDefMint), + ]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + + #[should_panic(expected ="Metadata values data should be 450 bytes")] + #[test] + fn test_call_new_definition_metadata_with_too_short_metadata_length() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;449].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + + #[should_panic(expected ="Metadata values data should be 450 bytes")] + #[test] + fn test_call_new_definition_metadata_with_too_long_metadata_length() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8;451].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + } + } + +/* + pre_states: &[AccountWithMetadata], + name: [u8; 6], + total_supply: u128, + token_standard: TokenStandardEnum, + metadata_standard: MetadataStandardEnum, + metadata_values: &Data, + +*/ \ No newline at end of file From aa81a311acd5fab6fc2f32f6c0b7b318c6091926 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:30:54 -0500 Subject: [PATCH 04/16] Update token.rs --- nssa/program_methods/guest/src/bin/token.rs | 1840 +++++++++++++------ 1 file changed, 1227 insertions(+), 613 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 1345d7d..1901fb1 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -18,8 +18,10 @@ use nssa_core::{ // 2. Token transfer // Arguments to this function are: // * Two accounts: [sender_account, recipient_account]. +// * Authorization required: sender_account // * An instruction data byte string of length 23, indicating the total supply with the following layout // [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// * For NFT Master accounts, its entire balance must be transferred. // 3. Initialize account with zero balance // Arguments to this function are: // * Two accounts: [definition_account, account_to_initialize]. @@ -29,60 +31,75 @@ use nssa_core::{ // Arguments to this function are: // * Two accounts: [definition_account, holding_account]. // * Authorization required: holding_account -// * An instruction data byte string of length 23, indicating the balance to burn with the folloiwng layout +// * An instruction data byte string of length 23, indicating the balance to burn with the following layout // [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. // 5. Mint additional supply of tokens tokens to a Token Holding account (thus increasing total supply) // Arguments to this function are: // * Two accounts: [definition_account, holding_account]. // * Authorization required: definition_account -// * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout +// * An instruction data byte string of length 23, indicating the balance to mint with the following layout // [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 6. Print NFT copy from Master NFT +// Arguments to this function are: +// * Two accounts: [master_nft, account_to_initialize]. +// * Authorization required: master_nft +// * An dummy byte string of length 23, with the following layout +// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. +const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; +const TOKEN_STANDARD_FUNGIBLE_ASSET: u8 = 1; +const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; +const TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE: u8 = 3; -type TokenStandard = u8; - -enum TokenStandardEnum { - FungibleToken, - FungibleAsset, - NonFungible, -} -enum MetadataStandardEnum { - SimpleMetadata, - ExpandedMetadata, -} - -// Remove enums...unnecessary structure -fn helper_token_standard_constructor(selection: TokenStandardEnum) -> u8 { - match selection { - TokenStandardEnum::FungibleToken => 0, - TokenStandardEnum::FungibleAsset => 1, - TokenStandardEnum::NonFungible => 2, - _ => panic!("Invalid selection"), - } -} - - - -fn helper_metadata_standard_constructor(selection: MetadataStandardEnum) -> u8 { - match selection { - MetadataStandardEnum::SimpleMetadata => 0, - MetadataStandardEnum::ExpandedMetadata => 1, - } -} +const METADATA_TYPE_SIMPLE: u8 = 0; +const METADATA_TYPE_EXPANDED: u8 = 1; const TOKEN_DEFINITION_DATA_SIZE: usize = 55; -const TOKEN_HOLDING_TYPE: u8 = 1; -const TOKEN_HOLDING_MASTER_EDITION: u8 = 2; -const TOKEN_HOLDING_PRINTED: u8 = 3; - - +const TOKEN_HOLDING_STANDARD: u8 = 1; +const TOKEN_HOLDING_NFT_MASTER: u8 = 2; +const TOKEN_HOLDING_NFT_PRINTED_COPY: u8 = 3; const TOKEN_HOLDING_DATA_SIZE: usize = 49; const CURRENT_VERSION: u8 = 1; -const NUMBER_OF_METADATA_TYPES: u8 = 2; const TOKEN_METADATA_DATA_SIZE: usize = 463; +fn is_token_standard_valid(standard: u8) -> bool { + if standard == TOKEN_STANDARD_FUNGIBLE_TOKEN { + true + } else if standard == TOKEN_STANDARD_FUNGIBLE_ASSET { + true + } else if standard == TOKEN_STANDARD_NONFUNGIBLE { + true + } else if standard == TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE { + true + } else { + false + } +} + +fn is_metadata_type_valid(standard: u8) -> bool { + if standard == METADATA_TYPE_SIMPLE { + true + } else if standard == METADATA_TYPE_EXPANDED { + true + } else { + false + } +} + +fn is_token_holding_type_valid(standard: u8) -> bool { + if standard == TOKEN_HOLDING_STANDARD { + true + } else if standard == TOKEN_HOLDING_NFT_MASTER { + true + } else if standard == TOKEN_HOLDING_NFT_PRINTED_COPY { + true + } else { + false + } +} + struct TokenDefinition { account_type: u8, // Token Standard name: [u8; 6], @@ -131,14 +148,13 @@ impl TokenDefinition { metadata_id: metadata_id.clone(), }); - //TODO tests if account_type == //NFTs must have supply 1 - helper_token_standard_constructor(TokenStandardEnum::NonFungible) + TOKEN_STANDARD_NONFUNGIBLE && total_supply != 1 { None } else if account_type == //Fungible Tokens do not have metadata. - helper_token_standard_constructor(TokenStandardEnum::FungibleToken) + TOKEN_STANDARD_FUNGIBLE_TOKEN && metadata_id != AccountId::new([0; 32]) { None @@ -149,9 +165,6 @@ impl TokenDefinition { } } -//TODO(edition/master edition/printable) -// => use balance for total prints allowed -// fix logic for NFTs to allow for printables struct TokenHolding { account_type: u8, definition_id: AccountId, @@ -161,7 +174,7 @@ struct TokenHolding { impl TokenHolding { fn new(definition_id: &AccountId) -> Self { Self { - account_type: TOKEN_HOLDING_TYPE, + account_type: TOKEN_HOLDING_STANDARD, definition_id: definition_id.clone(), balance: 0, } @@ -170,7 +183,12 @@ impl TokenHolding { fn parse(data: &Data) -> Option { let data = Vec::::from(data.clone()); - if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { + if data.len() != TOKEN_HOLDING_DATA_SIZE { + return None; + } + + // Check account_type + if !is_token_holding_type_valid(data[0]) { return None; } @@ -194,6 +212,10 @@ impl TokenHolding { } fn into_data(self) -> Data { + if !is_token_holding_type_valid(self.account_type) { + panic!("Invalid Token Holding type"); + } + let mut bytes = Vec::::new(); bytes.extend_from_slice(&[self.account_type]); bytes.extend_from_slice(&self.definition_id.to_bytes()); @@ -208,22 +230,17 @@ impl TokenHolding { } struct TokenMetadata { - account_type: u8, //Not sure if necessary + account_type: u8, version: u8, definition_id: AccountId, - uri: [u8; 200], //TODO: add to specs; this is the limit Solana uses - creators: [u8; 250], //TODO: double check this value; + uri: [u8; 200], + creators: [u8; 250], primary_sale_date: u64, //BlockId } -//TODO remove any unwraps impl TokenMetadata { fn into_data(self) -> Data { - if self.account_type - != helper_metadata_standard_constructor(MetadataStandardEnum::SimpleMetadata) - || self.account_type - != helper_metadata_standard_constructor(MetadataStandardEnum::ExpandedMetadata) - { + if !is_metadata_type_valid(self.account_type) { panic!("Invalid Metadata type"); } @@ -245,7 +262,7 @@ impl TokenMetadata { fn parse(data: &Data) -> Option { let data = Vec::::from(data.clone()); - if data.len() != TOKEN_METADATA_DATA_SIZE || data[0] >= NUMBER_OF_METADATA_TYPES { + if data.len() != TOKEN_METADATA_DATA_SIZE || !is_metadata_type_valid(data[0]) { None } else { let account_type = data[0]; @@ -289,10 +306,9 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec Vec (TokenHolding, TokenHolding) { + let mut sender_holding = sender_holding; + let mut recipient_holding = recipient_holding; + + if sender_holding.balance < balance_to_move { + panic!("Insufficient balance"); + } + + sender_holding.balance = sender_holding + .balance + .checked_sub(balance_to_move) + .expect("Checked above"); + recipient_holding.balance = recipient_holding + .balance + .checked_add(balance_to_move) + .expect("Recipient balance overflow"); + + recipient_holding.account_type = sender_holding.account_type; + + (sender_holding, recipient_holding) +} + +fn nft_master_transfer( + sender_holding: TokenHolding, + recipient_holding: TokenHolding, + balance_to_move: u128, +) -> (TokenHolding, TokenHolding) { + let mut sender_holding = sender_holding; + let mut recipient_holding = recipient_holding; + + if recipient_holding.balance != 0 { + panic!("Invalid balance in recipient account for NFT transfer"); + } + + if sender_holding.balance != balance_to_move { + panic!("Invalid balance for NFT Master transfer"); + } + + sender_holding.balance = 0; + recipient_holding.balance = balance_to_move; + recipient_holding.account_type = sender_holding.account_type; + + (sender_holding, recipient_holding) +} + fn new_definition( pre_states: &[AccountWithMetadata], name: [u8; 6], @@ -357,14 +416,14 @@ fn new_definition( } let token_definition = TokenDefinition { - account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, name, total_supply, metadata_id: AccountId::new([0; 32]), }; let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_TYPE, + account_type: TOKEN_HOLDING_STANDARD, definition_id: definition_target_account.account_id.clone(), balance: total_supply, }; @@ -409,6 +468,14 @@ fn new_definition_with_metadata( panic!("Holding target account must have default values"); } + if !is_token_standard_valid(token_standard) { + panic!("Invalid Token Standard provided"); + } + + if !is_metadata_type_valid(metadata_standard) { + panic!("Invalid Metadata Standadard provided"); + } + if !valid_total_supply_for_token_standard(total_supply, token_standard) { panic!("Invalid total supply for the specified token supply"); } @@ -421,7 +488,7 @@ fn new_definition_with_metadata( }; let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_TYPE, + account_type: TOKEN_HOLDING_STANDARD, definition_id: definition_target_account.account_id.clone(), balance: total_supply, }; @@ -463,9 +530,7 @@ fn new_definition_with_metadata( } fn valid_total_supply_for_token_standard(total_supply: u128, token_standard: u8) -> bool { - if token_standard == helper_token_standard_constructor(TokenStandardEnum::NonFungible) - && total_supply != 1 - { + if token_standard == TOKEN_STANDARD_NONFUNGIBLE && total_supply != 1 { false } else { true @@ -556,7 +621,7 @@ fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec bool { - if account_type == helper_token_standard_constructor(TokenStandardEnum::NonFungible) { + if account_type == TOKEN_STANDARD_NONFUNGIBLE { false } else { true @@ -636,7 +701,59 @@ fn mint_additional_supply( vec![post_definition, token_holding_post] } -//TODO: add vars for 23 and 474 +fn print_nft(pre_states: &[AccountWithMetadata]) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let master_account = &pre_states[0]; + let printed_account = &pre_states[1]; + + if !master_account.is_authorized { + panic!("Master NFT Account must be authorized"); + } + + if printed_account.account != Account::default() { + panic!("Printed Account must be uninitialized"); + } + + let mut master_account_data = + TokenHolding::parse(&master_account.account.data).expect("Invalid Token Holding data"); + + if master_account_data.account_type != TOKEN_HOLDING_NFT_MASTER { + panic!("Invalid Token Holding provided as NFT Master Account"); + } + + if master_account_data.balance < 2 { + panic!("Insufficient balance to print another NFT copy"); + } + + let definition_id = master_account_data.definition_id.clone(); + + let post_master_account = { + let mut this = master_account.account.clone(); + master_account_data.balance -= 1; + this.data = master_account_data.into_data(); + AccountPostState::new(this) + }; + + let post_printed_account = { + let mut this = printed_account.account.clone(); + + let printed_data = TokenHolding { + account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, + definition_id, + balance: 1, + }; + + this.data = TokenHolding::into_data(printed_data); + + AccountPostState::new_claimed(this) + }; + + vec![post_master_account, post_printed_account] +} + type Instruction = Vec; fn main() { let ProgramInput { @@ -699,6 +816,7 @@ fn main() { panic!("Invalid instruction length"); } + // Parse instruction let balance_to_burn = u128::from_le_bytes( instruction[1..17] .try_into() @@ -717,6 +835,7 @@ fn main() { panic!("Invalid instruction length"); } + // Parse instruction let balance_to_mint = u128::from_le_bytes( instruction[1..17] .try_into() @@ -735,14 +854,42 @@ fn main() { panic!("Invalid instruction length") } - let total_supply = u128::from_le_bytes(instruction[1..17].try_into().expect("Total supply must be 16 bytes little-endian"),); - let name = instruction[17..23].try_into().expect("Name must be 6 bytes long"); - assert_ne!(name, [0;6]); + // Parse instruction + let total_supply = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let name = instruction[17..23] + .try_into() + .expect("Name must be 6 bytes long"); + assert_ne!(name, [0; 6]); let token_standard = instruction[23]; let metadata_standard = instruction[24]; - let metadata_values: Data = Data::try_from(instruction[25..474].to_vec()).expect("Invalid metadata"); + let metadata_values: Data = + Data::try_from(instruction[25..474].to_vec()).expect("Invalid metadata"); - new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values) + // Execute + new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ) + } + 6 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + + // Initialize account + if instruction[1..] != [0; 22] { + panic!("Invalid instruction for initialize account"); + } + + print_nft(&pre_states) } _ => panic!("Invalid instruction"), }; @@ -755,12 +902,489 @@ mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data}; use crate::{ - TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, TokenDefinition, - TokenHolding, TokenStandardEnum, burn, helper_token_standard_constructor, - initialize_account, mint_additional_supply, new_definition, new_definition_with_metadata, - transfer, + TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_NFT_MASTER, + TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, TOKEN_STANDARD_FUNGIBLE_ASSET, + TOKEN_STANDARD_FUNGIBLE_TOKEN, TOKEN_STANDARD_NONFUNGIBLE, TokenDefinition, TokenHolding, + burn, initialize_account, mint_additional_supply, new_definition, + new_definition_with_metadata, print_nft, transfer, }; + enum BalanceEnum { + InitSupply, + HoldingBalance, + InitSupplyBurned, + HoldingBalanceBurned, + BurnSuccess, + BurnInsufficient, + MintSuccess, + InitSupplyMint, + HoldingBalanceMint, + MintOverflow, + RecipientPostTransfer, + RecipientUninitPostTransfer, + SenderPostTransfer, + TransferAmount, + PrintableCopies, + } + + enum AccountsEnum { + DefinitionAccountAuth, + DefinitionAccountNotAuth, + HoldingDiffDef, + HoldingSameDefAuth, + HoldingSameDefNotAuth, + HoldingSameDefNotAuthOverflow, + DefinitionAccountPostBurn, + HoldingAccountPostBurn, + InitMint, + DefinitionAccountMint, + HoldingSameDefMint, + HoldingSameDefAuthLargeBalance, + DefinitionAccountAuthNonFungible, + DefinitionAccountUninit, + HoldingAccountUninit, + HoldingAccountInit, + DefinitionAccountUnclaimed, + HoldingAccountUnclaimed, + HoldingAccount2Init, + HoldingAccount2InitPostTransfer, + HoldingAccount2UninitPostTransfer, + HoldingAccountInitPostTransfer, + HoldingAccountMasterNFT, + HoldingAccountMasterNFTInsufficientBalance, + HoldingAccountMasterNFTAfterPrint, + HoldingAccountPrintedNFT, + HoldingAccountMasterNFTTransferredTo, + HoldingAccountMasterNFTPostTransfer, + } + + enum IdEnum { + PoolDefinitionId, + PoolDefinitionIdDiff, + HoldingId, + Holding2Id, + MetadataId, + } + + fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { + match selection { + AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingDiffDef => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountUninit => AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::InitMint => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintSuccess), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintOverflow), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountAuthNonFungible => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_NONFUNGIBLE, + name: [2; 6], + total_supply: 1, + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountUninit => AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountInit => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::DefinitionAccountUnclaimed => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountUnclaimed => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccount2Init => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccount2InitPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::RecipientPostTransfer), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccountInitPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::SenderPostTransfer), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccount2UninitPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor( + BalanceEnum::RecipientUninitPostTransfer, + ), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccountMasterNFT => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::PrintableCopies), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountMasterNFTInsufficientBalance => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: 1, + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountMasterNFTAfterPrint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::PrintableCopies) - 1, + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountPrintedNFT => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: 1, + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountMasterNFTTransferredTo => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::PrintableCopies), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccountMasterNFTPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: 0, + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + _ => panic!("Invalid selection"), + } + } + + fn helper_balance_constructor(selection: BalanceEnum) -> u128 { + match selection { + BalanceEnum::InitSupply => 100_000, + BalanceEnum::HoldingBalance => 1_000, + BalanceEnum::InitSupplyBurned => 99_500, + BalanceEnum::HoldingBalanceBurned => 500, + BalanceEnum::BurnSuccess => 500, + BalanceEnum::BurnInsufficient => 1_500, + BalanceEnum::MintSuccess => 50_000, + BalanceEnum::InitSupplyMint => 150_000, + BalanceEnum::HoldingBalanceMint => 51_000, + BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, + BalanceEnum::SenderPostTransfer => 95_000, + BalanceEnum::RecipientPostTransfer => 105_000, + BalanceEnum::RecipientUninitPostTransfer => 5_000, + BalanceEnum::TransferAmount => 5_000, + BalanceEnum::PrintableCopies => 10, + _ => panic!("Invalid selection"), + } + } + + fn helper_id_constructor(selection: IdEnum) -> AccountId { + match selection { + IdEnum::PoolDefinitionId => AccountId::new([15; 32]), + IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), + IdEnum::HoldingId => AccountId::new([17; 32]), + IdEnum::MetadataId => AccountId::new([42; 32]), + IdEnum::Holding2Id => AccountId::new([31; 32]), + } + } + #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_1() { @@ -837,46 +1461,30 @@ mod tests { let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); } - /* #[test] fn test_new_definition_with_valid_inputs_succeeds() { let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: AccountId::new([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - ]), - }, - AccountWithMetadata { - account: Account { - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([2; 32]), - }, + helper_account_constructor(AccountsEnum::DefinitionAccountUninit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), ]; - let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); - let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); - assert_eq!( - definition_account.account().data, - vec![ - 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] + let post_states = new_definition( + &pre_states, + [2u8; 6], + helper_balance_constructor(BalanceEnum::InitSupply), ); - assert_eq!( - holding_account.account().data, - vec![ - 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] + + let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); + assert!( + *definition_account.account() + == helper_account_constructor(AccountsEnum::DefinitionAccountUnclaimed).account + ); + + assert!( + *holding_account.account() + == helper_account_constructor(AccountsEnum::HoldingAccountUnclaimed).account ); } - */ #[should_panic(expected = "Invalid number of input accounts")] #[test] @@ -915,11 +1523,11 @@ mod tests { #[should_panic(expected = "Invalid sender data")] #[test] fn test_transfer_invalid_instruction_type_should_fail() { - let invalid_type = TOKEN_HOLDING_TYPE ^ 1; + let invalid_type = TOKEN_HOLDING_STANDARD ^ 1; let pre_states = vec![ AccountWithMetadata { account: Account { - // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts + // First byte should be `TOKEN_HOLDING_STANDARD` for token holding accounts data: Data::try_from(vec![invalid_type; TOKEN_HOLDING_DATA_SIZE]) .expect("Invalid data"), ..Account::default() @@ -936,474 +1544,208 @@ mod tests { let _post_states = transfer(&pre_states, 10); } - /* - #[should_panic(expected = "Invalid sender data")] - #[test] - fn test_transfer_invalid_data_size_should_fail_1() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1], - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Invalid sender data")] - #[test] - fn test_transfer_invalid_data_size_should_fail_2() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1], - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Sender and recipient definition id mismatch")] - #[test] - fn test_transfer_with_different_definition_ids_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1] - .into_iter() - .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) - .collect(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Insufficient balance")] - #[test] - fn test_transfer_with_insufficient_balance_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - // Attempt to transfer 38 tokens - let _post_states = transfer(&pre_states, 38); - } - - #[should_panic(expected = "Sender authorization is missing")] - #[test] - fn test_transfer_without_sender_authorization_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect(), - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 37); - } - - #[test] - fn test_transfer_with_valid_inputs_succeeds() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - // Account with balance 255 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(255)) - .collect(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let post_states = transfer(&pre_states, 11); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - assert_eq!( - sender_post.account().data, - vec![ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - assert_eq!( - recipient_post.account().data, - vec![ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - #[test] - fn test_token_initialize_account_succeeds() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Definition ID with - data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(1000)) - .collect(), - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: AccountId::new([2; 32]), - }, - ]; - let post_states = initialize_account(&pre_states); - let [definition, holding] = post_states.try_into().ok().unwrap(); - assert_eq!(definition.account().data, pre_states[0].account.data); - assert_eq!( - holding.account().data, - vec![ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - */ - enum BalanceEnum { - InitSupply, - HoldingBalance, - InitSupplyBurned, - HoldingBalanceBurned, - BurnSuccess, - BurnInsufficient, - MintSuccess, - InitSupplyMint, - HoldingBalanceMint, - MintOverflow, - } - - enum AccountsEnum { - DefinitionAccountAuth, - DefinitionAccountNotAuth, - HoldingDiffDef, - HoldingSameDefAuth, - HoldingSameDefNotAuth, - HoldingSameDefNotAuthOverflow, - DefinitionAccountPostBurn, - HoldingAccountPostBurn, - Uninit, - InitMint, - DefinitionAccountMint, - HoldingSameDefMint, - HoldingSameDefAuthLargeBalance, - DefinitionAccountAuthNotMintable, - } - - enum IdEnum { - PoolDefinitionId, - PoolDefinitionIdDiff, - HoldingId, - MetadataId, - } - - fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { - match selection { - AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { + #[should_panic(expected = "Invalid sender data")] + #[test] + fn test_transfer_invalid_data_size_should_fail_1() { + let pre_states = vec![ + AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: AccountId::new([0;32]), - }), - nonce: 0, + // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + ..Account::default() }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + account_id: AccountId::new([1; 32]), }, - AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: AccountId::new([0;32]), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingDiffDef => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), - metadata_id: AccountId::new([0;32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::Uninit => AccountWithMetadata { + AccountWithMetadata { account: Account::default(), - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::InitMint => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintSuccess), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), - }), - nonce: 0, - }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + account_id: AccountId::new([2; 32]), }, - AccountsEnum::DefinitionAccountMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: helper_token_standard_constructor(TokenStandardEnum::FungibleToken), - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - metadata_id: AccountId::new([0;32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintOverflow), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountAuthNotMintable => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: helper_token_standard_constructor( - TokenStandardEnum::NonFungible, - ), - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - metadata_id: AccountId::new([0;32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - _ => panic!("Invalid selection"), - } + ]; + let _post_states = transfer(&pre_states, 10); } - fn helper_balance_constructor(selection: BalanceEnum) -> u128 { - match selection { - BalanceEnum::InitSupply => 100_000, - BalanceEnum::HoldingBalance => 1_000, - BalanceEnum::InitSupplyBurned => 99_500, - BalanceEnum::HoldingBalanceBurned => 500, - BalanceEnum::BurnSuccess => 500, - BalanceEnum::BurnInsufficient => 1_500, - BalanceEnum::MintSuccess => 50_000, - BalanceEnum::InitSupplyMint => 150_000, - BalanceEnum::HoldingBalanceMint => 51_000, - BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, - _ => panic!("Invalid selection"), - } + #[should_panic(expected = "Invalid sender data")] + #[test] + fn test_transfer_invalid_data_size_should_fail_2() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 10); } - fn helper_id_constructor(selection: IdEnum) -> AccountId { - match selection { - IdEnum::PoolDefinitionId => AccountId::new([15; 32]), - IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), - IdEnum::HoldingId => AccountId::new([17; 32]), - IdEnum::MetadataId => AccountId::new([42; 32]), - } + #[should_panic(expected = "Sender and recipient definition id mismatch")] + #[test] + fn test_transfer_with_different_definition_ids_should_fail() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + helper_account_constructor(AccountsEnum::HoldingDiffDef), + ]; + let _post_states = transfer(&pre_states, 10); + } + + #[should_panic(expected = "Insufficient balance")] + #[test] + fn test_transfer_with_insufficient_balance_should_fail() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefMint), + ]; + // Attempt to transfer 38 tokens + let _post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::BurnInsufficient), + ); + } + + #[should_panic(expected = "Sender authorization is missing")] + #[test] + fn test_transfer_without_sender_authorization_should_fail() { + let mut def_data = Vec::::new(); + def_data.extend_from_slice(&[1; TOKEN_DEFINITION_DATA_SIZE - 16]); + def_data.extend_from_slice(&u128::to_le_bytes(37)); + + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Account with balance 37 + data: Data::try_from(def_data).unwrap(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account { + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 37); + } + + #[test] + fn test_transfer_with_valid_inputs_succeeds() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountInit), + helper_account_constructor(AccountsEnum::HoldingAccount2Init), + ]; + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::TransferAmount), + ); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) + .account + ); + } + + #[should_panic(expected = "Invalid balance for NFT Master transfer")] + #[test] + fn test_transfer_with_master_nft_invalid_balance() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::TransferAmount), + ); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) + .account + ); + } + + #[should_panic(expected = "Invalid balance in recipient account for NFT transfer")] + #[test] + fn test_transfer_with_master_nft_invalid_recipient_balance() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo), + ]; + let _post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::PrintableCopies), + ); + } + + #[test] + fn test_transfer_with_master_nft_success() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::PrintableCopies), + ); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTPostTransfer) + .account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo) + .account + ); + } + + #[test] + fn test_token_initialize_account_succeeds() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountInit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::TransferAmount), + ); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccount2UninitPostTransfer) + .account + ); } #[test] @@ -1496,7 +1838,7 @@ mod tests { #[test] #[should_panic(expected = "Invalid number of accounts")] - fn test_mint_invalid_number_of_accounts() { + fn test_mint_invalid_number_of_accounts_1() { let pre_states = vec![helper_account_constructor( AccountsEnum::DefinitionAccountAuth, )]; @@ -1506,6 +1848,20 @@ mod tests { ); } + #[test] + #[should_panic(expected = "Invalid number of accounts")] + fn test_mint_invalid_number_of_accounts_2() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefMint), + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + #[test] #[should_panic(expected = "Holding account must be valid")] fn test_mint_not_valid_holding_account() { @@ -1519,6 +1875,19 @@ mod tests { ); } + #[test] + #[should_panic(expected = "Definition account must be valid")] + fn test_mint_not_valid_definition_account() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + #[test] #[should_panic(expected = "Definition authorization is missing")] fn test_mint_missing_authorization() { @@ -1573,7 +1942,7 @@ mod tests { fn test_mint_uninit_holding_success() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::Uninit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), ]; let post_states = mint_additional_supply( &pre_states, @@ -1625,7 +1994,7 @@ mod tests { )] fn test_mint_cannot_mint_unmintable_tokens() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuthNotMintable), + helper_account_constructor(AccountsEnum::DefinitionAccountAuthNonFungible), helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), ]; let _post_states = mint_additional_supply( @@ -1641,14 +2010,21 @@ mod tests { let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); let pre_states = vec![AccountWithMetadata { account: Account::default(), is_authorized: true, account_id: AccountId::new([1; 32]), }]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } #[should_panic(expected = "Invalid number of input accounts")] @@ -1658,7 +2034,7 @@ mod tests { let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); let pre_states = vec![ AccountWithMetadata { @@ -1672,7 +2048,14 @@ mod tests { account_id: AccountId::new([2; 32]), }, ]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } #[should_panic(expected = "Invalid number of input accounts")] @@ -1682,7 +2065,7 @@ mod tests { let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); let pre_states = vec![ AccountWithMetadata { @@ -1706,7 +2089,14 @@ mod tests { account_id: AccountId::new([4; 32]), }, ]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } #[should_panic(expected = "Definition target account must have default values")] @@ -1716,7 +2106,7 @@ mod tests { let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), @@ -1731,17 +2121,24 @@ mod tests { account_id: AccountId::new([3; 32]), }, ]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } - #[should_panic(expected ="Metadata target account must have default values")] + #[should_panic(expected = "Metadata target account must have default values")] #[test] fn test_call_new_definition_metadata_with_init_metadata() { let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); let pre_states = vec![ AccountWithMetadata { @@ -1749,24 +2146,31 @@ mod tests { is_authorized: true, account_id: AccountId::new([1; 32]), }, - helper_account_constructor(AccountsEnum::HoldingSameDefMint), //TODO: change to a metadata account + helper_account_constructor(AccountsEnum::HoldingSameDefMint), AccountWithMetadata { account: Account::default(), is_authorized: true, account_id: AccountId::new([3; 32]), }, ]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } - #[should_panic(expected ="Holding target account must have default values")] + #[should_panic(expected = "Holding target account must have default values")] #[test] fn test_call_new_definition_metadata_with_init_holding() { let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;450].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); let pre_states = vec![ AccountWithMetadata { @@ -1781,17 +2185,24 @@ mod tests { }, helper_account_constructor(AccountsEnum::HoldingSameDefMint), ]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } - #[should_panic(expected ="Metadata values data should be 450 bytes")] + #[should_panic(expected = "Metadata values data should be 450 bytes")] #[test] fn test_call_new_definition_metadata_with_too_short_metadata_length() { let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;449].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 449].to_vec()).unwrap(); let pre_states = vec![ AccountWithMetadata { @@ -1810,17 +2221,24 @@ mod tests { account_id: AccountId::new([3; 32]), }, ]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } - #[should_panic(expected ="Metadata values data should be 450 bytes")] + #[should_panic(expected = "Metadata values data should be 450 bytes")] #[test] fn test_call_new_definition_metadata_with_too_long_metadata_length() { let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; let total_supply = 15u128; let token_standard = 0u8; let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8;451].to_vec()).unwrap(); + let metadata_values: Data = Data::try_from([1u8; 451].to_vec()).unwrap(); let pre_states = vec![ AccountWithMetadata { @@ -1839,17 +2257,213 @@ mod tests { account_id: AccountId::new([3; 32]), }, ]; - let _post_states = new_definition_with_metadata(&pre_states, name, total_supply, token_standard, metadata_standard, &metadata_values); + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); } + #[should_panic(expected = "Invalid Token Standard provided")] + #[test] + fn test_call_new_definition_metadata_with_invalid_token_standard() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 14u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid Metadata Standadard provided")] + #[test] + fn test_call_new_definition_metadata_with_invalid_metadata_standard() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 14u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid total supply for the specified token supply")] + #[test] + fn test_call_new_definition_metadata_invalid_supply_for_nonfungible() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = TOKEN_STANDARD_NONFUNGIBLE; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid number of accounts")] + #[test] + fn test_print_nft_invalid_number_of_accounts_1() { + let pre_states = vec![helper_account_constructor( + AccountsEnum::HoldingAccountMasterNFT, + )]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid number of accounts")] + #[test] + fn test_print_nft_invalid_number_of_accounts_2() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Master NFT Account must be authorized")] + #[test] + fn test_print_nft_master_account_must_be_authorized() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Printed Account must be uninitialized")] + #[test] + fn test_print_nft_print_account_initialized() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountInit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid Token Holding data")] + #[test] + fn test_print_nft_master_nft_invalid_token_holding() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid Token Holding provided as NFT Master Account")] + #[test] + fn test_print_nft_master_nft_not_nft_master_account() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountInit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Insufficient balance to print another NFT copy")] + #[test] + fn test_print_nft_master_nft_insufficient_balance() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTInsufficientBalance), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[test] + fn test_print_nft_success() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let post_states = print_nft(&pre_states); + + let post_master_nft = post_states[0].account(); + let post_printed = post_states[1].account(); + + assert!( + *post_master_nft + == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTAfterPrint) + .account + ); + assert!( + *post_printed + == helper_account_constructor(AccountsEnum::HoldingAccountPrintedNFT).account + ); + } } - -/* - pre_states: &[AccountWithMetadata], - name: [u8; 6], - total_supply: u128, - token_standard: TokenStandardEnum, - metadata_standard: MetadataStandardEnum, - metadata_values: &Data, - -*/ \ No newline at end of file From 3acc51ac870b587488e262daa95cb535e3e133a0 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:50:13 -0500 Subject: [PATCH 05/16] rebased with main token program --- nssa/program_methods/guest/src/bin/token.rs | 2090 ++++++++++++++----- 1 file changed, 1611 insertions(+), 479 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 739295b..4b71128 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -37,17 +37,132 @@ use nssa_core::{ // * Authorization required: definition_account // * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout // [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 6. Print NFT copy from Master NFT +// Arguments to this function are: +// * Two accounts: [master_nft, account_to_initialize]. +// * Authorization required: master_nft +// * An dummy byte string of length 23, with the following layout +// [0x05 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. +const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; +const TOKEN_STANDARD_FUNGIBLE_ASSET: u8 = 1; +const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; +const TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE: u8 = 3; -const TOKEN_DEFINITION_TYPE: u8 = 0; -const TOKEN_DEFINITION_DATA_SIZE: usize = 23; +const METADATA_TYPE_SIMPLE: u8 = 0; +const METADATA_TYPE_EXPANDED: u8 = 1; + +const TOKEN_DEFINITION_DATA_SIZE: usize = 55; + +const TOKEN_HOLDING_STANDARD: u8 = 1; +const TOKEN_HOLDING_NFT_MASTER: u8 = 2; +const TOKEN_HOLDING_NFT_PRINTED_COPY: u8 = 3; -const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_DATA_SIZE: usize = 49; +const CURRENT_VERSION: u8 = 1; + +const TOKEN_METADATA_DATA_SIZE: usize = 463; + +fn is_token_standard_valid(standard: u8) -> bool { + if standard == TOKEN_STANDARD_FUNGIBLE_TOKEN { + true + } else if standard == TOKEN_STANDARD_FUNGIBLE_ASSET { + true + } else if standard == TOKEN_STANDARD_NONFUNGIBLE { + true + } else if standard == TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE { + true + } else { + false + } +} + +fn is_metadata_type_valid(standard: u8) -> bool { + if standard == METADATA_TYPE_SIMPLE { + true + } else if standard == METADATA_TYPE_EXPANDED { + true + } else { + false + } +} + +fn is_token_holding_type_valid(standard: u8) -> bool { + if standard == TOKEN_HOLDING_STANDARD { + true + } else if standard == TOKEN_HOLDING_NFT_MASTER { + true + } else if standard == TOKEN_HOLDING_NFT_PRINTED_COPY { + true + } else { + false + } +} + + struct TokenDefinition { account_type: u8, name: [u8; 6], total_supply: u128, + metadata_id: AccountId, +} + +impl TokenDefinition { + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Invalid data") + } + + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_DEFINITION_DATA_SIZE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().expect("Name must be a 6 bytes"); + let total_supply = u128::from_le_bytes( + data[7..23] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let metadata_id = AccountId::new( + data[23..TOKEN_DEFINITION_DATA_SIZE] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); + + let this = Some(Self { + account_type, + name, + total_supply, + metadata_id: metadata_id.clone(), + }); + + if account_type == //NFTs must have supply 1 + TOKEN_STANDARD_NONFUNGIBLE + && total_supply != 1 + { + None + } else if account_type == //Fungible Tokens do not have metadata. + TOKEN_STANDARD_FUNGIBLE_TOKEN + && metadata_id != AccountId::new([0; 32]) + { + None + } else { + this + } + } + } } struct TokenHolding { @@ -56,49 +171,25 @@ struct TokenHolding { balance: u128, } -impl TokenDefinition { - fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..7].copy_from_slice(&self.name); - bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("23 bytes should fit into Data") - } - - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { - None - } else { - let account_type = data[0]; - let name = data[1..7].try_into().unwrap(); - let total_supply = u128::from_le_bytes( - data[7..] - .try_into() - .expect("Total supply must be 16 bytes little-endian"), - ); - Some(Self { - account_type, - name, - total_supply, - }) - } - } -} impl TokenHolding { fn new(definition_id: &AccountId) -> Self { Self { - account_type: TOKEN_HOLDING_TYPE, + account_type: TOKEN_HOLDING_STANDARD, definition_id: definition_id.clone(), balance: 0, } } - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_HOLDING_DATA_SIZE { + return None; + } + + // Check account_type + if !is_token_holding_type_valid(data[0]) { return None; } @@ -113,6 +204,7 @@ impl TokenHolding { .try_into() .expect("balance must be 16 bytes little-endian"), ); + Some(Self { definition_id, balance, @@ -121,17 +213,90 @@ impl TokenHolding { } fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); - bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("33 bytes should fit into Data") + if !is_token_holding_type_valid(self.account_type) { + panic!("Invalid Token Holding type"); + } + + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.definition_id.to_bytes()); + bytes.extend_from_slice(&self.balance.to_le_bytes()); + + if bytes.len() != TOKEN_HOLDING_DATA_SIZE { + panic!("Invalid Token Holding data"); + } + + Data::try_from(bytes).expect("Invalid data") } } +struct TokenMetadata { + account_type: u8, + version: u8, + definition_id: AccountId, + uri: [u8; 200], + creators: [u8; 250], + primary_sale_date: u64, //BlockId +} + +impl TokenMetadata { + fn into_data(self) -> Data { + if !is_metadata_type_valid(self.account_type) { + panic!("Invalid Metadata type"); + } + + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&[self.version]); + bytes.extend_from_slice(&self.definition_id.to_bytes()); + bytes.extend_from_slice(&self.uri); + bytes.extend_from_slice(&self.creators); + bytes.extend_from_slice(&self.primary_sale_date.to_le_bytes()); + + if bytes.len() != TOKEN_METADATA_DATA_SIZE { + panic!("Invalid Token Definition data length"); + } + + Data::try_from(bytes).expect("Invalid data") + } + + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_METADATA_DATA_SIZE || !is_metadata_type_valid(data[0]) { + None + } else { + let account_type = data[0]; + let version = data[1]; + let definition_id = AccountId::new( + data[2..34] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); + let uri: [u8; 200] = data[34..234] + .try_into() + .expect("Token Program expects valid uri for Metadata"); + let creators: [u8; 250] = data[234..484] + .try_into() + .expect("Token Program expects valid creators for Metadata"); + let primary_sale_date = u64::from_le_bytes( + data[484..TOKEN_METADATA_DATA_SIZE] + .try_into() + .expect("Token Program expects valid blockid for Metadata"), + ); + Some(Self { + account_type, + version, + definition_id, + uri, + creators, + primary_sale_date, + }) + } + } +} + + fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of input accounts"); @@ -139,9 +304,13 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec Vec (TokenHolding, TokenHolding) { + let mut sender_holding = sender_holding; + let mut recipient_holding = recipient_holding; + + if sender_holding.balance < balance_to_move { + panic!("Insufficient balance"); + } + + sender_holding.balance = sender_holding + .balance + .checked_sub(balance_to_move) + .expect("Checked above"); + recipient_holding.balance = recipient_holding + .balance + .checked_add(balance_to_move) + .expect("Recipient balance overflow"); + + recipient_holding.account_type = sender_holding.account_type; + + (sender_holding, recipient_holding) +} + +fn nft_master_transfer( + sender_holding: TokenHolding, + recipient_holding: TokenHolding, + balance_to_move: u128, +) -> (TokenHolding, TokenHolding) { + let mut sender_holding = sender_holding; + let mut recipient_holding = recipient_holding; + + if recipient_holding.balance != 0 { + panic!("Invalid balance in recipient account for NFT transfer"); + } + + if sender_holding.balance != balance_to_move { + panic!("Invalid balance for NFT Master transfer"); + } + + sender_holding.balance = 0; + recipient_holding.balance = balance_to_move; + recipient_holding.account_type = sender_holding.account_type; + + (sender_holding, recipient_holding) +} + fn new_definition( pre_states: &[AccountWithMetadata], name: [u8; 6], @@ -194,6 +405,7 @@ fn new_definition( if pre_states.len() != 2 { panic!("Invalid number of input accounts"); } + let definition_target_account = &pre_states[0]; let holding_target_account = &pre_states[1]; @@ -206,13 +418,14 @@ fn new_definition( } let token_definition = TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, name, total_supply, + metadata_id: AccountId::new([0; 32]), }; let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_TYPE, + account_type: TOKEN_HOLDING_STANDARD, definition_id: definition_target_account.account_id.clone(), balance: total_supply, }; @@ -229,6 +442,103 @@ fn new_definition( ] } +fn new_definition_with_metadata( + pre_states: &[AccountWithMetadata], + name: [u8; 6], + total_supply: u128, + token_standard: u8, + metadata_standard: u8, + metadata_values: &Data, +) -> Vec { + if pre_states.len() != 3 { + panic!("Invalid number of input accounts"); + } + + let definition_target_account = &pre_states[0]; + let metadata_target_account = &pre_states[1]; + let holding_target_account = &pre_states[2]; + + if definition_target_account.account != Account::default() { + panic!("Definition target account must have default values"); + } + + if metadata_target_account.account != Account::default() { + panic!("Metadata target account must have default values"); + } + + if holding_target_account.account != Account::default() { + panic!("Holding target account must have default values"); + } + + if !is_token_standard_valid(token_standard) { + panic!("Invalid Token Standard provided"); + } + + if !is_metadata_type_valid(metadata_standard) { + panic!("Invalid Metadata Standadard provided"); + } + + if !valid_total_supply_for_token_standard(total_supply, token_standard) { + panic!("Invalid total supply for the specified token supply"); + } + + let token_definition = TokenDefinition { + account_type: token_standard, + name, + total_supply, + metadata_id: metadata_target_account.account_id.clone(), + }; + + let token_holding = TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: definition_target_account.account_id.clone(), + balance: total_supply, + }; + + if metadata_values.len() != 450 { + panic!("Metadata values data should be 450 bytes"); + } + + let uri: [u8; 200] = metadata_values[0..200] + .try_into() + .expect("Token program expects valid uri for Metadata"); + let creators: [u8; 250] = metadata_values[200..450] + .try_into() + .expect("Token program expects valid creators for Metadata"); + + let token_metadata = TokenMetadata { + account_type: metadata_standard, + version: CURRENT_VERSION, + definition_id: definition_target_account.account_id.clone(), + uri, + creators, + primary_sale_date: 0u64, //TODO: future works to implement this + }; + + let mut definition_target_account_post = definition_target_account.account.clone(); + definition_target_account_post.data = token_definition.into_data(); + + let mut holding_target_account_post = holding_target_account.account.clone(); + holding_target_account_post.data = token_holding.into_data(); + + let mut metadata_target_account_post = metadata_target_account.account.clone(); + metadata_target_account_post.data = token_metadata.into_data(); + + vec![ + AccountPostState::new_claimed(definition_target_account_post), + AccountPostState::new_claimed(holding_target_account_post), + AccountPostState::new_claimed(metadata_target_account_post), + ] +} + +fn valid_total_supply_for_token_standard(total_supply: u128, token_standard: u8) -> bool { + if token_standard == TOKEN_STANDARD_NONFUNGIBLE && total_supply != 1 { + false + } else { + true + } +} + fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of accounts"); @@ -267,17 +577,17 @@ fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec Vec Vec bool { + if account_type == TOKEN_STANDARD_NONFUNGIBLE { + false + } else { + true + } +} + fn mint_additional_supply( pre_states: &[AccountWithMetadata], amount_to_mint: u128, @@ -335,8 +654,12 @@ fn mint_additional_supply( TokenHolding::parse(&token_holding.account.data).expect("Holding account must be valid") }; + if !is_mintable(definition_values.account_type) { + panic!("Token Definition's standard does not permit minting additional supply"); + } + if definition.account_id != token_holding_values.definition_id { - panic!("Mismatch token definition and token holding"); + panic!("Mismatch Token Definition and Token Holding"); } let token_holding_post_data = TokenHolding { @@ -357,6 +680,7 @@ fn mint_additional_supply( account_type: definition_values.account_type, name: definition_values.name, total_supply: post_total_supply, + metadata_id: definition_values.metadata_id, }; let post_definition = { @@ -379,7 +703,60 @@ fn mint_additional_supply( vec![post_definition, token_holding_post] } -type Instruction = [u8; 23]; +fn print_nft(pre_states: &[AccountWithMetadata]) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let master_account = &pre_states[0]; + let printed_account = &pre_states[1]; + + if !master_account.is_authorized { + panic!("Master NFT Account must be authorized"); + } + + if printed_account.account != Account::default() { + panic!("Printed Account must be uninitialized"); + } + + let mut master_account_data = + TokenHolding::parse(&master_account.account.data).expect("Invalid Token Holding data"); + + if master_account_data.account_type != TOKEN_HOLDING_NFT_MASTER { + panic!("Invalid Token Holding provided as NFT Master Account"); + } + + if master_account_data.balance < 2 { + panic!("Insufficient balance to print another NFT copy"); + } + + let definition_id = master_account_data.definition_id.clone(); + + let post_master_account = { + let mut this = master_account.account.clone(); + master_account_data.balance -= 1; + this.data = master_account_data.into_data(); + AccountPostState::new(this) + }; + + let post_printed_account = { + let mut this = printed_account.account.clone(); + + let printed_data = TokenHolding { + account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, + definition_id, + balance: 1, + }; + + this.data = TokenHolding::into_data(printed_data); + + AccountPostState::new_claimed(this) + }; + + vec![post_master_account, post_printed_account] +} + +type Instruction = Vec; fn main() { let ( @@ -456,6 +833,48 @@ fn main() { // Execute mint_additional_supply(&pre_states, balance_to_mint) } + 5 => { + if instruction.len() != 474 { + panic!("Invalid instruction length") + } + + // Parse instruction + let total_supply = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let name = instruction[17..23] + .try_into() + .expect("Name must be 6 bytes long"); + assert_ne!(name, [0; 6]); + let token_standard = instruction[23]; + let metadata_standard = instruction[24]; + let metadata_values: Data = + Data::try_from(instruction[25..474].to_vec()).expect("Invalid metadata"); + + // Execute + new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ) + } + 6 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + + // Initialize account + if instruction[1..] != [0; 22] { + panic!("Invalid instruction for initialize account"); + } + + print_nft(&pre_states) + } _ => panic!("Invalid instruction"), }; @@ -464,14 +883,492 @@ fn main() { #[cfg(test)] mod tests { - use nssa_core::account::{Account, AccountId, AccountWithMetadata}; + use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data}; use crate::{ - TOKEN_DEFINITION_DATA_SIZE, TOKEN_DEFINITION_TYPE, TOKEN_HOLDING_DATA_SIZE, - TOKEN_HOLDING_TYPE, TokenDefinition, TokenHolding, burn, initialize_account, - mint_additional_supply, new_definition, transfer, + TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_NFT_MASTER, + TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, TOKEN_STANDARD_FUNGIBLE_ASSET, + TOKEN_STANDARD_FUNGIBLE_TOKEN, TOKEN_STANDARD_NONFUNGIBLE, TokenDefinition, TokenHolding, + burn, initialize_account, mint_additional_supply, new_definition, + new_definition_with_metadata, print_nft, transfer, }; + enum BalanceEnum { + InitSupply, + HoldingBalance, + InitSupplyBurned, + HoldingBalanceBurned, + BurnSuccess, + BurnInsufficient, + MintSuccess, + InitSupplyMint, + HoldingBalanceMint, + MintOverflow, + RecipientPostTransfer, + RecipientUninitPostTransfer, + SenderPostTransfer, + TransferAmount, + PrintableCopies, + } + + enum AccountsEnum { + DefinitionAccountAuth, + DefinitionAccountNotAuth, + HoldingDiffDef, + HoldingSameDefAuth, + HoldingSameDefNotAuth, + HoldingSameDefNotAuthOverflow, + DefinitionAccountPostBurn, + HoldingAccountPostBurn, + InitMint, + DefinitionAccountMint, + HoldingSameDefMint, + HoldingSameDefAuthLargeBalance, + DefinitionAccountAuthNonFungible, + DefinitionAccountUninit, + HoldingAccountUninit, + HoldingAccountInit, + DefinitionAccountUnclaimed, + HoldingAccountUnclaimed, + HoldingAccount2Init, + HoldingAccount2InitPostTransfer, + HoldingAccount2UninitPostTransfer, + HoldingAccountInitPostTransfer, + HoldingAccountMasterNFT, + HoldingAccountMasterNFTInsufficientBalance, + HoldingAccountMasterNFTAfterPrint, + HoldingAccountPrintedNFT, + HoldingAccountMasterNFTTransferredTo, + HoldingAccountMasterNFTPostTransfer, + } + + enum IdEnum { + PoolDefinitionId, + PoolDefinitionIdDiff, + HoldingId, + Holding2Id, + MetadataId, + } + + fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { + match selection { + AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingDiffDef => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountUninit => AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::InitMint => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintSuccess), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintOverflow), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountAuthNonFungible => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_NONFUNGIBLE, + name: [2; 6], + total_supply: 1, + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountUninit => AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountInit => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::DefinitionAccountUnclaimed => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountUnclaimed => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccount2Init => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccount2InitPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::RecipientPostTransfer), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccountInitPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::SenderPostTransfer), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccount2UninitPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor( + BalanceEnum::RecipientUninitPostTransfer, + ), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccountMasterNFT => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::PrintableCopies), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountMasterNFTInsufficientBalance => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: 1, + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountMasterNFTAfterPrint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::PrintableCopies) - 1, + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountPrintedNFT => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: 1, + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingAccountMasterNFTTransferredTo => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::PrintableCopies), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::Holding2Id), + }, + AccountsEnum::HoldingAccountMasterNFTPostTransfer => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: 0, + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + _ => panic!("Invalid selection"), + } + } + + fn helper_balance_constructor(selection: BalanceEnum) -> u128 { + match selection { + BalanceEnum::InitSupply => 100_000, + BalanceEnum::HoldingBalance => 1_000, + BalanceEnum::InitSupplyBurned => 99_500, + BalanceEnum::HoldingBalanceBurned => 500, + BalanceEnum::BurnSuccess => 500, + BalanceEnum::BurnInsufficient => 1_500, + BalanceEnum::MintSuccess => 50_000, + BalanceEnum::InitSupplyMint => 150_000, + BalanceEnum::HoldingBalanceMint => 51_000, + BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, + BalanceEnum::SenderPostTransfer => 95_000, + BalanceEnum::RecipientPostTransfer => 105_000, + BalanceEnum::RecipientUninitPostTransfer => 5_000, + BalanceEnum::TransferAmount => 5_000, + BalanceEnum::PrintableCopies => 10, + _ => panic!("Invalid selection"), + } + } + + fn helper_id_constructor(selection: IdEnum) -> AccountId { + match selection { + IdEnum::PoolDefinitionId => AccountId::new([15; 32]), + IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), + IdEnum::HoldingId => AccountId::new([17; 32]), + IdEnum::MetadataId => AccountId::new([42; 32]), + IdEnum::Holding2Id => AccountId::new([31; 32]), + } + } + #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_1() { @@ -551,39 +1448,25 @@ mod tests { #[test] fn test_new_definition_with_valid_inputs_succeeds() { let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: AccountId::new([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - ]), - }, - AccountWithMetadata { - account: Account { - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([2; 32]), - }, + helper_account_constructor(AccountsEnum::DefinitionAccountUninit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), ]; - let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); - let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); - assert_eq!( - definition_account.account().data.as_ref(), - &[ - 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] + let post_states = new_definition( + &pre_states, + [2u8; 6], + helper_balance_constructor(BalanceEnum::InitSupply), ); - assert_eq!( - holding_account.account().data.as_ref(), - &[ - 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] + + let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); + assert!( + *definition_account.account() + == helper_account_constructor(AccountsEnum::DefinitionAccountUnclaimed).account + ); + + assert!( + *holding_account.account() + == helper_account_constructor(AccountsEnum::HoldingAccountUnclaimed).account ); } @@ -624,14 +1507,13 @@ mod tests { #[should_panic(expected = "Invalid sender data")] #[test] fn test_transfer_invalid_instruction_type_should_fail() { - let invalid_type = TOKEN_HOLDING_TYPE ^ 1; + let invalid_type = TOKEN_HOLDING_STANDARD ^ 1; let pre_states = vec![ AccountWithMetadata { account: Account { - // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts - data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE] - .try_into() - .unwrap(), + // First byte should be `TOKEN_HOLDING_STANDARD` for token holding accounts + data: Data::try_from(vec![invalid_type; TOKEN_HOLDING_DATA_SIZE]) + .expect("Invalid data"), ..Account::default() }, is_authorized: true, @@ -653,7 +1535,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1].try_into().unwrap(), + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), ..Account::default() }, is_authorized: true, @@ -675,7 +1557,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1].try_into().unwrap(), + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), ..Account::default() }, is_authorized: true, @@ -694,27 +1576,8 @@ mod tests { #[test] fn test_transfer_with_different_definition_ids_should_fail() { let pre_states = vec![ - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: [1] - .into_iter() - .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + helper_account_constructor(AccountsEnum::HoldingDiffDef), ]; let _post_states = transfer(&pre_states, 10); } @@ -723,46 +1586,28 @@ mod tests { #[test] fn test_transfer_with_insufficient_balance_should_fail() { let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefMint), ]; // Attempt to transfer 38 tokens - let _post_states = transfer(&pre_states, 38); + let _post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::BurnInsufficient), + ); } #[should_panic(expected = "Sender authorization is missing")] #[test] fn test_transfer_without_sender_authorization_should_fail() { + let mut def_data = Vec::::new(); + def_data.extend_from_slice(&[1; TOKEN_DEFINITION_DATA_SIZE - 16]); + def_data.extend_from_slice(&u128::to_le_bytes(37)); + let pre_states = vec![ AccountWithMetadata { account: Account { // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), + data: Data::try_from(def_data).unwrap(), ..Account::default() }, is_authorized: false, @@ -770,7 +1615,7 @@ mod tests { }, AccountWithMetadata { account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), ..Account::default() }, is_authorized: true, @@ -783,327 +1628,108 @@ mod tests { #[test] fn test_transfer_with_valid_inputs_succeeds() { let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - // Account with balance 255 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(255)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, + helper_account_constructor(AccountsEnum::HoldingAccountInit), + helper_account_constructor(AccountsEnum::HoldingAccount2Init), ]; - let post_states = transfer(&pre_states, 11); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - assert_eq!( - sender_post.account().data.as_ref(), - [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::TransferAmount), ); - assert_eq!( - recipient_post.account().data.as_ref(), - [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) + .account + ); + } + + #[should_panic(expected = "Invalid balance for NFT Master transfer")] + #[test] + fn test_transfer_with_master_nft_invalid_balance() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::TransferAmount), + ); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) + .account + ); + } + + #[should_panic(expected = "Invalid balance in recipient account for NFT transfer")] + #[test] + fn test_transfer_with_master_nft_invalid_recipient_balance() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo), + ]; + let _post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::PrintableCopies), + ); + } + + #[test] + fn test_transfer_with_master_nft_success() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::PrintableCopies), + ); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTPostTransfer) + .account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo) + .account ); } #[test] fn test_token_initialize_account_succeeds() { let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Definition ID with - data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(1000)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: AccountId::new([2; 32]), - }, + helper_account_constructor(AccountsEnum::HoldingAccountInit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), ]; - let post_states = initialize_account(&pre_states); - let [definition, holding] = post_states.try_into().ok().unwrap(); - assert_eq!( - definition.account().data.as_ref(), - pre_states[0].account.data.as_ref() + let post_states = transfer( + &pre_states, + helper_balance_constructor(BalanceEnum::TransferAmount), ); - assert_eq!( - holding.account().data.as_ref(), - [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + ); + assert!( + *recipient_post.account() + == helper_account_constructor(AccountsEnum::HoldingAccount2UninitPostTransfer) + .account ); - } - - enum BalanceEnum { - InitSupply, - HoldingBalance, - InitSupplyBurned, - HoldingBalanceBurned, - BurnSuccess, - BurnInsufficient, - MintSuccess, - InitSupplyMint, - HoldingBalanceMint, - MintOverflow, - } - - enum AccountsEnum { - DefinitionAccountAuth, - DefinitionAccountNotAuth, - HoldingDiffDef, - HoldingSameDefAuth, - HoldingSameDefNotAuth, - HoldingSameDefNotAuthOverflow, - DefinitionAccountPostBurn, - HoldingAccountPostBurn, - Uninit, - InitMint, - DefinitionAccountMint, - HoldingSameDefMint, - HoldingSameDefAuthLargeBalance, - } - - enum IdEnum { - PoolDefinitionId, - PoolDefinitionIdDiff, - HoldingId, - } - - fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { - match selection { - AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingDiffDef => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::Uninit => AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::InitMint => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintSuccess), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintOverflow), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - _ => panic!("Invalid selection"), - } - } - - fn helper_balance_constructor(selection: BalanceEnum) -> u128 { - match selection { - BalanceEnum::InitSupply => 100_000, - BalanceEnum::HoldingBalance => 1_000, - BalanceEnum::InitSupplyBurned => 99_500, - BalanceEnum::HoldingBalanceBurned => 500, - BalanceEnum::BurnSuccess => 500, - BalanceEnum::BurnInsufficient => 1_500, - BalanceEnum::MintSuccess => 50_000, - BalanceEnum::InitSupplyMint => 150_000, - BalanceEnum::HoldingBalanceMint => 51_000, - BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, - _ => panic!("Invalid selection"), - } - } - - fn helper_id_constructor(selection: IdEnum) -> AccountId { - match selection { - IdEnum::PoolDefinitionId => AccountId::new([15; 32]), - IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), - IdEnum::HoldingId => AccountId::new([17; 32]), - } } #[test] @@ -1119,7 +1745,7 @@ mod tests { } #[test] - #[should_panic(expected = "Mismatch token definition and token holding")] + #[should_panic(expected = "Mismatch Token Definition and Token Holding")] fn test_burn_mismatch_def() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), @@ -1196,7 +1822,7 @@ mod tests { #[test] #[should_panic(expected = "Invalid number of accounts")] - fn test_mint_invalid_number_of_accounts() { + fn test_mint_invalid_number_of_accounts_1() { let pre_states = vec![helper_account_constructor( AccountsEnum::DefinitionAccountAuth, )]; @@ -1206,6 +1832,20 @@ mod tests { ); } + #[test] + #[should_panic(expected = "Invalid number of accounts")] + fn test_mint_invalid_number_of_accounts_2() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefMint), + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + #[test] #[should_panic(expected = "Holding account must be valid")] fn test_mint_not_valid_holding_account() { @@ -1219,6 +1859,19 @@ mod tests { ); } + #[test] + #[should_panic(expected = "Definition account must be valid")] + fn test_mint_not_valid_definition_account() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + #[test] #[should_panic(expected = "Definition authorization is missing")] fn test_mint_missing_authorization() { @@ -1233,7 +1886,7 @@ mod tests { } #[test] - #[should_panic(expected = "Mismatch token definition and token holding")] + #[should_panic(expected = "Mismatch Token Definition and Token Holding")] fn test_mint_mismatched_token_definition() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), @@ -1273,7 +1926,7 @@ mod tests { fn test_mint_uninit_holding_success() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::Uninit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), ]; let post_states = mint_additional_supply( &pre_states, @@ -1318,4 +1971,483 @@ mod tests { helper_balance_constructor(BalanceEnum::MintOverflow), ); } -} + + #[test] + #[should_panic( + expected = "Token Definition's standard does not permit minting additional supply" + )] + fn test_mint_cannot_mint_unmintable_tokens() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuthNonFungible), + helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + ]; + let _post_states = mint_additional_supply( + &pre_states, + helper_balance_constructor(BalanceEnum::MintSuccess), + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_1() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_2() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_3() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([4; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Definition target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_definition() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Metadata target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_metadata() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + helper_account_constructor(AccountsEnum::HoldingSameDefMint), + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Holding target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_holding() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + helper_account_constructor(AccountsEnum::HoldingSameDefMint), + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Metadata values data should be 450 bytes")] + #[test] + fn test_call_new_definition_metadata_with_too_short_metadata_length() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 449].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Metadata values data should be 450 bytes")] + #[test] + fn test_call_new_definition_metadata_with_too_long_metadata_length() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 451].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid Token Standard provided")] + #[test] + fn test_call_new_definition_metadata_with_invalid_token_standard() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 14u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid Metadata Standadard provided")] + #[test] + fn test_call_new_definition_metadata_with_invalid_metadata_standard() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 14u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid total supply for the specified token supply")] + #[test] + fn test_call_new_definition_metadata_invalid_supply_for_nonfungible() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = TOKEN_STANDARD_NONFUNGIBLE; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid number of accounts")] + #[test] + fn test_print_nft_invalid_number_of_accounts_1() { + let pre_states = vec![helper_account_constructor( + AccountsEnum::HoldingAccountMasterNFT, + )]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid number of accounts")] + #[test] + fn test_print_nft_invalid_number_of_accounts_2() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Master NFT Account must be authorized")] + #[test] + fn test_print_nft_master_account_must_be_authorized() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Printed Account must be uninitialized")] + #[test] + fn test_print_nft_print_account_initialized() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountInit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid Token Holding data")] + #[test] + fn test_print_nft_master_nft_invalid_token_holding() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid Token Holding provided as NFT Master Account")] + #[test] + fn test_print_nft_master_nft_not_nft_master_account() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountInit), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Insufficient balance to print another NFT copy")] + #[test] + fn test_print_nft_master_nft_insufficient_balance() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTInsufficientBalance), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let _post_states = print_nft(&pre_states); + } + + #[test] + fn test_print_nft_success() { + let pre_states = vec![ + helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), + helper_account_constructor(AccountsEnum::HoldingAccountUninit), + ]; + let post_states = print_nft(&pre_states); + + let post_master_nft = post_states[0].account(); + let post_printed = post_states[1].account(); + + assert!( + *post_master_nft + == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTAfterPrint) + .account + ); + assert!( + *post_printed + == helper_account_constructor(AccountsEnum::HoldingAccountPrintedNFT).account + ); + } +} \ No newline at end of file From de1bf770f0575f1292cc0cb89139ef85419e15cb Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:09:59 -0500 Subject: [PATCH 06/16] update comments --- nssa/program_methods/guest/src/bin/token.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 4b71128..ee777ec 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -37,12 +37,23 @@ use nssa_core::{ // * Authorization required: definition_account // * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout // [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 6. Print NFT copy from Master NFT +// 6. New token definition with metadata. // Arguments to this function are: -// * Two accounts: [master_nft, account_to_initialize]. +// * Three **default** accounts: [definition_account, metadata_account. holding_account]. +// The first default account will be initialized with the token definition account values. The second account +// will be initialized to a token metadata account for the new token definition. The third account will be +// initialized to a token holding account for the new token, holding the entire total supply. +// * An instruction data of 474-bytes, indicating the token name, total supply, token standard, metadata standard +// and metadata_values (uri and creators). +// the following layout: +// [0x05 || total_supply (little-endian 16 bytes) || name (6 bytes) || token_standard || metadata_standard || metadata_values] +// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +// 7. Print NFT copy from Master NFT +// Arguments to this function are: +// * Two accounts: [master_nft, printed_account (default)]. // * Authorization required: master_nft // * An dummy byte string of length 23, with the following layout -// [0x05 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. +// [0x06 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; const TOKEN_STANDARD_FUNGIBLE_ASSET: u8 = 1; const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; @@ -171,7 +182,6 @@ struct TokenHolding { balance: u128, } - impl TokenHolding { fn new(definition_id: &AccountId) -> Self { Self { From 9601e0053e3ffc8e0157c261d04dbcac6d49dbab Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:05:42 -0500 Subject: [PATCH 07/16] removed enums --- nssa/program_methods/guest/src/bin/token.rs | 812 ++++++++++---------- 1 file changed, 398 insertions(+), 414 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index ee777ec..3adf4fc 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -109,8 +109,6 @@ fn is_token_holding_type_valid(standard: u8) -> bool { } } - - struct TokenDefinition { account_type: u8, name: [u8; 6], @@ -306,7 +304,6 @@ impl TokenMetadata { } } - fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of input accounts"); @@ -903,243 +900,227 @@ mod tests { new_definition_with_metadata, print_nft, transfer, }; - enum BalanceEnum { - InitSupply, - HoldingBalance, - InitSupplyBurned, - HoldingBalanceBurned, - BurnSuccess, - BurnInsufficient, - MintSuccess, - InitSupplyMint, - HoldingBalanceMint, - MintOverflow, - RecipientPostTransfer, - RecipientUninitPostTransfer, - SenderPostTransfer, - TransferAmount, - PrintableCopies, - } + struct BalanceForTests; + struct IdForTests; - enum AccountsEnum { - DefinitionAccountAuth, - DefinitionAccountNotAuth, - HoldingDiffDef, - HoldingSameDefAuth, - HoldingSameDefNotAuth, - HoldingSameDefNotAuthOverflow, - DefinitionAccountPostBurn, - HoldingAccountPostBurn, - InitMint, - DefinitionAccountMint, - HoldingSameDefMint, - HoldingSameDefAuthLargeBalance, - DefinitionAccountAuthNonFungible, - DefinitionAccountUninit, - HoldingAccountUninit, - HoldingAccountInit, - DefinitionAccountUnclaimed, - HoldingAccountUnclaimed, - HoldingAccount2Init, - HoldingAccount2InitPostTransfer, - HoldingAccount2UninitPostTransfer, - HoldingAccountInitPostTransfer, - HoldingAccountMasterNFT, - HoldingAccountMasterNFTInsufficientBalance, - HoldingAccountMasterNFTAfterPrint, - HoldingAccountPrintedNFT, - HoldingAccountMasterNFTTransferredTo, - HoldingAccountMasterNFTPostTransfer, - } + struct AccountForTests; - enum IdEnum { - PoolDefinitionId, - PoolDefinitionIdDiff, - HoldingId, - Holding2Id, - MetadataId, - } - - fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { - match selection { - AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { + impl AccountForTests { + fn definition_account_auth() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + total_supply: BalanceForTests::init_supply(), metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + + fn definition_account_without_auth() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + total_supply: BalanceForTests::init_supply(), metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, is_authorized: false, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingDiffDef => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_different_definition() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + definition_id: IdForTests::pool_definition_id_diff(), + balance: BalanceForTests::holding_balance(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_same_definition_with_authorization() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_same_definition_without_authorization() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance(), }), nonce: 0, }, is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_same_definition_without_authorization_overflow() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), }), nonce: 0, }, is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn definition_account_post_burn() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), + total_supply: BalanceForTests::init_supply_burned(), metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_account_post_burn() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance_burned(), }), nonce: 0, }, is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountUninit => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_uninit() -> AccountWithMetadata { + AccountWithMetadata { account: Account::default(), is_authorized: false, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::InitMint => AccountWithMetadata { + account_id: IdForTests::holding_id_2(), + } + } + + fn init_mint() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [0u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintSuccess), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::mint_success(), }), nonce: 0, }, is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefMint => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_same_definition_mint() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance_mint(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountMint => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + fn definition_account_mint() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), + total_supply: BalanceForTests::init_supply_mint(), metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + fn holding_same_definition_with_authorization_and_large_balance() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintOverflow), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::mint_overflow(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountAuthNonFungible => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + fn definition_account_with_authorization_nonfungible() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, @@ -1152,230 +1133,319 @@ mod tests { nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountUninit => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + fn definition_account_uninit() -> AccountWithMetadata { + AccountWithMetadata { account: Account::default(), is_authorized: false, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountInit => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_account_init() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::DefinitionAccountUnclaimed => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + fn definition_account_unclaimed() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [0u32; 8], balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + total_supply: BalanceForTests::init_supply(), metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountUnclaimed => AccountWithMetadata { + account_id: IdForTests::pool_definition_id(), + } + } + fn holding_account_unclaimed() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [0u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccount2Init => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account2_init() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccount2InitPostTransfer => AccountWithMetadata { + account_id: IdForTests::holding_id_2(), + } + } + + fn holding_account2_init_post_transfer() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::RecipientPostTransfer), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::recipient_post_transfer(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccountInitPostTransfer => AccountWithMetadata { + account_id: IdForTests::holding_id_2(), + } + } + + fn holding_account_init_post_transfer() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::SenderPostTransfer), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::sender_post_transfer(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccount2UninitPostTransfer => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account2_uninit_post_transfer() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [0u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor( - BalanceEnum::RecipientUninitPostTransfer, - ), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::recipient_uninit_post_transfer(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccountMasterNFT => AccountWithMetadata { + account_id: IdForTests::holding_id_2(), + } + } + + fn holding_account_master_nft() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::PrintableCopies), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::printable_copies(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountMasterNFTInsufficientBalance => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_master_nft_insufficient_balance() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + definition_id: IdForTests::pool_definition_id(), balance: 1, }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountMasterNFTAfterPrint => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_master_nft_after_print() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::PrintableCopies) - 1, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::printable_copies() - 1, }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountPrintedNFT => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_printed_nft() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [0u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + definition_id: IdForTests::pool_definition_id(), balance: 1, }), nonce: 0, }, is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountMasterNFTTransferredTo => AccountWithMetadata { + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_with_master_nft_transferred_to() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [0u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::PrintableCopies), + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::printable_copies(), }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccountMasterNFTPostTransfer => AccountWithMetadata { + account_id: IdForTests::holding_id_2(), + } + } + + fn holding_account_master_nft_post_transfer() -> AccountWithMetadata { + AccountWithMetadata { account: Account { program_owner: [5u32; 8], balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + definition_id: IdForTests::pool_definition_id(), balance: 0, }), nonce: 0, }, is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - _ => panic!("Invalid selection"), + account_id: IdForTests::holding_id(), + } } } - fn helper_balance_constructor(selection: BalanceEnum) -> u128 { - match selection { - BalanceEnum::InitSupply => 100_000, - BalanceEnum::HoldingBalance => 1_000, - BalanceEnum::InitSupplyBurned => 99_500, - BalanceEnum::HoldingBalanceBurned => 500, - BalanceEnum::BurnSuccess => 500, - BalanceEnum::BurnInsufficient => 1_500, - BalanceEnum::MintSuccess => 50_000, - BalanceEnum::InitSupplyMint => 150_000, - BalanceEnum::HoldingBalanceMint => 51_000, - BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, - BalanceEnum::SenderPostTransfer => 95_000, - BalanceEnum::RecipientPostTransfer => 105_000, - BalanceEnum::RecipientUninitPostTransfer => 5_000, - BalanceEnum::TransferAmount => 5_000, - BalanceEnum::PrintableCopies => 10, - _ => panic!("Invalid selection"), + impl BalanceForTests { + fn init_supply() -> u128 { + 100_000 + } + + fn holding_balance() -> u128 { + 1_000 + } + + fn init_supply_burned() -> u128 { + 99_500 + } + + fn holding_balance_burned() -> u128 { + 500 + } + + fn burn_success() -> u128 { + 500 + } + + fn burn_insufficient() -> u128 { + 1_500 + } + + fn mint_success() -> u128 { + 50_000 + } + + fn holding_balance_mint() -> u128 { + 51_000 + } + + fn mint_overflow() -> u128 { + (2 as u128).pow(128) - 40_000 + } + + fn init_supply_mint() -> u128 { + 150_000 + } + + fn sender_post_transfer() -> u128 { + 95_000 + } + + fn recipient_post_transfer() -> u128 { + 105_000 + } + + fn recipient_uninit_post_transfer() -> u128 { + 5_000 + } + + fn transfer_amount() -> u128 { + 5_000 + } + + fn printable_copies() -> u128 { + 10 } } - fn helper_id_constructor(selection: IdEnum) -> AccountId { - match selection { - IdEnum::PoolDefinitionId => AccountId::new([15; 32]), - IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), - IdEnum::HoldingId => AccountId::new([17; 32]), - IdEnum::MetadataId => AccountId::new([42; 32]), - IdEnum::Holding2Id => AccountId::new([31; 32]), + impl IdForTests { + fn pool_definition_id() -> AccountId { + AccountId::new([15; 32]) + } + + fn pool_definition_id_diff() -> AccountId { + AccountId::new([16; 32]) + } + + fn holding_id() -> AccountId { + AccountId::new([17; 32]) + } + + fn holding_id_2() -> AccountId { + AccountId::new([42; 32]) + } + + fn metadata_id() -> AccountId { + AccountId::new([31; 32]) } } @@ -1458,25 +1528,21 @@ mod tests { #[test] fn test_new_definition_with_valid_inputs_succeeds() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountUninit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::definition_account_uninit(), + AccountForTests::holding_account_uninit(), ]; - let post_states = new_definition( - &pre_states, - [2u8; 6], - helper_balance_constructor(BalanceEnum::InitSupply), - ); + let post_states = new_definition(&pre_states, [2u8; 6], BalanceForTests::init_supply()); let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); assert!( *definition_account.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountUnclaimed).account + == AccountForTests::definition_account_unclaimed().account ); assert!( *holding_account.account() - == helper_account_constructor(AccountsEnum::HoldingAccountUnclaimed).account + == AccountForTests::holding_account_unclaimed().account ); } @@ -1586,8 +1652,8 @@ mod tests { #[test] fn test_transfer_with_different_definition_ids_should_fail() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - helper_account_constructor(AccountsEnum::HoldingDiffDef), + AccountForTests::holding_same_definition_with_authorization(), + AccountForTests::holding_different_definition(), ]; let _post_states = transfer(&pre_states, 10); } @@ -1596,14 +1662,11 @@ mod tests { #[test] fn test_transfer_with_insufficient_balance_should_fail() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefMint), + AccountForTests::holding_same_definition_with_authorization(), + AccountForTests::holding_account_same_definition_mint(), ]; // Attempt to transfer 38 tokens - let _post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnInsufficient), - ); + let _post_states = transfer(&pre_states, BalanceForTests::burn_insufficient()); } #[should_panic(expected = "Sender authorization is missing")] @@ -1638,22 +1701,19 @@ mod tests { #[test] fn test_transfer_with_valid_inputs_succeeds() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountInit), - helper_account_constructor(AccountsEnum::HoldingAccount2Init), + AccountForTests::holding_account_init(), + AccountForTests::holding_account2_init(), ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::TransferAmount), - ); + let post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert!( *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + == AccountForTests::holding_account_init_post_transfer().account ); assert!( *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) + == AccountForTests::holding_account2_init_post_transfer() .account ); } @@ -1662,59 +1722,39 @@ mod tests { #[test] fn test_transfer_with_master_nft_invalid_balance() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_uninit(), ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::TransferAmount), - ); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - - assert!( - *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account - ); - assert!( - *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) - .account - ); + let post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); } #[should_panic(expected = "Invalid balance in recipient account for NFT transfer")] #[test] fn test_transfer_with_master_nft_invalid_recipient_balance() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo), + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_with_master_nft_transferred_to(), ]; - let _post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::PrintableCopies), - ); + let _post_states = transfer(&pre_states, BalanceForTests::printable_copies()); } #[test] fn test_transfer_with_master_nft_success() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_uninit(), ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::PrintableCopies), - ); + let post_states = transfer(&pre_states, BalanceForTests::printable_copies()); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert!( *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTPostTransfer) + == AccountForTests::holding_account_master_nft_post_transfer() .account ); assert!( *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo) + == AccountForTests::holding_account_with_master_nft_transferred_to() .account ); } @@ -1722,22 +1762,20 @@ mod tests { #[test] fn test_token_initialize_account_succeeds() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountInit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_init(), + AccountForTests::holding_account2_init(), ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::TransferAmount), - ); + let post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert!( *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + == AccountForTests::holding_account_init_post_transfer() + .account ); assert!( *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccount2UninitPostTransfer) + == AccountForTests::holding_account2_init_post_transfer() .account ); } @@ -1745,213 +1783,169 @@ mod tests { #[test] #[should_panic(expected = "Invalid number of accounts")] fn test_burn_invalid_number_of_accounts() { - let pre_states = vec![helper_account_constructor( - AccountsEnum::DefinitionAccountAuth, - )]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); + let pre_states = vec![ + AccountForTests::definition_account_auth(), + ]; + let _post_states = burn(&pre_states, BalanceForTests::burn_success()); } #[test] #[should_panic(expected = "Mismatch Token Definition and Token Holding")] fn test_burn_mismatch_def() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingDiffDef), + AccountForTests::definition_account_auth(), + AccountForTests::holding_different_definition(), ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); + let _post_states = burn(&pre_states, BalanceForTests::burn_success()); } #[test] #[should_panic(expected = "Authorization is missing")] fn test_burn_missing_authorization() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization(), ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); + let _post_states = burn(&pre_states, BalanceForTests::burn_success()); } #[test] #[should_panic(expected = "Insufficient balance to burn")] fn test_burn_insufficient_balance() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_with_authorization(), ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnInsufficient), - ); + let _post_states = burn(&pre_states, BalanceForTests::burn_insufficient()); } #[test] #[should_panic(expected = "Total supply underflow")] fn test_burn_total_supply_underflow() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefAuthLargeBalance), + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_with_authorization_and_large_balance(), ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::MintOverflow), - ); + let _post_states = burn(&pre_states, BalanceForTests::mint_overflow()); } #[test] fn test_burn_success() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_with_authorization(), ]; - let post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); + let post_states = burn(&pre_states, BalanceForTests::burn_success()); let def_post = post_states[0].clone(); let holding_post = post_states[1].clone(); assert!( *def_post.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountPostBurn).account + == AccountForTests::definition_account_post_burn().account ); assert!( *holding_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountPostBurn).account + == AccountForTests::holding_account_post_burn().account ); } #[test] #[should_panic(expected = "Invalid number of accounts")] fn test_mint_invalid_number_of_accounts_1() { - let pre_states = vec![helper_account_constructor( - AccountsEnum::DefinitionAccountAuth, - )]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let pre_states = vec![AccountForTests::definition_account_auth()]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); } #[test] #[should_panic(expected = "Invalid number of accounts")] fn test_mint_invalid_number_of_accounts_2() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefMint), - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_same_definition_mint(), + AccountForTests::holding_same_definition_with_authorization(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); } #[test] #[should_panic(expected = "Holding account must be valid")] fn test_mint_not_valid_holding_account() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::DefinitionAccountNotAuth), + AccountForTests::definition_account_auth(), + AccountForTests::definition_account_without_auth(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); } #[test] #[should_panic(expected = "Definition account must be valid")] fn test_mint_not_valid_definition_account() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + AccountForTests::holding_same_definition_with_authorization(), + AccountForTests::holding_same_definition_without_authorization(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); } #[test] #[should_panic(expected = "Definition authorization is missing")] fn test_mint_missing_authorization() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountNotAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + AccountForTests::definition_account_without_auth(), + AccountForTests::holding_same_definition_without_authorization(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); } #[test] #[should_panic(expected = "Mismatch Token Definition and Token Holding")] fn test_mint_mismatched_token_definition() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingDiffDef), + AccountForTests::definition_account_auth(), + AccountForTests::holding_different_definition(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); } #[test] fn test_mint_success() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization(), ]; - let post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); let def_post = post_states[0].clone(); let holding_post = post_states[1].clone(); assert!( *def_post.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountMint).account + == AccountForTests::definition_account_mint().account ); assert!( *holding_post.account() - == helper_account_constructor(AccountsEnum::HoldingSameDefMint).account + == AccountForTests::holding_account_same_definition_mint().account ); } #[test] fn test_mint_uninit_holding_success() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_uninit(), ]; - let post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); let def_post = post_states[0].clone(); let holding_post = post_states[1].clone(); assert!( *def_post.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountMint).account + == AccountForTests::definition_account_mint().account ); assert!( - *holding_post.account() == helper_account_constructor(AccountsEnum::InitMint).account + *holding_post.account() == AccountForTests::init_mint().account ); assert!(holding_post.requires_claim() == true); } @@ -1960,26 +1954,20 @@ mod tests { #[should_panic(expected = "Total supply overflow")] fn test_mint_total_supply_overflow() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintOverflow), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_overflow()); } #[test] #[should_panic(expected = "New balance overflow")] fn test_mint_holding_account_overflow() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuthOverflow), + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization_overflow(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintOverflow), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_overflow()); } #[test] @@ -1988,13 +1976,10 @@ mod tests { )] fn test_mint_cannot_mint_unmintable_tokens() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuthNonFungible), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), + AccountForTests::definition_account_with_authorization_nonfungible(), + AccountForTests::holding_same_definition_without_authorization(), ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); } #[should_panic(expected = "Invalid number of input accounts")] @@ -2103,7 +2088,7 @@ mod tests { let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), + AccountForTests::definition_account_auth(), AccountWithMetadata { account: Account::default(), is_authorized: true, @@ -2140,7 +2125,7 @@ mod tests { is_authorized: true, account_id: AccountId::new([1; 32]), }, - helper_account_constructor(AccountsEnum::HoldingSameDefMint), + AccountForTests::holding_account_same_definition_mint(), AccountWithMetadata { account: Account::default(), is_authorized: true, @@ -2177,7 +2162,7 @@ mod tests { is_authorized: true, account_id: AccountId::new([2; 32]), }, - helper_account_constructor(AccountsEnum::HoldingSameDefMint), + AccountForTests::holding_account_same_definition_mint(), ]; let _post_states = new_definition_with_metadata( &pre_states, @@ -2372,9 +2357,9 @@ mod tests { #[should_panic(expected = "Invalid number of accounts")] #[test] fn test_print_nft_invalid_number_of_accounts_1() { - let pre_states = vec![helper_account_constructor( - AccountsEnum::HoldingAccountMasterNFT, - )]; + let pre_states = vec![ + AccountForTests::holding_account_master_nft() + ]; let _post_states = print_nft(&pre_states); } @@ -2382,9 +2367,9 @@ mod tests { #[test] fn test_print_nft_invalid_number_of_accounts_2() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_master_nft(), + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_uninit(), ]; let _post_states = print_nft(&pre_states); } @@ -2393,8 +2378,8 @@ mod tests { #[test] fn test_print_nft_master_account_must_be_authorized() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_uninit(), + AccountForTests::holding_account_uninit(), ]; let _post_states = print_nft(&pre_states); } @@ -2403,8 +2388,8 @@ mod tests { #[test] fn test_print_nft_print_account_initialized() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountInit), + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_init(), ]; let _post_states = print_nft(&pre_states); } @@ -2413,8 +2398,8 @@ mod tests { #[test] fn test_print_nft_master_nft_invalid_token_holding() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_uninit(), ]; let _post_states = print_nft(&pre_states); } @@ -2423,8 +2408,8 @@ mod tests { #[test] fn test_print_nft_master_nft_not_nft_master_account() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountInit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_init(), + AccountForTests::holding_account_uninit(), ]; let _post_states = print_nft(&pre_states); } @@ -2433,8 +2418,8 @@ mod tests { #[test] fn test_print_nft_master_nft_insufficient_balance() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTInsufficientBalance), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_master_nft_insufficient_balance(), + AccountForTests::holding_account_uninit(), ]; let _post_states = print_nft(&pre_states); } @@ -2442,8 +2427,8 @@ mod tests { #[test] fn test_print_nft_success() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_uninit(), ]; let post_states = print_nft(&pre_states); @@ -2452,12 +2437,11 @@ mod tests { assert!( *post_master_nft - == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTAfterPrint) - .account + == AccountForTests::holding_account_master_nft_after_print().account ); assert!( *post_printed - == helper_account_constructor(AccountsEnum::HoldingAccountPrintedNFT).account + == AccountForTests::holding_account_printed_nft().account ); } -} \ No newline at end of file +} From fe3c11fd3e1185df9c355a304f1a5be9a927422c Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:24:59 -0500 Subject: [PATCH 08/16] Update nssa/program_methods/guest/src/bin/token.rs Co-authored-by: Daniil Polyakov --- nssa/program_methods/guest/src/bin/token.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 3adf4fc..5b1bc01 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -74,17 +74,13 @@ const CURRENT_VERSION: u8 = 1; const TOKEN_METADATA_DATA_SIZE: usize = 463; fn is_token_standard_valid(standard: u8) -> bool { - if standard == TOKEN_STANDARD_FUNGIBLE_TOKEN { - true - } else if standard == TOKEN_STANDARD_FUNGIBLE_ASSET { - true - } else if standard == TOKEN_STANDARD_NONFUNGIBLE { - true - } else if standard == TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE { - true - } else { - false - } + matches!( + standard, + TOKEN_STANDARD_FUNGIBLE_TOKEN + | TOKEN_STANDARD_FUNGIBLE_ASSET + | TOKEN_STANDARD_NONFUNGIBLE + | TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE + ) } fn is_metadata_type_valid(standard: u8) -> bool { From c49a3afec7edee82a381c397284b16c719feaae8 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:25:21 -0500 Subject: [PATCH 09/16] Update nssa/program_methods/guest/src/bin/token.rs Co-authored-by: Daniil Polyakov --- nssa/program_methods/guest/src/bin/token.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 5b1bc01..f7ee9cc 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -124,7 +124,7 @@ impl TokenDefinition { panic!("Invalid Token Definition data"); } - Data::try_from(bytes).expect("Invalid data") + Data::try_from(bytes).expect("Token definition data size must fit into data") } fn parse(data: &Data) -> Option { From ca7cc10f7cc5b200f45b414c7b3a1275094da354 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:26:19 -0500 Subject: [PATCH 10/16] Update nssa/program_methods/guest/src/bin/token.rs Co-authored-by: Daniil Polyakov --- nssa/program_methods/guest/src/bin/token.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index f7ee9cc..714c44d 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -153,18 +153,10 @@ impl TokenDefinition { metadata_id: metadata_id.clone(), }); - if account_type == //NFTs must have supply 1 - TOKEN_STANDARD_NONFUNGIBLE - && total_supply != 1 - { - None - } else if account_type == //Fungible Tokens do not have metadata. - TOKEN_STANDARD_FUNGIBLE_TOKEN - && metadata_id != AccountId::new([0; 32]) - { - None - } else { - this + match account_type { + TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 + | TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None + _ => this } } } From 5baa7592833fcac5b3a1afb989161deb02b2339d Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:26:33 -0500 Subject: [PATCH 11/16] Update nssa/program_methods/guest/src/bin/token.rs Co-authored-by: Daniil Polyakov --- nssa/program_methods/guest/src/bin/token.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 714c44d..c1066c5 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -232,7 +232,8 @@ struct TokenMetadata { definition_id: AccountId, uri: [u8; 200], creators: [u8; 250], - primary_sale_date: u64, //BlockId + /// Block id + primary_sale_date: u64, } impl TokenMetadata { From 072cf5cd0f8ed3a822282c829e32792020437f0d Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Mon, 22 Dec 2025 19:51:41 -0500 Subject: [PATCH 12/16] various fixes from comments --- nssa/program_methods/guest/src/bin/token.rs | 107 +++++--------------- 1 file changed, 27 insertions(+), 80 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index c1066c5..45c1000 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -77,32 +77,19 @@ fn is_token_standard_valid(standard: u8) -> bool { matches!( standard, TOKEN_STANDARD_FUNGIBLE_TOKEN - | TOKEN_STANDARD_FUNGIBLE_ASSET - | TOKEN_STANDARD_NONFUNGIBLE - | TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE + | TOKEN_STANDARD_FUNGIBLE_ASSET + | TOKEN_STANDARD_NONFUNGIBLE + | TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE ) } fn is_metadata_type_valid(standard: u8) -> bool { - if standard == METADATA_TYPE_SIMPLE { - true - } else if standard == METADATA_TYPE_EXPANDED { - true - } else { - false - } + matches!(standard, METADATA_TYPE_SIMPLE | METADATA_TYPE_EXPANDED) } fn is_token_holding_type_valid(standard: u8) -> bool { - if standard == TOKEN_HOLDING_STANDARD { - true - } else if standard == TOKEN_HOLDING_NFT_MASTER { - true - } else if standard == TOKEN_HOLDING_NFT_PRINTED_COPY { - true - } else { - false - } + matches!(standard, |TOKEN_HOLDING_STANDARD| TOKEN_HOLDING_NFT_MASTER + | TOKEN_HOLDING_NFT_PRINTED_COPY) } struct TokenDefinition { @@ -154,9 +141,9 @@ impl TokenDefinition { }); match account_type { - TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 - | TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None - _ => this + TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 => None, + TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None, + _ => this, } } } @@ -528,11 +515,7 @@ fn new_definition_with_metadata( } fn valid_total_supply_for_token_standard(total_supply: u128, token_standard: u8) -> bool { - if token_standard == TOKEN_STANDARD_NONFUNGIBLE && total_supply != 1 { - false - } else { - true - } + token_standard != TOKEN_STANDARD_NONFUNGIBLE || total_supply == 1 } fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { @@ -619,11 +602,7 @@ fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec bool { - if account_type == TOKEN_STANDARD_NONFUNGIBLE { - false - } else { - true - } + account_type != TOKEN_STANDARD_NONFUNGIBLE } fn mint_additional_supply( @@ -1529,10 +1508,7 @@ mod tests { == AccountForTests::definition_account_unclaimed().account ); - assert!( - *holding_account.account() - == AccountForTests::holding_account_unclaimed().account - ); + assert!(*holding_account.account() == AccountForTests::holding_account_unclaimed().account); } #[should_panic(expected = "Invalid number of input accounts")] @@ -1697,13 +1673,11 @@ mod tests { let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert!( - *sender_post.account() - == AccountForTests::holding_account_init_post_transfer().account + *sender_post.account() == AccountForTests::holding_account_init_post_transfer().account ); assert!( *recipient_post.account() - == AccountForTests::holding_account2_init_post_transfer() - .account + == AccountForTests::holding_account2_init_post_transfer().account ); } @@ -1738,13 +1712,11 @@ mod tests { assert!( *sender_post.account() - == AccountForTests::holding_account_master_nft_post_transfer() - .account + == AccountForTests::holding_account_master_nft_post_transfer().account ); assert!( *recipient_post.account() - == AccountForTests::holding_account_with_master_nft_transferred_to() - .account + == AccountForTests::holding_account_with_master_nft_transferred_to().account ); } @@ -1758,23 +1730,18 @@ mod tests { let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert!( - *sender_post.account() - == AccountForTests::holding_account_init_post_transfer() - .account + *sender_post.account() == AccountForTests::holding_account_init_post_transfer().account ); assert!( *recipient_post.account() - == AccountForTests::holding_account2_init_post_transfer() - .account + == AccountForTests::holding_account2_init_post_transfer().account ); } #[test] #[should_panic(expected = "Invalid number of accounts")] fn test_burn_invalid_number_of_accounts() { - let pre_states = vec![ - AccountForTests::definition_account_auth(), - ]; + let pre_states = vec![AccountForTests::definition_account_auth()]; let _post_states = burn(&pre_states, BalanceForTests::burn_success()); } @@ -1829,14 +1796,8 @@ mod tests { let def_post = post_states[0].clone(); let holding_post = post_states[1].clone(); - assert!( - *def_post.account() - == AccountForTests::definition_account_post_burn().account - ); - assert!( - *holding_post.account() - == AccountForTests::holding_account_post_burn().account - ); + assert!(*def_post.account() == AccountForTests::definition_account_post_burn().account); + assert!(*holding_post.account() == AccountForTests::holding_account_post_burn().account); } #[test] @@ -1908,10 +1869,7 @@ mod tests { let def_post = post_states[0].clone(); let holding_post = post_states[1].clone(); - assert!( - *def_post.account() - == AccountForTests::definition_account_mint().account - ); + assert!(*def_post.account() == AccountForTests::definition_account_mint().account); assert!( *holding_post.account() == AccountForTests::holding_account_same_definition_mint().account @@ -1929,13 +1887,8 @@ mod tests { let def_post = post_states[0].clone(); let holding_post = post_states[1].clone(); - assert!( - *def_post.account() - == AccountForTests::definition_account_mint().account - ); - assert!( - *holding_post.account() == AccountForTests::init_mint().account - ); + assert!(*def_post.account() == AccountForTests::definition_account_mint().account); + assert!(*holding_post.account() == AccountForTests::init_mint().account); assert!(holding_post.requires_claim() == true); } @@ -2346,9 +2299,7 @@ mod tests { #[should_panic(expected = "Invalid number of accounts")] #[test] fn test_print_nft_invalid_number_of_accounts_1() { - let pre_states = vec![ - AccountForTests::holding_account_master_nft() - ]; + let pre_states = vec![AccountForTests::holding_account_master_nft()]; let _post_states = print_nft(&pre_states); } @@ -2425,12 +2376,8 @@ mod tests { let post_printed = post_states[1].account(); assert!( - *post_master_nft - == AccountForTests::holding_account_master_nft_after_print().account - ); - assert!( - *post_printed - == AccountForTests::holding_account_printed_nft().account + *post_master_nft == AccountForTests::holding_account_master_nft_after_print().account ); + assert!(*post_printed == AccountForTests::holding_account_printed_nft().account); } } From fcdd6e96a51a31b41e45fead977584df53d28702 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Tue, 23 Dec 2025 17:26:38 -0500 Subject: [PATCH 13/16] clippy and fmt --- program_methods/guest/src/bin/token.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/program_methods/guest/src/bin/token.rs b/program_methods/guest/src/bin/token.rs index 45c1000..aac0363 100644 --- a/program_methods/guest/src/bin/token.rs +++ b/program_methods/guest/src/bin/token.rs @@ -137,7 +137,7 @@ impl TokenDefinition { account_type, name, total_supply, - metadata_id: metadata_id.clone(), + metadata_id, }); match account_type { @@ -159,7 +159,7 @@ impl TokenHolding { fn new(definition_id: &AccountId) -> Self { Self { account_type: TOKEN_HOLDING_STANDARD, - definition_id: definition_id.clone(), + definition_id: *definition_id, balance: 0, } } @@ -409,7 +409,7 @@ fn new_definition( let token_holding = TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: definition_target_account.account_id.clone(), + definition_id: definition_target_account.account_id, balance: total_supply, }; @@ -469,12 +469,12 @@ fn new_definition_with_metadata( account_type: token_standard, name, total_supply, - metadata_id: metadata_target_account.account_id.clone(), + metadata_id: metadata_target_account.account_id, }; let token_holding = TokenHolding { account_type: TOKEN_HOLDING_STANDARD, - definition_id: definition_target_account.account_id.clone(), + definition_id: definition_target_account.account_id, balance: total_supply, }; @@ -492,7 +492,7 @@ fn new_definition_with_metadata( let token_metadata = TokenMetadata { account_type: metadata_standard, version: CURRENT_VERSION, - definition_id: definition_target_account.account_id.clone(), + definition_id: definition_target_account.account_id, uri, creators, primary_sale_date: 0u64, //TODO: future works to implement this @@ -705,7 +705,7 @@ fn print_nft(pre_states: &[AccountWithMetadata]) -> Vec { panic!("Insufficient balance to print another NFT copy"); } - let definition_id = master_account_data.definition_id.clone(); + let definition_id = master_account_data.definition_id; let post_master_account = { let mut this = master_account.account.clone(); @@ -862,9 +862,9 @@ mod tests { use crate::{ TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_NFT_MASTER, - TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, TOKEN_STANDARD_FUNGIBLE_ASSET, + TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, TOKEN_STANDARD_FUNGIBLE_TOKEN, TOKEN_STANDARD_NONFUNGIBLE, TokenDefinition, TokenHolding, - burn, initialize_account, mint_additional_supply, new_definition, + burn, mint_additional_supply, new_definition, new_definition_with_metadata, print_nft, transfer, }; @@ -1367,7 +1367,7 @@ mod tests { } fn mint_overflow() -> u128 { - (2 as u128).pow(128) - 40_000 + 2_u128.pow(128) - 40_000 } fn init_supply_mint() -> u128 { @@ -1889,7 +1889,7 @@ mod tests { assert!(*def_post.account() == AccountForTests::definition_account_mint().account); assert!(*holding_post.account() == AccountForTests::init_mint().account); - assert!(holding_post.requires_claim() == true); + assert!(holding_post.requires_claim()); } #[test] From 5d30de695fce050ceb7934747a7949b61274628d Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 25 Dec 2025 16:59:49 +0300 Subject: [PATCH 14/16] fix: use `u128::MAX` instead of overflowing `2.pow(128)` --- program_methods/guest/src/bin/token.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_methods/guest/src/bin/token.rs b/program_methods/guest/src/bin/token.rs index aac0363..f561b82 100644 --- a/program_methods/guest/src/bin/token.rs +++ b/program_methods/guest/src/bin/token.rs @@ -1367,7 +1367,7 @@ mod tests { } fn mint_overflow() -> u128 { - 2_u128.pow(128) - 40_000 + u128::MAX - 40_000 } fn init_supply_mint() -> u128 { From de751952af27e3d685a47d62c82222a3ac1c1ebc Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 25 Dec 2025 17:05:06 +0300 Subject: [PATCH 15/16] fix: fmt and lints --- program_methods/guest/src/bin/token.rs | 151 ++++++++----------------- 1 file changed, 47 insertions(+), 104 deletions(-) diff --git a/program_methods/guest/src/bin/token.rs b/program_methods/guest/src/bin/token.rs index f561b82..30dcac4 100644 --- a/program_methods/guest/src/bin/token.rs +++ b/program_methods/guest/src/bin/token.rs @@ -6,54 +6,52 @@ use nssa_core::{ }; // The token program has three functions: -// 1. New token definition. -// Arguments to this function are: -// * Two **default** accounts: [definition_account, holding_account]. -// The first default account will be initialized with the token definition account values. The second account will -// be initialized to a token holding account for the new token, holding the entire total supply. -// * An instruction data of 23-bytes, indicating the total supply and the token name, with -// the following layout: -// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] -// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] -// 2. Token transfer -// Arguments to this function are: +// 1. New token definition. Arguments to this function are: +// * Two **default** accounts: [definition_account, holding_account]. The first default account +// will be initialized with the token definition account values. The second account will be +// initialized to a token holding account for the new token, holding the entire total supply. +// * An instruction data of 23-bytes, indicating the total supply and the token name, with the +// following layout: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] The +// name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +// 2. Token transfer Arguments to this function are: // * Two accounts: [sender_account, recipient_account]. -// * An instruction data byte string of length 23, indicating the total supply with the following layout -// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 3. Initialize account with zero balance -// Arguments to this function are: +// * An instruction data byte string of length 23, indicating the total supply with the +// following layout [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 +// || 0x00 || 0x00]. +// 3. Initialize account with zero balance Arguments to this function are: // * Two accounts: [definition_account, account_to_initialize]. -// * An dummy byte string of length 23, with the following layout -// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. -// 4. Burn tokens from a Token Holding account (thus lowering total supply) -// Arguments to this function are: +// * An dummy byte string of length 23, with the following layout [0x02 || 0x00 || 0x00 || 0x00 +// || ... || 0x00 || 0x00]. +// 4. Burn tokens from a Token Holding account (thus lowering total supply) Arguments to this +// function are: // * Two accounts: [definition_account, holding_account]. // * Authorization required: holding_account -// * An instruction data byte string of length 23, indicating the balance to burn with the folloiwng layout +// * An instruction data byte string of length 23, indicating the balance to burn with the +// folloiwng layout // [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 5. Mint additional supply of tokens tokens to a Token Holding account (thus increasing total supply) -// Arguments to this function are: +// 5. Mint additional supply of tokens tokens to a Token Holding account (thus increasing total +// supply) Arguments to this function are: // * Two accounts: [definition_account, holding_account]. // * Authorization required: definition_account -// * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout +// * An instruction data byte string of length 23, indicating the balance to mint with the +// folloiwng layout // [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 6. New token definition with metadata. -// Arguments to this function are: -// * Three **default** accounts: [definition_account, metadata_account. holding_account]. -// The first default account will be initialized with the token definition account values. The second account -// will be initialized to a token metadata account for the new token definition. The third account will be -// initialized to a token holding account for the new token, holding the entire total supply. -// * An instruction data of 474-bytes, indicating the token name, total supply, token standard, metadata standard -// and metadata_values (uri and creators). -// the following layout: -// [0x05 || total_supply (little-endian 16 bytes) || name (6 bytes) || token_standard || metadata_standard || metadata_values] -// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] -// 7. Print NFT copy from Master NFT -// Arguments to this function are: +// 6. New token definition with metadata. Arguments to this function are: +// * Three **default** accounts: [definition_account, metadata_account. holding_account]. The +// first default account will be initialized with the token definition account values. The +// second account will be initialized to a token metadata account for the new token +// definition. The third account will be initialized to a token holding account for the new +// token, holding the entire total supply. +// * An instruction data of 474-bytes, indicating the token name, total supply, token standard, +// metadata standard and metadata_values (uri and creators). the following layout: [0x05 || +// total_supply (little-endian 16 bytes) || name (6 bytes) || token_standard || +// metadata_standard || metadata_values] The name cannot be equal to [0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00] +// 7. Print NFT copy from Master NFT Arguments to this function are: // * Two accounts: [master_nft, printed_account (default)]. // * Authorization required: master_nft -// * An dummy byte string of length 23, with the following layout -// [0x06 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. +// * An dummy byte string of length 23, with the following layout [0x06 || 0x00 || 0x00 || 0x00 +// || ... || 0x00 || 0x00]. const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; const TOKEN_STANDARD_FUNGIBLE_ASSET: u8 = 1; const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; @@ -243,41 +241,6 @@ impl TokenMetadata { Data::try_from(bytes).expect("Invalid data") } - - fn parse(data: &Data) -> Option { - let data = Vec::::from(data.clone()); - - if data.len() != TOKEN_METADATA_DATA_SIZE || !is_metadata_type_valid(data[0]) { - None - } else { - let account_type = data[0]; - let version = data[1]; - let definition_id = AccountId::new( - data[2..34] - .try_into() - .expect("Token Program expects valid Account Id for Metadata"), - ); - let uri: [u8; 200] = data[34..234] - .try_into() - .expect("Token Program expects valid uri for Metadata"); - let creators: [u8; 250] = data[234..484] - .try_into() - .expect("Token Program expects valid creators for Metadata"); - let primary_sale_date = u64::from_le_bytes( - data[484..TOKEN_METADATA_DATA_SIZE] - .try_into() - .expect("Token Program expects valid blockid for Metadata"), - ); - Some(Self { - account_type, - version, - definition_id, - uri, - creators, - primary_sale_date, - }) - } - } } fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { @@ -495,7 +458,7 @@ fn new_definition_with_metadata( definition_id: definition_target_account.account_id, uri, creators, - primary_sale_date: 0u64, //TODO: future works to implement this + primary_sale_date: 0u64, // TODO: future works to implement this }; let mut definition_target_account_post = definition_target_account.account.clone(); @@ -862,10 +825,9 @@ mod tests { use crate::{ TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_NFT_MASTER, - TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, - TOKEN_STANDARD_FUNGIBLE_TOKEN, TOKEN_STANDARD_NONFUNGIBLE, TokenDefinition, TokenHolding, - burn, mint_additional_supply, new_definition, - new_definition_with_metadata, print_nft, transfer, + TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, TOKEN_STANDARD_FUNGIBLE_TOKEN, + TOKEN_STANDARD_NONFUNGIBLE, TokenDefinition, TokenHolding, burn, mint_additional_supply, + new_definition, new_definition_with_metadata, print_nft, transfer, }; struct BalanceForTests; @@ -1054,6 +1016,7 @@ mod tests { account_id: IdForTests::pool_definition_id(), } } + fn definition_account_mint() -> AccountWithMetadata { AccountWithMetadata { account: Account { @@ -1071,6 +1034,7 @@ mod tests { account_id: IdForTests::pool_definition_id(), } } + fn holding_same_definition_with_authorization_and_large_balance() -> AccountWithMetadata { AccountWithMetadata { account: Account { @@ -1087,6 +1051,7 @@ mod tests { account_id: IdForTests::pool_definition_id(), } } + fn definition_account_with_authorization_nonfungible() -> AccountWithMetadata { AccountWithMetadata { account: Account { @@ -1104,6 +1069,7 @@ mod tests { account_id: IdForTests::pool_definition_id(), } } + fn definition_account_uninit() -> AccountWithMetadata { AccountWithMetadata { account: Account::default(), @@ -1128,6 +1094,7 @@ mod tests { account_id: IdForTests::holding_id(), } } + fn definition_account_unclaimed() -> AccountWithMetadata { AccountWithMetadata { account: Account { @@ -1145,6 +1112,7 @@ mod tests { account_id: IdForTests::pool_definition_id(), } } + fn holding_account_unclaimed() -> AccountWithMetadata { AccountWithMetadata { account: Account { @@ -1213,23 +1181,6 @@ mod tests { } } - fn holding_account2_uninit_post_transfer() -> AccountWithMetadata { - AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: IdForTests::pool_definition_id(), - balance: BalanceForTests::recipient_uninit_post_transfer(), - }), - nonce: 0, - }, - is_authorized: true, - account_id: IdForTests::holding_id_2(), - } - } - fn holding_account_master_nft() -> AccountWithMetadata { AccountWithMetadata { account: Account { @@ -1382,10 +1333,6 @@ mod tests { 105_000 } - fn recipient_uninit_post_transfer() -> u128 { - 5_000 - } - fn transfer_amount() -> u128 { 5_000 } @@ -1411,10 +1358,6 @@ mod tests { fn holding_id_2() -> AccountId { AccountId::new([42; 32]) } - - fn metadata_id() -> AccountId { - AccountId::new([31; 32]) - } } #[should_panic(expected = "Invalid number of input accounts")] @@ -1688,7 +1631,7 @@ mod tests { AccountForTests::holding_account_master_nft(), AccountForTests::holding_account_uninit(), ]; - let post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); + let _post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); } #[should_panic(expected = "Invalid balance in recipient account for NFT transfer")] From 9d37a8806973a34a406e04a3c1835b175b652dd3 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 25 Dec 2025 20:46:37 +0300 Subject: [PATCH 16/16] fix: proper type for token program instruction --- Cargo.lock | 1 + artifacts/program_methods/amm.bin | Bin 464896 -> 460028 bytes artifacts/program_methods/pinata_token.bin | Bin 388880 -> 390164 bytes artifacts/program_methods/token.bin | Bin 403728 -> 418484 bytes integration_tests/src/test_suite_map.rs | 50 ++++++++++------ nssa/Cargo.toml | 1 + nssa/src/state.rs | 34 +++++++---- program_methods/guest/src/bin/amm.rs | 54 ++++++++++-------- program_methods/guest/src/bin/pinata_token.rs | 2 +- program_methods/guest/src/bin/token.rs | 2 +- wallet/src/cli/account.rs | 43 +++++++++++--- wallet/src/program_facades/token.rs | 8 +-- 12 files changed, 130 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad97136..993f2df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2658,6 +2658,7 @@ version = "0.1.0" dependencies = [ "borsh", "bytemuck", + "env_logger", "hex", "hex-literal 1.1.0", "log", diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index bde909c5fdb830c7c6a2c94cab46dff37dd6dc5a..ef6fcf9bec5df105f6a4c47128a29b489a58a4b2 100644 GIT binary patch delta 151312 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%0D#C4Q$~yvDo+DCQ8{813C648^^}!&BhSQvkoI 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

s9uhj zWlX{Q6#R+v$&fYJfy{kISjPmbQ0vZ5Yz>Dn(;Y! zmT`YP-*_NiY+Q)HH6DcXp zIW-S+^^a=J0N4WDVsiawjmA^Jxj6m;r1%vqr(Q`AZ^imMDdJwcr{cr3kBeup9A-nD z-^ThgC6eEK4=(8pXcrgni`~zbaQ{QXbPD9KY3~xQGe?Eo#ompJ+8jt!7Pd~{2XI}D z9+u6chwysiN3cEY=Hg_DuRrw-Pf$>ByEf1)da5yjdo>2|As%T4@G+id{3)Khmmgpa z&f44ib3B;uuEo2AQ*)ZPRhwAh#^?X$6y%VgcPL$*lTq?VDy%=}BIou?oHafKO9$#= zY}fcQ?3en6aeJoX70P5&DkqlLD3CR3mr9)7CKat_gjvL87mYRz$QGF{9hfz|1DCb+ zYdjOr-p{*2^34D%@ilqo`hPzK8I$m!RA?M>i)aq^3s19qrQ#0c=c{A|rtXu92hh%W zueKc4w6_zLWCb?kjl6u;qhjoSDF%>9zM>)tU+&Kb`>3$9tMHiVP}aB#&uZ`2=si5; zU)~?!;tt*);bkZJ=ZZgZnTh{}=Vwg8b_!;gExOUlE%vxCAdXejWc{ysR6?e*q&*mUQ$E32WuJj2GV3;tl36An)VBCVvH9X#5G5 zeXjLaVcA9M&#>%5_38`-vWc|de^_>j`b#XkK>anAeX6d-vIW&YVA*2oAF=Er_0QNI zRhf+x_@%y~Ifs?ZV!hmcg=G`!#pl4iQ}HHlO(i*MhGE%)>NOH)m*~Z887CAuYBr>j z!*pQ#RJ=vAllqzHUk9Y(O{fhF!E#uwbI0*^EW1QcM7{sTan1na%V(O$aXcSuhdJ5& zYl;kbs2kvDEW7Mn=iki=XGNL%hBikw-6Z>9ztlJEXDsJ|d9PMm)ryC5E*b3fC5M%L&m3gkDu@}vOq@vGo36p5|Sm~$%561B>qsC!J zab`0ffTz%2vM^vgpE&OwXe}L0#x+TP^7+4KDA<(iD=ftQ4)=Z$ONYslj$XzxAoZ(Q z2B2Pw%dwtUesI?Kk5~reuD|K%X9{FQny?Yq8vl;%F4~O!Qs1zeT_6MQ>n3e~t|hWU zgWV=yl+7&|?dcuRR;@4hAF>PDb?402)vxit@QQBU9k3n1!PqbL4V&pe>UVb?=9sv& ze`ZksY!{F3fB4arhI4}orC5H6Em>00wOD>SExwCoFaGVU@qe)V)LI?|GKVfbIpKQ@uoY`VJ*sUisfmwRFOX%qgS-9`VvI&zV9recc>Uc6{pEkx% z!LrXazb}?usy?$P*Z&Gra5f2ZjnBpMyM9_>0Pf0w)feDAWf!V%$WS1YL{2VO5BFQ4xpH$SJg0T!BH&!~j5zFsSCJXm_c!7yOjper~HU2EFGx7go`H4!6 zuf-kttxIixhZDIgj^pw5FBR=X!4y+r7rfHAHJ)&iZ*WiC@nrA4aV;Ihla6&h30IhS zK5p5^=bwfLG<^ z$@mek#Cq=Et2dJxH-7#=H|`Q1~0{a=|BG$#B9%Rbi%|A*zbPPKznUSf@BVVMK!9kBeqspcQ)tns69 z$5XlfYe5eR`cXlBJRWI$A}+&2NRTZw${DhxdD)Q6ftYv<-el@ujSJYq8Xu1bXH3Ba z3QCP{z!Qyc!V8Qi;XL!{w<%cmd9v{Fdo24{eH)fJpuQbvWS?t6IR$d2Qs0f`OsBpV z%b8AnAC@zndN!6boBCl~;v{doAH{yDZ`ikEiueEQGdYpSx%|?@Qt?qxn_`1E#l;`b<3HRY z7qjZTrn@BbAq9S^Z}`|)D%4~7#oTrz$%$szNr{s>d<6r`ukFUWfcR`@jX#X{WlrQ! zAVqc5XCesMWjcngMS7%#{2>&o%0CBNQT^EY8x0rj6)eoAc@q@9f5DWd& zU=9T(NtFNJBI=C&Qr|G>z*M}8c<~V*Mzdv*Y!rpnWf1v6u#K#kPZ=vF+g86Pk9g1j{7U4t~N(2T6zfo!GSf zDOl#%>UjMbz{?cKKJVeya1)kE*3XT&Q*TZ@OsaLxL$ORsbp@75mE-EK!ZInfe#<=0 zd`v>kpMEm!G2qN%*WhXjWS__Tj5E^{eNyq9i2s<34>!9+&I{+_I<{D{u*tE#7+rwd zoPLmtr=yFo-NhGU7FmB;>Kpbsg+r06nGWDWEDgjjoftq-QX$Tk0o;tY6#4<&5_Iry zlP~SJIEgt#ek)p#IWQxRyZ)uY=P1a!&^P!3&c4X|C2ZGVaoj*Wo_GaD^B^Mi+oh5Y zUNdoN|I48M*AmzDml4(m6?VwyRA~mV6VAKD4`>%Gzf+zpOeicrdam9R%MY5X_r~($ z=<0oOCVQwaXh*>|Hu-L3$cgAQset2Mg0IF!CVxCGGoFCw7~g=G8Q+B08BfY*{o7sU z2QY;M`E~VV;SL6u8Q+G>jc>t!}<=KIN0YU&5}vX%;FB5Fl3Ww(}?(XTx7f%%Rbi=)1f^%5n(-3X3nQT_K`O57A`aHB&S-_z#uG> zP8+Dn=MIWB)Wh}xIgFX4Bi(@epUUGqTUg@{W7%c#yWljRfBTvO*{7Ot&}pf7AF79A znFH!4a5e+Xb8CEXU)GQT=y_r!mR%;tx!htsjb)c;{a>*Df`5v?zein9=TB=S6uAoH zu&VJ!Lb>lX4G|H+byn&G}`x&F7Hz`jP?5l3ep%D{EtU~yIlqc! zz_LqZ3m(~t^9EaB9hO~s3~$L~i^aF?a-QJgBwMtri$B1FimZVA{z%-wmlVi|bsx1q zCl#-;JSs^CMOfBwfb(6t23U64OM&0Pi>~y$Y&o`D;Qb^&S^xO3qF})oUttZtg2t1j zMf3$;JkH102i|~}nfMmm_AIx>m?OETGDl4O2t1{L=l>jfkV$kA1udx%Zvi|N+dGwDjbUKF6d<9lHbKx^3MzM&o^=MGYuD-f_Owhg~=u^9ZWTr{HKEaXG~o37aB|c zr%m#C|6gMgq{8RMQejJz3N50)OVntzf_nWRJfxtL55Y|Fwmi)Ve{CiDY^6yKW^e+{b z2Nm8ADy%S;{H;O$HWQZ)8jK}B_pGK{=m@Oapa0I|_NUhtQME~s3LhFvg)LGc8DE_Lw~0&sKgN>Z`Ru0ohhx9gH*~{| z{Y!;Qf(k=|3dP2fKQqX$FmV}BrLp8!1^Mrt?fYk7^$i~c75)q={AJ>jzuj0m=x|Qc zH9i>orM@A@Sn|(0hxNCcw4X_k4$d={3KOu+zrn;M|0ZL}e=NwKXX29oWX2Rog^z;@ zpPIN-s4ujvY84yC{^^$nejrNTKuh5jZk9Sk&<{Og1K8%*T1-%Ns3_`_H_*t37rEwnfG3xC%$mi*&_{1bd!&Oh}HdA=aN91p>Z$NS4=F>ZB@ z_hnfAEze|0MWb;?^XOKAyPCg@R$~1Z5#>?sEoXQBK&PfOS#RE$R z>P^_rkw39t>KpztmMyUVdAxAA)(`jqTrEz{zp3az3c8yPI^r_pL$U3!6ZT7e!(r#~ zK7a;WrjqCXhp=?0_X|H^X|FYv%? zF1)~4>QBI{P5m2ixmlr`Fe{|jpBB+1(||PCg%L^x9q|+smku7s@()-h3oj*c596nC zq4BeL4(;tME6grljZ3e~_y+z*!Qvadzr-8PcCT2dup_TNHsN?cxI3PRk8%xOiWi#r zZMekvB|O-89d4U31Nc@dl=%UCkK0Z1UXNuBk|iB&zzd9j#Y>HU!}5=GYW^R%fB~qt z;Eaq&3;w3Se)|0%ENh_g)WBqo)md29SiJ+*fAv!~@sZ9NKN`yxRreUk{+CUv1;Ha(SSES5^Z9E#);Trq z{0x7OX8>C85(zS?y1E9oW0^$y8tu9Zc&)}9DQcE<@LwzgmJX$Z{R{cK$$vJ;f8N9;f041|pSwD!@IR9v6}~i<3TZxh zV0TG#`6NQ)Qs2!x8-x5Z6PID!Y%KW?;T7iCcm!upZah{p z(Oe4bIG->LNP|xqOM}aU{P#^<@>dv3eqE6NlZkU!)i?ZN3Zz1-3z`mSH|!Ul(2OPj z1iVZCgQ7P`l7nd^F7#^{U5+n1$G^V37nkFBD$B|#vkA|H#cs= z=p&qKyb`xQ*Ps9X#$!$VA3V#M(|RgO6>>T-30b(#cn92eitlhIoJ|M2x-~u;ACC8M zF2Z^E0O#rW5`2mC%ed9lxIMY7q@q_T7?~8rUW%7eAwC_jg?_;OZt(;75&uB^5ZB;d z7qZ1nye-~lyg!a^_4NLz%PL<9qd+#9CKO}YWa`VXY!UTnEL%W*1upnZ-8>qL`x%eJ zGD$W68Z1X$(m(gA*HPdXZhnnrmfz}rzO&2EL@Owee^Y*z>#!4739&ZV1=nKvbBBz0sI$h0l;?6W6pO=tLJlok zz5&Y|P~U`Q7pW&<*+ne$PlHn^kWHK{e4GfcGrkRPF}@x5V+&}0xzsm*^}id-CfE4A zxWsI+x18PikJkzm$Ue~u?_t>m>JKE&chc1#;p>U-PJwKZEzTPM8_ONRGJNyUO#-AS{!z6_y989ZY+2 z+#ifr-+7?xf{!5LI-K;+I&`L>cA%ex=i_Z8=y~8V=cI*HG!M5e_s4aMVTm=qBQBfi z59`aEH9lJM?>f-+&j7BVp!{w>!m+q-h4(mokJ*IR;I+or;SI(U@fPC@?!XEr3*T+U zBL|wV6-=dIf=QT$rx{PjHO6;f*<#w@Ol+?W71%HAfrKL5cp33{i*TIx#zhbK22RGcrv4SU#^le!+wcyui>UvOm@Sqp{A@vnf_xIR z;C(CuQm?=zSYGT(1Dl;Sz7@*=)!T5}Jnsg)l6czH@5BoavF2yGP#`1Lg05KBNPQHR zHBcXe?Jntw{lZ=L$6IX%iOe+kPjXzg}U9hP0-COuy(<;*3Uytr9XA!{ff8>2&= zgui3iWT()J92LDxhq8|^4F)vS#AS{QHVT*e%EI2j=GKNWSMV3`>}A)e?G zBz`fTV>}dZCSRZbZ^u<8eivSAT!}j{fL+`G-p0|KcmQ(#Pet!hFxL#=H=LIw#PL7z zI^w#CJB(zLoA{x)!qo4NSDN?*c%$)Pyqf%$a{ZG{b}I#ikNABy1Mk3y<5My5SMWp= zUyA1#zlE!em*XGlKWi_=dUX+&!%962%bZbV zxEjkoQ;)~?)H?zDrM}??V>#1R;&s$-Ns?@Vw{hFKe$KpyGq&IZ3i7F-4JbD_}vBmVw=l|iqu$~!H zS42_XlYT%g@Ji#IGeJS?z0J zlm9ect(NQmLJI1%0KbGc8ZW_ROe)5k8{@eHFyE>92bAYS>tnY4e?ek{)sb`^M5*8 zMS+~Dk|iB|hUKtQuf}p%ssD%NFie)_(U(|`0*!x-GCGZDUjM zxqAi)a$7yMly3uZ)NF8m7s~+SIl^7?4&(Wb8y&{yetbHX0jj5CIchRBu7M9JkTuo@ zcDaW0JC-raZTC4?*0_`NOe|~I!?~qd0lD~e#CDP%7C09#e%?I)ccY-qi+)xggI60L zkEgNEldDIyx7d3_;QGLuaDmC+8h9I?XyU1>nmX$$&OZxG!j2?Z zx5CvXzB^uLyf@xvyg%;os$YR@Txfi7;7*yKpbIWF6^_I!jgQ48OMC~t0-qE(A5Sy+ zr{h}V%-Ix_z2+M{FK}VtL3p;wFTz`lhvSN+zP-_b$24^&x|)JTroy#&gYiV%<8|NQ zq`))&<1*zx(Jj+y=gA3pA19%*-Fn$X6FejR4a6jXPc%<=* zxD3ZvQ+faYg)87Rz{0a#+pT958ac2If zqjnU;SEIPa7SX@3Uw9df?V0d};k?t~)ZE6M`5LgCN9w2~7pH;|yhr3X@0&_|AC|+q ztBcoS*&;k=Bws5$`$A1s?(<9Q>wnh{^%I`{+28tc`r z$0$zCxHWOPnB9Qoux!KMU*ZuzLxCJtZQO{z!g5rocOT7CQ{J)ZQsD8z*BG!1{hB&2K=0JvJ2DE3wQu=^-H)I z9~$pq&MVFu{}yLt6KTQs6v!r0ug7!o;S|Vyf1A?88s7)Y7E|wsYq35joZ_tUzL?)7 zZalG#E0y_2fhJr-g6u=}b=aPmCQ1X13tx)E_QdobZc1gBZA>MH^&i)AlgcdTC3t+8 zS6|1MbBIrL-hN$%!-|3eSCD%>Z>QNMInI+!hjLgw6b$GQ6PK6GbAtgrVd7H%9o*Ke z;Br}m!KUDSY}a^&X+S#o1lu)UKaQUjqQ0KlUSkEMgFdW)oat7X`Z9oDg7!1@CP6yb z6m;;XiAw`}j^}D-CTZKi`{TtXo*nogJdpv$r*8RYb4!$+{J@vg4HS}ePy z%r&t6MowI8fovDgy9tw@>wGh|`O5tvuQ(*T|e&ExjLbabG1-Q)kTs+_S z{J?_(UxJrao9F*w6ij>HcQ6vKFuo%2)q%(3bteCM-2DUJUIy2(g_4E8|4aO0^ZtJ- z1^E~IglTxB@pNqG$Q{@(e8~>mIdaMbEADGV7mo>y;ko(Spyl-ZzR|O{eg9n0qs74M>CF+c#H53rvtVf z92&S2t~LWW0vCSh=S24j%>OD=(31q~69b=wYfS#Bc$4v&xaA7pK|fqzJP;QdUzDL> zhAFrd&oM3zJSuPrt}*#n;ib%p9ocMh%QhQt!r9L6;ng4e0cKWEP_2`U3V-5`pZJ7r zcmi>~r#s}j#K~^qC*^Skadj?UV0;9Q@_hVAECY1;ndoQ=WB{&!uZ1u|tZOjLS>q${ z*Tmy9BLkl4tnmu`D{=ix>6^|Pe;d~mPf@_^qt zV!!a2PHZRf#bunDX-^O1hp^1CwN#4#S)uzw#MszmStQ7DJrBz!-plz1 zECY(yfES5JOr}0gE=J9x1~VWzYWBar=`PJSX8!XpIa1Ki?DHJl<`Vybf%EWM6EDQA zR{A+|G0rg_iVLWp9H#v37>_jZyS(N4lZq;RLG1hS6jR}0-1<}B;atf#eiF-olO-K3 z!296tack-5b7zgO!7{+){)4IV1qF7WuETz*Z}?U!+^!uoi@rB;*~IA^nht1(z&qi^ zX283t^pDy5J(?BXOzmvCjGQC@U3z~y+3Y2Y4Q z_POuie!S55Vdsu+1r91}8qX;UDs(2Hh6Wb73iF&H3;)DySrU(lpT%2DhcDv1)qX&W z@igPtopam@pPZT0G~u+sXW&JqfphSb{~j163xA@+3ym*yj$dFfz?DJ#v%s0v6s)EJ z-9)V?C)N)33fvZNGaa@MoQ>P9Ik0hn2jjt(!Do=P5U{0`wPOE#>+@mV$|EeTU=mY~$+#X97>g z3rzm4xW;$}Zuf<6f2OrO|KDp1@O`+zRCp-xBe=-K=i#hN_1ch*KE>^@UVgXX!Y};* zQd3xAXXZb@YiJ6@I|tqs&oB+_iSxhm4Ym#3K5#Z3VDb;fHHqn;4m->N&!+niQ0$7N0VQZUg}xGwNSTw&sqa4!bXDz4292QK^C58!z` z%lPHMuc_ny8NeG9EH(RZy(ARrEalbfR9?xLcyF9#;-m4VZ~Xwq1TGDHEiRyb<3B6# zCYAf5tWH8?Ge5-eym_Diz-q_aZK@P+q!`*Jj)$ z=Q!_D#@`LOV^a6OnKvfH`?>g4w5N;Qrr(ZdGs%-B72PE_9Jb&d5>}do`|uT>p=?1^75T@kjT^W_&m9XBwCl_yJsO z;&bq9WL9Ry3C*K_Ttb0NG8xcxUqSMF2R<2ZF&&EyT3nUHW&P99qZDi~6+Xf_Y=LCqD;du5ET_hKGn&Q=0}sO6OnbNB zezccMgR(+9-JaMz|EHqe8VjUL9>=llf@I-4qJj6rt%mxyVyEC7YMoCxYWci#B)u23SNqt|H-U< zj)Jbg_z^F{3vgCC*RoZBh60^bJt+`tLN6?brMfqk!%BTJmZLy@3YMcl-52L$rck^B z8R*0#7(={3$H6T>T7|qeQ(B%Tc6$5zA4aepy~9 z$YG!buadCNe9w0&ex0};B|kW8{*PE@wfbkg+U(1y41)-!KMh=ivo^&8lFz({q(OnHM58`73kHb}_{sf%+rytObIFA9hPS&3@8wFjZAYOmo z;oxlJzwtuTK*Vct>*j%5;$B;Qhr0xBjVBO~*I#zQUalZHH24q^USt|*kIT(~`r-}S zd?Q@qCkiEY4wz>E(VX&g7XB$L$m>YvxCM7cM3qpWCB!^g143HhCSMZM^5b z?42xMzpZn8F=LnA7{n*vEA!7(m`cI|8r;Jj1q-p9c#?(B0przJu4ZyS@P)I+*I_wo z)Zbz`s?^_OIqcNy@1=kH`=c93=tlzYF5(XMu1u^I_QeBgEi$i#`O6;HErhjUappZV0#2?4KTKf1?fuA#G{_`(6Q!v3)cm;2y!8DsdCeb%|#t45GyvzOk zV_U{O@Mz;G__D;w^M5S`BTT|>4@A*$ay&*HHr|9LEvRO5?q`Ec|8{}l>k6KcW|Ec;ab zI$mbH49hOk_&a!=@p8P;_Ec;0P8J111UX5jwtN({( zldHeP#l~M_nbaDu#UnDN;0Friu+W4bu^cw)pRpWP>Wx?qJN55a4omfBER#;X70Vn_ zZ^IcmuC<_n0-1E`^ux(vqi&Anuu`|gau}(1#`aXbEA|T?-^2Dv>znC3|H-LxL9^ss z-NsaqN3Y|s%-Vd3GtJ(1P8#KRLjr$*x0zY~2`<^k&-xnNYqUQPG{^@8vM9)*MVZB& z=5U{G5(eV@{TKjo8NhP9n0Pjp{Cd3F~vV@XBd2VRfongRTR=kMzW^gFH^>9;_p{iEDgvxdpS?>jl`h{p%< z>jU42-=smkV|oJ17Sr|?1bz+=@bOHvh=L+t5WR}08NU(uoxscST$BGHu3aTe;p$Pk1^D~_!H;qEqu>Nrp&uUEn82A3FE*Zs`x<|W3yrto9>#~xrY+0^A+XFco^@IvUjX_r4l3|(CjKT~Z3grsjyfdy%zu7; z+Z7C^MVY0?Jnj=Deth5)aU0iw#QWgiO@~+F9tZgWjl-MF03O1FlQ{j;z+eR z@dy?NIY2Hzsnl;b_LR%3@AVFX}H2PP=IG2>^nFY zuQfj3c`>a?`?G@hgU<2#ONVnP*k~Gf92XqoJ9r9L7(eH%TWoz0|0QreUTErX#(9T! zaQ!oYzbP1esP9nzDR*(-bg~9LoZ}5d{t1EeaEYmZ3htNV+dBjIHM?j$&bgfHzwWa+ z6!b6&AL6-(`36=xPjH)b?CZQ!SC@T`qT5q?oV<3Uui1rO+lUU{@}KW)MzL4~=v%rx*Mp4-1el(j>$M(c4-t{>npIN$hp zJkodzE-~Kz6!UMIDM&xfac#VVvyS-mAbxh>bMZP;|9m{Hiyz=1JlD7=BMq2>;dq7d zXk2GJ2Im~^8!UA$ZkBA3cY=6z;16-Jsh?R%LCbEw!O!pv<2B9`-9F1%&@|p9a96z0 z)ISTy9*8mo=L6 z?|KTRG634=(BPw1&v2XE-49@2ys4l=w9akfYw!XSzX7i`F2gO4_VuUYn@#%*alXdo z`u`FIeYF5Dan_g3e+Kbwfg5nCsh{;MPe#T&;^-JZpk4906S${jjk*6xMdwnGcdSo1 zAD0*pa^B$1d=CZj#{$p8<);4AxLpt5{z6=sSkC`!0asAtI@sb9;`sK!(Q};HOoPpF ztDe4top7%4ZqDmm`~4f^@%1MaUC>xS1_QbX_cIM#idPyJ<5tJ{4o5l9cSp&BApU&d zMYy}vPp*Ib3kVcUJl;3>23};kXi7QR~bNs~RJpXG#`g!i#`*ny` zx=lC`_sH`dT!g0>UmCbL@MUSHU-nn2!F<_Pw@lz4M(SX z|B0uYHSF+06y1TjHpT6oh3_@(O~CgUKZXZnxIQNfUk;=|uGi{k0>2RWB|OLEFTv}K z-^8`XRd|c>`#96FukY|<3Ob(Q{U5x+Jl}tT7n}TV@T06zni0!B-sMHkY^VGBd*J)b z8lQkynE1&rGXEYh30IIX8@F%`&cdzDfF8to#*gCF#!uk6roC18apUcHo^j41=6~^- ze#BiCMbZ5xp%C}w+^@UjVl3AJ^-yfL(C{EWI`9>^-C4f=)foz&G95mMpT;_gs&N%g z)1z$S-|=#+N6i5*F+h$Q9ne9z)HnyvHtvEKQNOioFH=Z?9Cq5k#es(g9*#Gd{Ly&P zxxW4wEVpRdUTNU#nmQ9rq@c=Fn1ojh^bOpCd+|mjS<+ECE;`T0@4<77@5l2m>=0GC z^T=0ti;36bk^x#@uKyI2H5R0zU+_ZX-=)H!4$+pZ~iGn#=fN#bt=z!byc%MIuSDAhOU;G(PyLi?s$r>kHC>8C9 z?G|el#CJDl{->fg6jZaxw1Iu`;ETKuz-7h<;s2TeoP&F@g|z+vyq365vN9}NP~%g* z<@&=GHs#YMh@!?C=kOBVjUGW-obJsv*40qv~uOR%)39)hJmcm3t}{V9+UX~Jdr zTHKcc8Q@H3jaOiK?WV58@?KAUKbE&(>Id=XcnAg3UbVAqf0lIgAqDc*Dp}Ie$5`HS zsXxW?mP=iO@|b3DO#4PI>g1+F$;hgXj>@BhE0K;Cj`gWqF$%cWkA<*k-_1D2Og z>R++EWK#cz<<+kG4=k^C)mw0{(dPaC-xL&((31?=M2EbZSSuWcSN8S|cq4ZKK#SQB2xyPEjd&Kj@9 zyA#)I!EQ?vYkUtZ54Y;Q@Sdi^0cfj#K2$1y1FGiCA)n#s zJe@C?^DMWC2Lthcd;^jEop4jo!Jj5B?f(_Dzr8U||I)yIuQwfGd+Zl}{|DOv9%SND zKM&jcgHrDZD~}l6<6a5?kDnP{pA(Ro30?~lplx){2rcbZa`MxMaG}v)yDtl zEPvUN_Pf8)G~Ux$`e#W+y(rjX8aN4e9U|8@S@<9_o@jigbE&({emaOR4E!RVW9l!) zD~6i)|F2W9aj5U`Z7iECS<=zF*iNEq>=*tqZESbZZFlhtNUTs-l4Q<&Z1QE7{e(9T z<@0~}lE4~nf3xWT(#x8Uc1I~1%gY%sqEzUn3hx-xzLBd$$znpcFUG#R-_@#k|;tEs$GQ4a=2UbrO z{``(>jjwu}`7fJ1S<+D{39`x5*Wyihe+uNdeZ*PgbFu6q^%GdOp!z8+TUh-Jz6I-f zV0Fe7Xu|)nY;yIN*v|T|v0v&NYO$U5KbW}8@~n57uJMk6Gp#77qQjQ7Drw~}j20jtzn|L2Q-?%TXGd?SD|EA7F11X3``VKF`)y9|NR-=5pIPj>zB{;|AUxnuz zUxQaRn&F6aaTU5Om=b9g@dkwG9xLp6= zpuq0qx3FL88>+C~$KTB4cfi=h9aG7f?F7zrvW2RsBxkzUu*~xK#D;gho9~R!Vf;9r z=FfCk29!;HM!w_mp6O5~O)a)-ygu*-YzO?SsV|q?->@Cua<67ej0uNTl{WE||DcEEx6yuT-KkG-~iN;q39vAo;Jj3MQfLmVS+bhFuane5n zm`cG~os~?=Y4}Uy>Dcb`JFs8)^*e0$`CUvhsb5V}{5_%yzNn1T&J{`hq<@(d&r={H zN|sdga^P2ScN2dD&%e@tbZa|aYU1g4x#Kb30k0%qPjshC{7RnxTgiwR*K`VMNoeEz z5#C}tXuUj&vd0`0|5mKzpNA)0AXc+!K;xg?deuuNhXJVPv z>Iy8AR$YnP;=L)5`foeC>n~5Y6dX>%E-qoCvo`QMmRYUdj7yy4mC9D^7k-2i+spAC zvzmSjrorXM>o1qzb{}zAa9CA0lO2+Z#yTgBrlRqIC*Td{FuV~Lm;v5`Ti@jm)9JW} zaXFrKw|V}*hl2Tc`wI8tWyTNVYU8|Myx@;SIdf zH1JN~QF|j7j){f&$}j@WcoBbUgjYE;;ODPQCZ}1`o$G zjE}-q_xpGcEcJC4o)GwCoUsX~P%zt6I0M%jpM!H|`vwODzA*4$Tx9Zx;Mxa${ShBC z|Jyy}3oa)i-*_xuY&<^j^?`51n@s*>-0xxE{;jy&c*e)f|GY@Z#2#g z+%0f-oIT&y?}_^vpNQu?sd1kF`!p%wpWF_7CeC_FEAVp+fzQKTO}sGhAe?97MYvEc zfBzp&!3-_HqXUl#d^N5%`Pbr{XZ-*t;=!fsrc$+xxpDoabf~psMhkbF4 z@mYcU2Ofx{7k&MU0$+^f#FQ-D;NV>5L`L6i-sTE4;dXo)3F>mZ&HVNIZY+~Z2x$8kd>;k8t(U#`ZGNDhf9G zg6Q+WYXg6UvtDm(AgaY}jo0I1<6i>*-o#x0H&Y;!C|T0cR$P6i_cpxL#2awiJReX0 zhf}qQH^-kFx5WQ7-WmT-EzkeEQt+h~;N9^$<2Lwf<9+bA#{1z~UQ!_i=GWclEwl%)3l$ED4Y1u#HsSs{2Y%i=}gJ~JHyTfu8TgaK>A^8?^+Xh5C|J_a7j z8f;pEAq2~F!KvWI;M({9W})CEX5bm{+swc=@J=`&&jpu)tC&NLKf~E@usj!>0^Y*p zS>Qdemt=JhXWPM5GqC@U76sc;&=3l;!Vz$U8EEovEIf=;!J`>J4vzlViO8=M&Q*8< zIQ?Vx`+q753i6!`3cn|Xe>w)&f@2uJ3f_SFvI8aH$p6G?$KCe+zsu+N_4}!#9|R|| z`o9EsHBb;QBCv=QScR?NhG3tN9|lik@<#hGM2y4WJ&Z?y$sv2g?+k^X02{o*I24R# z3X{QA%;3x5m=ed}YVbZL{|C5?aTU1TXHNaA;Io)D_U6X2NcoSX=z+q3Wx3=OFlD*qG%#hk!a>@7YNBflJvYa zl1Ux|?#eh9OgSdy#sm~lQptkJV44Mzp9F7UoCl_)lk%BhN;=82!ITq{=YT0EBo~4y zrvw|?LKN_w%hOQ5Ce0cSG#esJmMPW?mRn0PfvcIqt>6t`I6a5~YX=-|1Li~6j^wplMNJ1VAHwJH(~KPI z=EnS|-DL#|D5RCxpA!FbRb7r_25HRiBW4ctRJb=dn#t3_J79Lu0_XhaCsUgG(4sQ8-WGnc#+{?D~HJ3bdn+!TI3W*-j6)fRn*@ ziXI*S4`OofL4@>}qu*HJXoXvVN6*Io|0lxWf1{w98R(&KFYr@nQ2x4|1a63s+8b`& z0@Dylo(`rVk~{-ULnL_?IDZzd|I2~`6f9;H=7DLY+i&tB)Ul8$0P4t>RXObb(kFR@e~cVMDt zFnNrDg2)L+p$#~X@f`|xRQN9NVpcx^yqj?{cpu|1c*7~DJ>#b+*ue_!Q@E$Xy}`R# z{dDkN#u?yB#sk1j%A5uttIGzyWK*|7HaMOsj8=Fo_%MdVI?snBT@J1U$I_61cYzmU zTDKN_2AmG=B)HWf{Bi=@OSj?Hx}#w4*N%t16;4<9VQ?v{p9x-k#?c=PK2zI(TPp+~ zWV{)CR@SHe|5jcAen;UQVC@^HfnDGxjQ<91$M|D#C&qih5yqc^a|K)b|1VHbz$zRD zZ)JR(xZH8zw8G^ISAdH!B=+XkDoM`xB6v5rE$#nk%;FB)1ybQoFpaU~PT=EUxv%(z zU@0E}uDZ{uKNwt;?C?&LvS2O>C~GCp2UjzG3Y=Ns$e#hn&vE!!aHDw+KL;Md zcqur)(2<*9>^nsJ2+WHpaJGt?m%)7JF%ZXultgkV?zSFZH^VfH#h}tL-rT=WG=#6n?*)$*jQq#=qkt6b&8?+^f49itpMt3YDgPOm_qZF_*(z#! zfO(I5F*$WG8!Q~4^&iv3DX@4fcq#LEqQXpK4a)@xX0~}#o4&DO?U>bkiTAPzNW^~u?|6ST0C>VhP zd&3tI6z-&O0vSM$We*2{)9;n~Zf!6)9u3OhlJf-H_TAb7FowwPKkZx=p@4=!R#*%k z1a_lbADdEl2={WXH6d_8!B!4zIafi*@}4gC5A$M<&(ZYKF+=b-Zo!P4ND z;Bv_2)$oR8HcNS9a2pI6QoxF63fQ0mSs@JtG^Ucff+@=--v_2Fm)sr9*MSGX&Q?*= z2h7)jzD!Q*z_c=D|Nm=Nfo>4UR}2_TP6jrDxdX2=Iq7dv^xtA~(mzy&{No0WunJ`0 zxMJWGlaqnwr|at926ncJn$}<*fwoLe`n?Pk>}AugufqMnO`>GT@#QmcZexcBfx9(z zc$mVED?Ae1pVc>>K*7pr$G`;e(q;}%1@j0^2Rj>fR$$)4m{)OFjt)(1WFMA~0+WN8 z9-C(|eMG>h*`PG=8mmA<@JFSAH<+A4cu>(l%;cnhRM9`dsfQL|k!@D7uc{&7YB6qA$wSBm~OQcn9nGSK== z-4M0~J6lD~9bg_pACr@QI+#z}423hnxwQtp+CVUEV*iVyR21+Ami2O32796s{k$F~ z6iA60d+mf%m}Nzz0lD2F`+WW&6$>?0+{PxVsg@t|AzEM|LN|uyHVh56*Zl~+&~hOlYv3teQc#1rtk=GDU*)`pJDt2 zxQWr+S!ySsAi{VmnD=lx*x9i3g1Lh^7i@<{pb`z)ELNW!-k{if4P4uQ3fUhOgKsDX z4k`u?GdbxWRrF6VIXMtrUbjOn%6a>!&=M7RkJ^CcMh1E+26{6&Iq;yO{}7Xtex9N~ zvz+yx49r#x%ux(%QVeWna?*cO(ci}84+Z6-vFgY1G zUe|zjipfd;D@FesCMW&YXL$q-uhtd?&W5EH%tPp7ax#!!$ADLRn90e3M-=@>nVj?s zxW0jfXCA9S1{Np=7BM*)*vbt+znIBM|4)kkyG%~{WlSIW@72C$705ujVxWS_$w0db z9zw6y0qkrQHF01b!8@6p^!q#dH2=KXFIWX~V1Qy^Fq4ykLdSrmzmUmE|7k^k36qol z+qL@E{P${qW);Z5Ulaqon4Aop0rRE0Lg7jakf1<_!kH&Q?*A0_H=qDHh)eg*_u)G?>N|sKFd?HEVE;!nxq+ z7S7IV61XtN;c4JbtsI^S<_^vVW3&5DA(?{$?qM;0IFJL0;)g?_tzc=9gPRqDZ!$UA z+osri8yx>-tkw+{n&bEHyWo;3hR?cRp9(%eL6i9oe+F({=+i2NLMeFAT>De&RR1)1 z;yi~dz=>cwVj}q!Z~^Ma2-d&DPp<`zyd^lsfC8ODA%zYoSiuZ-0`Fv;3SJ9?6Gi=A z;HBVb!2`jg(E<96O7_Nq%b=eucsjVU~QrOA#yU9 zfKc%I!@%@t))t&mAo&{b+BNng7R39&n*WTvSMu-NV zJ#U}O%@@22Oz -oExLihK}`cHyeV+b>a1DC)}runRUM5w{ki%81Oh!{>H5qqm} zx_-P*!#@@`twR3MnaD&@Fc*^CmKZak@YBl}k|lAqLo^FaZ$_b6Lk6qBbRv>6n>cs{ z$9Q-|L*~Hy;H@8Fh=hX+s}aHOK5eR~Uu0axWwE|KYu=NAH==NZK@Xos-+UncP{;jX ztDGEZ>&3fV{61}qFmM1I|B6rB9c9-qZGg8NWg|jDzg0urFNT}jWDfiVOt1IZDfA8Z zEjSyE9?Kl~q7mKM?6a;^j(?P^YzZoWPW(Y&H z4oN8VePNtoUymUcIkE{nw2x1V1sfEyK`98)5TCYH7)(lqLzu@S1djsKLn^Js7_I@+ zLnbSQyx;vU>m?R4Lcaym9gf|E`~sL>Uh|UR0o{=UPg^--SUqUl17rM*Pun4qY$7-p z6=arw1?FSe1zjr}fRGD^K6?OfK$_-+ymenZKd{_M%AH`kN3=vZ5QphSFCbb$`yVUI zQ9v&gS|KWwg88)R@sP`UKaiBK0n__^BuDjgS&))w}<>%vHI^m{(*azAzO1919S!|B2L;mG<|v9}XR*z0kZ zeH9E5Xr|#tBQW2)g|iWn)jsP61Ty#{m>##AC>*!~PF&-&9^WVVx*S9VET?(KNF=eb z%30e#o`i3;yPRp~nvAvmUpQb8L(pWpOM8ip`F-Hsj39Xw z(N(Z9dauvAC`2B9J`+j73_OvKdHsP;J1!i!dltSGj|-Di1?Pe3PNv<0jk#Ee+=zhS zgJ8O7l`E2N#k{&Xb9o-_|KrnWXd%|-{XQ*M7$^egm-@7Jg8v5Ianh$1iXMgLBO(W# z9!>;D%AAO92dC2!kI?V70QL^~td~+*Ij{ihf6v1{twEraV!k%{#<~brtdq~WgXv@QIl{oLOYmAP@F2kh!SwBSxieb@rjJ;U5b~&JvGzkQ zr>&8J0{V7)si;s0rmt4ZX?X=qpJJEuIPo`FSkC*bi_PR<4VXSe4!MD`RRFZr!pyX^*_Tv~VO zj#=ii-XpeKM6?)8_lT?%4g_(71HGmz|0kr6@&79d=%rJgn%V~Ym^k7oau%Xj!1RJ5 zxiFmr(+h|sXTE~n?KWp=-2tX&MCIOe#!5Ia6&Is%z)S1MPB1@Y9=^N*yH~mdLlHE`)p*+% zk1F^4TsPmi>gjAw*YyPRPOqNiN-^(`(mR_AD?JJBlz@4p(lfZn@FD$zS>wh9BV)#o znK~vbcg&2D!>3LdJ97N+sgtwDPZ>3G^6>1@S!2eJ%o(1Ym7D8N9UTtk1aneSf}xSw zk0%F1qaH9b&Y_v?^PYq#f7ox+_8L!u`ObMyq8WX`^Qif}TOSbR4+d=EmaCpbcgS)e z!TjJNl)GI-_4lLTsGDT|u<5((>FoB0%==057I~bSW@go(?g;!}v%@$FB;W+JZn6by`v|%!gfr; z$!5QEaO8hi=(n$jqn#W9_9B(%w0kK&f=Ag^+#PM{!Va*L#C^p z-q9?$Rrk45f(%jvrmKk_aHodFzY+W2h+q5*O_E124u8&|h6+*&9$Z9s|4x!oRdRKQC zK2wun7-*`8qx>n!=B}UU-Q6h>GSL^2Uj=!TKVnr3Ve+E5op~`vZ&Td2h5nJte6*Jy zZG=Vza{}SWD1TZcEfUW52ScHen4hD9DUq~rF!Fd8zhCQ;o;7~L_%1iDnp*#=X?9hk zK4v5JHXFDJXOp$~Jk5NlrJhxfAf!-b{Hbp!EaUa9Y$mnR&vd+Dcmn~mbgZ6L{Bmnu zciq_MK*)CZ!OWirgTs^IdU9;N{STzvpsL|yNFaQ3wu3k83yr_(yK$YA4N!8a3fHM0 z4BlM*0P;NRrW&BMycvfAT6~WAT&&*0JQUDxv(}5a8|H_#xZE@FhV=qS&K&Wlz9|~O zDzZd>r1&50^gSP%;b`x@rt6fx$NYPHZ&P#iNxf}!I5j7blARUI3S@@@DLL5Lp3;9| zwkp#v{_KVx1;br(bEl2#;*TT;1F0b#8>9vzk+hVwAXeSydV6Ec;ivT(H@4%yj-BF` zU+JGWy1#xaS>QVUoT=2eJbDdBwfdR9j*Di+d3|-)8%8VjI?nl{y+PA`QJ;U~^}!$d zVLSEgi$C~-CJcW)ZG@A5&_=yYT%#uqYxBnn$<8Xhb&G2ZnrTVzxTI&rUPG$k=r)u* zJU%@$e#%rcqr!7f@t#Zix2|hi+F6bR)ehBqgKJ7>HTC|=xKV*Gd; z*U7ybkGEkRP=x*;2K@gP4WrI+&5sYp%~~-2oL!m-e`BHTg~rH?;W>vx*Lx!wu;i?%{y0kJ9y~|Y5FYl!4=*(FP-x*+GX1s_3tS%Zo?8;Qig~{{=pekAVOH delta 155889 zcmdqKdwi7Do%sJeIWv=Fk_icuDLk_A+&G2Ev>)n316k}zifvqCThMZj&0uI za+aFDvhyoUUAsjy;@7RIPA+IZ)Px#WW?6B^f*EOWs6&t^#QNNR+8qM6{)MTJ9 z-?;9V?T+kt$?K}tRY$ztrTNZivnOdVhO0JS&bWJ>&c8WkHQDv; z{9KKg&ewIXYrwN#6QoVlE+F3PI{r;6qR{H9{hewK6aQ+yT6Ib4ZLc%axxd;f^*#~I zbc#@}(+KHXj7V~e;md3>g1IfCMZe2vN!}&GnY(1TFD4_on2e6ZWNb7heP`!LzdpC) zlTK~((9k_%SfjRTts`-0YKPnDw|+Z@wax3RKjL+2j*bn3{9bjF7(OM8;q3NFi}X}x zwhRwkAS0s}$Y|~Y8SA^i>s+NtUrn5n<1%pe0vQ^)prh+4m2;H{Bxj3YmrM1fqt+^I z-2!VYhFk5vZeM0P_lW&Ns-Nj?tU>J!Z8*jn(>m;yBx#9mw`9*!Z_25a*dNtb6Cv^u zA7?mbo6b%$QAuaIM9fkZ_ZBPe)LaYJs!8bn%7S%jkX?UmI#)UDO`<(Cu98dnov-5* zl_&DQS6$_Grb|+Gg{SWGI)@4osj-H1YL}3~Zd9jsIT;GSuOxl`cbeb%vF+3r%joI` z##dt`8^!SJjHH6^s0{(sbm>=ck0zrE9`I^1q<*}h$%ukOnhcvCnEb=?_{SdBvp?_G z;^xKP1(n%f9M{C~DphK|`LMSsb;m#J#@|$H;uW7Jj?dAI;|VQ$%ZK#%t>n7((^~e& zi*)ffr*!7E!T9Fu{C-C_zO9wW@JPe4X_}U;8qwvUJGU+HYFb>hQ|aBs-;tM$vGC|Y z!}#_d-D_x?F?2u|ZCTxDdr41;#nK^iEWyY$tJH?=!VprOw-WKechUO zid*;Ab?e@S9h$e^rJGM|*Nv83Q}&kssm0$`g=CttZ%@{=NfCL7{sj%wL6zt-b#p3<{#enF4FwVN4xSQp`3 zef-u6J^S{jwX3qfWWs-bT+9CSEiL<;@w?dULy5`epVwVfYAl|w8_QewFSuAgzsgu~S`#bJXrk?$W+cpM^$Sy0>z>@D zT5I=UPg?gf(b-=;sl_+5j(&YW&)#&cCazqo8H+b+#`3M2v7%cuO`$U#y7|d2&3w|W zo1b}9&vrbb#oySViL2{K8`QEt`w&ZGz9w2*8HFw_{;QWs$H*`v@r_&c?8X;UTK4Ae zY4Mx?Rg1qhP0zl?(7b)EmVJAZX86ma@r}c}S5yn>8{H)RxlJ-KvPp&pHh}_fm64j; zWK_RR#?Ib)j0Kz(TQvFH!CedLSWnJArmpVMvEM#t@7Qnux!q&G1F18`kI-=OcldPi zcVuvPN7qf-<`=5(GiINdX$(&)4)Y7Awi?5~*2VGP>BjL%CE}GI+3PO9*0N{LXz@3h z%8f5-W0m>`cZ%a*B>i9OBK6AO=*BCb*S+=IHS-CVTBv&b=HJ+(LdLO7zTut5@y)t; zB}Mwjb@7cG)neAX4W+ude+P@n#bRQ;Fq&g|VyCx@S7wv`a?)R;8{e32Z3J_FsqSsu zsmE_=&{Nr4)X=|G%oIJd-FW3kn)t@AHRBt<(~PfA(uFKpm0#^3${DLTa&Ei$#^=fZMe_f(CjRDcigAIBd&bg*wx)_7;o-ew5s}k-+1i@< zi4xsA{c+Yr83^r0Ys-$M2|ITf-?)(qHf!RglxDp2IOjl5i$Vno4JZ^)*xCvno6H(1 zS*^)KlXnM6qNkE$BxyL$u*+MBgY?G!0)&# z2a102F>8lxWLJdBeMV@RPqg&-4F3sVVOPZ23E7QVZ4CJZvfIagN3*++u}HJd0Zpc! zuj?KAIN*GI?DtdW9vl1p+;H*t^QViy51iREmgcFodrarql-a#+lA?3iA&vX175OnV z1U&=Ns^+9S%b~WfBA?Eq(xvP~>A$iu+p1*?^4muQhmAtvM!&PG(0dVD(p4B0fq2@*Csw8{_h)435Tm(oX>a3J8tN6G45G5ln6pq0A;Dr1pH@ zBKG_u8O|+|kj-$wjx#NS3dIG}H#j14T5KH~i&i;4~C-cfA8@Qz{wTsw;m*tBFP z4OmPA7Sn*mG+^<#0=ADUVEecNb`%R(vV#%0f)Tia5x9a8xB^o1w)_y=q^UIx=_BT6 zr%XvDpl9BhWMS>}9*^nFKw9WmZmv$minNN>Sfo`FQ^&}V^+SF!o*r7SeL#^JhN6l3 z^E8p7lU`Ug+af(eeJ+di2=|p(q(>B(QnW`z1weZQRDuJFn&2DK!{6tyNDrTaP#Jy| zzh0%|yA$d_!L%Y(fYpkoA>OA*7QW}Ga)1d%(*U75!hKaVKtVSR&hH)h(7g-C{|d+gK5F8?E!*V&!m!SaG^Sw4JFi66Y$!HQg05Iy%W%xv#=l z)?Z;PA0(``LIy@_#Im*g-dG`h{Jv&ug^Z0(64y9YdcNx_q+h{cg$ye=ze0vaYf|Ex z;Yu=l#WklZWkf}ssiXkD&s9dzq7*zRz^Hu&7KZZRdY@h)i3gG*UhlG5e^H315+e3yX8ld9&?)Ok3 z--9Y0uvO&;uJuqLaHEF`fm>C2ptH)j#z|Rcn~ZDfNPD)QiGU zb#}w|AS<_P@l8uC!UmEPa($~s_QgN;sBV#j5LEGB9I(hjBbb?#{l(Wb^V8)zB&44G z*+Sjh)T0qzoxSNVfO@sLmgCMi-i`__wuB?;@+L`$Y7{?-}INCov8l?a-#S{>ur zo4?FCl&nz=p`gj}n_q-T)+gIUa;`?v$k{Vxq)!sIQ-kL9X4g0J4WVZd(z(ehP5hTu z8p5nJwej1mG`0COUaOp+mO=H0lLT6c>f)QNM0NQ@wN|2ETLlrO(&QTAw>Gk8^*Zv^ zt8_x@^&FsjyhEr)HQ4F&CM#V;r5mI0EdpPi{>(@nWvTBJy~{OOfrvX41+Qok3TmPt zcpoZ@szP%42I!4em|t%o%xVC6>aB)SF!?6MZ*_n?R0AFXi|GyVpL~~TTtQj4gIoxI z`$^S+rtDkSfejnaHfG;^6ZAOJXe=4l#nRIZ?=Wc5e99gLW`OlZ8qKNA&ED8RCzLzkb%g(#3P%1(<3UB)v#nAp~*VYqSwVYeNWY+QIBT6<2!E!DOyRfmK1tD>!vP#hf1-L z6kDzJs>o5ZtGBd3aVn}Yd*(&@V6}z35b&X7ef;NU$S+gnPKP35&2p#9bUVv9C_52a z+-1pA)?2cea=!0T-=?!%+i!&pl@fNZ71mt{OCy`f+;4}uH6xO2w9{%%!q=DxLWpzc z^9vc+(!k%1DPv}`L54E*{C!jea}6@w2h`!r`aa4Lc%ukr8fDB1Ym&ac^km2dcS zm5LG@>;~x8GyPh2{VQ7hj_+#OpZv2HPo1d+3l7v~H~wXj30{@BiI$hzEzgzOEzgwN zEzgwME!WHKhUd!ch9}GOIHBBbc&0qx@KU?s$uhg)dYRquoLe;b&zFKGE@TK;R6GnguV=HKWw>_dTbU!`3~qeXtWMSi$Nez=9*=%gb* z+K&8aryTj=cIJoMk#DurZgtL?A8u#9)vn@jXI$eN?Hbo;*I1)}0=K{E6*>bS{1%Pg zxDdTL%*r&fE?9^v&7S*3|4ENnf~Zr;3mvZ!w(7q4n;c9xpAS?18uAX8 zwt4!h#M1bi-y-ne;7#*^tF$H3KRQ`P&rUWU4&57U5!t~OSjz8d9gV+nW-ok2k1yqK z$#2N=3SlnCW=%62T4v124sNy5y;1p_|HxiQ{P=VZmnHA*=z?WgmA;fyz0T}x*%~W5 zEaLMIgCLKyzB5fiX6iaVTjz+s$RT;0zu`IyfpvV=Nj+(^B0M7%pRW*YG5s4m>;66d zBI(Abryj4tcO!@BLSbMdpqIo9Jt$@-gCdd%8ZsA*4}PY^2qNq1-dDoDZ;D^|M2V2Oy6pCk z(T|Un7)lPdK%uH>3XNAaq`CG#9XPUBKT2o?o?32P^(O8{}ChM}> zUd{LIJNdqScVgeRf6MBdLBZKC-)jDD#+9i*gBV`jEd71WAa9=xWSV6t<1=>&hF|y7 zy=J=SlaY2G2;Ilo=(D?({!+*GC-wN?le(hI;`1K_ttFd4X-x*_nt-A(pw&Qubsbe7 zVst;G#g9L!XO}#Pi0!!6F^%P`4e8m`{7+AeQ0vU;1i9w?9Zxu2-dz=%x5uHGyDByB zqa~V%CjCY<LQ@~JF5QEk)8jaT6{?ZYp&9+AYV?$pE(`g9c7wXS*Ce+ zmue!A^u{-S2<)>AEYz>1IvQD+s}iD6@Efj%|A^0TunK;o{y+cS2qwMR`F}p4@Hc8* z|GxR1&;R@Qf-E*Yaq5xy#!piCFN#LjNGmD z+3mB1*Hfy+kIfd@QYnm?xrX@8kCkM<{2wBF!3t{yOaJPIY}tpHR95i}y)nD(-|`c^ z%$jKFOQPi=?f8Yf{XeY9mVx%k*^8t(eb$w!v#jOKf-W_pRhVk1(>XFQl{GyLoyGYw zmOGzGpU$LDW75xOO;2M@Ph(9_%TM~_BB)Q7fxcQJkeqHrGSfvQH$6Uo1KqlvZrv`j zu7HRy2+)%#Eq|0H`)En_g3s8!xDrH}Eqm7L2K%nW-iM#IyAau1mn|XTgf2WR+5r?7UX zuy&{Hf5%erq(m$?)$k{$8ZtBWjk2rnh|j;8b$se}@}MrrT=I$7Jh!p94gqZtL8*CJ zYAgb*kPb>96DK6iLSa++IDzG|a z7%L?Ht8XLQeUO6LG0VZAmRvW8Tz5Va_FY=`&hJl<>lTi@?3-Z4Ta?)DpE;lJL?7`W z3-&aFe72Ke>Xg3GyQDvN7em@9L#sPwxaQ(5GNNx`Y%J>1bfDQ=r6|k<`kPFriHapp zF~;*$VqZtsK2Z12?dEd}7nO+R=Fb*hV6M3)&jaRS*yi= zw%q*UB^R3KuPXakAh(=pT+S+8!{2hIQ2ni88kh68M#l8D{H>6_vn%;qC;g-9Z>8N7 zeV*5qS#E3Yv{R-{yq2gYdleCEErFpc?UQ9_jhUD`L-_mdGS}ZSL->H57tau}zE1PA zz$ow?;v>N7TW4Gu?&}n-%QU0)gOK94LApPn6{M1)L3Lsr z+1X`wUeMGLQstCWP7~$Kpqz`5+KeLYI*YVRZ$(YpIF=7df3d@dHd<4*C$aedV9HYehf`)&^eBFW z6~xXPo3`=oOr>Tai0dXyrPGt2O6^Uu3R{yu;MKy&zIPVVb)a zU7b4h2pY+vUVhH0`xaOR0j=)5@k6DFo_+rw%OKz@@xg0#(US2BsZTXxxkfP?nr=2U zT{PJ!W+c60W@d87W1rBngP(vRx*BrpTASRec9p_dI%m0d9_Yw6PkSr4R8wOIyr3;P*=t<*$eML6p^dXc7I9BcAwTdy4Gsh7uO;HXh*2Kx>6v2 ziFeb17U;v$|EXmwKdp;UD$^KueG63?YVrkJwd{_!wD^~!dfbIpa_|tnxLJ>1bTd-G z9D>~~mDwGK(eLW@@$P@r<2!z$$1CqaZJ$vRKDZ389xrq1*$b$+}v2r4mS(7qCxiutO3rDh+DS(`Tya}IG3uy}p))K##_;tjulTqR= z^5-n%kA64t*Aagm@z)c7J@I!De-{zYS64AF($PMpN#=Vqr zFJ;_M84tWi8TV4gy)vAll8AmEg?#uu3b~&`?x&CkDdeH|DCB+$xu1sIPZb%Cu8lu)du6&oL|pzo6hoawROPxYq@ zIzbt5raQjt);8}MS}6lt+%nYemf_Xtwfa3U>wS%)zs)a|QBQxbUn*Ok{*!(rcZs##*k%gxcq{o+h;+S$V7DuqGmsO7CzhHH5R)&70%vZ85<{BMCf@C&wUBY5 zj`}2{r0SC_^pvU)>3~(Ck{&b~Cp#34~ z(UmA#R?4v6CL=X%9p9~>N9ju`9Q9ORJtI=V$N)VcBL&;g3iZ_+&z#bU zYLuYNY(C%~ZzZFo%y9OkFH+~1*$vB{+D-y%Xodt8p`NlTQWexv5t<(b+fe+#Q@+|; zWwhkH;t+iL;ic?`9h!0E6;1RbW9?tcF51B^dPSEmebGVwWU0PLM3VL92e6X47E70F zcWH)CulN2it(!lDwfa$7OPM#K?fFQBE=zKk9xO>-D#eyX2SxiL@3oI=QagL;L9J$y z)JHEpsE=G~UfZL~sx1o-R<&OuC$%p;IH~Uv@3rOVITbrH3OfSJ5vf_>UDu;Y$Ld7~ z9W=s8WB6mpmowzcrEg><2nkk%byC@vT6nOs`4Z{LEj;LHU+8t}^Vlkv>>tw9sf8Jx zKF>Nx(nA-EU~--j%*+#^+`NJoTRweg``8K(4Hkcghqo6NENW;(+UWwN^I>ZW(x|Q7 zNDrMUMODL_*;DXyp?GHBQHN0cvR}a@e6xajlME>c{~WgB5e-=JjWTM*BgjAMkiHvA zrTlMr9;b$b)?TwMgw`=l-&USS3mRyDpe;`J(U8{7z7jqcZaO-S}IlH zwN%P!_8^+UU1$j(oFpAjPAZIm2zI}(-rqRn@d@yXakv-Ua?)4ej8gK@k!^(nWO%Un zJ2JeD1z1E*M@}{~cN?UCbb}0>-5^8yM&nR#SR6hXHV%7ch$C$?ShO41mK#9f8$e7O zS8Ol_@FLKJW-)2P~Ii;%~F)QpXX`%g{|G;p$bxi?ChAiW9Fn-EAQBo6n6jKe2GAe9z+*Fw5?AtldbqrYu3NNh4l z4Bg4Qk&>szNF8pg0jt%3)oO}Z$wo@zx!yYYZltuPQrn!~b1x0_%8=DSuM8;I>t%FQ z2fU2VWbtfUovDzLtMd1e63E9!O6i#?vw)S-d$xGNNY>D}NMP*dYH^ zb?=8fd87ma8beCGd89P=(gIQvv0Sxz?PGbw)R#w0?I5PA_draKy$dm=-WM~0m~!uj zn3S!LjhH%5b&CHAVoDV;lh?vbovSu_S+O>|KZcl|qLO;)dsZPLaD;-;cLD3WR)!SJ zA^%q}b$OkST8Jq%4l{k0LePsm3qs0FCX>QY ztlDQ4BAQI498RC%cdmvC6*8)!LSV9n3PDPbf|Nc3QhF4mH1khHO1-HTark75ao7_E zfrJZKNj`UEoH^uR@wabyH;XXLHVm^3XRz%+IwP1=j^2uF?p9=8w}F(JD6@$&r%>h; zb^|7um|g}j$qLncjQow{ZzO+{aU|7-a&_cF3b>F0E~J13Fd*lX=|VDHNTv(Pw4md= zrP4$HJq(ndy`%a!8XYUvz>O0});Zi#52<`!xWQ*)S_-(~hjPv#i? zZF4}Ab3l`+IWjPc-P~F1=JZRUCN33+J+qC&ZL>j>vq6)y>DmO6SUl4km+vBpJJzWA zIu($y)#X$$m-(tx&#PW8hR-K-7NM6C>M=k6 z)YKXRf1pt+M*F|TX#b>!RPj6&`Z+?sK1+PH83hh<+iMvX>K^L2*w7M3}fFzafrxboFClf6BT4!!z}Z zoy#|3+rR(GnflCBVm=m17|);25i$n?m-6Y+6J>S@4nAc-5{{hrE*FL5hv82cR4kxaAbooK| z*%h*4Wck60+;Z=0oVaB*D-M>8u8`&Wii726m#56vIC3iomLII7LJt-5CtX{XA9Uqb z$kO)Z2TM^|IQo_!bd0Q!P6~2TkZtd)EK!!{S%Jd^8WkeCGlenQ6Haup3Z1g+# z6b)XNr1p#r^K*xamhrK{;&0#ZWADQ&blDp!vCivJCo(3lidC={<`5Sba#%HdL7lir zSQJb`ttqH;Kq{E4M!sMrsFo2cLA8up394o63l7t*IOR(@mOsw13@rNy`~tR3&OA?I zRg~~h&?NGyQ+X2k)Tum)iq)w+iHbRSXL0iOa`MjN47Q1BEu#!Y!VWB#6OIT^Rz>T&NyV4jFGi289nEsfKutJE0_L_Wrc>w z!1lBe$dz||vb!YTnP(<;CjG@iXH*A@Ba#~4RwzV-T;q(`ml)nspJtYFij>V^Dq72B z>`a;TIo;BK&RtN}$)`&*G<*XM-$27R&{H_T!7>>dE|cNYWimpZ0C_^^5Gi588>y=> zg8D`gN^WF%Z4{}N+(zcv&0M*e8%%3YLrR{hEbh8=$l}m#T4#m5>(UWL7qQ6iG=j-H zMJRJ8YxYjjlBA5EmWUReMk<)0ECrJYW32EyjF#jbBAmGcq3j)oU$2B?|6mENrez9d zFfvv!Sz-QSbtt7HEqX@nR?1T_gZvfP-$wFhT%6a~AS-y14l3BI;(Y@iFqE-p+{D9JD%b{{uAm3H9eA|2PBkz^3vW~0 z;`_GHZs6>34ZQh%8u-^Tylj$;JOH{{Hi-(S&>+Q1LxU@LvWW&kvGz96AVs-0(V&zf zWud`Uz)5TqtOSkpsG1=>KR<*wGK5qANCSi88u;P&Y2cs?EJLL7KphQ55{X13mU;%s zLT@uI0=6~NBA^Fj9|e)WhOBrW4N~zwdZglg^r)U84CEWQmJtm6kp`YOu7MvIYhX%= zB8qeTEOUFF^gjTRzwA6JoI!)8QXy=Pf+xc?2-q8@LBO^!4N~#QR;>6IhIk6`Ep%uK zBN)v$aD@_=yjKIcSW^(r4%Lin;Z=6+ZAl%Mz4aN zIW!0exgLAQNBnFW1ZZ@86ok8trr0Wqy^TG?Re(=dk*J2uz)TI9 zfk{qt>su9q(JQBXSV2zth=QE*Q3W#`>Q;O;J+CFcnsR_jp!iH9@dy+ZR02f>l|T`g zvLuRD0wju70wju70wjvoHwX1I0Wxq^9^t+e!eZYD_oez4b?k=m#Qc+j%T2{kTWcio z3lYdP5O#?X%v~Zv`XVEgTqIgDi;Nb^2=uXF2j<9dZVvY9b7XYk=p6KAbG*(R7o7Cj zD4XY)uH;;smP!AxqtcPDB*&#Etg5X_v^TxF-e^XzUzGB?H4W1lTFDTi8I%#7Nnge> z-dAhMxRDgVA|r6om)aU-SG7&krf)F(YG7L&+32<;hm;z^QoeViC#I%V@IIK&dNZdl$=hF^{6Zf&x}hz@rrK zEyatYYa2kk&o- z9V!h9)8WWS>Mm=M`ByihQeGl`?TymkzC^~hfcmx|Xs@Btnx!yUN)5RN9;S2u3rY>C zJ!u5>r5(Ezg_T^Ywo!ij42oc84O?f82Hq>E&BDU zJglp7 z=}@K(0_@aLr5G)1PuB@l+jbd4AN0=WUI%8GB9x=7Vb({c(Fk!tM%hoO0CTWTi=XSK z;J$j+z?CGvl0;V;q1=@W8R{uiPvIO^awE%$UrhXB;ujN-=7D%iHI+j(HKMHKu0FOB z8;aeP)!>As-tF+tmPTr5x=^S#(6^E@xY^OSGR5{oTez2iBzW730B7mNygMOnfu(&0xUGWGHtT zlFG|iGb$dP?Le&&T$Vy&$cIBDu$p*N8 zTn}qRuv{r<6%tblT40aT&jL><{cJ6Ec^p7})ufwd1XIhViBQipBXnY#Xepm=v@Dx0 z!adW$X44ss3HMtmsGJ6tbM@HG)nmx$cggu$dfZ0K3AYHBR~X@CTodeD$F!|u+SV~` z>&UP4upG4~px_lOuaqgJqzNvo1XXgFkFEokuaiFggV;%Y&~)~A^1Xx<9w;mJGL1P) z4}61F82pG;7!0&kmeRRS7Og$-!}Jo2NTt9PFi1d!8T-G5{uNAmcm;RLj(|#!fJ)u^ zyo3D73VoiCxh8WRjvPKJbnlIZW%zIYi0{AWcK0bm6Ig;{xUPHGayxI$V@gWgXnq*+ zs6PkMv3iZ+&p>ow#u?77VOrOiYhiq&qig8g8aj6!ox9Gv4(2#KlB$$tBl8ZHQE@p9 z;1A`hN4n0=Kj>=bYVxQ@mX6NHa=>%2bY#AaCM%>PIsc$z3qs!-w{+IblfEsUgU&6a z*;^q?_Rc?8lAABXd)-p(MMB)?J}CO;SvW@Pn|DylP(GZJo}=sm_dz{1K%~BbZ8C5F z3l{soLj?5=mV~BLW6{7f1~VH(D7T@&MT1DjIBnSd;_tw4y2ujdm9{qLbF&f6cr3PX zy>^izQcXwCOmNL5IQl^%VgeE2!7WL(e+AZmyBLr?6f) zUW4BRiEx~9K}{a{dxsIq)XJj@LIFLeAQaH^fA63?mwf80F8TDg6q!{ccRuU#c3Zj= zh3Fh9yYloesoX1{vven3`Mjk&@sb((OZU%jZxu~nuiG`eqKC&Ejwg+v5rhdbY7UzOK%BFdSIU2Nv^>T*mgB zVQs76i5b?m3YJG~q_QlcwjB!>jCU4{r(cc^;PM5_@sZ2L`zS|XgLuA@w9+= zV4dELxXNo7p&CZ0h7n?m77 zcmi^|uZq3>F2q${H-@+(Ag-}aroT+5Q21G8p#bblsluSut-_$)`>Kq^<@F+jfV~Ae zEexF&KFJwxlVwH-vaAKNY#g#OOJJkEgD&7Z9^Lyxg!doCAmqjh9m5dK`~4o>`vVL- zTqEFsUwYq_!GlvvAen^ELNKfL++|yaxv#6jBv-o`n1-hjFD=r!MrsCb)vg{~) zf@LvcA(uR2J3{5KRLW2!150IDjvxWH3`A0hfGrCVm_*A$1SV0zB&U~^0BP>g_eU;= z%Ks2@DdLtWRwuEqz#)IL3X78>UJ0a57nQT2GsWNGbGt+fh$2`Ds>M_(gsD>K74SqK z79?1R1accVlr|6#Kd=SIn_H@oYq z3%rE%y*TEIM1G=wo4OG;)*zpA8^2Sw%b>~5Z4_Q9LK!8=uVm;ejh0*mQcy^#WQF0^ zD_BHsAv116Cfy70p%t&n=?AsP(uLnreKUn@e%wdRoyUrIjxj3y8L1)h6OU>nmjMFdVvF#_f1iO90^WEAum1p5m(LqpycYd3~{-<%K_E77fatH+-vfKG63EVfwwJ6b!3f2 zC|35}z=<}2S~a^$AfH!XZ@LBKbo=$@ZlG_=4PH+bDxchkigaIU0^xVu#>uN16;5VI z*P*5XS+s&Ix-rBb24P^N5y40!iV;U_^g`l6Hmx9=ZVgXBs`nRC0frpJhx$+_yIseY z!Q1teSDTEEWw~Gzu2)#XiPSjh1jB*3IMRqP(unY@%CpxTA(zcDWX~KCJAo9b4-y#< zKzSPq&gR)hY}srGh1n1aSSpMvavV}whrF(tZv@M+JQx5EEK{)32=>fn^!Pr3WdhuA zVD)7w8ES{@*#e`NyBxt$9D)JTY2?xZ>BIrS!WnQETb{nqm{ER#n7Ql%V`k3<#;g+; zz@t%VALl^%EGnBtgJ#j7S(NoImaIHRe2n-Q@iFqGCipB;biE>TEg>=Jj_VZAZb&ZF zQ5e-(#k-s`unR1gLC}ExWB5swST~>rd(z2C<+RpA@l>-Fs8%`JLWNZFc97^V)v1<4 zh_T5!#VA;vICEhF7vdqMy+VdI;UQ%Ux)^L9!vpAMzQWdFQ96!R6&H_f5@D8O5@9h8 ziKp!Kb4pW!V|qtVN0O>i=i0|L<7g)wik;-@=*tLT=8? zycp}$%%w`WVI4$PClVEeUBY*s2T51kx9Spje#ljJbj{bf;CF*EQCL)B*!BtL=LSks z%G=)W87KVYbC?PIzHx%_9>xjg=RCGi!vy1m7bX}d81G@6kk4nzFV%?bFJHhw;lk9L zwlxCFi?zyH4z$k$oy>z;n=d1*-6(4~);BM|5;&(YShhQ_ALFKs+3l>cPwCTmuT<1o z`gj2+ilDRG>w@WW)gxxWMnWlq1M2)cu9+Cv!2M}HBeBxs4OGz2y5t+TO?$T^*m?S7LW4g~JBRCZaILi7$-rL2+cd$MxoTcd<9F?cNER z-mUMg={;JCcS{}RX%gir3RHjY4olOU`opSTw6jUHvl+Cr+~N6CG`$v8J#>`_NT9PZ#{A+Mopz_IN2Ao3}{6_0xN zDBiaaTqy%@5}@Bb3PRk4Fe_JHEOh9vNP|jN;jbQ8Kn0hfl2x!Am8^pBjuxWpK_#mY z-ZFGZ3YMdiJ?aL@PBMbYab>3ef;4DRQ%4b6l%qyf@C0hqqcF8S3VSN%mXlb)a^%Vi zoLUo2GnBHzdbhEYl!MIqVE$*dszIi#QyHc&yP(Qn1G->rD2^e75T zuygb%*V1=QXL`qvz%w*v4u|4VKdnK@sNgb`j0%>cWK@uoHg?oUJW56dm!V`-upA}h zQ6G(k-Ou4gxiJ5Ur-c&Z`i_g_42AS1^hF^-+|II`-HKBK8R=m{e;+g`<{J5V23h3RkQhYeQG89WaNs z>?mA)25p&wNjSTd`k^bV3~oSvun-fZBv)CEqP%>PN}ogw@L-*+bk|CUTb-t; z4npZ`g{55k89E(9QI4{D;-_K?p|_ zhA4mof%K#xLRXD55xqOvE;1N~Y76UyiMi~c!d zt$XtXajkt^tMvP7Y+R6dyogDMy9)WSA{&QEm!!HS3de#X(niH&1w4=KO33TNkFL_B z+p@?KBgm~Z6p^-uC5J;LG%QwJS|jf2d^kv{5U%~sP#T#}Vq^OH#dOKh1fj^!PkIrNHhCaN&z6twVGxdim zw1|M=-xf0_K4@3y%>MBTyR*f7Lu>Q-4!%1d`@f*VD_n2wK3L3{_&~m;xSRO{BV#u- zSj;zcHCh&z(dw%5lJAXl|3y&pwYCeNQ@6;NUMnKy4kNHEcgD-*ovptJjzA9gbjkKIwg8voNl!zF(M^Wkct~>xusJ>r- z4(uz#`{s~}P|ht?1`@32AW`3)3i6%iKvA$-Wx$K3Po*P%4m(AKCscvJQxKPZc-wrP z14@0r!@;D|YszURa1RHTia(&z@%@5o*me9E;$%|+t5il3tXGilPE`QkY1^%5aIp*T zniOJr*Q5|UX(|_=(J}9ultb8(VYg^AV_T=J63cD3nquijw}D$tqs?i%)lB5wYOWZr zFczP#5G!%3x$<0vSk_%(taz78&Cn>`FV|XbG|k5I_oZT^IH$ymWk5l^SX$01@nUIt zvm6G&j@nKvaZ@Ff6U%`5KBt^i{Oil1&=~Lb-@nRWP5GR&+v+Qt8dQdsBl+m-3 zEN7Ngzk7gd=9lMJ(@%>lC+*3H{e#!JC%?iHm49Rjr%?8fVT!*fZn`1oAIUf_-!S`a z42PEF8}S3L^HjbO8pMBoN&lY5+EibOwWj}Hd91Zo;OoUDjadY1n>B5%`8|1$vjpxex7Mfr*uGkB?<-@)8TJI2Yxl9g`f*n~SaemZ_7!ey#s4?m z)mm<7PeWqmce=I1+3CLgP7jUZc6J||Q+;ze+poR{RXng&VG7_{g&}|&i|%SIH@A6r zwK27^>qtA^U2SSUna(!6;ht1)?exgmCI#Pc{H`It313r0jFQfSK|9&;3Uye`Uypkt2P!A|H^oLw&bRR1= zLd)gL`Vj|S{%{-mdcI+IjV~*udi5TJ%bynu!~A@Sb+o?@Zp#;@{Z&3}6jW2Fppfpz zbRbUxq`zu+V0iENO2^eX9tXC5qFAXmm>>Q>;rt(7T+Ejq$oKMs_o;B)w6(^)&QxLe zwdd`O&e$K%*c}^J;n93U-#Nx8ncI(lZ-M=$&_D4;3vOYQVCjh(o5&s7nQz>Tu{u)k z9WRIQO>C5fl=>i{zTkFJB~i}{8Q$pa*%(qApWZko>th&9jU8$t3kc(km5mzpS@+f z?Y-kgg?^x&2%#FW?HYd+)Tf%+)QUIH`7K-t|t4|IhDrtGM#GCYt*Y0*_)4PB9zIyna zDqF$tLrLfjh>&ep)8xzk^Z{PG_>?Y{yXiN711hh!-Ao$=H`5VwNm{v?URiK6oxS}! zT$A;WxtaD5(~85gwK)0vw5D84d$7tIb20rhb9u9Dh^mlbyuzJE$CIne-uxZ4H0#ZD zb2BcciR&hAA8{ERNGI##w@-92?IlGkDb}hKjZAq{e3SV|Mpk1VNKM4lsdAg*=0GM; z@E?uxz=hS3@*lmN{bAiv-2aYciJkWvrP$ z)O5bN?ZymbD*l1>D3Vayt!`IdK{1stEP$IAJ-gX(8+YZ@Q$6vdQCEKMh(;@wR zHwh&ua`fF~$zcSC}_qk4)c z`{q9|wWj-#aO(OzN?|kqA&l~~#kMy`-Y?4X{*-#d2xs=1w+W2GJ4V@`&d^05Gf6qf zH^3V44Sx>Q^B5&(JFd4dN-~d8GB!pTb6nq&t1e=cSn8d~?`Z$cD=P^pI(Lx4b4v<40qv%D9f{%KThx*1zgN;&{4=XT_QS>540Rw=3 ztOk&l27pn(1ccd+>p^YSyEX-k@-}usA>GC(V596WzR%QxP2OIiTNvi;+bj$de|smW ztuc>Ma6GRZ*Nmde%fo^_jZt9q-|& z9xQ^puaCV8i;QzrpKKsSL-u0|i%g#2hMw;I*+X8WO6!bU4B-9VVaBeoBEyS#ddOI%REjcolJF;dDPYgM=;2i~wC z$^!+#BbNX9OueBb+`fVJ>{Gg_zw|Va(Pi#Z$NbtM#Ij;m=iNd6G;$vSi5KauGXOZp`+$MWD^CT)4%F$b-Z_CZnpSuN? z>lPWx+-y2~J;$(`GAcci=y50;Snt?KGc zvGIw&r680MeQ3MdY>za-UbGeJ`y?aNhth$=F0^G*{0#Ucmf^6&d-53wP0RYK9C~mW z$9xYo-w77=01iZQI1s^+NNn_0BUsLIS%!&8PYRTM0)#{l{Pe(j#BFqweL|+ug8bip zMj(~5-VykdpAq;eYv^`7^1qHpe%>;P^MpY5jIa2NKq4`TM*r#02&C*+8~(38BVfG} zsh$z|DQ_SAj7FbXPNN|vnW~HOLp@W5QHMqMq6O=X7wL(_Visdu99O16&=ZbB&=VTh zJw_*)mA##Fyr0~mnYFz3_9>2Iwq5oXURuEIbo}O*wfHUn!T~gTr+8_fZoJgb6LdU| z^IY$I5vbX0{sic6ztf!h*eu}#t^~$b-)=q(#QUWA956C^yZH}~&AKd%WpsGtPIdvB zZ?xYr4578YP6Lzv4)^{?;)6fYgmSUTD;_uiO&J)^Waqz#U#1p4KEK7h{PEdKw&EuA z307LJDZb=Q+v(5mELp z;E{~EVozN8xtxy+$m19Cw81+2HG~P3RD(4*F~Z3@5Pw~9Y3t?oo|*VkhP@(_>eUQI zNW0bR3Xsjbp5dY+nKGM7`X2i$sebeJ57vl`Ip$PJw|d4;@cM;CCh__nL_*FuPU{oxpka|`2$2TcTs4~whRTg{7Q(>f;;#O^Hr1Q*oe#?W+gHAyl!iPUYnp`%GsSwtWNjro$1yYH6~y6@1l6 z(ydeJ6|rEa)lBQyn{x^p-qDPgpJoy2YR1b?Gro6x_}=k}aH`9P^Bo_!5py|A;-Z+u z#YQd^GaPaj zVlQ)x7!xBurbdApOypM2qX15i#he~plPQ1`1aqXw7G+{Lp9aq-e+~I-$X`Q#PN2Sv z=#Wy}T}1pvvTSrAo$}HtFP-wzDNdTR%B+r?1mPNP6Rf^WM%yu|+oEnGTrT}1mwTO= zq!CPV4XV()^h$b%-NfQrdRI&DYUv%m&MH`}E3gH86k9;9M@E}3m$A8*OJ6bxy_FPP z{xReV79MwkV&7tl&th(;b7Qq8btwtBg4)IPrkCAD*nv&%Vh(m15~3mD9IlQ=E)}w? z+>kFr7&xFH7GsLimyn@>1n~*t6Vfqyg_zM*Va#~BLPQ*uMr1L>0M7LM+|LVe&o9Is zz;OFwMvPl+xmHG`l_^}tJuQ7)?}jb|p;Q{dmn%ic;W0vsJz{BBm2udEOUX7|N^)u7 zDK2-0o0q~YE|t;bQW;ylRN%VBILO`3=eb1iJeLTaoEnBMLwo)Va;qIzLPb>MbjCco1%e0{n+-s5BKs}}CZ54p=P9Mh=vPkz%RD0=MPuCP?$+lX+3HE&;yP23KqW?b6V9W;6@ zt%wfq97{XX*)#S#*xJK(ET#*NyJDN_f4_$;w{ypKWb$U;sms(E86SMrIxjo6AJemg zzvU4Wb)0<{)LMhSy}2&CWIxWcpBOv9)LSSJ2X&Uxeop#ER{I?`8r6y_VB_5$L`>@LcjuNo!9b6$F)3|6BqGgaUSUamho`TMGuy6 zs5E51^iAub4yK?nyK_qZosNg>cRIG6u-@s&ZX->%x)&XG)Y zU(4$pnJ5c?7NZrFfxWX}uc9L}Ycn(xE{)=-S}${t5S3=uLh zmQ-5yG&l^q-=5d|z!{yhrseHOYyOiWYsQ(UP3W{+ZdaO}&iMfT$_qSYS!ehkx~cB7D$WUJup!YcNgc z{!YvI;@dQ`idUVGi-a?H)!>z+`PbV^b?{caq2G%4^y}Y)kV{}kUju!m!;hSWe;I`! z@ZjvHkHXf7Z1+b*{Kb!mtZN=m#LN@f9X#Ju_7$FL+EtSM(v#MtWp_SlJ=K)m#@ zLOjZh-LGY{B!0~N$Io1!TBFuii1npBl7?7cEvzprrSvd!(!!jCK%881TODG3h4T7= za?z3uGu~mwHq6|F)kfs#sn1^g=eSe?VvrE2f5lLdE$Du6kjah9DBiic_GfrBM@@2oeSW)0r@!e+< z@ePN-iA4tq2F7bPnW23{NRQKyZB7yEbsGMYPFO#zt8gG6!hw9~B%Xh8FyDfM`7jRV z!zV>b`r5I4Yj2g&0T|kWRvFog)^cyF3^${pYzDr|s{%+_t*H2g7#;gYIkV1kX6dca zKdmy*&H>AXdjA%Y(L5#Ofuc%f)KsW)DKNyu*2JXfJhZ&n+#lGT)3Z}>UsJG%Z+i@*4f zRHpN276tqjqCzNp5;SHXo`P;U0^OqSG9QL+3CzU-{n>}a zvlW{04D`;^&^up*-U+wkkY4#LSdGtuEf2FGaj!mtd-dGIMnB}rA;^_OkShmILX1Hj#A>!O|N4E* z;k^vaT84WqL%h!LC)Y8T`4fNdJ+NzAjoCOl;BKNZgWESVmG^s2@KF1`GMv0mMpoY^ zS~AxOnY&Iz^y`g?@@}8Gm-Ta>^lf>74n80PnRQ|Y-tA}V*NG_J?W4H|*ioqmq;K>g zdiIbE;J6}$yn1a5 z8hSv{(AG2Cp?6;gfdDGWYi4wdiK^anqgiP#X`3nheJjm9PpM~(R+1}5G)wZ%Vjkp(U~;$&^18(#b=pw- zQAbx$-(04MX@nLaFH7osL{6D6~Ie@Ij0c5giq65fG(|8Av z2D(1;Z*c(mKX3r~9e8JSg_g@@_vl0ikn4Wm0i?O^z5G9>lmkc{K2C4|nH%Q-(((hp zeE)iDG5@^?>bAS(>OG18nrHlP_<=w6&r%aZwHQF;Zm4_6}dF2r+-Ybt<@pu5R;_(1r!44ys z>=2<$hl#$6vT!BvHCze2>PpF-|9~HX-AdqXUy8Ek2I19nU#pPMuT-}+o5)ZJY^3r^ z`Sgc9^3d%bdE{Y_Ji6OcB)YrD*$@mrURX=Gq1j`E5OlU6=xjmI8Rj-&xEwjkGPFhr zHVuhj^EnEB{%g1lc(qD)?H;?}{SXwS>)=^g* zD>d+znsqePwes{`ibXqlI@-~)veL}5veHwQDcb)%GtaZTVA}cpdA%NH=k}SI&&+)0 zGoSg)XOJKT3ECn-+s2ek8?`uE4reB&G$Q8s8vsR%!Rwi&rY7JbOHLEl0FbNyf8;EB z00!Y$Zo?KYck?Ikrfg@jEl%SC=vz=D`gT*y6loTJBr4o7m|t|OHcU4(pl>VnF{x%8 z@YATuDbM)m*yb_*-&>8o@vLsY;t$_(-v4{owr4c@)|6Eb6SS39mG3p%w#Axp+i=MF za-v*gVBB^rjN6WdOs^w}vU~*NwjaT`?J4+iI?#}|)kOJe+gZPHTOVz^>@#lr=d_KH zw&MSRwhd=P$%Zo#owi9}%J<@yQ3(t@;-+I_0pvp?Knece=pA4Fit$p5kKpmuNE#r9 zj4j5+9=-@BGh9uYLYB@SzSC*mM<6#5Y9+hYQWBwVW^N9wmN1vVY6-vY(p=ravM0os=X+Q(cHjY`0+X^Bd(@Rq0q3vY=^1X?%H#}0O8+Zqu=m^x5i0x^6E z#E@OB-T`8`8N_hQa!9q_B|2SGGM*`S<9d8`v*Tbt-!IRO2MG#03psIE1u6-82{Qf2SLrz6=>r{@>Oyew!?bQ~bY^7W#juc*}fw zQ&0MTr=2eYC20kw4V&VoF`y(dimUA9Fj)4h%7`PyQd*n`&SHDxzqqmvON#d0geoF&}VQ9Cg1~&&!=?8(C$3U6^zr|l$>E|>fT{$QbJ2LEnyfc z-M|=0*4&JcG5`bHiBo^|swA39R@`+ukE(y8JqwBY1202l%QLjYhX z000wbTY$M8s#-6%0E0VJh2|BQ+!1Ozw&sb=N9fl+)%2JAKr;dguzFYc3Qjz^LgQudxxaR*Gj1r744!X@q^edDz~-s zbQcsB4p?2VFv1!QY~Cj~#Vzz9xP|`oJsr@R4oCo@|6bfRuEAa7<19f265tv-Y~H89 z=6$1k^|x^JO|TdW9{cxml5w0Rgmd$0RlPVjp9+_8^C@r{H;)Yh=jL&Ri*xf7ah3Oo!DY( zlRvRLf7F$^Z5{;AK9fmf)PSd=EXGc3C(x|XPM}%Coj^^qMyq8DzFNv>du@>~bruh`5WF6Bxffq84SS0lJ`2U4lRB=w+A^XiOyp?~uN=pVP&Mcn zKK7b}MY)Ue71MJq~wB~*$Ps#4 zS}9;WL3<5PL-DcXsK!__F+%|wbHNPf8-#HwTR+wMm{Co<%cv&3X#`1uNd9HEI<^px zu8mH6nk{VU!k%xx3;l!LJ}TuwGbDx@f-*qSuv@JurikPo(ZqBn8oWX z!t{bxfx7~L`F4p|Ft|<#-Z>}L!2oDbnR2vgJE)(Pod)h`(PPwhC)8_ z9*p?B0LdLwgR2F4K&P06q9m3$2f7o?B_IPlGj5L}vlUp=x2fsd1MXJt*UZ3|{mWv^ zDHak1iWg(?a&CUosV0`0VI1CPEg%3uz#GOv0dFG@DAjH7D5|sc zf3YTjo&OVso7PEgaVO&6tkHTOea2er?*>>C@>wv=^P9@qUjC#-vmAgHYgPlU;1IJ0 z463mSw;P{9u&gw+2Oj`8z><&AC30%P6AEKsuL$GRx1OO-r zsF-gHiTou27%NfJ&xbU$FMs7$`|_NS7dsXzNhGW=$T(UvFrSP5&s9=NaxqnMVagb6 z1I+KFzl%esk1-}1856Z4J`wRrh)?+#V(VZ-wq~6U+%zujg1S!TE5>Cku-Ke2yUa~t zWq`RbBJb)wvOVPf%ob)&m_N1SqU-cX8zO>1l9idjP=exa$s@%MEUW=5mAEpt;=OHfSz4xDA@i z4Q{jT(@rKP!U$z<4USQ{3pMDSxV~N-3<6yPwK3KqB{+s*ZVqhc-QIAvnzY2ijAkP} zMkoi4QOaBbu80H2)L_7aAsWDLM%dKGSueAxjol2lsg2zXv#AYjV#pitgKcMoQElkm zFh904no(^vb-D^bI4jzjh;Xbb%mpFm2DL$}xj}8vY8cc;JZLv_Yr+5z_u_+7ke6-2 z*z2yv7V})t`I6yk5(K0v$9rL4a29F_2~bNoD-3CKE5J2SS;t*;K1Xa>PRNxNc7L6n zpzRrUV9u&xM=V$>!4QSfJ+;=rpszucPGwh*=d ze-iNt;J3dBgL^P0$}8fSgL_GkrWnCM@>V#YOT${_NYVUbuq@&@s1|!Es4=AE#wcx} zwa^w?3#rgrNQKtIEh;pPG#2ilLW6E-A)fr?6vFlb3cE#B!P!_-LD!-2sn9giRJcWj zrje3KBNZClOKGHK@~1j9jkHXlL!4cSD`UjoocRC$tbltGit5s zpQ_Lp8tQa4QlY_OjWec3Iy64zF5Wt6K3ezJ%3Zgp&=?x(cKx{u4ell0L5Bu&pD706 zHhzh$rmklC08HJS@n*UT4e0(~snFDDDl|k_p@DTWU4^E&=FfF#!o{MEU2lXXRDwPP z^k#593bUas0Ih*TDG8mlcxdtVz)Sz^zHK>gSCT4*D`2sOUhC5RXMx|{o zSm074RI9K(S`B@@)wwv`#afR8_Y5rMc#Qxe9o!PAvSWdY&Meyr85{(C)n#u=nccET zb(t4vK8_{wcpfT(emk27;R9B*%b13Y2O#|mOBa&N0}HWBscBVR{uvH6NK0qinh4jW z|7$U_#8TbuBpzNj6HA2&Dhg#J(_KcY%@5{O6fl#H54*F;ZDL=C(65m{|?Lzy85c1&w5u4Nu;u`?|v zT4`SrRcKI2bvKnbaoI03C<Q!1!K2A>`1Np--`@BaXsaom^#XjgI zW}R_MK90m!66Y=)r6<-bRnrUDGs)QURV`Is4walQ=S#}&m6CJ!QH-!0@J3Znz=f5g zswXmgpOP|7r)* zWzc8z5^SDF;Ib$b;nHzxoslpP0ERmgjW|)V6K7Hx&iRqZ+8L+YIZ%+hJm1+Cz}o>h zUixxc(a{;u+_Rq5PPYq~_TEURiu@gsKMncYBYz4`yHj!6owoX6XM3Zhq}wHDdKnsn z{KZLRp(O|#=9J6O+^d4--rj{k6F>M@tu;9JKmLfCJ_6_TILEiHUaTf2EWt>^xqjzI zRDQ1Cd=bQ0i`9&?IHm`R6AN%mkMsPLk|h|rOVqTg#poJPjVevq02oe#!U2vnP}#ef zsn@yG#vgKbIvbz8ALD|fO-gdfT~6)s5xnP#V|K_MHbeHXHBj{6Xw5|rJdbDXIoTNW zKWs;^cK#3h5x;>;rm>4tD?VXu&6Q-RobvrhEjC+>?S+add&DZama(q}Pz`OLQVP{m z7H$xaTP2sm=oyPIE(HLL>f~Bxf*-DdT}pwQ3sWOdM&sKWf^||aRDv`=7k5TVu}j$7 z!U=9bwK9X32Bw+=e=byV_JY9gL^^2mvInb@b73`&Z4hdqzsOtzw0v23X>%B&Vm}NK z;C~TO3e#FV0kf_R<~)~OBEcD4DZ69>w-!5xsupZZ<>ZUEUMUZ-Sj#GbO#pi|As1U0 zR+&=V9bx`j+$Gr?iNeDH6V}z3Tg#aFaIE@JY-O)nC6_WL9uWp`gv>2vTtD+e@fGoD zrPvA7#-Jjom;p~Z@d`rb&a}XnM>-iN=);l+0+U>;Gp)3>(!RE}l5U7~w$F`4Ijj*@ zR0M8r0xD9>0uoRYxCX5L%q_K|BFvwFiZFiyD$+vf04^XsH__RlG|`z` zU#Q~}2)J*N2V7Obt!Py|u7)o77OGV>*o!f@1az3W#RRS85$H4K=77R87lb;4`+

@F=*k+1}mjVHiOw{Kx0Z6L;jiWAp`pF+Xw(apE9=ueflkSp5u{bCoai8 zhdKT_xPs?Eem}-rA~mgCN9y8>yE?J=0Ddu~OS-b{oQl_nT&CfmI90uznD8KbFlBTc zJn3<4o@#n=gI$iW|0U&8MqXL3oAl2TKI5jYd0X{HME`FGx@kXh7L%y&yw zfdF(N)E%Eq_zOz_Fu;lTUAXQdwj5A-(hLq`4*TrE^2!w(jZuxv2}HY$l`tm&@v8tp z8FQ2k3+)z@30kY6*gQd0QOVzbc}$#{*PXH$@q#N)?|4JlSFpRZJnI3^KreAyvTZfn z3(twTx{aW(dZZ^=pv!wx04;}|?>$;ZDM`eY8AMzjULn@wY>Bs2u*_G%d7{9w3Q=JB zQ`iY=FgZNj-p0O6o5>_PEx1J3es=xT_eBLko`E1W=*c|si7 zZJ@au7ucY@F)CyeW!^9S89fzRP3PP(qiEq=e?FUL6z+d9RpUMD&sXemXf2%a*9979 z@{FDMyng(4C~ee2*1$9Ve4Ztm{G&A=l+cUH(NN{RV+Djj+3N>nTc{1%Pyaff5vIJ* zrM%)EZA#itvV4$D-N_J-_oP3gZMJ`-T;V zPy>3uMga@`agGxjy6ZGb_MXdlNvC8VXmLjcnU!iSgq&4Vn#ZY0C2`oLv1?iJYDVWa z*qyb(PNR*QTF?f3cN_56C2f$PjcToG1IDa1RyngP*eqF=nJEHn2?ChothP8yH{K997P*RZ~HzEmG1+7X-i4}{p)!Bi>XJ%iKt3!U3tGl|= zjQw_|a|PcM=(4?c+f(AQFR&(GkR2)nOQy#fysr(8Bw8?IoJT!1HdAx}&HQ}uo?t_#e;o5tZ>Z~ES>bVPdOYar8Wg;1Dhn%^;h^0v| zpL5*%NA>6u8uE~8ormMh0vuB3 z+QT+b(pyX;6K`hVMn!S>R2MrRP2X$8&@wVDR>p4dtKgMfUXu^T*9ayHo0D84m^8Sh zIHYB6EhG|f?FKXUEbtp7XL2rZT8d$eW6!n*+_lOjN@^)KxwS-v)&jNx5Iof22=q8! z{4yvwtj$%F?WKxydo2dm*>EnINN3D$^XAG+2HE*iuB_}Rm7O~vQo&vW*aD*pW;Nq@ z3*{A#E%2vMG)V&An$o=Gx%{RR)Ql3`(3po4#2kV`U`4P>R<`BB95{1J4S)l;)_}%f zlELwSp>>ncHxC`9AA&+FFvH@5RN4iGuXoSq&=N48;r{C zT%)q9)Ck}VGq6wy%p|eGfEm2GjfUb}4w!k#fNKE2%#Kp})8oX1H|#_4v{@*Gm!Zz2 z60_?A1F`(4R8rdKm|dgrTF`xXE$BX65wPMu{1?z&_(H;qLGwblm{x~E!#8w`c|@q| z1BZm$i%qu}3Yy}Xcf2@O)6}Wfm@NNs;?W?ak(-CA%81Y{%H^i64@y~YuuWMcdLt4{ z!SP7TNd@wUv*6-!a|WhO&;wDflTN%OgiJxGTret$j~!yDQcdU<2{$&#-q0s&P5wDKw#LOC`L)Ci2>GQ(0eiHy%-(Q;9{A6K(YS9)Y@~+j2S{f zfP{1905JxW0MLLle2lRgTma-Q#voS&z?ft%!5HP?=w=>{Zf9-|4v}9*-g{v^{sr{L zy=H_N)gZhwW@N97;r<@pSTu-kw8bgOB`~STUl&u55qn{RvBX~ygTxdE{7H}Dxb7(! za2VVafMEZTzchw3h8$dnEWvfkm$Bh~6PJ)*4s)hJ!fN;w$9`yeB3hnQ6XWJ9oAGfR z6gQzZKEXc2fmI0}^e&09n5>vN5G1zEm7Q%%AyBM^KoPSf5tBs4BvCO*VBVh_YquI@ zHRXzof`MA2f=9to@iEXvp8~SF(7@vEK z7zlaFLQ|m%@|3;k#u{8N zI*v!YQK4<9&~{X4JFC!HoW5Sh$tzA}GnzM5UJ3>Y8VC}!93<#nMuHj;Y~_{Zw-D@e zgYZCj(g;w2V1vr&3(~XgT}HGe#T#sfi|}BOpyGj`LCZmdaI}64H4Y}g1YWLPj+blS z1t|cvP6M@Ow}>*1hX+hqt}m?_`X7s9{>4n;RZS|vvOW)VB?n^D)i}0?QL1NOc~tST z6$SJ)eE*{gW=w*s=%enkGuO50Bem&ZucYLkl$=A(L*L5v{zpBFnH2y_%nGLFCFzfF zzyOm}J^OhnbIwvy{nH+9YC`E}mz=p{BxTSP$vJqT#LTr>;&6jvZK-6531PW_}${oCCbXIqW1X2{PoI zCyMq(iS;cK>l-i!mGO)mlWPo~=Y$d)Q(AMXB__*9c)s%^bmA!|RK|%ZPzg>o zosHaLs)3Mnb|JwZ(Wj()FQ%y^t5VMLr&-Dg}{S zlo63ghc#?(B-XG!h@YF4FE-nksE`6t(!(AE2K7 zN|@4?U`hI+!QxgJ`>Cc0Wqgt^aDpAU*U7bKYz8cdF%~1%IJJf-ZEH+wy;o9qotM?hC?mE8G3stRQC}TH)JoV**nAQg z!G2^r7SJ77Kr4}U`!;ND_X8u_Ak79AQc7@f$cV%JjDjZWjx7N84hk#;02iZ(5aF?f z5kf0I|D|irZNVld+bgWV^(Vvuty-Mzsxvv*q_is`&UQJ(AD^Sg|LWXkFzMkICb$wb z$OiKi)}wUtu=89Hk;0*oH(T&iF3BAX``f-v~i~8v{BlVHqQ1nu}XSLtTR0) zR_Q=uogHdgW0M*`)giIg{5e+!-sJY?PDyLc(0nEg|7vU5^B?3w6VaHgAV8}HhErl;%})5NMBh@ zdokK2Y#pVh7GtjD*1}mI@;C!~Z-bZcqiS6%&AIgA@6KexrX~2&1!T^pFMex~Z}FFH z%sPw>Uncv0ANjIfKRw(yoWs`0^EuZ2s&y-1$N$&>mT@vG??!{LuIFaUo_4OS=kjD* zvg_hGv@5}t`cvy#N;Ho|({SFj z(XL;}jf=U`oR z4C@Zg2}wAHLO8nVm^O6IyP_}o^Y#k(J^4~2I35P)FyKT0js-7ov=Gcn+|<8qtg2JNyjIQszyXPpEJZ8@y~XTJ_d zjGN(X$iLPwY%;=L+}kj0F2e5L(=g1z;Qu`^4Cgni7{FHo197Iq0Pb!Wwub?HxnY=x z0o>Iv>==W$qhZ(?2JfYYVV4-Z>KFcdKam~nUG+crl_jgo_DiBHopcrbGL-rD{qj5O zcs!-@t;n1A9V~D$QVn|HF=aQ3bDg@9( zz>Ys1qgW6}l3T+y%!>S@U6-yW6kkzsg&Q{-4On`N9-|8~N4kpR>};WA#QoK|M5!|X zZ@4(0ScT0Vf$*RPX=@mc(cvY8;n5NdT-tcP)tD8MJ6LkKmM?*FeuVIFU>F0C6EwU06Z|xn@ z+2%HY8<*8a@t)x6!O%4vNTMRPSsZXoR%^xk)g1tP9%=wMBCD0+{pC*Sr*gP)Sy7PC zLJ792_|{R`iI3tb4FI>uvS1Xm47W@F00$hA)f|KP4(UAiH-H<1mpm|v@04L82izj7 z2}bc9(%B|7034TP#vr~^`g=Is;+QNWM)4gIdPZ}=pf#?tg_x$e{DbX`GdTz1{u0my z1Ly(Ahzmq#mL;2s>+-9;1|fAb~9~ZC$1sDZ4Fxn-Bk=`yQ~l6^F06X5N_8 z`qxCnmv8-(_5bH&^&G&6dK8{Dqan^~6e$h-XAuPvdVl@m~hkU?_w(VI(%U~NoI z*X5Ifj@Z3ejAdbh2$wg@Mu8S{|CS((*!R^CD)Al7Zb5AG$hy;{Kre*m5?CGcm9kXQ zN(ph75>mj6v6%9L=|!2$^5vPVkltauHvjDIh6^jhz36U*ERsDJ-AM)_s>kzfHIgxU zK3E6sZ01D`458gk77HO znRPd4P!LHl^PQWqDeV+Q<_Qpa(e52V)5-(G#W&(fGsJG&Yj-{`1I+yWMjm$v=pQ_4duyCV~KwH_)Bif@Eoc?ZdRtp&C}l z(N)oe?_w}o2GI-AB-xzDhEDlN8rg~rG+)Qid1|nW7PTUuiKc!%hz^J$`+dm_tpNbZ zEKQJXVrxCwqQP!kEU6(r2tjd}8(h?2?@Q;#p*h@&_MpC&D_x~R*=W*5&9pX-yerBo zzmKksCsWMz03Vv~de(};tbv z-zP{8csLuEXFMWsQ+_{PnTRs^Sj@}kV^It{?_{kz?zfmGOD+ViaMKJr&5nf?5%TwGD9vflO;hqX>u)xWZ{Y+e-vc-IUT`nPZHU0)F#_Q z+OU}JE=?oT_0c$6L(B}V`E#kE1L+|E)Xk&09R!I170+=W>_F_i&)INr8#lmkv$bD%@xaNWENTO zi{#y1w^WNPr~9%^z z^UO1ruuhU20D;<70wZHrXHP?#pk+_H;kuRut)b(3lOZj6BI8<-=-G85QILxs>P>og z;q$&hZ394PF~%&x%kOa~4FvsX6b@Jz?7lFF%pz_1tf_ko+g)tYDzVk&V{5T! zR+%Te2zd>q|3{bRlUAa@lODPuA0(gGKFUik=7W!5SOUl7zBGiilH)nhyrbg5U1^hV z5!b_0hm)Nmf8$=RAC4u1Lv9q~K0Shr(c0B0icTDLn@qIVXmAT!-ROeRQB8$&p*jUQOb4D_dAq-6`vg+l(o&?ViajWZ)% zJT?HzhOy+bDA4#lU3(8kw!r;8v}hda#X)*cxHpU=ZHSp8I`^#GGoGCHu>|hj_mY8f zOOBAT&os((T1@2rXK3hs;4(N;%Ri;V?<4=v>+Fua|4#W96a?fuSU{c&<8`s0(;$E8 zp8Nn=?yCj8R7g4qa>Pba5m`vg(=m#c5B#sYati6pyTw(O^&LI+AQ>Vmly{k?PsJ$F zk+^hf0Ej(P13{!uXYChb<1!uo5OQ%m+55UHA0m%yBi7!_oivk7A{sJ&(U9@8M%8Z6 z^|L^U0mW5rBJ|8G@-J34=GRL1zS$%h&*6RXhpUAA(ef;28Yyup2{><&*hF@)HJQuS z*~;g}hxt(jiwEnO9~u?wsfXP-k^T@?A8iwugqcaLM7X1s=zedLY~BbH&Uqc$;e+Q{ z9X1`efQ;AC;Xb&4tRot4G?tEDL^_A9OF+5Cj|GgnD7to0z`R=j2+%C%Re5X8nj$Vi zXe=}@7U35bV}TQ%SQ@zm+zC8b%i6ddOUQmdKi+F;Kvgy@B|m5r%ie{qdV;i3#mMXe zqM)%Z(hs7E_V%KBXYk~V|oo~e=^M-2{A zAjXJ2-p!kz7pK)Pl`eGz49TPL+%{!;NkGb_@Z2`#=@PP&%^!>LN)HV?z3AE}!C3N^ zj~z&RIYDQ*=hvZhB0Pff`;w354$p941g&&plf^xGHhRg41A;S7 z5*!$kU=|B=ZyH@n3fl6zl#Q{N&Hy`-EKaVI*Hw6YW|m8e! zPvpFZ)|Zkttwpl&F5?-aHc!4}bHll)DbiV2&xGOUtk>ahM<4 z=~rcB2^&jbeeTBv0a<{tz3)O3EoQxmDf!e^J4PJWs^Zsz<}|GD(5r?DRwWjsY~&yY6pc21_)D|AG#a#9cM za}3!c`%1d>8FURi*qg7UhnNSI0!}Z``e(@NnSccA(5@1Zv)1_-jyGDU8(0yxwU7pXrn%1vDvqZgK zq7D}ZlkVBz!cOXn#u7xYB{?LXH>R#qvlBSbN4oUz#p{5*m(a+H?9bf0*O3BVBPh%7 zXsK|i!)J8Wha{aIizi0+ko6?pw>r5U&ym$cAiMnUH2MW{qdoQnz{ysK;^b`JFz;R! zYEa;+G*zd8l3P@EkK49^j3-*7s%hm$%+)b`2-vA-BibRFdyw|pgo!IMRcD$$#K1IR z(*|{SJXy#nrMnIrk@E5j*^i(@%;q<#V-pz?!ZR$Y!(^oKFOrr`-x(qbK2U2hUE)C@ z&cRw;-l4HClH@QBC(kC}RQw+u{vzOvW⋘1&^q~v58DyzpaB#cJ@(H${;xNf_@Mw z`#0dgeE6T3@*yJWcCW>BjOV6tl_V_8jZtLo|DYT7g>>v6v{xlb7WFN!v6w1($~b4& zKLJ{&GPVx=kGHupk5#Fp1X7IunH|tY)%wd|eh5Rlm zvIw57q;t!!F`diH3k7m2wrcG-?4G$5Qk8fNS*O>#o+r13WjLMY%&_geYIczb(Q~wgi zn4T;o$qkD-R_yQa!JD{1;p(oT_sE9tnMq({evYpFTGeVejBxNZCP3zgq3@tXR>N9F^2KQ zDgaqq_mZq)^nq)6@NPU@+^|8Ht12gf%#BQpg+eR9B7(FIR z38o7UlOc^J`%O&to0#l3G1+fovfsRAvR^ukRr!jbLGA@d$OwJyZJ{Z#<7lQ9cI%Oq zms8h*e)TRmaCor5#L!OvB(F0D3d3(Bz4T8KH3J?uK3HUX5ti60GpBxt!EKha8OxPo zu5zaIuVNQmU9b~YUH&Vcc|-h@W*BNZjKTd;B}=Df9NxrT>qnK$AUGxt%taVBs!4|- z?)XUh#(~M?HEg7F-yCn0=J9b`|c&kn*Yb65VtE#RMp*77Dl0uKz|~Jql0AKa=R%f0MIX z$ECWL9wR4%c_$>7ZKC5o0n>ywIrEGgXyqrQRT#*NvTNW5RZ8BdZVui+k9jY51oA#}n7Jr{g{)J6i*`QfJ)6R}9CtO@)!BATj>! znKbq^*~=4bw{LQxm!QkIsW8_hGjatf9y<&_#L#Y(2X_7wQRuMwDL=0*T~uswl=8ghI)(1a2?ba zsv5c1++NP7S6EE7zq|P&4;d|A{|@({vFFGuqFir3Mjobw=`v%1^8xPMufT9>Nk+02 zD^|zgD0=K`(&m2FzB+p)L-Cd{EVWN=s|11PqH?sR5`ujh`j#>_`wH8eXyiv7LZ-0RH`c5b1_Ch-OJF-zLy@(pV*OM6^ zq+P$q=2p~e7CrqvX%){mdDzea^Vt5t-2O4NBwW(is~I%%A{n8dyozaUnr4ArLy^Ed zojQ;pp6AYcl*u8rzI(`pM1m1HGJ{^asPDbd?1?{+tg!Pa&*;tev4WKdY~3i3X`X1{ zX3DoEU#obn&4a{w~1{F^8Vw#aEWw~!^#=)DF2%qF7|V)XwuJ+ z-7bM=dGK7f=VvmI&k&Hm$K2spNSVxb9LVqp-?@w5i!5a_YJ^2huw6xhke4r{p5H*o z2OvYxva#;atK>Pw%;!MGQuprbAYFeB%nVspkOs_Q_5oXa0~=M82!8o-dg%tKF;@Vx z*<9*Ac$3t~ny<|5AaWTAGcu;!yV%zgk&2w$x=|qS2?3;>jz$78h>VX>ha$Hzm!Yh( zvI@FYk;k-z2R*x7!_%^Co~{4WG~6I3G~vk0dxo*E)NzJH(GdpO%CZ1&F6|VmZ}u{V z2%J(ZGstBOxW#zVMI(*!^7b59#q*4T242%F#$$gJh744kWj%oFOVQS?kR`K*~&y4x;#Buq{a zo|IkmQkWbiJS}$7&~UwNrd@?WzXs7`VRDORi*f}Le%)DU`qjk4Lh1}6?CYI$X*f{F zVSl)jR)*`~>UO#hh09*KKW{izp-L`xv$!xVw-|q^&lcgY3OMDjXb9fRkv+EO0MC3zye|1PA_df@Oj^pO60vxRAqp%9RLBGOjurmFzFr5GPCV@R-) zZ)yc>ryfL#p?dfwdL|O+;VBRAaMwr5RdPH>gZ&$U9?x-s7Vo)iU=*C**Yr?JIiYP7 z8{GDfvAA5T@o#?C9D^ry7F%t8zU!#@Gf2SZ;NR-PeLcf$h+Bq!mK#v4sR{LPP7?# zh8~NRd)&{XjGqe7rcVR{%rHf93@Rx5ZfgDcjTU~2VUp7njt(>02E9M7tO&U(WUCr8 ztc?rvNA78DvmWa3KV-eyLH>bb+G5;>igpI3?X2I+ zhSJn7@(9s{ZNE@Q7kRqRbE%8GQEv~9CfK$-dgv#+~++*eUYA32Iv^^lMG zLKgRwZ?x3V#m0AFf6|qA$*bGuqmiL2hq_k+`pVF2*}T!uU3c>WewVUWAU@=i>y*KE zkMAYdllDAG@C{3dWEROjzV1dc*NPY^`za)Ea=qRS*Y(u(8*XHqqK$Gk%d!)0+l9i>RNr=Q$I^mdt@Uh0R5DT=;9lX7LdFVEauc{Pi1m0h_`gX|(12wFYu zWtUr7M4oG$bZEcnG`rkR07Hw{Ygr)gsHaEmSkZVgK7XXOB$lq~FB@odp4?ny$*QNF z^5k~$VjSkWjBHJI;G!wUZZo}=Q|jsBJhYO7I*%sj$-Tuq-}WU`(T^8OCWFAo$$A>w zU%nhT^X`u)3AFnFkbXP*icO=@0|fQr<8Bo^DgcARzm{p-41d~O2g#2!RT5TT;3KACRxfn#2J5vsd4moZEchpj@%#-sb8rB~*fCf~Tk;KhbufzK zh=y)eQocM(l#R)^K3|7+KuMZooyY1notiZ`GcwoF>1XwfjaU3(78`v)82egoRl&$wMF^9-^+0?z+I z4-V7oh(=x=21+QJl1qmV#{lM_P~i>3^%i??x-TL|n^u?t-ABqjZaW*7jYLCrAl*j+ zhgfY~75&|G!6+RcSnPI>l8=kQYVRioE1oLDP&p@?ei1_uq)|*5zQG_0wAWsolP-d@tTkE)eh^c>OA*bjE1 zz2<-c0RRZ=I@3yDjIlGV^~Kb6rm1uFJl@VUf37c2Cf(zUF=o;ezL@eX+I^m$r!0$3 zoF`Ap+>UNH)osTqs;u27{J-pgFbm^4t!!R+{-KAvFV2&jHoY4)M9#s&&;=Oia6Ws! z*HdwbJk0YgF2X3zcBMPdo$2+P#`_MU-HYWm#oO4Z^S2e7G`nOcrsrkMssWco+qhIUt_5*!GE* zc%XZlL(bATVq=DTt5Y5kR2wp*xjv^iBRY_WJE9wevyK(%TZ+o+ zn5!GZ5ipm-iV|@~Td`KTu^e?g(q_>jbR?zOw*-<9?h#hLpr=adKgTQi!!0J_<UAuUJXh3e8vcfy)tY;ztY$KcZyYA< z7d&PS1*_=fH?UyosrS4gKQ26t`k4-YQ(i7&uB@VG-UKtr^BC9A?uXb9^7pprQYi zTNQKYW#43@xj_53oJ@3Lo#V;Q7A1eD#a-m&g~v(Vw74HcoarZjK3i82SN4Y%r*QJZ z^EtYRIOBB@SAMgY(-%=-CoS@lmhop!nMHvewYXojxZnJ7whmg@?~KrLVKmQU>!gKW zvuKxwv`d2-BE0--u)}WkJ^2k{cA@o(bB+6}qioPUkFH;_qKVu3zPwEzbj{pH|1Do8 zjqqg(X!l#9igvRGJ{T;yEQ)j-(yDLJ;zCfg7Uy|Gt7cfk>SIyoeS+t*1F9d2Rq>Dp z@z`b67@LYBXUj5h|lA#6&Yk(A1MU|2rkYJ^7@Jqp)(; z{SPL%OFxwN%4WzTETJ@u$xEi+9(bWwJ%R{M2*u zKrv|6#jBflz@E-QNqsg5$`Yp_t#D4&GVDG#d03q@5<#-*qK!R1>^xI!+zs4hGS=i#yZB7i65{ zaca8NV#@v9Wh(vMeV9v_VRPSb&CN%_kuLPa1$k1uFCh{M#j}Mjv*B7cr$GaH(c$09 zSA3xJzmtEz8~43%l(W@kuDzCh1gBU*%Q9U-hU@MK)aJ>zPg)c}dIxEo&V`m{hHGkS z?KL%R>opA*-D&Cfpo<*Cb-n4C@8!z^19^QdWlS0?>xaH!{%{$aUrYsY7#ylW%Ar9& z$dg)f%p@-m$Vge@ehJm6=>HU5`h(nAMB4j_$hrY;OfaaZjd9ohATKv+tlx{`w85ak z_iWP`l}gr&TAADZVeAT{o>tnTg;(*ga5iBiJ?&B!_u!|QlK)fvh%y?#B1^mU(Dp|@}$<<`fM?k){B8sH($f`EA-TLtb07G z{ReladNzg74d5MKr4w(+|M0V2rS;i7!u%Uezlog{uZig@U3F9L9mWB8TZl%Kx1^_U z${phU#a`8mwU`aRi=q!-r>TF)W7=wovJWsdKhujC@mI4k;-R)UAA|Y#>>a2~P|!`f z^$%1=cqCVlO>#fL$EEjl%DqQcY9nP&Yx+qCWjXz$rP5SB)0#GmRvwh6 z^`eWSl?UWQz33;=%E%CFZ&k`g$HCvNl@eEcsFx}w!G8*03=bFnPx0Ft;6KyIf1#28 zherPYHuC@0$X_q~#i)oVkdTH2LaDPs1)4PSN5bC+K=k~{@Qa2Ccz(A}yOEkGS;txRaDgZdOnLugHFWr(3K>!I1PN;j5id#uu>?`fMVB_dN0K2a~x z(2;UO|ENa((LO&-YoiQe@D{aEdWLMsQKj~x5^uCohK8Jhzqjy5#3^YZ-TSIiYvIp} zQ~HF=gujFEKNE-i``}L({tx1mz9CoP&*Xl0T)fhZglz4nN>-7en^hG2q*eJb>%I3OH5JH zdJcd;aB3DP4cqfK_(i=$`&RkY!jr?iMPe-&_9k-w&qf1Bn9NdAY(EXBsMks$taI{6T9iY#rDH$pI5XUCO=)?r+G`>oF`|z#AcL|?CO^}Wx(jU{5N9EXI^r7~c zy{m?)QW)~QIE-#;ue1m`F4Rn6LQB9^u7+tee&`V z^sNp`jJ8m{pYhm8Rq6*AZvhc);OhPz@W;cyC!mbTM4EAzQb8wmRLpYgQS`x%O5dm# zN2!t-3Ae+A9XP2PZV25QtF)kRbx~l573+C|BekBp_ic2UaZcK6UHGJ%)yC+YKWo1Mr@ zkh*FBx{lyw7tfkg#7Z`gQ>Dp>a^brRANyltoRu}Pe}XT54g4xTJ^hRL%}}cQH{&-W z=K3ESim?ItS^Pa(q!n%oD`4h|1nDa3#)vobA03f^|Nh3FDWN}hRk|4_&PtGIj=|Kb z<=iO+GajBby~x_fI&;cw>AvxJE)*GPkM2q;bz~`_#a9;z82Xq$2tNy3kE9bvCrG*Y zoewt%ZV}wcqx}66{YXRqDWqXxC$zlbT3$)ZyK-cLl&aM?dJ zBuLW`xE7ydT7oneft6E%z2=!pKN{9svC#1yl_qQ;ADlo<*@1?V|l@xjHz4X~^ zum}6@rSE4eaq{q4^v7%^F=D~wTRJ7NyRt%FGLgR8U5U9Z!cR;DwM4xc!ed#R;f{tY z;nOENLvEm7M|k)ok(VKy4lVK}h=DN1yKrX4Z2bz1dSq zY_|Rh5I-$VTZotUJk5a4rGxq?iHY$KsGMA~QILaA_s_s@ zEBDi9`Y3(n_y_0*eZZ$&MS8|y!QbL;<$1YRq5HMFl_v=M(IrQDgQOTX_;I^+BgPy2 zwymP=$S4tf)(wJ)7EYyW`hjS`{iGkNe(>)JQm2=ZemA|@PnqAR(==5Yi}X{05XO#% zg!C&|`0$h>>jO)AvQ>F2;u*Y9 zCQKKbgiy>%Mwll|r_bA!38@~04}?EMmN+Xs8gxr^SDr!-CN z`j9Hwk!39Ez!1m&bo$|Mg2>XDzS3Dyc*pI1?Z#@e?05XEGtMKQ;J%Mizey76iig*LuRQNee{9~i2)D4Ut#DoOw z2bL_DVl9|IzhKD&bLPyPQZU;(dGhudf_nXZhI$W2<@DJLrc9kO-#QtYJ^n}@H_k4Y zTqNfH!f6FS_5z;zBEn!$Rnp@gUNC?9?5S2MXePbZUzy)@$xM-lyR=}45*<6U;L#=b z&zw^@Lo7dQk%CqJI6e!l84Nn%8K88H_zK~S!ZA#vkEGC9)HF~Tlz0T;vGA|J2w)u= z`G%j0*Q3ct-=^~iDk%{wXW{k&;$OhWSjLsJ=&pfEPx;s^`t?AikI^t&mAZ@adJR%S z$L7M%_6X-t7~7K={^cJK7mtB>z6aFfUO`}Z?Qx*_NP@JrQTQr^=l(N6T5yzZ8l=QU z90Y7u=a%?bTTaZT?+;Suhmbj9pkJ6x2Mt!z?ZsN1}lB!({t#r zh>wn)>xYAA4y!xD#30?2p#;mr=h7$gm6*=qUt|1!fx zF>}5V?G7x{16b@&U#van>4afQT7=_aRceLs=isvXzwj`9c9@bEu@8P$Up?IOF#T~D z)~wJ4G-bGw7m~X`>_nC&MT|}FWRAL5spcK#k^mUyn^Y63zcSx;g6`& z0K|QXg&PtEDbUYq7XAo*V3g9g*~%7)QhrdPlpdVO!d@GtEQ7@ z_iW_v*2w=L{A~W|=^+~D^Go;7g`d$9-9I0{S>tp+W0SFMEtYzsBt7s61O{f>2Y*UH zrv3OG2xJBPY{k*@KZW0nj}7#*J-F`Qh~I2)o8B(9Pp_0Tn{A0rwEjsJ7?7|OzmxH? zJ%OINLJNb#rw)O;Dw@7OR>|;Tk+qU_ZTO@_Y1(~>(o%fuHTTf{!q38P_az~YeH)OU zeGE<~D18z;X^wHL%R|<~?8%VwTdOS$ZPvc$dKYBuOWR?Jv&1&O}Zpwq%c=>)z* z3lb9~`~3NH_&9yUpCD;TqI3iw5=gWl9}L42b|pA2y+kbF7k~q{(CUBiN-H0sLnkTo z<+n=c+mj%dx`4C{e?|S=KTcA%(4djx(q z9Ci4m@J9srAA}$E0a?}BlaBy40CayC{GtNx4-1sOB(}a>3-J9 zY!D&eiWy2?upa&jO_-s?(>*hkVA`fYX~_Z4RJt&CDBK7=;b&SWB7DtErQ1k7JQK4! zupqn#pcN$AGqaJOvAzr;dj6k$ei}0iTT0t%I(rrx40qEkrAwS`wP3OTfjq2%8J#jD zJ!~4GeI_g2Qgr`xt&Sbq4=orvefEQl2h`&x$SiF#oiiH@EPy{cTgfoKx&{N0c3z}J zQ}1jgOg^%PemGm{6BFwa3}z>g7)BD0Xd?JD7tNTX^hwM|JmU@Sfy){bgv~Q!o*swa z1Q+R^nWMzT=;?MNoDJtRxTvF4zlPS$QMyH3gg+U6tost1 zA?^2ipYwU|ygTRdxObkP&-?S|e9q_m{eHivtq;H0t|IkG=80dp=SFBjbjvtKI)%0E_GdU{i83wGcNT|M%PNY45z?|$HQHa|7^Yl%6P{g zcN_n{PQ6>($B2=G+;jYuxzi?>r&3ppkt;q=V+OljccY65Vqg4vWGa>Lw@gS4>+U@f z4$(pUJ%oa1@mGdm8U-HWe={EUFPWI?wNIye|89~Yltqp{a`CaspEEJllkb_nJu%fc znbFDrb7Jc7#00Y3!;f`5|TTM#kCNmE5drjgc!)yGrCZ+l&>is2?Qav(@Ql8f!|JgIK zMgAP%7v7o5$gCoHoB#Ku)bWWC{;_uuU*zAwpJYxi|F}C+z5Mleq*^6+_VT})oI13% z^u2~TQ|`CFlZwXpN8gz`K66IFuBI~ohPzT7{ONb5_RU<2Cxm_jCp+}^Kfjar81(DC zt7)6xdvPkqKk%;90m;pW`b(=)U0cg&>|`!%_OHDwbyza%Fn{h{oI?8@<}aO->Yh1) z&0ggHa2I_W>UXP59gryZPpC{Cm7G`Tw|kt=Sc-Yxn%!qt)8>S~>)}*q|D(!O=fo=i zyUNrdnc01JHLdXzcX0Prcz3E3FR0knl;@AEPVMLa^mwXmutxG^W12tlZq{hM|McC| z-02AatGiRjB)0mU?_q0H`WN4mI*lq9-jg~fQS1LLiFD%LRL{(5$Lwm#_eb8B>JrqI z%OP=^Kb@S!JpZM8>GN9u2l-R)cblB*o7sQBZsA$%X#M?(lT$rg6p=F~;lDP8HMq1g zwNK_~^2YmLPEH+_xw&XpQ=R|n6h`idDXA`rQva+eY_U>*`V<-(;lDH`H6U~2aZ>fM zG7t8>FSUPi)$zgb`r960J8$LIYn6ZTeT3Kfci+eK+3LS~AN|_uH{O>Tn3;d#uBJl& ziK$!*481?qD$(CR_x{vj{JHyn{?(j~{_^`-*P=mmG2F0?_owzvO!8Y-QDK$er;6C| z{>Um~7x+`F*to3*OX~;8-%qNz-OU&h^ro#Gw$|^-emP`U(@_7aX{r7FN9U&6`4v-B z8OfX>{w4RNy0(#9jRJ1UIt}sXPEB=96#K7ErRrJ!cT;I^li&UUhO*zOyP7)r-KNpy z5f7wVWftHnf5S{X;ek{xy_-WAg*H4u-)Eh+tEs?WK8fulwJ+naUhQ{#klB>$pYR}C zx745dAUjvS|DGfk``a3;YlHleysRT}`w6ZVT9K zdrePuOlFMxx^N!cY!Is7j0 zH%L&u|Hs2@+@XG-8Prqj-!X#<*7!?i(9?}K?P{9mU;8wDXqu5aFquC#tU@-)CXR*u z{nHe>R8;{QYOAu18a6vwyAg zzn;y8nBedKC|fz_!Cg%a;nct3QKtTCf9j)bgM9zPM^gi{vL9xZILUg0+LiimJd*0> zANyFUN21Ih`B>_-WLCAm^s&^1)Zep{X_>8-$dTcV`#3%RT}_84 z`Mu|)(up1Zk~wn7ecu1^u2ip?b2<8DKOY=tGS}yN-UR=Ixf~$sRs`9>=ofY49O^Hb z8;t(HB-dLh1&(YndgX4rbo*fDWp$U0HSuw#)jEI9;|$zze*=Fq2fx&OsLHM3P*sof z!}(oO!~AZ*d7&$Ca`Q|6t{S#wO-(9;mD*6lCgxPlZq9!m!U+8>e`fdxKan~lnX$@0 z?}^l@jOr6lFfkVRjq+!bf5MaWgFRRNRQPwupK||+C)p?s{tu!8zwJ{&NN@SG#y|b3 zpvQMS#p;&(3!mahSmyumRO-si@^5!F)%ssgW=FVjUaFU@-8{DO4nOfU^JtoX)zgf6 z;g7l-`W4eTE{*1d-_QT@X*NKa-*!HmC(l1=K9%PAQ|6~mYcZm6R}>$gJTkU+A}A$jTS_ zM=vCFw12t$srIKYWPDcp%NMdKcTytSbQv=jv8EY+$)3=-l*3cbqExFEgYcT9|G<1U zVF|0p>E(t+sZ-c)FD+vIclv)XqTUMs;Kl3=>-@_XGqGAF{ke-dsPa|EcNVA4Xi>Ch zQ&WCR|GKBS_B*~d)jOHh(Z8;iyO3Icd2Om^YIx@+|Jg~Y{aY8ap3}I`DfJUeQUjCO zUHp@maE!|LuU*1=mikXDVK$WbjZ5gjEdS`GbYPZ0aVa}+@qtZE{rwZBx#Q-9KAujY;XH?Izn%?!~)Tq*BS`qyheYD|uMGY(RLSobZyK+#c2p@Lwi9`QrfpbNRb=fd8lb z{d|DG-;4asEAo$#zbgm$7s_9*c5dbGUY+}x^BoJ)lNS{E`_-imJ$W1X^2|c=i`9vuD*vJpzT9f~(xLvMS`l0_o z9iI@6;Br}>pGaN&d$!&zclj*lAK390oECnhBOZmfVQuR{Jda0ia!oJ!FXMGs^Z$#> z`2LAp8B2b9F7!rY%|EsU4-6-mgsTXsGJXVC8^3|Q6QT;X;%wu6xlqhCJ`oohUyF;4 zXSGao+cG%n;Ti(AW9{KKoIwY44RZL8EYqREc;oR={!Mrj*79?4AtT10U2r9ziT?`kH1Vx@(pg~YKLQWd_%yr4ND{Kl8du>Q;}@j@M($F_Wx4kr~YDgWW7H!V}CIUWp6M ztbYhsoBCeG^GtnPa6>rh)4^`hfur3PGwX|SuBqS}oNp?ai7RMO4_t5HQqzI0xYl@I zoG z{P*!Dlm8puN(a)XxeB`RAf~@Lb`QmavF6{5XPEpa@pz7u#jgB6aGg1F{)ziD0y=^R zaSKyyM&xX~{q!E`=78Ht*vXglm>a9S%#o+MaQ-hPK}X;t z0?JLmU$~A6G`}Zz_Nz^W=imlh=sK9j*{1wrTxsGz#XEk8I{0}G`(F)LbK#Qoz9c}d z?$ixaRAC0$SkE<`O?tUoi53a2}3 z{tPUqU$u|r^y{4V9wk9eCK@mY%aKl9gR707!nMZp@hanmc%5-A-emkN-r8)=|Id-I zvpFH@y@2I(tR1MsaynMOg5`9qehn8Hzk!DuufZdX-^HWVa{hmxgbA8}Kg3nW>v6U5 zKXEPQ6cKFmUC!E}1osAVO{mVma!sgiiS_pl!TJAim!JVh;H{=We=KJ|%^!f}dO&>~ zmh->*1T1Ga^&l)~KlLeCPS-4Q{!4nNk|5{va7lWnV>y4TOR)ZoLMGX5&YFKamdkMU z9at{I)s8KyZa;Kx7iS;KN(!n>JHGd73 z8yEGvSZ-W`^_LNNp9Hyc(SQ%J+_|XNW4Uut{}apgg8CCI*9+>;u>R&lI+SrhXw7el z<%&qgpC#$FAz{6#pe=5|$C4`*^mh)z@c=BBXzJszT%xH@!1|*SDL>j-^RLI;*rb~a z+(?4l*=WF6tiM8$3Tm7+|0ygNnd%uqwav^PD#Bl9-1}@`;j2G;~KC(mfLZ4F5Y3> z1IrbX<{yOR3Q2u1mh->50L%GbeHaeT|C(?(33C2dAA$AvIWhtxoi)D<%LRz~N-P&3 z>Z`H-o=3{hbk_XYxy(Pgq|$_H0_2iP{W#Vi07(TOIBWht@J9!eu}Sz2lr;0(1HNDh*Y=6gD{tF!Cr8Lv*!25a_^`ffXj@J!()t3z;Z#N@q^MN z%pie3K?4=e8gLVqD4D9mL-cKaRBN#2fH}ir=FzR2i z{-{bS=+-^7=I3F#8&>zkayP8*h2>63-5blDQn+ybFC;;3l{BC)mRlwDkywAVB^|iT zS@SQ)@{CAbj^!DVny+Sg`YSIf?>h(ia{hmm1bGIe1?FIR2BfaR`m->pfG=JI*8GpK zJZ4jWjO8(#`d?U{vZ?E_JY|!G`JeQ@AVD6pg-g==3fJPpNstEHkR{gqw*05uzpJzP zPq}?p@6CVe@6M#WoO#8XF9%t9D#k+l1js>29*c!b(v#^Rk4V(=pTqe-ZUKW1@eRnp zntv;nM>6V({HHvUQBUGO<&lp1F8*^?U}>KY+(W_w6EFpDGp>@d-06f%(tD8q)L*>G z8ouJJ`LAKQ`&GZef6CpjdJX?6_lCjxO9$TNzvbpo1K#I9<5ZLX2ZIqyCcVGumJDf4iXl%Xye|@oU<6C|3_XD~ z>5#_1hGhiTyZE2^JUAG#$(dn+vrU7t&9B7^vZ5iaz?1kfrEaoE@%Z*p{v5o;xCWQ* z73DvL^Gc%f|45Uto&>$X_yh02N3q&6WO+TQ&=fcmk2x!<;8xthPI7kdTz;-{lb@|^rYyJ;-+5u6Ax8aK1 z$iHA|P~(5Y8{HgY{kfz%gh|9#i8UY(Z!x>&p;&&7toeQMb}DE=f|S1mZ#I*13YMQM zYy1QF@Uz3O>`DAfxF*b(^Do0H3s&*XQ-<>c!Vks(-q zJFVrTm_$DJ3^aqClmi~ zyx#c#uy;^Y-+ytrIJo|xPg_Wky&znY-v4k89_1R`r*~-0?}X(*r0#;{SK#Wd_;KTI zxYjri%df=4_Sq&qNmyqBdSTh^w8Gw4e?~6b@O)>@zYxoTNPRJuBb~Yw%g@c#qwtIp zbN_z@3G+AhrM^5#PSdNtHu~?3z>hbt6=D;8lWCflIZ0`T(lOTsy zEwB*Feyy&>Q}7THq=HYKHUD!gyOsJ&EPI8z0n1*a{tx~$Z4$mALG}U-_zuggS8v6# z8>xT7GHKO6W0|DtU$N{J>K#~qb+7&lr&pVVT_mhGP8`ay-8chpGj55yoD)^p22U_< zi>r*YvHXf(JFxem%s=@#zb14fKz`P*?u=I(=iqwd1Mm*x?s&8-E!PhR;%I4Xn)hqC z+mlIw*ZerNv6`F(vxn9yrSc<8LBlmxtnWxuX-?)FExUl+OxDzWU>^AOe}$8{6Zr`)Smt9nd*-_MAJZhr=`%PR(O~ND@PeYk1+2hwKF@XyKDaA9pVv8G z-4tG6ta0wn)Rgo4tVFm1bGagt3zG4!z8M*b;Pk8Qr5i|))2${HWF~^sYb#ganHGuQ z{GIE(N6SQTAkj7a4$E;v{begIx!CR0xvdkjpEr9HmN^j|s*{}m=93`XScm94EN8O@ z*Wms97(gyixVjED$#Q-HBIk4ULHsyCPS>2)gZxLZoXl9SzZ65c7s%>RUUDhavY>28A_S|pNS=2_ky!c zgR;gq;cb1Q6&i;(^_AzZ!IJQ9Az{^#QNRSegBPyQAI676CzkiLX#7I#9TmA2uQ4O= z0+#o_X#C4K>%3@={1@*$m-D|)s_a4zy{5oE_*4q$MPo6R7szOZC*eGkUxDT2GMYaY zA4$GmwANsG*^K5tjVGlE(5u$G!I6^@D#Y?e8!dkX z9z%W>2{MAE*pARBoX(I(*uj$Ut{`Ev8N#dZcH_}l{(XRONqX1gO5+=G2NORQ=a~;I z#$$Phj+Vcz5A%PENw}Q=+2$H>2i|F1iDesV{=Ha6NPQocO`@KPWfQ2UVcDeWhx;)9 zWC%53CIK>J>e+abaW%HP)#Er?8k-8)tz;7G*|g0*>}HHec(O@)L$Qo-HicxbdLT`L z4521uc1(D(N%ZXXKXxnGZMG)D1Ir|iFfydMu7Xqd=lpKUS7CN5ufgrc`CU01(x4uQ zuEw$#>De;<840o*X@ln+kO;Q%Wv+oou)g<5di_I@B?bCkf^;$xZvykGFz0g->hGmee?2ezAJCyo{#<8dgK1~-u?hw3}`O$7C= zONhDuE#t^04Q_S;UvXrU2Dp$33Jhfv1RZp4G7ZQY=yTci>uzB%3 z=1}`UNpC5hfrCvh&;RR5sM7?T>dSskh2fI$THprqd2SHw$9-_t`O$%E1Ri=q6n`mR zaAM@kurwGhN$+wj9g>ClpY+N}kPd`P(z^z)#d=_w>8$y)v2;jXjip2C$8nDFlh|&e zr?Fe)_}|#X&$)sg4|cP5;(m!>jYha3|2LCRDr`%%Ou_|G1}f`*w|b?IR7*@b<1Tpqk?{}!B3k5cn&}Vf5s~K+vLj%1TXGzt0eI~`f)F4I&ct{ zmuUqTt!%_a&dullB_zlzxr(VoCQ-X1LWgzokP5G01auM&!1Hi0i70V7mREvld^vub z{FWrhvHu}lG&stC1m|&ymo5zgGQCA4tliSX8y+~rdj;*PLADV%ZHF2$GY{!3QRS-EMUv^aw1QNUsOxrb!sj2n3UeBU=NO zO`z_65VzM9P?zCylRqEJZrYz6T^jrn+v|fN1>Afxsb{$kE$p2L=0qu$BWe18!+2o8 z2ypx%A;TMoWdyqW7#d~KmiHTfdVWqAk_TB@Me>LA(odD z%EJ6lco&n9%i9dKg2`B3QW!1??|wYOI$m%5CZ2F!kT2)o zg!eWHITtlg3U3?U8U_UUzu?mIqY8h+wZkL-f!ALcc_&^mB61VncxmM1aa`t?^4iDn z`X}LC;}Ud@uftpEP;mFlkj}=fm~?vVUxRm;4*!JPlAjfHH|gznycfLxGORD@?T^dN zfhQNsoN?#>q}PK4c?)K++tJ_|&RW4)Shlfx7?w9*YJA#R^Y6s6&DD2fdD*4LFLO?7 z!1E-?98j;s@`g(-@U^q%Z^kkw)ZgOC#^2)_;~#Jx9!UY2q@9Wbr<*J2LW0bJa7lVy zae;9+Txpz#XBhXywZ^^h3gg~*vvDCFf0B9r-NK!)kkBQ1L`6y+gyD- zmIIagL@Y--^+{L`bn3zQq_jymjRZNeX}}qHw39r|J_|=nW7D7>oD0~-av+go`F;63 z>0~e1oZz^@x!|81%H_~IhJ10CLgLv?Ws{08$7wlGXu`83$bms-u>|~uYVi`O~rN*R{@nVmS^Bbsls$&y2`l;JgIOoalt5!#^F)=f5&(G@(yl9=)>PSGWR| z{dm~SexB#<3r;wK+bK3lFe#~EBbIHdBhc|k;*GP8N;JQQRvNtK_(U+L&T}1Day-X> znMB%wp9qji!v`Tj4~G@=A8wJe+0DhfZ<1Sav@|wNG?w@p@yO`pv)2is`G1y#w@;b` zd5LPU2}n52Sp&|%XE7)8gYcwx3znDEYW@T)FLIR;kaIjn;E@^Pq{PcfF zkT=+B!Z*0e_&dDXcq<-%Srq>hE*Ta1XDn~P)%t$L#g|9kf#r?3Ek zHqi0d-5WRw+XkM+GRf*)17Bm==Kb9o9&lVDm}Dg`|6D9{s=;|GmPy$Vod4;->m&v*uLA7LKPS7Iq2JTIU_dzyIZz~Q*$+HS6W zFX0^#YoNa=AlLPMikULO z<<^M`a@;1kIt&QvlLn^7Dwt;SrGpO}OZ-Y)aDCK)m+-6`Bd?P9ap@@GbrR$~*t!O9 zVtL24`fV)l%T}+&|X9hVyVRCvdkxT=AIrJY1hP2|Y>JVcZJ`FX0Ya zOnAL<|C^%@6yhUdlKV^ItUS>RO5c6-fNjRT?4wpt1UWoIIFUEEfmEve=Z0gXR;|AM&bSj*r zKle%mBb4WS?IC>F%?Qd%6J!#feiX-l8S-3ek%YRV_@8k%jwrIt^}uvCmN}wFx;wCJ zA}zla*PHU^%AwYjzX!|v(zSe3dLW-(GXi?xc!&K~W@)+W@x+sOesA*M$Fj+SXTj{( zSts)}8*BM9uxvv0G%RyK{V^U-2a4Sar%O)ZexD)M1I5EwwwdhbawPi%%O=qZIuGW4 zkqR$x`4?f?=B3Vav24<_oJW~WB)jE3yKlm@H^l_Veq3cN1)jz7>i2L-c+cS);}@{~ zkf{#$H*@9{JlOa(d{eXe{QnIS>YEdi-WqHt*}FJe8k-K{Bq1FrcAMx6yoTTT88t9NGPi31MFT%10Wv;>ZO$TMf ze!vZPL?gBhXWtq57jbF$$!c0Inf5=4L!}AG#an0~T(~*KKN=r|2b<6N4#pl8YJ35f z2GoaPc|*VYa9k2vj{iyT2oi>yfd07FcmTGWF z@+6iHXE<-h(&1Ke{*!=<*=8~V4Ft$>U?h*pWXN)yPv_($Lt5zk4mT>Y2C_}1d~3N; z!Me!?pOFaGSniZ0|9&hhIL!G3T>)8t*=A*$;Cv&_x+~gdW3e59@v-=Oa7JYmKLuY) zUE$KgtHRlnqx@OU?);nZ9wVWX33vvVjC4cBCi(&wnEbEsD%L2U7Gw^!KH2k%i4R5q z_rvyr<|r)j;ga-@!JA9@{@(~!!R28>b3i$k3bnvBSURM>4!_|fSG_mjXlZQP&~5iC zqA@04M*LB{+xP$Hm;gBt)fh{GcY*@iL<{ddlP~e>j3xf(Sp2UhU*dO&-S`J{pzA5S zZ{uz_S{j@3jHQ87WAUe(d}*M>SmMXU;%~v)KJ7F%O^6kEGFISelP~cLjHQ8p#Ns!Y ze2L#^Eb)JyLi=`B|7`-KK=ATyHw04PpuxM3(7`xb8k-7?CI0MK{JADy>K{JXtiKev zJyzh3Sb<7oiC+|pUt;p5fn~-L|4A(Vv$P420-KDbK$1HMJ3^UqN71}AHnlRA_(Nmy zeN4X8-_Ka$(-*`Fj4%OG;8J5LFew&)m&up-dyFOinOOWHlTUoQX^BaY0{@5=*kJOd zfsMuz|7$FMhsl@tzlj5E?C%#0P7Z33h~wXrntdMy4;lP~da8%z9_Sp5G?KJ&k^=|__w1=^oxhp2_u z0Y^(?)4s+Me@ra?Sd%aH7aL3bs3>0czZTvVCO`^YWh@1%@MLrHc@P&*iOyyZVY`<+ zV#-T>*PqFQP0H)-`mE-B+Ls1iiB<4gbAYTjKRhy)_|38SZ%w|$e{U@DZHMmO!E78X z;qj-jX>Uy+RSKLKD{zv@mj(tKOZ?ba{CJZu@wXXE`~nvrw$CP5YyzafQe!Fbg)0zt zyM_0a$(Q)AjU~R_>ASCSdmJs?s)Y{QmjWlm3Ji)BIK^1v$Hd}qHu=(_Ta6|DnOOXy zAm6RO6j%}~@OiAjmnL808;qrawrA|V#@RSp*u=&XUo7#>?Xw96ngD6wWMe6ClN4yK zv4uCzCnVj{3Mev@plJ57KT zxZ79?EQ%FaV)7+^nX$zGD;8gG@+JNYW8%|I@}$7_C?!u0nwQ3=7RJ)RA+h*FO};eH z$5`Uei^X4H@|pjQO(RT#6u323V4}&F1|}Ix{M=al6DD8c=NU`Iepe*d-!kOFIs zrNDQ#0P$N*zQq4zEb-ZAbFX(_bavbuml=1&@~=C}!u;oXJ_#etz1u)sZXQOTjP)PS zlY6}i=Wr|Wg9LmL_%rNf(l!)3UdGd9+n36f}!I%JVv9!Y)Z-f z{46XD%C)6HBY-KQ-#0ch2q|Na0`!+Gh=0XacFS#yxSNsh}5LYu2bYwrf;q z%FCP>P6s7kJKWdgOZ~%eDfNf#v(?TgVVY_1LcGlQVq8y!d(c7I#`ofNCjUO1JuO<} zsW@+xdqP5d9WKB@hwy*!ip#kF@9QdT$Fq?wmqh_b<9Wtqc!u$VxZ>fc!H4jg8Id2s zV;+e-3(E?GOVWD`ZyuSB66TVy-2^;=V??SH^_j%>rx(%5wGS$xzd+i*}a zob_#(^)d%?iITlwzjOKaIwNujmRUXmZ^k;Q=3*HUbpw`3Rp`p+pU1;(tWVMAVVNVP zsj&Wx^ZESWep~J2^ZD{Q6<+Qd{KfQGX6;_*?!HF*;Am-V>SQeOePi)Qnta(yjy~7T zf2l+YTpTM<8Y?i$SmN)E#ouT0rGcr&62Ckazanh{q`-^DQs9eNfv-%y#D8rp@tJ(J z!EWPfCrLk!bV~O{-x6dd{pH6Dt%xGUJ!Q+k3!M4`(OfAxj3yh`Kv9b8^CST%j zGbTRW}HHy7afJKkzM4(~L+1&{Wl5uJc5jnjDWK4sKIi) z+V~p0o%rD3zzWR73m%Q)XX67H@`GG`Yw%W+|1KW(SP(BeVZwW#gyCUA;16*Tn_!fy zV23kgNqT=_*<|68^mbv{WaTzmkYEDjGsnO=7iWR_}y1F_xh@^Q3qDQqmWd~huO zG?OoT#TmvDe~pWG2f-HJbtXUx++ZvP9&iQ1PPFi*n|z6%VJz{>WAQ6YzQn&6I&5DG zY>XB7G*;kqV~PJg7XPQomj?bemiYZH+{P*6q|gBA80J` zrLp)?CST&OFqZhcB;Jibt+w#)F#%Fwim?=!F9pK>1@Q|_zQorWOZwEE3jAy=@q1phdx!SI(bCwokFmrb9*aNXqNsh!H8%B+6*xCmV7SSb_=}9C zfr+vBNhV+7?=qJ7r!Qjt?T{`o0a9SGu@v|K+xUN&e2L#+Eb&`o@jsb-iT^om5~M&j zpNZ|{NZ5+;kT3O}wKEhdy)G{7n)+w_EUWjpVoIBqG-Q4)|3XlnX;S{T_$6;o!ni zegtq836&JcB|s{8%ULUU2hStFgUcUqX=u$q4twR%FV?QYb*2NO@fzdn@tDW8gXx5K zBMGypAd7mWhjZ`(ESo^Q7H81oPR>8$W#n_^9OUm`N&_Z-FkXil&>;UNTtNAwoaf*i z9RWH2ze~abO~8#<8Vr}Dw;f9X_3v0JRR4)bVLsar8q6CRTJw8isbAd-%N&p;ErZ^h z1ldO6!mo+3Y$J7FEJLh55_d8_8p}URulYrIvhnd!-h2RjB9;+np?x}V5()b6*2|Dr zI7@`&--Kn8giF#Jhh-C}Z;^a+`!fMQM}B((q`qgIHGUD6P3De&NpA@WvduJL8MgcR z^Eg@>n@+!kFaHo9%n5Ff9>lW7!K}uEO8Kz}mTOVDeYywBq?9=(w^u8*K3RX+?>CZA zIIo*)BjJ6Di;X|WIaf!MF!M4lp~%mX3R%aqox=(e-nqDfeBG^IaMt`fT>mr=0d&9a zI4VrgfX=vLe$=5Hyycn52jH3ok-Os$%?KWdJ6zQxtd1_`<2>U-aN$+vB|wLgaG?q4 zgKLcY;bq20;nl{+;4DTcTzD%XmJw4IW4lQQVsrlEGxH1CW(=8ZL)k`;V%Y@2fd_wp zWetMk1lzRjMSK~E5$Kc%y8ft#g#ff0ZgZ-WcZpSi$>eX0ANS{=tn51?EPi6#U{soIn!ZH%-7Dn6j zO59;_*VXtfxNu9gOHrCSTf{K8p4WO@%WED6Wkf^l=qytcTt= z@Gz6V2Jg@jVUqob>z<9uH{zUYqVhRq%n=-nAn~V^vHtx?Xi2x_Kyf1h{mi5pizgV5 z$I_v2;ceu29+t=gY{JrG^}n&Kk^29z ztbqEz*lv<7I9m9iNHz&0G>ryjlBXL;kWG;7w$a|D%zB1cXZ`C~hP*5l7C3Aqztdoi zbP{&Gj9(dIz7rH2>HcRLlx^IO2RgPx?d1Va^TPdq2a_NLj>LnXi>?Q5cMi+&8+%+% zzFrSJ=dAfJ;7UeB=ghxxHEu(IY@#+-ut_N&F6o5VmV_cRg!|%}=75BkgO`!tJZW&o z^I8GlwZ>PDj&8Sa#`z|H0zQrWJzWQ$!9z7)egs%cLJtwI;Z3FkPvBW5e?IO+ zc^%>nc$LZj1g|sY6XlH13qgnE_`_=sNEjR@1l|K@Q=wjh9f_Bj`~moQ@`FP$+w=;& z(d1u)cNkaToR!fCjK$r9^1=G^!weGa9C!*Bn*#IkaN~uz%(xbhr9$0}KgE?VMh$+B z3+p0(iHC`0{bd9iNVwTlu*X&5EDo}%wjGvPtZt9ztLBeWN;bYEPU@n$zsD1*=oKVlhTd{sf@R76Te~e`ktN(>tjpqE{k`&o5 zy{m(S=71EIIiLkvV3}m<)>yWoIt$CJSMP~suTt-YWzMMg!KaNj9|CkDLG}U-=z?Xx zR(Hj+jn&<-Y%+Bownw_2I9eK;dKt@s?qu94eQh+$Zy=$n{IKMjFkjsQ=e!h6!aZ<$yGh6H$EKe zlhI}J{AJdw(Hg%%K>llyUyk|pnBT-@CjMP~u4(XRT#WZ&1Z9%%e=Y5q`0i;E>PWiMfo^H0)ym;{;C z;lc+BSoQ+-Y+Pkrjh7iej#nE$iPsxHjdvO^z!}#`{lWP!=`ALqC`gbdc~KK)*L6z^ zZ#j;Z#-2V5{>sEFILV=EKMEauC{UCGE@2;TRI6 zhpn7fV%cprx(a^7vc_6L|FQfo6U#bC`CGB9aWCgpSk|z=^NVH$zwk6O2X^5>W`q+JJQBLj-2Z2g zAQgm5rq>eNP0$8M3*WiJb`xBA9iL!RzL20qGSgdvWetnnozCZ&2AfAP=X!qMbsgvb zEEzDL8Edsz?}Y3QWiS))z zzaZwt70kblCSVx>*3aXN52Fs$#rz7+HTkP?f8)3DFyr^}G~!bQQ z;H}2#&as59F?YjR8=?vh!h?(t!PATjV?HA0{&-dtpZ1O=VNI0aofz{eF`tUJnfNpD zvX7$<4#Ro>jC=vEFbAFycyhD(5#XgH)HEmXkNn^|tf$i|XLef_p7r95mcyv6uUoVecQ2j_nhhHC;9ti`3qA7FbV{0EK}K6JwNNcd$Lk90WjbZ|$$ zUYB#E<2bQ}LUMAt8Ov@zIKloe32R7@{kqTv?0E%GD(P`>qru7LEG+x6=2v4GLe2jQ z%WkXr2VcqA(Bxl(WjC&M_2-uJlQ>q8BO%W}XkZixvc}m2i05P34Li_7>0mvU-Adi& zD$LM(&dY)_G11yuWpYw%RQy!PP`o6=mNjl5pF*#l|gT&Wd?YTw~%p z;H|j%{NI^`jE&K5(>3NEF&~6GnfOC+k#Qj|F+Kthu87WlrFhNtod5N7cs~j2bPli= zti)BuU*jw)2<8a+SvT$8K>L_G;PIvdopH^-%nIV-Pts9Bk02p(e$0p9@=v1t!kGKw z3Od|5*nnJ5I0svl4v)mLLh3SHohG5DEAXHsn1F{cL)2X05iBF570kk$@S(2!Th5yQ z4wey9zlU?M9@w_yv?OT2?*$99cx!_mTz2(ew`4qW$3c|EXw!wN|K11TiO zojcN|0$GDOu?A{PzO2Dhu?FUwd@27XUTr4nyD`6yvpa823EmYkUxnxC5YmMicr_J_kx4{{ zwmWNw{)l-e-W)aPCB`uVpXms2|KEaye$5F9Zx83-WI_X1#qzI<`35}9lplwyjBmsB z#*?h&`TyNE0Z+y|Oo6Gm;tTC@!g~m>GWKJxj`?xC&cx5dTb$+m=Pe|mXj9a{QY=Fr zF1&Le%bZZJz-!H|+KYId$$uGd9vH>HiZ_}3)p$hs^*q`ClHOZVzy!R5vu}thcn{m% zZXJ#mzR85`Zg<*^d}=~{!MTOq>;WtdGG&6@ZUdJ3v-SLc|E=8nvD=goAP1riSay>_ z=OMT8oqBU`xDLx2w{sQlIf0jdGo+JTeEvkfF3kw!y8LUz#1}X(#X)?q{xsOA39f>V z%^J#n{Uf%U=$Dv(!^1a4oA57OZ|vPn#a~AGna=L`m+;!e0@~tErodjf!FXSs_f=G3 zPR!k6&cppp{K2^Dt8T7+w#=a<tDk$6@zTv~X?;%MPFci7I6 zikta3|0~Y_TQ;O?s(xSl3^^|96=QkYoMju^yM3e5s%kGlWzx3;!r!%vHF?a7)ZT#Jmj`nD}4uP~$&wsd3ZIjQ>27kTITnKjYSTvvE7T!+7tQJH^}u7knM9 zKrY_SoM;_vu%vfdS^{o#2MUga<8a}Bq5`*JkNj{*xii8OO#WiLj0*Ld?qAN~X5$6( zcpdrbFYspL^j9SGH{VG38cPqgz-BBRP=AZFv97^hw}jUGeQ;;;gCip=RP3zz194CC z-6y4K?>d*D0XN`-2}qD2x7XFqn*TVK5mP^jWzvO9ruQ_qlXwA+7QWJnqe+}jd6Ux| zn^|MskKe;G={8YHjsqV|locFqeN3rHyaUrh9;mIh| zTWUHKyaeEV66`kpDCUpx0<+CO#X}i^lr+c)C2!>dW^*)WTH-R}EIi4$J+7p@tT5}! zhsPwWHUTHZd~(b~@Fo*~1}^_^)PZv(-gr2c4u?z9y9l3&`vv#^bl@JBpaD~`^iW-e z?KXW7M@wVVLlXal#;3eTOuihLmSfwYmCX*0zX|Up60*OE*5I|6-@yG%{yQRei5ntds)Nf*F9rB}NF2Xy$O-DBp)j>kEhEK#i59e%&@)zR4 z-$h=EM;kxyJkNEg{q4KwcNC{hfzBjU8F$5NjCnIxi$DW7Vv4z zpW_1JSGf3dCxwPAeEV_I?!Fk8m6|VKf2ae7DAp&R(!2yx(H}184mZ zt&}#C;i+cK@8V&m!F4fzgvXft z^hOe9aHy3Y$+@5jul|Wgr-`tKXWhxorpcdxYse25zK(|_UVTr@_s9GIZiwR3-ouih zr%Mi<+i|~}q6dWs+{IPvwy1$TyxsWVm=BG)56)O|0b^f zC93b;nAaII{}bLvB-EM$8*#z*sDjU8{wn6L@gNib4KC9)WChz-@+j8icf>WJX`c>s zi4p?m#@qvMG!^8>dTOtd({Rc3e!()UHUBp(vs(QJ zmN}r_iDi?hoA9KYc>dp+6zM>(dxC`KfZljE1(Gg*xU*Jp5q^yPgv-CjS@Wl0*(B;J zyaMZ-crmc~{@=?atR|qZtMI?hTEP~)-BkELoW&;U?czJ$8(QN#W0?c$96Sgg;o?sb zr%k}AB;11cZ~9k zdH&`WxlS*lf$e5DIU(jjIRB4mHyVPyKO>)k`x&1T^YEB2noRpU&5&Fo6@DKL(MW7> zJjx`$d1-9A65A2^<^~=}vjWA;N*R&lWWF6whw>>SzS@+R6}~4;!UzI}N&p+-H|MYo z{vC#xcj3`y4O3I-fN?9lz__h*o~!?)SbqA{Si{)&eD!k90{c_#ld zJjnP;JS#FUf!sa8yCLQ=IAdqj;4Lvvz_}*>4m@aV^xLmmd@hz-F*(kBipMm^Geb9# zFs(Tu;e8$RH!**QH|}yDHdEz^_c7$A{3$V?idUKZGx2t{JpUg?!V-Ge+VwDv`~4lQ zaV2iWq|6BN`DT;LA1afK^54bce~h^imzer~!<%TY`ToD#{an|ZfP?T(7a&7+7S0feq_jSra01xb^Xcjy1S<^-vdf4nQ^0=z6J5a##68>z5G5Y6jR@G7j= zc=zH-SWn;2;b|uSOI$1Y(!TVt)dOr36VMlL$86#t{}Mbo*Y~s6Ofaf*UfT26y0AnUVjFIr-r3`I$J+#J9o4 z#(U!WG3NV!dy(*~Ia}?63z&rA!oM1X`x$q^8Rk&k726+~cEfTU(DHd$jsxnRc*$6E z{_jPC>}KJT>Gj4dDG)r@V~~eCYXukK56Evvf^5QjoHc(6mfcKUg}36iF8)Pv+625z zg6wu$;8mPu=EN3f&Ho>k-BSG{mfcF-h-EKOZ^yD1sDH=l2_!Iwf;H$qjR%n?;6N<1 zSPSH1nZ@cu@B*A7K}O&_XU)F=%Pd!qz_OR9FP+Bxlif%YE+atp0`=us_5yV|?sQV* zYw)k+>s~U`S>tD8nbhiPoNYGoT(ZUM?usy#YzE>jH3;Giz=kpu5 z0+M4t$CtpfIseO~3_8S@P|xG@e>u=prNaD=#&Ly2ez2F3|8km7q2zQs*j12Q$s6N2 zJxZsffe~Cl$X*Z@;K%85L4mW$mxs@nOrfdLD8B}?TQ&cK@LyxutB!K<3(N}21xWe> z5@f&B`~9}lLlZAWyuD)H2M;&<@qW0ZMRZ&^0FN=wi}~Q)o%RYyu$$m894)*w0^3b+ zgvpmRxCqZM4VK0{3a>Z$<-0k}KLAHUg9)g>JB-KTel4Rlyfx-@%y&xp@zGB>KEqkJ zL_Yi>9`k8^a{fEYCg5Z63Jt)=$9y8*X!1|N-p$d;<_28QD(dh!Jknh=KE~^eKgFAk zH#z4e*=s1j-^07-9}shQoYO{DC^-Kmyq+Xf?h&nF0UmFL^gLW{JP8jsUW8j^MdhE3 zc}2`G;u&p;589{4t27~)`uwXzc)bKTuZj6xoYgMMUx(M44*iY?wU6?5;!Va0p4*GV z_US+im!J){#vS&GDrkr6X+W=dj>g5z^58(kpqJsYeWLiQaEZyEf!CS**`ZzgbYN~Q z;0fGd3e3lyIz|mF#s$X9Vtyg!I$UhxU&U1&(@}+Q1__bhjro0CZ7TQ(uQuL@w-|pG z^H(u{jlJ}~QG?%*P|A?$Zqoh{CZRcy6yZ*tqWBYVHTl7@olP(rSD7LA@kZk}@o-cA zU7VJL;B3YwD44l>K%bcV;muUI7af=L{iRqAJmJDOr*SrxlbPgKIcxrdSoRwALo=Cw zvRi4wBLv89r=ErF+ejb7lgZB{KpJ@4S>xB@spMz5{GXjQ|5rSl{5@R$0Y39j5;UN@ z&$C?0Qpzo{9B{T_UU*H)~n!E_$lLW z@sq}TJ?eQ+IJ5q|Dusj^6HtyHH+~q;HGU1xG5!u$8+Uk&(|6Bk1Ulm_W&{R_i9bZn zf6{^LNvJ15x6z}x!)=k@#yd;}+i)ur-=&%(qw&eO$oK|aZ^~EWp+P?TzjWYT66To# zKjRU``_JKIV>}qIHNFY2FrJG?8^4Dq7;nc_Vmbdw2M(Ca`TF2!$nvnv+HgsFJ+aJc zbuXN64n)1NOj6A+#Krh>5~ThMoi+brER)!sf0JG*2{KDHU=)^Ft-b=w5UQ`jvQ5>a zv1}ss^;kBM`bI39NIe$k-)ipv$CDsKtO2)S*@o)d@lfMCuxujDuf#Is>U*&ax%xgV zL$02Rt8X>;|Iej3k9n}h`< z$RyH$#aQ-h^-?U8PQ4tOWiQhFm$5xmzlz=Vmk_?cM}j?8x4oaA$#AHw zO@%k3Z;`Ky9GbtwGHC}%KGW=m$9JE^<6^!A4>Obe_Qz>|%^}gOzl(q|w@3T^Qe1@d zX;o(NXSmGdXVkC_52XX-O9#f{ZRF=-iLb%gY+^mGe1InxMD4AwNrwsjDIgVed1Ci~ z+?addP7|U-@9B6o70ye9<)=AQ9Sg7Li+L8_X*w_`O@hZ7giFGEQUYi|d%O;pnfwjT z+Tpw>ch5gK<^udX_30JUd00kF+e=>*OSlBrnt)Nb&bS;m7+)LnjWLhKJ5BtpIBR0_ z*);9VBVpTN-J5%~5PN1DZ^X+ea4gBPO`C8H))DIU6y=T2#gmLD;c3Q;aYeKF{of}f zEa($;;ER|WV*U@_Y~r`zTso|e@Asa^7Z`EEb@(_u{)njlfw-6E%jf^sk&tcHcs3q> zWK>|T^9X8`N&4Sd{`WEefJdA1KjTBRL2B6lX-4Fzuzu!$!s|{#f$6~Mc!dj){Ii`$ zutw5>DY5(qVxErInDR4m-lQJEH?3t8Zo*Ze!}I@v^QpOiw8p(~w(()kYh4Gfi{;-G z^EjMq%HM`d%yD2oUK`}I{<4OPNvJmkmO1N?{}{{vCFbAoR#W~jy#45Cg}i5YhICBi zOy@djUxxIQSisPj&%}dG1;g+>;|uUs<4c@%#Ae0v=Qs!JFC$PxLaPB$51+=fj2Gb* z#?LwrPKGP+Z7hFl%s=5Zru=rips2fRpAP&%!X&ee@)mGsV|*sAFuoPeu}4=29papIhp*H&iXOjkXZg1F`tDeoAT%3_34Yc zdlNFlA$){{muOHA9IJ8u@ll7~#>K|(<1*uqaJlhDTtj`GgDK9D6IYu2j76OPSDS>^ zB#2Ahf#x)4$_G`H#C#5}H|2-pomcRoRLgLMX5mT2(F)AL)y7XQ;{3nbBs@dF8sl2r zV7wgXo)A^I(pfvaBbNVn%-&)SGp2mzV&-3iNoYgBPUAgs?uk)@9dMp;XIyOD6;Cqm z;arvqN94v>{>|xF!mW6fsX+2ePl`HFi8mWhah~8d*{8AmO)>u)w>mkhFTI(Bm(8Tx zvzBAC3Fv@NHTlQm(o>=e200fe!!^7kmVZyoQ*c#OKJ7g~!j>q(dl+{b95pZtze)$F z-;_%UyFy1?Cv$VAwF;kp9>C&%J;(kj1R-5&F22M9|^CR1}??b%>fCo4A&T6 z<*W~z=g0CF$GjA;Gv%Mh`KP)L%K5L3gyEWiU&Z~;=Ax2SmUDx*ltb}p(HdpqHO6h6 zw=zN!e|jwc?3mBRTU>eOKVO?AVa(8|flKi+<1*)su7PJ_`L!`Wi`Sd-D{$HAQT;FB z%FuHBPk67nga+3@;@ReaAiqV-t?>d=VLP0EM%2LGxXied^8&Y*T-2N&oPQEtX>$VY z(VwwEaijtY4A-F_T-X?@`yK?;tb|M zxbVwpEOSD=Bj&$j_Li|pO?)O^Z+7ECoS?lldri>bMI?M=0`9^8#+ttz7hK-mTknQ2 zV>xHXVNr)#<4wlxV%|IEj@Ucbod3I!@P^q9PsXpC4&8uP8&~7P3!@G^frlH<$8VVx z*nr=`oST9b-iiN_HWl`KjyD(^pN(rdJ%?>4K(6^ zn*5yS*$Z$BSK%qR+{6#Xvy9Keg_lMhIuCC$_1*D2^Z!eeP)op9#-HLRfgA$HmBaj2x$jWFLLKYzRa=U`Tt=g$Pj8kznG7Xxd?j|QTz$G*7#(+ z)%Y}Ac0&|jg6D+}&;RF=u*n2mh%0$&bqMXq*{~9iF;B&o;xglUylPC;z?WDa(}hdY zYry;AA+EkQb)nt)mv_07AQftXY%C3`_r}s8bw~Ux9!!FCu-IAi2V!~bral?VV?Ff{ zEKkAQ`9J9mC1FnzMiLN@iHqUzQs$SHNG|e_w5{N+q-~%K|<8v zQCJ=(Yk^~MHu36XajrSmmpg0zHMj@)NfM+3Go3YmHa<`;KvFJXO^^^Z@Gj0b`9C{r z1;64$On&*6n;K9qbtP@Und`DfvDUjnp;6G#wiKpG!m8d&74`AcwrlV9(w`Cs4x zCcniip*6qtE6l&+2+$t(BS5SHN8y2{g3FvW|8jh?$)Dz|`48hEtrz%?eIC6;GG z+CUpDx9jS*SZ-d`*;wwD)q7*PidJ{TtBup0Ns!BK4amWA*{nVQ%Vo2=JC>_t^?~>w z#`#$8BQ*aIyx#awEZ3YapZEWgAlGzSpdXftPW4e(E;`l6V7aPQAB*LpQeBMYno>Ow ze`S0!{&%y}NpA=Va*d}2hGMzKQ=f@c#CMwV@;TvCvHIsn z`EvZ{AN`FrP#Xn!Qo(z%3f7r?DgRNd{KqC=%KwDz^}+UL2j`!J_d5yO8In$PM~>Cq zUS%6|K`{u+BbDo%!%^YifQ)$@-f1pCZo^q)<}4m#&t=f%vjcMTKm~X|KO@118NK=7Mx`iaH z9v=<)Qd~~{d0sdvKf}XKgBfpd-EVvt&buWl-_Mzk|AIO2P%PiaX)`m z=i#-+3!Qb6wSIH={B|+7$6HPLj&E}O>2zDv<1Pdg8Rudd@^DFdJ#Yu(gKz=vLV)bH zCC(au4wg-%J`c+XsxNpm9S*T3j3D4O0(6ty>#X_rVHtAuRBUJcG#o9BO%G!`>t~vL zndHx>Nw8~N7xOE4Ck?iuR$0UU;z?$NzK{6_Ty64y#@>YPu1_53aItZEXDlJ{*6slr zc(^Ih8s|=oDr|?#jQ5VYQ_NlPq$ob^<^F$b=K@|ubuIomC+7szsGw9)QF3@0Ld3ii zEM8I3YrW`2r6snhf<{XSjwc-PxAPp7~6crT}5fK!S z2=X$R|8LFg&8+NC1i$|0YYo5k+H3E<_MUU*%$$S3`*R$yE1ah|-{F%TE{6Ln`wDnH zBW7RL4>lYP0cTfOMl?D&|Nja>&6!q^^sa(aH^h7mtmk+b93PF%*TQ;^4`pYQA?}ih zHnt(KjL<@yWTRUL%an&3n~$K^Bpw=}!EhttnU3pVNhlWwdB)>+szE85$*}J6G>50d zI^lbiznm@ag>Ayre-^*j5cfwKcnKb%hIEm`izR^4FNK>FzX|8CvqlFGq&WOR%(PEk zuSU>8IeZFFQT#bPTk)3;Z*zDD{DiV^f(u5+2_=||-XTl-B#^yV5b+@n9|reR0gi+h zDsB(2P<*_@ogD58uNC{SeGp4Fajt{>54z_bH zQ~n$^CjNc{R^fw0lM`VBNyBHV08$f!V4ctv4iACHtAwwCSJObA#PgC~J>2ewxPy9fNl8Fy(LzymnF?-~@PnU3~uk30$vu1pFombubCt18;%t>xBl`^Km%d7<`2w zR|V+sFM0^u0B6Jflztf8ZcJyd-1tuyJ!~6T1TVq9!sr{}l+t%#?)_>EPBI2(BN(q6 zfM+Pa3!XbA9)Xz-&vy7Bc%!nfhqug({U2+f{;O3+t1zfl+-fl=FU7_1M#aB`S1BF` z*DL-9yhQPb@C?NVy~_HVuLPy9G6IS(g;(AiXI$g(uN|(1w!KG z?xXCx{F@O~_T3#WfJemkzBdqoY!uPKoyxH61D1aY%RXRv5G?zE<;!8&2P|I+%RXTF z*YIrkWCXGiO*S0qIsZ>ZAnVjR+zHD%wLAm%SOu16!m>;)-v`Swwfq1q%hd85SXPd>x*l_zk#T@muhG#Vg?@irBO~Wh!%gs$r||14~g`J`nuiUah8y*V-e1a7bRd^lI} zMR1|wDtN`ivHvgOEf1^n|78ethF3%edwXz|!`Hxhe~knFpToa}tCaqaaAkdL@54jt z)%m|J6vPHMIXoVotpZGhS3Jty@uVnIvJ#&2_t^gfc&*|!a0l$8BjJ4}{=O1y6oKMK zhquAolztc7du|-?TX?kMtfeivwZn(N{!_}K4T9c}#{qu?k5GIpJVS9uxXnLe`z{W5 zcenuVr|gTCQh#%lpd5qsihm4G`)Axh9|`zm%x5`#uEXcS{gnNMaF=nNy*|u=Eb~!I zss9(}#R0F!ph@vqhi`HCHaPF8*nhIaQ{iz+Ki%Pbd<6B%;C{IJ={VpVc(mfbJN&rA z|Ae1V_Rqi*o{8hV054SRFLVTp9bN)^&&C0k!#VR~ej7eV@%s+1cK8!`WNhzy>k-U} z1>Poj=W}rbUpc(P;oWfc^Ra!i!^zi~0;O-&g8BUWU+#qHp=ijQ&l1cx(l z?hA23-QdwL$J_&+LJ@{9)8+jCQv`KZ0Q+!iLF`cH@J$Ynhbxu+M2DxqRZ4#+yw))F z&!g7}QVZh-?{|2P!+(Q2DEql^p5lMP{S`k0Hz|Gr&UqzlPtHGli)Abbd9lMw;JnBo z)GvqED}EbJEs8tvzQd~>{se9p+K1;qo^nFaM+Mjf4^aFS+~Hqw13TaX#Z3+;mbKKU z;3{Q*kmay_Mj!`)>`c*-^bUt>V0&e=mtm{#4g1ReG`Ms??0*Ib89|-sQEY5aZwE7J%Sfod#I0-kwvNdlsSG~60#htYz zltM4p7fPq|ZKosy6Yv6+kh}sKYBG+J%=U&WynJWcVn@Iu8S;hl>A1ka{{V@v~c3`Ys`J)^KBVEGZ)mm-J` ze&rHDeDEumu&#l5N-smY0@ew<>+mYLfd+dJRC=8KhUeujjz{Q_H>f`?I1Gc)%HT+c zkAmkb{c&*XC2_!9xUb@_4(B_3l6>KCjdCc)V7B54c&Fmt4xjFDUpVizIH8}w=O{iO zZu5GqzsPq4RSy3GE>#YL;J%8lfTt+F3ZAR@Hx7?**#A9(mC9ihoVz^E@OtX)APKXnF2A+%FUJL#dUJv_iOag7*;vWc3 zGJFbL2wVLXa37_g01s38XWUtn1{hO-ASc58$UhRpBl8{eE(ZRD!04_mMXXW zDJ(^9dHqV(zf`#ue1U;fx#i8URJr9wSc=s0Hdu<(@(x&v)begviq!HRc(h{gUFuJ& z)*58J8&$34Yfs?}F=<{Y;1NgXb&#L-0z=^88;tg7%x@1RitvNr#_?2PpgJVeiY> ze*s*jxB=Ev_;0Z{2le0BycB_+!{>N!CJFUPMyJaToQ`Ekw{bp|)90>PEqS>LD099U z)(LENcso2KN-#SAeS=^pMHim0*=V}H$4jOyagXxh0>vl8wTesO%kj5c@g;B_Y)@X} z;3;rNdMz8-0AD%$HT)v>_T6pnD$nb~h(-suZox7|mb<}Bk!hbP=#D_<$Ql&DGDntA zh8HR>hGojEz6_QcvRn!8Qv74MN%4QdIg`|Vx~C)PO%H59o`OiQ1;)zd5XOcC~C!i8bOT`9PIGn4j%!JQuaqX zd@OwQ`k6?loz;*CK#W%q#72gKuB;y1p3unbV&EdNpo&`6sYUH5)livLZHX4EOgK!gk zT-bkp;m~mCfqn%nb8Pt?xC3m@70Hhxw)!+&s4mI0h8y~Z{Alw+>a>F)2 zFIbAma&NdFdD6Asys1YBfTw4z@O zOOZxL(pv)8!$%vP8qvl3wq z*{%5M2Y8Z}WtYpG%4^1_VM(ArLF8QV4NL;Q*9ij=ocdrQOrX1AAC?|g8Hbl(>ESZN zSr75;==5-%u|Erzs?Hew|E}gA=8(O=b~UGWwMvGEy!ZT{@J1l0RR({62eh&ylJNcv zH{EGB4lYE(Id{eUCfq^sw{U^t?jMtY;sJ1LzjfTe#UfCAiNk~84@jV+7uC!yhCvQ~ z_)5791Zu*8}Sscu?tO$QQups17wa z{O^yczba+0ObiaO2_(F?;He~ZJgv$q@z(H~PwCSRAMEg2L;Y7P zK?e-_sS)T1*DCG=k5b$fmIR|C>E*%yP~1cO_YPUM*AtG9#^xd)fgZvertp%B9)^!p zuxvWq!H-T8&@QVvuq1FSQDoH=O-+OihFdi}mQ^4fIKk*kRf94m7r;x=Cq>VH{p%3u z5g6_8jqpe{LgV0u!)%We-mUO@#gpJC4v+N#yiD;-LcU$|U zw*i(0tbQY`d;BFFAC1jl!M4NHf0p;PGLRk~v9@IbM>~8hyiq0C(cw;T=@Fexo4mPo zI3FH1Gc0O({gd*_`M;xnhyK+&1A6=g!wQ6Ww6YV?`of=tUy3q}KpXC*aY> zeyicgA?a<0A4h+X(Puu3*y_8$GUt|0gjdY;V}}bvL2U3-SZcyLRKejCg#vz560WX^ z^_PgAA@9Z?siB(;hvx8jxEp%=Y4|*0AAvP^2Ehd|B@hPq)UegBhh8bSMf3f3Vt@Lu; z@zV9wzYcJjGLQhbIsqmqy#$yHYyYWAFa9q%{tJ~}{QtF{^``^8sthE+*Kpp^c1RQ6 zZin~4HA=Fk!0p8d(=FYI5<8E+#aI%7R z2T$WG(4<4d4vtQ4{zL?l;eb>mC{O`p1O_?{{9Nf}2!H7`Fi7de{}#vpHl@ekZ=UD` zn4%0Mzza@*mz7=;c*XH=PiBP0dg{Nid6yHQNf}6h6F+Y`#QAW1G&Y|E>me>w zdh!2-<9~_LQ~!<4gPj01%0L3#?gW^m^fCf>IR4X=Ui=q0{)?2J`seX_C%_VAAOUtb z0d_0B`0sK2y)WYN5&s^rF4CT{Ue15K3`U@burxOCB*3LkfXkF#{I78QhbX=HPj>vL z?$w9q-^S)Uod7fT8btR8{L2aOs?tjWuQ~q9lwK0}2G+~g+h{m!pWQwSfgYl4I6ioh z3hNPi@hskMvk&yeNZwxOD0^wJ0{9A3}qEokc zr_6EqaJaG?=l|&7<97somBTS`jp7sF35q+zI>Byme6aPxI>95iM+ptWNT#en>}~%g z#04^x}VuM{HMUS{cxGR-~@Qt3Gj*&ph4*+fvt}JcBPjD zb~*k{o8tDv1Ww%Ca!B*x_+YDr^^g`Sy(I7p$Nv(gmjnho{xzHT_CH$Zw>tqQIRWl) z0!&kSNnnBFzewpNfyIvh5?>iefE`YN-AXS3_Ba8&FXQnM{~oX|%AQIu{zb4JLBCWP zNPtV70GBDf1h~QpFhuFaf3o90Rq4h5&X)eZH$xdnfPXmwUR8Pt@LJ0N-ZG^Z|8E@s zZ;6Vx^Y=Wi0|^d6h~p{+B!cS1P^u|62R|?0mJ#Kmr6# zfV-4l0^F?w;6F?0#sA-q|5BwF|K-Y``p@!KC<6)5>;yFT7g1}a7ylAi zk6^jdi+``!U)Eoi*IOA#fL}QQu2Omla7`Q_^dF}5;y)eMTlFl5?}wuV!}Xu-J*Wam z0}sQxhsUyaOTbw)Cwq6jvX=(mfeTNJ&n2rI{s`71vc~#H`%h!@rwDX|?{rLr0lSz6 z*INfz8a(nV-V3S*+dF(5JVfbp;Z=EYf?eU!J@%ge`2qq2I>VFT_-Je{gmr>zc;6ri z6qxr7CCXkByu^t&Sm|YiYod5j|D^_gg<$8VT(1wU9>FW3TDV|bCcIx?cm!N?XUui* zuxS}@o3Xzgo;x-AsdcfR1~&vT&w}e7l`G5!z1av@R*TjH;QcemLZ=+T-l3**aSUNixI16R{yTLb@nFb&?62|ops1UAF+a9S=!AzXS0{&PEd z6U+!*4p)(2j)~U*4|pmQ-iR#r{>?dD5cq4S@Ln$w>}tz5)aJ!=RCPqc%T+b8>_;>L z_a~s#$jcpAns>xK{x~-gKE79D8f>3QgbyTeOL>^!g|IwNU`E*YZbdNivW&OVIGo>! z47mlP%J4W?9x|~-_AD$<$CVm=R_8?cl?v?*kAdal^f`tfhjafHFT)4dgY|pXub)nI0W`688{3#TO78`!C>H{3)fd z#~<|MgUR)ge_Ix>U&0hUO%aN}T>tJ@n($tHhB-Dx@(%n&b;ipzd}A3SG&JKqVFFf_ zlMoFKGCUEMhg8~|IbRCPLngC~{ttb4I9aWd=V7_Qu@8E#|8nd7>51^$Yi1aS>*1&F ziHqj!GnnI<8E?KRvgzbY{ybycq6tHA8L^@_4hV*ZrC2@@|VMp(y=uh`vA| zH;Jw=37q#oFza}l;d)rUP{{HjtUtYKhQjjwKvusLmhbnm-19u1uCMRJ$Gg##{0bh) z)?6v?KV$^nL(m0*o%5yVGlZOc|>M$USi3tr^d^8_x;^^5JtI6JXpxdO(KNhL^)~m#f`M zORHMe%oLdC`5DqE;Z1PeI~lLH;m#K`g*>vk)9^r;2bFx!ZVVF;40$`VcinG+^_(uh zgsOj@vz;lz;e#0!`4o&1XzL}tmtcK%8*~{X@?<8w0YOIa6o^E2V| z`(m&RLE%%Gz3Y5bP0PJ~`!Ig0{YUY#D;Un+{t2%a%oJ4ojuBCFeihuLcn0iIBerG^ z9mx$1{)ZWFw8?1fA6m{)-XGZ))@H&UN`lGj*y~lmJK&Y?X1ui~feS~mm+-;k2*dZo zawF41!+*V=&z6s31Pr&jf&E`TwW>8mw*Y}2@~$_wOz1E0fXx}N)&zJKUa^gniQz`L z-8UJp&UEO~I-V2S5_fPqJfSH*TYg-}=by71dBtM_Ts?+>UuC=o6JQHGV{68nVECG` za)V;Vo;i=u{7)Ir*|Jr9GP?nm-+s3z zvn4(P`4#Iy#-Zn}?EM(noof^O!yvHet6s_>~BUOKLozTY>gc!Fy~pFy(Ol{7sGOMncXL*!g51mf78I*u-p;Z z*9>{{q?TLvtjR66?$f3u!tW7V7{&L!H3;M;ky$2zOYUIE#`3FJhea8G4$GHL(oqug*Knm7Et)RpO=d|fI(xc2i@k6_69R6%h0XZ+{qTfn;2{O1>IQw7PQ(%^zM zSiQG8RTx~mHPt=%{l}>uiSnTBS4ek%j7QTp{?&C&>Sw`YTT&+_N`jZx{XnhEu@Q3cC4rSj)}ur^hd2o_6n zy(PKv>r(Fqo7NNKi?6_EKTF*xITT21`N0%1`OSt@k7QAK@RFo<^oG<)@~a(k+0@c8?T+w4u~PTb5eT_js(g5AD@?rqq?zYgU;QI`3V8FBbCd@C{M@1UoR zW^P93r1OIQ-=@0Ho4hmCJt6E^!38^0rza~4gVT4XN|>+k zPkHpGSJ3vG6jR85l9kNHuGG)6iV6#Z;Y|n%OU&PL^S77$Elm^$jo+qvC5tM8kDF3` z5~UFqhuV^4NpUdoo78E^l88|gy);?MoSvLMR+8$({0<{fvaB>Xy*X8$EUT0p&q$V& zRqu57&?Z??8Mew$iYgdh`-+vD=Pni<5ej3`yDYe3c<3h^u|v6O`ZRl9EdcNv1IOfHNvC-;b%Us;TUU#V+}wxyZ!3x%=N&-cnSq!}r*il>9LJ zV(Q!fK@CVb{*Z)Npf#r8%u1~xf?nLm?R#|>$<~p(|8nXTZY=}$Uv|_o*@XXVnZ!PG znAg}kef+CIdPO>~P0irJy-I6(4KAvvEGsFzvNSkpPr5WX_04p{yfap$&q|!S-vmm^ zde+wd?wX!O#g&CcGn8m!gt= zhTz@x0ggxEK4Tnki0|l0>%Frd4#{<&rrRI+9g`M>+IK%q5B@Igyz|zjyC%O&6CAQT z-8U$BKh^fweWxGvc|SGC-?#n%N2sLyKMCQ7r}L7^|0G5f;(O}udwSyQR~4oIA13<; zj)wmoFK+f-wg*~r-%)Y4;GD7W_w@?v2lnGTx=~bo0F&x}|LeSx1MkVcI#SZ>`_k|a z=;dZv(f7qTun^xp8h&^Va3*D6T`Bv|E2GTW;B#7>itb*T?lAAkP3fLNaM~ZU(!sJZ z*{NV-U3N}#bmw4qUG@nFjs9_GKAz;W`5-eUyJNO|@K`r7ID1TXr);^D-p~*Xm4C|z z6S<3nsbjLcCHeb2f75MW&GPbtmlkCo88nT_ZgXgFcuQx#=8p>&!-HeSW*?U>*qrh% Wj!gQE&2Ez(10OSQ@Yw9>fBt`kh7y$k diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index 2c3f15ec9c8ce827f3dbb5df1f6a5244dc63df75..bc33231edfa1730d20dcce376750c8cce78c5779 100644 GIT binary patch delta 103148 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 F_J2XKwMzg1 delta 102068 zcma%k3tUvi7yq5vy~{&TV0np%%id){M8pSTX1b^#7MYe-nkfdFmMK2-*)A?18X9`l zv9iKvYHAqjnyD=XXhw!*Wo2n;Wxv$0pOu-x|2y{q*sp*8KcCNK?s?3aGiT16IWu?A zHJ2jFzK$r1rjzqm#YFknzittF34%~DZPmcX`j@6tfj=TJLXaR36lFuHP&V4CJiS&5 z0)LFH=+&Q5q55|#o>kxWrh%VVMv*KZ>d2Mze7_8P(JU4SvRFXOrgXv_g7AY&*e8$l zdyERht zFO{L$bU_F-i-QHvB/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 z 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 diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 1b5e61d..06ba995 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -347,11 +347,14 @@ pub fn prepare_function_map() -> HashMap { assert_eq!(definition_acc.program_owner, Program::token().id()); // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 + // bytes)] assert_eq!( definition_acc.data.as_ref(), &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 ] ); @@ -506,11 +509,14 @@ pub fn prepare_function_map() -> HashMap { assert_eq!(definition_acc.program_owner, Program::token().id()); // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 + // bytes)] assert_eq!( definition_acc.data.as_ref(), &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 ] ); @@ -663,8 +669,8 @@ pub fn prepare_function_map() -> HashMap { .account; assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // The data of a token holding account has the following layout: + // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] assert_eq!( supply_acc.data.as_ref(), &[ @@ -754,17 +760,20 @@ pub fn prepare_function_map() -> HashMap { assert_eq!(definition_acc.program_owner, Program::token().id()); // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 + // bytes)] assert_eq!( definition_acc.data.as_ref(), &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 ] ); assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // The data of a token holding account has the following layout: + // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] assert_eq!( supply_acc.data.as_ref(), &[ @@ -844,11 +853,14 @@ pub fn prepare_function_map() -> HashMap { assert_eq!(definition_acc.program_owner, Program::token().id()); // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 + // bytes)] assert_eq!( definition_acc.data.as_ref(), &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 ] ); @@ -980,11 +992,14 @@ pub fn prepare_function_map() -> HashMap { assert_eq!(definition_acc.program_owner, Program::token().id()); // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 + // bytes)] assert_eq!( definition_acc.data.as_ref(), &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 ] ); @@ -1116,11 +1131,14 @@ pub fn prepare_function_map() -> HashMap { assert_eq!(definition_acc.program_owner, Program::token().id()); // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 + // bytes)] assert_eq!( definition_acc.data.as_ref(), &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 ] ); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 07eb0a2..f1c7709 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -25,6 +25,7 @@ risc0-binfmt = "3.0.2" [dev-dependencies] test_program_methods.workspace = true hex-literal = "1.0.0" +env_logger.workspace = true [features] default = [] diff --git a/nssa/src/state.rs b/nssa/src/state.rs index cbc9f31..e7d2077 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2283,7 +2283,7 @@ pub mod tests { // TODO: repeated code needs to be cleaned up // from token.rs (also repeated in amm.rs) - const TOKEN_DEFINITION_DATA_SIZE: usize = 23; + const TOKEN_DEFINITION_DATA_SIZE: usize = 55; const TOKEN_HOLDING_DATA_SIZE: usize = 49; @@ -2291,6 +2291,7 @@ pub mod tests { account_type: u8, name: [u8; 6], total_supply: u128, + metadata_id: AccountId, } struct TokenHolding { @@ -2300,14 +2301,17 @@ pub mod tests { } impl TokenDefinition { fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..7].copy_from_slice(&self.name); - bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("23 bytes should fit into Data") + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Token definition data size must fit into data") } } @@ -2746,6 +2750,7 @@ pub mod tests { account_type: 0u8, name: [1u8; 6], total_supply: BalanceForTests::token_a_supply(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, } @@ -2759,6 +2764,7 @@ pub mod tests { account_type: 0u8, name: [1u8; 6], total_supply: BalanceForTests::token_b_supply(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, } @@ -2772,6 +2778,7 @@ pub mod tests { account_type: 0u8, name: [1u8; 6], total_supply: BalanceForTests::token_lp_supply(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, } @@ -3053,6 +3060,7 @@ pub mod tests { account_type: 0u8, name: [1u8; 6], total_supply: BalanceForTests::token_lp_supply_add(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, } @@ -3151,6 +3159,7 @@ pub mod tests { account_type: 0u8, name: [1u8; 6], total_supply: BalanceForTests::token_lp_supply_remove(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, } @@ -3164,6 +3173,7 @@ pub mod tests { account_type: 0u8, name: [1u8; 6], total_supply: 0, + metadata_id: AccountId::new([0; 32]), }), nonce: 0, } @@ -3262,6 +3272,7 @@ pub mod tests { account_type: 0u8, name: [1u8; 6], total_supply: BalanceForTests::vault_a_balance_init(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, } @@ -3667,6 +3678,7 @@ pub mod tests { #[test] fn test_simple_amm_add() { + env_logger::init(); let mut state = state_for_amm_tests(); let mut instruction: Vec = Vec::new(); @@ -4072,7 +4084,7 @@ pub mod tests { // definition and supply accounts let total_supply: u128 = 10_000_000; // instruction: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(b"PINATA"); let message = public_transaction::Message::try_new( @@ -4087,7 +4099,7 @@ pub mod tests { state.transition_from_public_transaction(&tx).unwrap(); // Execution of the token program transfer just to initialize the winner token account - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 2; let message = public_transaction::Message::try_new( token.id(), diff --git a/program_methods/guest/src/bin/amm.rs b/program_methods/guest/src/bin/amm.rs index 2946d0c..9488db1 100644 --- a/program_methods/guest/src/bin/amm.rs +++ b/program_methods/guest/src/bin/amm.rs @@ -424,7 +424,7 @@ fn initialize_token_transfer_chained_call( amount_to_move: u128, pda_seed: Vec, ) -> ChainedCall { - let mut instruction_data = [0; 23]; + let mut instruction_data = vec![0u8; 23]; instruction_data[0] = token_program_command; instruction_data[1..17].copy_from_slice(&amount_to_move.to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) @@ -563,7 +563,7 @@ fn new_definition( ); // Chain call for liquidity token (TokenLP definition -> User LP Holding) - let mut instruction_data = [0; 23]; + let mut instruction_data = vec![0u8; 23]; instruction_data[0] = if pool.account == Account::default() { TOKEN_PROGRAM_NEW } else { @@ -1093,24 +1093,28 @@ mod tests { const TOKEN_PROGRAM_ID: ProgramId = [15; 8]; const AMM_PROGRAM_ID: ProgramId = [42; 8]; - const TOKEN_DEFINITION_DATA_SIZE: usize = 23; + const TOKEN_DEFINITION_DATA_SIZE: usize = 55; struct TokenDefinition { account_type: u8, name: [u8; 6], total_supply: u128, + metadata_id: AccountId, } impl TokenDefinition { fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..7].copy_from_slice(&self.name); - bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("23 bytes should fit into Data") + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Token definition data size must fit into data") } } @@ -1246,7 +1250,7 @@ mod tests { impl ChainedCallForTests { fn cc_swap_token_a_test_1() -> ChainedCall { - let mut instruction_data: [u8; 23] = [0; 23]; + let mut instruction_data = vec![0; 23]; instruction_data[0] = 1; instruction_data[1..17] .copy_from_slice(&BalanceForTests::add_max_amount_a().to_le_bytes()); @@ -1269,7 +1273,7 @@ mod tests { let mut vault_b_auth = AccountForTests::vault_b_init(); vault_b_auth.is_authorized = true; - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17].copy_from_slice(&swap_amount.to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) @@ -1291,7 +1295,7 @@ mod tests { let mut vault_a_auth = AccountForTests::vault_a_init(); vault_a_auth.is_authorized = true; - let mut instruction_data: [u8; 23] = [0; 23]; + let mut instruction_data = vec![0; 23]; instruction_data[0] = 1; instruction_data[1..17].copy_from_slice(&swap_amount.to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) @@ -1308,7 +1312,7 @@ mod tests { } fn cc_swap_token_b_test_2() -> ChainedCall { - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17].copy_from_slice(&BalanceForTests::add_max_amount_b().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) @@ -1325,7 +1329,7 @@ mod tests { } fn cc_add_token_a() -> ChainedCall { - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); @@ -1343,7 +1347,7 @@ mod tests { } fn cc_add_token_b() -> ChainedCall { - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_b().to_le_bytes()); @@ -1364,7 +1368,7 @@ mod tests { let mut pool_lp_auth = AccountForTests::pool_lp_init(); pool_lp_auth.is_authorized = true; - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[0] = 4; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); @@ -1384,7 +1388,7 @@ mod tests { let mut vault_a_auth = AccountForTests::vault_a_init(); vault_a_auth.is_authorized = true; - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::remove_actual_a_successful().to_le_bytes()); @@ -1405,7 +1409,7 @@ mod tests { let mut vault_b_auth = AccountForTests::vault_b_init(); vault_b_auth.is_authorized = true; - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::remove_min_amount_b_low().to_le_bytes()); @@ -1426,7 +1430,7 @@ mod tests { let mut pool_lp_auth = AccountForTests::pool_lp_init(); pool_lp_auth.is_authorized = true; - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 3; instruction[1..17] .copy_from_slice(&BalanceForTests::remove_actual_a_successful().to_le_bytes()); @@ -1446,7 +1450,7 @@ mod tests { } fn cc_new_definition_token_a() -> ChainedCall { - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); @@ -1464,7 +1468,7 @@ mod tests { } fn cc_new_definition_token_b() -> ChainedCall { - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_b().to_le_bytes()); @@ -1482,7 +1486,7 @@ mod tests { } fn cc_new_definition_token_lp() -> ChainedCall { - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); @@ -1736,6 +1740,7 @@ mod tests { account_type: 0u8, name: [1; 6], total_supply: BalanceForTests::vault_a_reserve_init(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, @@ -1753,6 +1758,7 @@ mod tests { account_type: 0u8, name: [1; 6], total_supply: BalanceForTests::vault_a_reserve_init(), + metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, diff --git a/program_methods/guest/src/bin/pinata_token.rs b/program_methods/guest/src/bin/pinata_token.rs index f988be9..0461379 100644 --- a/program_methods/guest/src/bin/pinata_token.rs +++ b/program_methods/guest/src/bin/pinata_token.rs @@ -82,7 +82,7 @@ fn main() { let winner_token_holding_post = winner_token_holding.account.clone(); pinata_definition_post.data = data.next_data(); - let mut instruction_data: [u8; 23] = [0; 23]; + let mut instruction_data = vec![0; 23]; instruction_data[0] = 1; instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes()); diff --git a/program_methods/guest/src/bin/token.rs b/program_methods/guest/src/bin/token.rs index 30dcac4..0f7b628 100644 --- a/program_methods/guest/src/bin/token.rs +++ b/program_methods/guest/src/bin/token.rs @@ -458,7 +458,7 @@ fn new_definition_with_metadata( definition_id: definition_target_account.account_id, uri, creators, - primary_sale_date: 0u64, // TODO: future works to implement this + primary_sale_date: 0u64, // TODO #261: future works to implement this }; let mut definition_target_account_post = definition_target_account.account.clone(); diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 480480d..440c6ed 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -4,6 +4,7 @@ use clap::Subcommand; use itertools::Itertools as _; use key_protocol::key_management::key_tree::chain_index::ChainIndex; use nssa::{Account, AccountId, program::Program}; +use nssa_core::account::Data; use serde::Serialize; use crate::{ @@ -12,17 +13,20 @@ use crate::{ helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix}, }; -const TOKEN_DEFINITION_TYPE: u8 = 0; -const TOKEN_DEFINITION_DATA_SIZE: usize = 23; +const TOKEN_DEFINITION_DATA_SIZE: usize = 55; const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_DATA_SIZE: usize = 49; +const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; +const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; struct TokenDefinition { #[allow(unused)] account_type: u8, name: [u8; 6], total_supply: u128, + #[allow(unused)] + metadata_id: AccountId, } struct TokenHolding { @@ -33,19 +37,37 @@ struct TokenHolding { } impl TokenDefinition { - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_DEFINITION_DATA_SIZE { None } else { let account_type = data[0]; - let name = data[1..7].try_into().unwrap(); - let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap()); + let name = data[1..7].try_into().expect("Name must be a 6 bytes"); + let total_supply = u128::from_le_bytes( + data[7..23] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let metadata_id = AccountId::new( + data[23..TOKEN_DEFINITION_DATA_SIZE] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); - Some(Self { + let this = Some(Self { account_type, name, total_supply, - }) + metadata_id, + }); + + match account_type { + TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 => None, + TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None, + _ => this, + } } } } @@ -344,6 +366,8 @@ impl WalletSubcommand for AccountSubcommand { #[cfg(test)] mod tests { + use nssa::AccountId; + use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition}; #[test] @@ -352,6 +376,7 @@ mod tests { account_type: 1, name: [137, 12, 14, 3, 5, 4], total_supply: 100, + metadata_id: AccountId::new([0; 32]), }; let token_def_view: TokedDefinitionAccountView = token_def.into(); @@ -365,6 +390,7 @@ mod tests { account_type: 1, name: [240, 159, 146, 150, 66, 66], total_supply: 100, + metadata_id: AccountId::new([0; 32]), }; let token_def_view: TokedDefinitionAccountView = token_def.into(); @@ -378,6 +404,7 @@ mod tests { account_type: 1, name: [78, 65, 77, 69, 0, 0], total_supply: 100, + metadata_id: AccountId::new([0; 32]), }; let token_def_view: TokedDefinitionAccountView = token_def.into(); diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index e7bdca9..1c8a899 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -20,7 +20,7 @@ impl Token<'_> { let account_ids = vec![definition_account_id, supply_account_id]; let program_id = nssa::program::Program::token().id(); // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(&name); let message = nssa::public_transaction::Message::try_new( @@ -131,7 +131,7 @@ impl Token<'_> { let program_id = nssa::program::Program::token().id(); // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // 0x00 || 0x00 || 0x00]. - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[0] = 0x01; instruction[1..17].copy_from_slice(&amount.to_le_bytes()); let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else { @@ -306,7 +306,7 @@ impl Token<'_> { fn token_program_preparation_transfer(amount: u128) -> (InstructionData, Program) { // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // 0x00 || 0x00 || 0x00]. - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[0] = 0x01; instruction[1..17].copy_from_slice(&amount.to_le_bytes()); let instruction_data = Program::serialize_instruction(instruction).unwrap(); @@ -320,7 +320,7 @@ fn token_program_preparation_definition( total_supply: u128, ) -> (InstructionData, Program) { // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(&name); let instruction_data = Program::serialize_instruction(instruction).unwrap();