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] 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), + ); + } }