diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 43066bf8..c1f72b4c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -364,10 +364,6 @@ pub mod tests { }; impl V02State { - pub fn force_insert_account(&mut self, account_id: AccountId, account: Account) { - self.public_state.insert(account_id, account); - } - /// Include test programs in the builtin programs map. #[must_use] pub fn with_test_programs(mut self) -> Self { @@ -459,6 +455,455 @@ pub mod tests { } } + fn transfer_transaction( + from: AccountId, + from_key: &PrivateKey, + nonce: u128, + to: AccountId, + balance: u128, + ) -> PublicTransaction { + let account_ids = vec![from, to]; + let nonces = vec![nonce]; + let program_id = Program::authenticated_transfer_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]); + PublicTransaction::new(message, witness_set) + } + + #[test] + fn new_with_genesis() { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let addr1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); + let addr2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(addr1, 100_u128), (addr2, 151_u128)]; + let authenticated_transfers_program = Program::authenticated_transfer_program(); + let expected_public_state = { + let mut this = HashMap::new(); + this.insert( + addr1, + Account { + balance: 100, + program_owner: authenticated_transfers_program.id(), + ..Account::default() + }, + ); + this.insert( + addr2, + Account { + balance: 151, + program_owner: authenticated_transfers_program.id(), + ..Account::default() + }, + ); + this + }; + let expected_builtin_programs = { + let mut this = HashMap::new(); + this.insert( + authenticated_transfers_program.id(), + authenticated_transfers_program, + ); + this.insert(Program::token().id(), Program::token()); + this.insert(Program::amm().id(), Program::amm()); + this + }; + + let state = V02State::new_with_genesis_accounts(&initial_data, &[]); + + assert_eq!(state.public_state, expected_public_state); + assert_eq!(state.programs, expected_builtin_programs); + } + + #[test] + fn insert_program() { + let mut state = V02State::new_with_genesis_accounts(&[], &[]); + let program_to_insert = Program::simple_balance_transfer(); + let program_id = program_to_insert.id(); + assert!(!state.programs.contains_key(&program_id)); + + state.insert_program(program_to_insert); + + assert!(state.programs.contains_key(&program_id)); + } + + #[test] + fn get_account_by_account_id_non_default_account() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_data = [(account_id, 100_u128)]; + let state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let expected_account = &state.public_state[&account_id]; + + let account = state.get_account_by_id(account_id); + + assert_eq!(&account, expected_account); + } + + #[test] + fn get_account_by_account_id_default_account() { + let addr2 = AccountId::new([0; 32]); + let state = V02State::new_with_genesis_accounts(&[], &[]); + let expected_account = Account::default(); + + let account = state.get_account_by_id(addr2); + + assert_eq!(account, expected_account); + } + + #[test] + fn builtin_programs_getter() { + let state = V02State::new_with_genesis_accounts(&[], &[]); + + let builtin_programs = state.programs(); + + assert_eq!(builtin_programs, &state.programs); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_data = [(account_id, 100)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let from = account_id; + let to = AccountId::new([2; 32]); + assert_eq!(state.get_account_by_id(to), Account::default()); + let balance_to_move = 5; + + let tx = transfer_transaction(from, &key, 0, to, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_id(from).balance, 95); + assert_eq!(state.get_account_by_id(to).balance, 5); + assert_eq!(state.get_account_by_id(from).nonce, 1); + assert_eq!(state.get_account_by_id(to).nonce, 0); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_data = [(account_id, 100)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let from = account_id; + let from_key = key; + let to = AccountId::new([2; 32]); + let balance_to_move = 101; + assert!(state.get_account_by_id(from).balance < balance_to_move); + + let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); + assert_eq!(state.get_account_by_id(from).balance, 100); + assert_eq!(state.get_account_by_id(to).balance, 0); + assert_eq!(state.get_account_by_id(from).nonce, 0); + assert_eq!(state.get_account_by_id(to).nonce, 0); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); + let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(account_id1, 100), (account_id2, 200)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let from = account_id2; + let from_key = key2; + let to = account_id1; + assert_ne!(state.get_account_by_id(to), Account::default()); + let balance_to_move = 8; + + let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_id(from).balance, 192); + assert_eq!(state.get_account_by_id(to).balance, 108); + assert_eq!(state.get_account_by_id(from).nonce, 1); + assert_eq!(state.get_account_by_id(to).nonce, 0); + } + + #[test] + fn transition_from_sequence_of_authenticated_transfer_program_invocations() { + let key1 = PrivateKey::try_new([8; 32]).unwrap(); + let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(account_id1, 100)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let account_id3 = AccountId::new([3; 32]); + let balance_to_move = 5; + + let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + let balance_to_move = 3; + let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_id(account_id1).balance, 95); + assert_eq!(state.get_account_by_id(account_id2).balance, 2); + assert_eq!(state.get_account_by_id(account_id3).balance, 3); + assert_eq!(state.get_account_by_id(account_id1).nonce, 1); + assert_eq!(state.get_account_by_id(account_id2).nonce, 1); + assert_eq!(state.get_account_by_id(account_id3).nonce, 0); + } + + #[test] + fn program_should_fail_if_modifies_nonces() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_ids = vec![AccountId::new([1; 32])]; + let program_id = Program::nonce_changer_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_output_accounts_exceed_inputs() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_ids = vec![AccountId::new([1; 32])]; + let program_id = Program::extra_output_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_with_missing_output_accounts() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_ids = vec![AccountId::new([1; 32]), AccountId::new([2; 32])]; + let program_id = Program::missing_output_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { + let initial_data = [(AccountId::new([1; 32]), 0)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in the program owner + // field + assert_ne!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + assert_eq!(account.nonce, Account::default().nonce); + assert_eq!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([255; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in balance field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_ne!(account.balance, Account::default().balance); + assert_eq!(account.nonce, Account::default().nonce); + assert_eq!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([254; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in nonce field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + assert_ne!(account.nonce, Account::default().nonce); + assert_eq!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_data() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([253; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in data field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + assert_eq!(account.nonce, Account::default().nonce); + assert_ne!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_transfers_balance_from_non_owned_account() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let sender_account_id = AccountId::new([1; 32]); + let receiver_account_id = AccountId::new([2; 32]); + let balance_to_move: u128 = 1; + let program_id = Program::simple_balance_transfer().id(); + assert_ne!( + state.get_account_by_id(sender_account_id).program_owner, + program_id + ); + let message = public_transaction::Message::try_new( + program_id, + vec![sender_account_id, receiver_account_id], + vec![], + balance_to_move, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_data_of_non_owned_account() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([255; 32]); + let program_id = Program::data_changer().id(); + + assert_ne!(state.get_account_by_id(account_id), Account::default()); + assert_ne!( + state.get_account_by_id(account_id).program_owner, + program_id + ); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], vec![0]) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_does_not_preserve_total_balance_by_minting() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::minter().id(); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_does_not_preserve_total_balance_by_burning() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_account_owned_by_burner_program(); + let program_id = Program::burner().id(); + let account_id = AccountId::new([252; 32]); + assert_eq!( + state.get_account_by_id(account_id).program_owner, + program_id + ); + let balance_to_burn: u128 = 1; + assert!(state.get_account_by_id(account_id).balance > balance_to_burn); + + let message = public_transaction::Message::try_new( + program_id, + vec![account_id], + vec![], + balance_to_burn, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + fn test_public_account_keys_1() -> TestPublicKeys { + TestPublicKeys { + signing_key: PrivateKey::try_new([37; 32]).unwrap(), + } + } + pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32],