diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 59aae6a..0109c2b 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -3,7 +3,7 @@ use nssa_core::{ program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}, }; -// The token program has two functions: +// The token program has three functions: // 1. New token definition. // Arguments to this function are: // * Two **default** accounts: [definition_account, holding_account]. @@ -18,6 +18,11 @@ use nssa_core::{ // * 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: +// * Two accounts: [definition_account, account_to_initialize]. +// * An dummy byte string of length 23, with the following layout +// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_DATA_SIZE: usize = 23; @@ -52,7 +57,11 @@ impl TokenDefinition { } else { let account_type = data[0]; let name = data[1..7].try_into().unwrap(); - let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap()); + let total_supply = u128::from_le_bytes( + data[7..] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); Some(Self { account_type, name, @@ -76,8 +85,16 @@ impl TokenHolding { None } else { let account_type = data[0]; - let definition_id = AccountId::new(data[1..33].try_into().unwrap()); - let balance = u128::from_le_bytes(data[33..].try_into().unwrap()); + let definition_id = AccountId::new( + data[1..33] + .try_into() + .expect("Defintion ID must be 32 bytes long"), + ); + let balance = u128::from_le_bytes( + data[33..] + .try_into() + .expect("balance must be 16 bytes little-endian"), + ); Some(Self { definition_id, balance, @@ -182,46 +199,7 @@ fn new_definition( vec![definition_target_account_post, holding_target_account_post] } -type Instruction = [u8; 23]; - -fn main() { - let ProgramInput { - pre_states, - instruction, - } = read_nssa_inputs::(); - - match instruction[0] { - 0 => { - // Parse instruction - let total_supply = u128::from_le_bytes(instruction[1..17].try_into().unwrap()); - let name: [u8; 6] = instruction[17..].try_into().unwrap(); - assert_ne!(name, [0; 6]); - - // Execute - let post_states = new_definition(&pre_states, name, total_supply); - write_nssa_outputs(pre_states, post_states); - } - 1 => { - // Parse instruction - let balance_to_move = u128::from_le_bytes(instruction[1..17].try_into().unwrap()); - let name: [u8; 6] = instruction[17..].try_into().unwrap(); - assert_eq!(name, [0; 6]); - - // Execute - let post_states = transfer(&pre_states, balance_to_move); - write_nssa_outputs(pre_states, post_states); - } - 2 => { - // Initialize account - assert_eq!(instruction[1..], [0; 22]); - let post_states = initialize(&pre_states); - write_nssa_outputs(pre_states, post_states); - } - _ => panic!("Invalid instruction"), - }; -} - -fn initialize(pre_states: &[AccountWithMetadata]) -> Vec { +fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of accounts"); } @@ -235,23 +213,80 @@ fn initialize(pre_states: &[AccountWithMetadata]) -> Vec { // TODO: We should check that this is an account owned by the token program. // This check can't be done here since the ID of the program is known only after compiling it + // // Check definition account is valid let _definition_values = TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); - let holding_for_definition = TokenHolding::new(&definition.account_id); + let holding_values = TokenHolding::new(&definition.account_id); let definition_post = definition.account.clone(); let mut account_to_initialize_post = account_to_initialize.account.clone(); - account_to_initialize_post.data = holding_for_definition.into_data(); + account_to_initialize_post.data = holding_values.into_data(); vec![definition_post, account_to_initialize_post] } +type Instruction = [u8; 23]; + +fn main() { + let ProgramInput { + pre_states, + instruction, + } = read_nssa_inputs::(); + + let post_states = match instruction[0] { + 0 => { + // 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: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); + assert_ne!(name, [0; 6]); + + // Execute + new_definition(&pre_states, name, total_supply) + } + 1 => { + // Parse instruction + let balance_to_move = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to move 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 + transfer(&pre_states, balance_to_move) + } + 2 => { + // Initialize account + if instruction[1..] != [0; 22] { + panic!("Invalid instruction for initialize account"); + } + initialize_account(&pre_states) + } + _ => panic!("Invalid instruction"), + }; + + write_nssa_outputs(pre_states, post_states); +} + + #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; - use crate::{TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, new_definition, transfer}; + use crate::{ + TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, + initialize_account, new_definition, transfer, + }; #[should_panic(expected = "Invalid number of input accounts")] #[test] @@ -598,4 +633,37 @@ mod tests { ] ); } + + #[test] + fn test_token_initialize_account_succeeds() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Definition ID with + data: vec![0; TOKEN_DEFINITION_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(1000)) + .collect(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: AccountId::new([2; 32]), + }, + ]; + let post_states = initialize_account(&pre_states); + let [definition, holding] = post_states.try_into().ok().unwrap(); + assert_eq!(definition.data, pre_states[0].account.data); + assert_eq!( + holding.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 + ] + ); + } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 78b8b7b..340341e 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2299,6 +2299,9 @@ pub mod tests { state.transition_from_public_transaction(&tx).unwrap(); let winner_token_holding_post = state.get_account_by_id(&winner_token_holding_id); - assert_eq!(winner_token_holding_post, expected_winner_token_holding_post); + assert_eq!( + winner_token_holding_post, + expected_winner_token_holding_post + ); } }