From ecdb4ba130aab054daf571db791177aa46aeaf0a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sun, 10 Aug 2025 09:57:10 -0300 Subject: [PATCH] add test for extra outputs --- nssa/core/src/program.rs | 2 +- nssa/src/program.rs | 10 ++++++ nssa/src/state.rs | 31 ++++++++-------- nssa/src/tests/mod.rs | 1 + nssa/src/tests/state_tests.rs | 15 -------- nssa/src/tests/valid_execution_tests.rs | 35 +++++++++++++++++++ .../guest/src/bin/extra_outputs.rs | 17 +++++++++ 7 files changed, 80 insertions(+), 31 deletions(-) create mode 100644 nssa/src/tests/valid_execution_tests.rs create mode 100644 nssa/test_program_methods/guest/src/bin/extra_outputs.rs diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index ce58a3b..d3d253a 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -9,7 +9,7 @@ pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; /// - `pre_states`: The list of input accounts, each annotated with authorization metadata. /// - `post_states`: The list of resulting accounts after executing the program logic. /// - `executing_program_id`: The identifier of the program that was executed. -pub fn validate_constraints( +pub fn validate_execution( pre_states: &[AccountWithMetadata], post_states: &[Account], executing_program_id: ProgramId, diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 6c25828..2b6c041 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -85,4 +85,14 @@ impl Program { elf: NONCE_CHANGER_ELF, } } + + /// A program that produces more output accounts than the inputs it received + pub fn extra_outputs_program() -> Self { + use test_program_methods::{EXTRA_OUTPUTS_ELF, EXTRA_OUTPUTS_ID}; + + Program { + id: EXTRA_OUTPUTS_ID, + elf: EXTRA_OUTPUTS_ELF, + } + } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3b7b77b..b699ab0 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -3,7 +3,7 @@ use crate::{ }; use nssa_core::{ account::{Account, AccountWithMetadata}, - program::{ProgramId, validate_constraints}, + program::{ProgramId, validate_execution}, }; use std::collections::{HashMap, HashSet}; @@ -29,15 +29,18 @@ impl V01State { }) .collect(); - let builtin_programs = HashMap::from([( - authenticated_transfer_program.id(), - authenticated_transfer_program, - )]); - - Self { + let mut this = Self { public_state, - builtin_programs, - } + builtin_programs: HashMap::new(), + }; + + this.insert_program(Program::authenticated_transfer_program()); + + this + } + + fn insert_program(&mut self, program: Program) { + self.builtin_programs.insert(program.id(), program); } pub fn transition_from_public_transaction( @@ -129,8 +132,8 @@ impl V01State { let post_states = program.execute(&pre_states, message.instruction_data)?; // Verify execution corresponds to a well-behaved program. - // See the # Programs section for the definition of the `validate_constraints` method. - if !validate_constraints(&pre_states, &post_states, message.program_id) { + // See the # Programs section for the definition of the `validate_execution` method. + if !validate_execution(&pre_states, &post_states, message.program_id) { return Err(NssaError::InvalidProgramBehavior); } @@ -143,10 +146,8 @@ impl V01State { impl V01State { /// Include test programs in the builtin programs map pub fn with_test_programs(mut self) -> Self { - self.builtin_programs.insert( - Program::nonce_changer_program().id(), - Program::nonce_changer_program(), - ); + self.insert_program(Program::nonce_changer_program()); + self.insert_program(Program::extra_outputs_program()); self } } diff --git a/nssa/src/tests/mod.rs b/nssa/src/tests/mod.rs index 8d49dbd..2c04e2d 100644 --- a/nssa/src/tests/mod.rs +++ b/nssa/src/tests/mod.rs @@ -1 +1,2 @@ mod state_tests; +mod valid_execution_tests; diff --git a/nssa/src/tests/state_tests.rs b/nssa/src/tests/state_tests.rs index cae157f..6763f17 100644 --- a/nssa/src/tests/state_tests.rs +++ b/nssa/src/tests/state_tests.rs @@ -4,21 +4,6 @@ use crate::{ }; use nssa_core::account::Account; -#[test] -fn test_programs_cant_change_account_nonces() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - let addresses = vec![Address::new([1; 32])]; - let nonces = vec![]; - let program_id = Program::nonce_changer_program().id(); - let message = public_transaction::Message::new(program_id, addresses, nonces, 5); - 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 transfer_transaction( from: Address, diff --git a/nssa/src/tests/valid_execution_tests.rs b/nssa/src/tests/valid_execution_tests.rs new file mode 100644 index 0000000..66ec49d --- /dev/null +++ b/nssa/src/tests/valid_execution_tests.rs @@ -0,0 +1,35 @@ +use crate::{ + Address, PublicTransaction, V01State, error::NssaError, program::Program, public_transaction, +}; + +#[test] +fn test_program_should_fail_if_it_modifies_nonces() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let addresses = vec![Address::new([1; 32])]; + let nonces = vec![]; + let program_id = Program::nonce_changer_program().id(); + let message = public_transaction::Message::new(program_id, addresses, nonces, 0); + 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 test_program_should_fail_if_output_accounts_exceed_inputs() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let addresses = vec![Address::new([1; 32])]; + let nonces = vec![]; + let program_id = Program::extra_outputs_program().id(); + let message = public_transaction::Message::new(program_id, addresses, nonces, 0); + 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))); +} diff --git a/nssa/test_program_methods/guest/src/bin/extra_outputs.rs b/nssa/test_program_methods/guest/src/bin/extra_outputs.rs new file mode 100644 index 0000000..f15c097 --- /dev/null +++ b/nssa/test_program_methods/guest/src/bin/extra_outputs.rs @@ -0,0 +1,17 @@ +use nssa_core::account::{Account, AccountWithMetadata}; +use risc0_zkvm::guest::env; + +fn main() { + let input_accounts: Vec = env::read(); + let _instruction_data: u128 = env::read(); + + let [pre] = match input_accounts.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let account_pre = pre.account; + + env::commit(&vec![account_pre, Account::default()]); +} +