Merge branch 'schouhy/add-init-function-to-token-program' into schouhy/implement-pda-for-public-accounts

This commit is contained in:
Sergio Chouhy 2025-11-28 13:55:19 -03:00
commit ed00876800
2 changed files with 119 additions and 48 deletions

View File

@ -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::<Instruction>();
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<Account> {
fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
if pre_states.len() != 2 {
panic!("Invalid number of accounts");
}
@ -235,23 +213,80 @@ fn initialize(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
// 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::<Instruction>();
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
]
);
}
}

View File

@ -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
);
}
}