diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index 455d998..8cc071f 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -31,7 +31,7 @@ pub fn produce_dummy_block( } pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction { - let program_id = nssa::program::AUTHENTICATED_TRANSFER_PROGRAM.id(); + let program_id = nssa::program::Program::authenticated_transfer_program().id(); let addresses = vec![]; let nonces = vec![]; let instruction_data = 0; @@ -51,7 +51,7 @@ pub fn create_transaction_native_token_transfer( ) -> nssa::PublicTransaction { let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)]; let nonces = vec![nonce]; - let program_id = nssa::program::AUTHENTICATED_TRANSFER_PROGRAM.id(); + let program_id = nssa::program::Program::authenticated_transfer_program().id(); let message = nssa::public_transaction::Message::new(program_id, addresses, nonces, balance_to_move); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 2bb5db1..aea82b9 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -6,8 +6,11 @@ edition = "2024" [dependencies] thiserror = "2.0.12" risc0-zkvm = "2.3.1" -nssa-core = { path = "core"} +nssa-core = { path = "core" } program-methods = { path = "program_methods" } serde = "1.0.219" serde_cbor = "0.11.2" sha2 = "0.10.9" + +[dev-dependencies] +test-program-methods = { path = "test_program_methods" } diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index d3ac729..8ed3fc4 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -12,7 +12,7 @@ fn main() { // Continue only if input_accounts is an array of two elements let [sender, receiver] = match input_accounts.try_into() { Ok(array) => array, - Err(_) => return, // silently return on bad input + Err(_) => return, }; // Continue only if the sender has authorized this operation diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index ab4fe02..bde852c 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -11,3 +11,6 @@ pub use signature::PrivateKey; pub use signature::PublicKey; pub use signature::Signature; pub use state::V01State; + +#[cfg(test)] +mod tests; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 8b6fac6..6c25828 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -8,15 +8,10 @@ use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor}; use crate::error::NssaError; pub struct Program { - id: ProgramId, - elf: &'static [u8], + pub(crate) id: ProgramId, + pub(crate) elf: &'static [u8], } -pub const AUTHENTICATED_TRANSFER_PROGRAM: Program = Program { - id: AUTHENTICATED_TRANSFER_ID, - elf: AUTHENTICATED_TRANSFER_ELF, -}; - impl Program { pub fn id(&self) -> ProgramId { self.id @@ -69,4 +64,25 @@ impl Program { .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; Ok(()) } + + pub fn authenticated_transfer_program() -> Self { + Self { + id: AUTHENTICATED_TRANSFER_ID, + elf: AUTHENTICATED_TRANSFER_ELF, + } + } +} + +// Test utils +#[cfg(test)] +impl Program { + /// A program that changes the nonce of an account + pub fn nonce_changer_program() -> Self { + use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID}; + + Program { + id: NONCE_CHANGER_ID, + elf: NONCE_CHANGER_ELF, + } + } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index b14eb72..3b7b77b 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1,8 +1,5 @@ use crate::{ - address::Address, - error::NssaError, - program::{AUTHENTICATED_TRANSFER_PROGRAM, Program}, - public_transaction::PublicTransaction, + address::Address, error::NssaError, program::Program, public_transaction::PublicTransaction, }; use nssa_core::{ account::{Account, AccountWithMetadata}, @@ -17,13 +14,14 @@ pub struct V01State { impl V01State { pub fn new_with_genesis_accounts(initial_data: &[([u8; 32], u128)]) -> Self { + let authenticated_transfer_program = Program::authenticated_transfer_program(); let public_state = initial_data .iter() .copied() .map(|(address_value, balance)| { let account = Account { balance, - program_owner: AUTHENTICATED_TRANSFER_PROGRAM.id(), + program_owner: authenticated_transfer_program.id(), ..Account::default() }; let address = Address::new(address_value); @@ -32,8 +30,8 @@ impl V01State { .collect(); let builtin_programs = HashMap::from([( - AUTHENTICATED_TRANSFER_PROGRAM.id(), - AUTHENTICATED_TRANSFER_PROGRAM, + authenticated_transfer_program.id(), + authenticated_transfer_program, )]); Self { @@ -140,118 +138,15 @@ impl V01State { } } +// Test utils #[cfg(test)] -mod tests { - use super::*; - use crate::{public_transaction, signature::PrivateKey}; - - fn transfer_transaction( - from: Address, - from_key: PrivateKey, - nonce: u128, - to: Address, - balance: u128, - ) -> PublicTransaction { - let addresses = vec![from, to]; - let nonces = vec![nonce]; - let program_id = AUTHENTICATED_TRANSFER_PROGRAM.id(); - let message = public_transaction::Message::new(program_id, addresses, nonces, balance); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); - PublicTransaction::new(message, witness_set) - } - - #[test] - fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data); - let from = Address::new(initial_data[0].0); - let from_key = PrivateKey(1); - let to = Address::new([2; 32]); - assert_eq!(state.get_account_by_address(&to), Account::default()); - let balance_to_move = 5; - - let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_address(&from).balance, 95); - assert_eq!(state.get_account_by_address(&to).balance, 5); - assert_eq!(state.get_account_by_address(&from).nonce, 1); - assert_eq!(state.get_account_by_address(&to).nonce, 0); - } - - #[test] - fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data); - let from = Address::new(initial_data[0].0); - let from_key = PrivateKey(1); - let to = Address::new([2; 32]); - let balance_to_move = 101; - assert!(state.get_account_by_address(&from).balance < balance_to_move); - - let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); - assert_eq!(state.get_account_by_address(&from).balance, 100); - assert_eq!(state.get_account_by_address(&to).balance, 0); - assert_eq!(state.get_account_by_address(&from).nonce, 0); - assert_eq!(state.get_account_by_address(&to).nonce, 0); - } - - #[test] - fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { - let initial_data = [([1; 32], 100), ([99; 32], 200)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data); - let from = Address::new(initial_data[1].0); - let from_key = PrivateKey(99); - let to = Address::new(initial_data[0].0); - assert_ne!(state.get_account_by_address(&to), Account::default()); - let balance_to_move = 8; - - let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_address(&from).balance, 192); - assert_eq!(state.get_account_by_address(&to).balance, 108); - assert_eq!(state.get_account_by_address(&from).nonce, 1); - assert_eq!(state.get_account_by_address(&to).nonce, 0); - } - - #[test] - fn transition_from_chained_authenticated_transfer_program_invocations() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data); - let address_1 = Address::new(initial_data[0].0); - let key_1 = PrivateKey(1); - let address_2 = Address::new([2; 32]); - let key_2 = PrivateKey(2); - let address_3 = Address::new([3; 32]); - let balance_to_move = 5; - - let tx = transfer_transaction( - address_1.clone(), - key_1, - 0, - address_2.clone(), - balance_to_move, +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(), ); - state.transition_from_public_transaction(&tx).unwrap(); - let balance_to_move = 3; - let tx = transfer_transaction( - address_2.clone(), - key_2, - 0, - address_3.clone(), - balance_to_move, - ); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_address(&address_1).balance, 95); - assert_eq!(state.get_account_by_address(&address_2).balance, 2); - assert_eq!(state.get_account_by_address(&address_3).balance, 3); - assert_eq!(state.get_account_by_address(&address_1).nonce, 1); - assert_eq!(state.get_account_by_address(&address_2).nonce, 1); - assert_eq!(state.get_account_by_address(&address_3).nonce, 0); + self } } diff --git a/nssa/src/tests/mod.rs b/nssa/src/tests/mod.rs new file mode 100644 index 0000000..8d49dbd --- /dev/null +++ b/nssa/src/tests/mod.rs @@ -0,0 +1 @@ +mod state_tests; diff --git a/nssa/src/tests/state_tests.rs b/nssa/src/tests/state_tests.rs new file mode 100644 index 0000000..cae157f --- /dev/null +++ b/nssa/src/tests/state_tests.rs @@ -0,0 +1,131 @@ +use crate::{ + Address, PublicTransaction, V01State, error::NssaError, program::Program, public_transaction, + signature::PrivateKey, +}; +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, + from_key: PrivateKey, + nonce: u128, + to: Address, + balance: u128, +) -> PublicTransaction { + let addresses = vec![from, to]; + let nonces = vec![nonce]; + let program_id = Program::authenticated_transfer_program().id(); + let message = public_transaction::Message::new(program_id, addresses, nonces, balance); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); + PublicTransaction::new(message, witness_set) +} + +#[test] +fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + let from = Address::new(initial_data[0].0); + let from_key = PrivateKey(1); + let to = Address::new([2; 32]); + assert_eq!(state.get_account_by_address(&to), Account::default()); + let balance_to_move = 5; + + let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_address(&from).balance, 95); + assert_eq!(state.get_account_by_address(&to).balance, 5); + assert_eq!(state.get_account_by_address(&from).nonce, 1); + assert_eq!(state.get_account_by_address(&to).nonce, 0); +} + +#[test] +fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + let from = Address::new(initial_data[0].0); + let from_key = PrivateKey(1); + let to = Address::new([2; 32]); + let balance_to_move = 101; + assert!(state.get_account_by_address(&from).balance < balance_to_move); + + let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); + assert_eq!(state.get_account_by_address(&from).balance, 100); + assert_eq!(state.get_account_by_address(&to).balance, 0); + assert_eq!(state.get_account_by_address(&from).nonce, 0); + assert_eq!(state.get_account_by_address(&to).nonce, 0); +} + +#[test] +fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { + let initial_data = [([1; 32], 100), ([99; 32], 200)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + let from = Address::new(initial_data[1].0); + let from_key = PrivateKey(99); + let to = Address::new(initial_data[0].0); + assert_ne!(state.get_account_by_address(&to), Account::default()); + let balance_to_move = 8; + + let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_address(&from).balance, 192); + assert_eq!(state.get_account_by_address(&to).balance, 108); + assert_eq!(state.get_account_by_address(&from).nonce, 1); + assert_eq!(state.get_account_by_address(&to).nonce, 0); +} + +#[test] +fn transition_from_chained_authenticated_transfer_program_invocations() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + let address_1 = Address::new(initial_data[0].0); + let key_1 = PrivateKey(1); + let address_2 = Address::new([2; 32]); + let key_2 = PrivateKey(2); + let address_3 = Address::new([3; 32]); + let balance_to_move = 5; + + let tx = transfer_transaction( + address_1.clone(), + key_1, + 0, + address_2.clone(), + balance_to_move, + ); + state.transition_from_public_transaction(&tx).unwrap(); + let balance_to_move = 3; + let tx = transfer_transaction( + address_2.clone(), + key_2, + 0, + address_3.clone(), + balance_to_move, + ); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_address(&address_1).balance, 95); + assert_eq!(state.get_account_by_address(&address_2).balance, 2); + assert_eq!(state.get_account_by_address(&address_3).balance, 3); + assert_eq!(state.get_account_by_address(&address_1).nonce, 1); + assert_eq!(state.get_account_by_address(&address_2).nonce, 1); + assert_eq!(state.get_account_by_address(&address_3).nonce, 0); +} diff --git a/nssa/test_program_methods/Cargo.toml b/nssa/test_program_methods/Cargo.toml new file mode 100644 index 0000000..7da210b --- /dev/null +++ b/nssa/test_program_methods/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "test-program-methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "2.3.1" } + +[package.metadata.risc0] +methods = ["guest"] + diff --git a/nssa/test_program_methods/build.rs b/nssa/test_program_methods/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/nssa/test_program_methods/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/nssa/test_program_methods/guest/Cargo.toml b/nssa/test_program_methods/guest/Cargo.toml new file mode 100644 index 0000000..12bddbf --- /dev/null +++ b/nssa/test_program_methods/guest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "programs" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "2.3.1", default-features = false, features = ['std'] } +nssa-core = {path = "../../core"} + diff --git a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs new file mode 100644 index 0000000..08007f6 --- /dev/null +++ b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs @@ -0,0 +1,18 @@ +use nssa_core::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; + let mut account_post = account_pre.clone(); + account_post.nonce += 1; + + env::commit(&vec![account_post]); +} diff --git a/nssa/test_program_methods/src/lib.rs b/nssa/test_program_methods/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/nssa/test_program_methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 04bd26b..9d52def 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -188,55 +188,11 @@ mod tests { setup_sequencer_config_variable_initial_accounts(initial_accounts) } - // fn create_dummy_transaction() -> nssa::PublicTransaction { - // let program_id = nssa::AUTHENTICATED_TRANSFER_PROGRAM.id; - // let addresses = vec![]; - // let nonces = vec![]; - // let instruction_data = 0; - // let message = - // nssa::public_transaction::Message::new(program_id, addresses, nonces, instruction_data); - // let private_key = nssa::PrivateKey::new(1); - // let witness_set = - // nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]); - // nssa::PublicTransaction::new(message, witness_set) - // } - // - // fn create_dummy_transaction_native_token_transfer( - // from: [u8; 32], - // nonce: u128, - // to: [u8; 32], - // balance_to_move: u128, - // signing_key: nssa::PrivateKey, - // ) -> nssa::PublicTransaction { - // let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)]; - // let nonces = vec![nonce]; - // let program_id = nssa::AUTHENTICATED_TRANSFER_PROGRAM.id; - // let message = - // nssa::public_transaction::Message::new(program_id, addresses, nonces, balance_to_move); - // let witness_set = - // nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]); - // nssa::PublicTransaction::new(message, witness_set) - // } - fn create_signing_key_for_account1() -> nssa::PrivateKey { - // let pub_sign_key_acc1 = [ - // 133, 143, 177, 187, 252, 66, 237, 236, 234, 252, 244, 138, 5, 151, 3, 99, 217, 231, - // 112, 217, 77, 211, 58, 218, 176, 68, 99, 53, 152, 228, 198, 190, - // ]; - // - // let field_bytes = FieldBytes::from_slice(&pub_sign_key_acc1); - // SigningKey::from_bytes(field_bytes).unwrap() nssa::PrivateKey::new(1) } fn create_signing_key_for_account2() -> nssa::PrivateKey { - // let pub_sign_key_acc2 = [ - // 54, 90, 62, 225, 71, 225, 228, 148, 143, 53, 210, 23, 137, 158, 171, 156, 48, 7, 139, - // 52, 117, 242, 214, 7, 99, 29, 122, 184, 59, 116, 144, 107, - // ]; - // - // let field_bytes = FieldBytes::from_slice(&pub_sign_key_acc2); - // SigningKey::from_bytes(field_bytes).unwrap() nssa::PrivateKey::new(2) } diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index d07b4a4..da92b82 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -78,31 +78,7 @@ mod tests { use super::*; use tempfile::tempdir; - // - // fn create_dummy_block_with_transaction(block_id: u64) -> (Block, nssa::PublicTransaction) { - // let program_id = nssa::AUTHENTICATED_TRANSFER_PROGRAM.id; - // let addresses = vec![]; - // let nonces = vec![]; - // let instruction_data = 0; - // let message = - // nssa::public_transaction::Message::new(program_id, addresses, nonces, instruction_data); - // let private_key = nssa::PrivateKey::new(1); - // let witness_set = - // nssa::public_transaction::WitnessSet::for_message(&message, &[private_key]); - // let tx = nssa::PublicTransaction::new(message, witness_set); - // ( - // Block { - // block_id, - // prev_block_id: block_id - 1, - // prev_block_hash: [0; 32], - // hash: [1; 32], - // transactions: vec![tx.clone()], - // data: vec![], - // }, - // tx, - // ) - // } - // + #[test] fn test_get_transaction_by_hash() { let temp_dir = tempdir().unwrap(); diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 70f1986..1757636 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -498,7 +498,7 @@ mod tests { ], "instruction_data": 10, "nonces": [0], - "program_id": nssa::program::AUTHENTICATED_TRANSFER_PROGRAM.id(), + "program_id": nssa::program::Program::authenticated_transfer_program().id(), }, "witness_set": { "signatures_and_public_keys": [ diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 34f8cc2..b18b07f 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -86,7 +86,7 @@ impl NodeCore { if let Some(account) = account { let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)]; let nonces = vec![nonce]; - let program_id = nssa::program::AUTHENTICATED_TRANSFER_PROGRAM.id(); + let program_id = nssa::program::Program::authenticated_transfer_program().id(); let message = nssa::public_transaction::Message::new( program_id, addresses,