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