From 2e582e7874eb8c164d3753f4f7652bda6c718a79 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 12 Nov 2025 19:08:46 -0300 Subject: [PATCH 01/13] add multi chain calls --- nssa/core/src/program.rs | 6 +++--- .../src/bin/privacy_preserving_circuit.rs | 2 +- nssa/src/public_transaction/transaction.rs | 5 ++++- nssa/src/state.rs | 5 +++-- .../guest/src/bin/chain_caller.rs | 19 +++++++++++++------ 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 3ecee30..d36cf8f 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -25,7 +25,7 @@ pub struct ChainedCall { pub struct ProgramOutput { pub pre_states: Vec, pub post_states: Vec, - pub chained_call: Option, + pub chained_call: Vec, } pub fn read_nssa_inputs() -> ProgramInput { @@ -42,7 +42,7 @@ pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec let output = ProgramOutput { pre_states, post_states, - chained_call: None, + chained_call: Vec::new(), }; env::commit(&output); } @@ -50,7 +50,7 @@ pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec pub fn write_nssa_outputs_with_chained_call( pre_states: Vec, post_states: Vec, - chained_call: Option, + chained_call: Vec, ) { let output = ProgramOutput { pre_states, diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index d8ed15d..530c87e 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -31,7 +31,7 @@ fn main() { } = program_output; // TODO: implement chained calls for privacy preserving transactions - if chained_call.is_some() { + if !chained_call.is_empty() { panic!("Privacy preserving transactions do not support yet chained calls.") } diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index d118d0c..cce4ffd 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -105,6 +105,7 @@ impl PublicTransaction { let mut program_id = message.program_id; let mut instruction_data = message.instruction_data.clone(); + let mut chained_calls = Vec::new(); for _i in 0..MAX_NUMBER_CHAINED_CALLS { // Check the `program_id` corresponds to a deployed program @@ -147,7 +148,9 @@ impl PublicTransaction { state_diff.insert(pre.account_id, post.clone()); } - if let Some(next_chained_call) = program_output.chained_call { + chained_calls.extend_from_slice(&program_output.chained_call); + + if let Some(next_chained_call) = chained_calls.pop() { program_id = next_chained_call.program_id; instruction_data = next_chained_call.instruction_data; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 4120824..b0f60eb 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2096,7 +2096,7 @@ pub mod tests { let expected_to_post = Account { program_owner: Program::chain_caller().id(), - balance: amount, + balance: amount * 2, // The `chain_caller` chains the program twice ..Account::default() }; @@ -2114,7 +2114,8 @@ pub mod tests { let from_post = state.get_account_by_address(&from); let to_post = state.get_account_by_address(&to); - assert_eq!(from_post.balance, initial_balance - amount); + // The `chain_caller` program calls the program twice + assert_eq!(from_post.balance, initial_balance - 2 * amount); assert_eq!(to_post, expected_to_post); } } diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index dfd77b1..c4a548b 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -5,7 +5,7 @@ use risc0_zkvm::serde::to_vec; type Instruction = (u128, ProgramId); -/// A program that calls another program. +/// A program that calls another program twice. /// It permutes the order of the input accounts on the subsequent call fn main() { let ProgramInput { @@ -20,11 +20,18 @@ fn main() { let instruction_data = to_vec(&balance).unwrap(); - let chained_call = Some(ChainedCall { - program_id, - instruction_data, - account_indices: vec![1, 0], // <- Account order permutation here - }); + let chained_call = vec![ + ChainedCall { + program_id, + instruction_data: instruction_data.clone(), + account_indices: vec![0, 1], + }, + ChainedCall { + program_id, + instruction_data, + account_indices: vec![1, 0], // <- Account order permutation here + }, + ]; write_nssa_outputs_with_chained_call( vec![sender_pre.clone(), receiver_pre.clone()], From a94440fa1f6ccd030d56c1434755d175cc913338 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 12 Nov 2025 19:18:04 -0300 Subject: [PATCH 02/13] rename --- nssa/core/src/program.rs | 6 +++--- .../guest/src/bin/privacy_preserving_circuit.rs | 4 ++-- nssa/src/public_transaction/transaction.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index d36cf8f..8db2679 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -25,7 +25,7 @@ pub struct ChainedCall { pub struct ProgramOutput { pub pre_states: Vec, pub post_states: Vec, - pub chained_call: Vec, + pub chained_calls: Vec, } pub fn read_nssa_inputs() -> ProgramInput { @@ -42,7 +42,7 @@ pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec let output = ProgramOutput { pre_states, post_states, - chained_call: Vec::new(), + chained_calls: Vec::new(), }; env::commit(&output); } @@ -55,7 +55,7 @@ pub fn write_nssa_outputs_with_chained_call( let output = ProgramOutput { pre_states, post_states, - chained_call, + chained_calls: chained_call, }; env::commit(&output); } diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 530c87e..6696245 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -27,11 +27,11 @@ fn main() { let ProgramOutput { pre_states, post_states, - chained_call, + chained_calls, } = program_output; // TODO: implement chained calls for privacy preserving transactions - if !chained_call.is_empty() { + if !chained_calls.is_empty() { panic!("Privacy preserving transactions do not support yet chained calls.") } diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index cce4ffd..cfee8db 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -148,7 +148,7 @@ impl PublicTransaction { state_diff.insert(pre.account_id, post.clone()); } - chained_calls.extend_from_slice(&program_output.chained_call); + chained_calls.extend_from_slice(&program_output.chained_calls); if let Some(next_chained_call) = chained_calls.pop() { program_id = next_chained_call.program_id; From c7b415b2f4b57b46b0544197c7d95c4fd553219d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 12 Nov 2025 19:55:02 -0300 Subject: [PATCH 03/13] add max depth reached error for chained calls --- nssa/src/error.rs | 3 ++ nssa/src/public_transaction/transaction.rs | 8 +++- nssa/src/state.rs | 45 +++++++++++++++++-- .../guest/src/bin/chain_caller.rs | 22 ++++----- 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 8ed9657..2299731 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -54,4 +54,7 @@ pub enum NssaError { #[error("Program already exists")] ProgramAlreadyExists, + + #[error("Chain of calls too long")] + MaxChainedCallsDepthExceeded, } diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index cfee8db..199d60d 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -11,6 +11,7 @@ use crate::{ V02State, error::NssaError, public_transaction::{Message, WitnessSet}, + state::MAX_NUMBER_CHAINED_CALLS, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -18,7 +19,6 @@ pub struct PublicTransaction { message: Message, witness_set: WitnessSet, } -const MAX_NUMBER_CHAINED_CALLS: usize = 10; impl PublicTransaction { pub fn new(message: Message, witness_set: WitnessSet) -> Self { @@ -183,7 +183,11 @@ impl PublicTransaction { }; } - Ok(state_diff) + if chained_calls.is_empty() { + Ok(state_diff) + } else { + Err(NssaError::MaxChainedCallsDepthExceeded) + } } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index b0f60eb..d53609c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -10,6 +10,8 @@ use nssa_core::{ }; use std::collections::{HashMap, HashSet}; +pub const MAX_NUMBER_CHAINED_CALLS: usize = 10; + pub(crate) struct CommitmentSet { merkle_tree: MerkleTree, commitments: HashMap, @@ -251,6 +253,7 @@ pub mod tests { program::Program, public_transaction, signature::PrivateKey, + state::MAX_NUMBER_CHAINED_CALLS, }; use nssa_core::{ @@ -2079,7 +2082,7 @@ pub mod tests { } #[test] - fn test_chained_call() { + fn test_chained_call_succeeds() { let program = Program::chain_caller(); let key = PrivateKey::try_new([1; 32]).unwrap(); let address = Address::from(&PublicKey::new_from_private_key(&key)); @@ -2091,8 +2094,8 @@ pub mod tests { let from_key = key; let to = Address::new([2; 32]); let amount: u128 = 37; - let instruction: (u128, ProgramId) = - (amount, Program::authenticated_transfer_program().id()); + let instruction: (u128, ProgramId, u32) = + (amount, Program::authenticated_transfer_program().id(), 2); let expected_to_post = Account { program_owner: Program::chain_caller().id(), @@ -2118,4 +2121,40 @@ pub mod tests { assert_eq!(from_post.balance, initial_balance - 2 * amount); assert_eq!(to_post, expected_to_post); } + + #[test] + fn test_execution_fails_if_chained_calls_exceeds_depth() { + let program = Program::chain_caller(); + let key = PrivateKey::try_new([1; 32]).unwrap(); + let address = Address::from(&PublicKey::new_from_private_key(&key)); + let initial_balance = 100; + let initial_data = [(address, initial_balance)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let from = address; + let from_key = key; + let to = Address::new([2; 32]); + let amount: u128 = 0; + let instruction: (u128, ProgramId, u32) = ( + amount, + Program::authenticated_transfer_program().id(), + MAX_NUMBER_CHAINED_CALLS as u32 + 1, + ); + + let message = public_transaction::Message::try_new( + program.id(), + vec![to, from], //The chain_caller program permutes the account order in the chain call + vec![0], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + assert!(matches!( + result, + Err(NssaError::MaxChainedCallsDepthExceeded) + )); + } } diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index c4a548b..5ebb6e6 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -3,14 +3,14 @@ use nssa_core::program::{ }; use risc0_zkvm::serde::to_vec; -type Instruction = (u128, ProgramId); +type Instruction = (u128, ProgramId, u32); -/// A program that calls another program twice. +/// A program that calls another program `num_chain_calls` times. /// It permutes the order of the input accounts on the subsequent call fn main() { let ProgramInput { pre_states, - instruction: (balance, program_id), + instruction: (balance, program_id, num_chain_calls), } = read_nssa_inputs::(); let [sender_pre, receiver_pre] = match pre_states.try_into() { @@ -20,19 +20,21 @@ fn main() { let instruction_data = to_vec(&balance).unwrap(); - let chained_call = vec![ + let mut chained_call = vec![ ChainedCall { program_id, instruction_data: instruction_data.clone(), account_indices: vec![0, 1], - }, - ChainedCall { - program_id, - instruction_data, - account_indices: vec![1, 0], // <- Account order permutation here - }, + }; + num_chain_calls as usize - 1 ]; + chained_call.push(ChainedCall { + program_id, + instruction_data, + account_indices: vec![1, 0], // <- Account order permutation here + }); + write_nssa_outputs_with_chained_call( vec![sender_pre.clone(), receiver_pre.clone()], vec![sender_pre.account, receiver_pre.account], From fd4ebde1fb76d33a4289d6959a2fb789fa3b4208 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 17 Nov 2025 15:43:01 -0300 Subject: [PATCH 04/13] fix account passing mechanism --- nssa/core/src/program.rs | 2 +- nssa/src/public_transaction/transaction.rs | 47 +++++++------------ nssa/src/state.rs | 26 +++++----- .../guest/src/bin/chain_caller.rs | 4 +- 4 files changed, 32 insertions(+), 47 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 8db2679..1a9119c 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -17,7 +17,7 @@ pub struct ProgramInput { pub struct ChainedCall { pub program_id: ProgramId, pub instruction_data: InstructionData, - pub account_indices: Vec, + pub pre_states: Vec, } #[derive(Serialize, Deserialize, Clone)] diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 199d60d..c1eebd0 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -115,11 +115,22 @@ impl PublicTransaction { let mut program_output = program.execute(&input_pre_states, &instruction_data)?; - // This check is equivalent to checking that the program output pre_states coinicide - // with the values in the public state or with any modifications to those values - // during the chain of calls. - if input_pre_states != program_output.pre_states { - return Err(NssaError::InvalidProgramBehavior); + for pre in program_output.pre_states.iter() { + let account_id = pre.account_id; + // Check that the program output pre_states coinicide with the values in the public + // state or with any modifications to those values during the chain of calls. + let expected_pre = state_diff + .get(&account_id) + .cloned() + .unwrap_or_else(|| state.get_account_by_address(&account_id)); + if pre.account != expected_pre { + return Err(NssaError::InvalidProgramBehavior); + } + + // Check that authorization flags are consistent with the provided ones + if pre.is_authorized && !signer_addresses.contains(&account_id) { + return Err(NssaError::InvalidProgramBehavior); + } } // Verify execution corresponds to a well-behaved program. @@ -153,31 +164,7 @@ impl PublicTransaction { if let Some(next_chained_call) = chained_calls.pop() { program_id = next_chained_call.program_id; instruction_data = next_chained_call.instruction_data; - - // Build post states with metadata for next call - let mut post_states_with_metadata = Vec::new(); - for (pre, post) in program_output - .pre_states - .iter() - .zip(program_output.post_states) - { - let mut post_with_metadata = pre.clone(); - post_with_metadata.account = post.clone(); - post_states_with_metadata.push(post_with_metadata); - } - - input_pre_states = next_chained_call - .account_indices - .iter() - .map(|&i| { - post_states_with_metadata - .get(i) - .ok_or_else(|| { - NssaError::InvalidInput("Invalid account indices".into()) - }) - .cloned() - }) - .collect::, NssaError>>()?; + input_pre_states = next_chained_call.pre_states; } else { break; }; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index d53609c..3dbb2ec 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2085,27 +2085,26 @@ pub mod tests { fn test_chained_call_succeeds() { let program = Program::chain_caller(); let key = PrivateKey::try_new([1; 32]).unwrap(); - let address = Address::from(&PublicKey::new_from_private_key(&key)); + let from_address = Address::from(&PublicKey::new_from_private_key(&key)); + let to_address = Address::new([2; 32]); let initial_balance = 100; - let initial_data = [(address, initial_balance)]; + let initial_data = [(from_address, initial_balance), (to_address, 0)]; let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let from = address; let from_key = key; - let to = Address::new([2; 32]); - let amount: u128 = 37; + let amount: u128 = 0; let instruction: (u128, ProgramId, u32) = (amount, Program::authenticated_transfer_program().id(), 2); let expected_to_post = Account { - program_owner: Program::chain_caller().id(), + program_owner: Program::authenticated_transfer_program().id(), balance: amount * 2, // The `chain_caller` chains the program twice ..Account::default() }; let message = public_transaction::Message::try_new( program.id(), - vec![to, from], //The chain_caller program permutes the account order in the chain call + vec![to_address, from_address], //The chain_caller program permutes the account order in the chain call vec![0], instruction, ) @@ -2115,8 +2114,8 @@ pub mod tests { state.transition_from_public_transaction(&tx).unwrap(); - let from_post = state.get_account_by_address(&from); - let to_post = state.get_account_by_address(&to); + let from_post = state.get_account_by_address(&from_address); + let to_post = state.get_account_by_address(&to_address); // The `chain_caller` program calls the program twice assert_eq!(from_post.balance, initial_balance - 2 * amount); assert_eq!(to_post, expected_to_post); @@ -2126,14 +2125,13 @@ pub mod tests { fn test_execution_fails_if_chained_calls_exceeds_depth() { let program = Program::chain_caller(); let key = PrivateKey::try_new([1; 32]).unwrap(); - let address = Address::from(&PublicKey::new_from_private_key(&key)); + let from_address = Address::from(&PublicKey::new_from_private_key(&key)); + let to_address = Address::new([2; 32]); let initial_balance = 100; - let initial_data = [(address, initial_balance)]; + let initial_data = [(from_address, initial_balance), (to_address, 0)]; let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let from = address; let from_key = key; - let to = Address::new([2; 32]); let amount: u128 = 0; let instruction: (u128, ProgramId, u32) = ( amount, @@ -2143,7 +2141,7 @@ pub mod tests { let message = public_transaction::Message::try_new( program.id(), - vec![to, from], //The chain_caller program permutes the account order in the chain call + vec![to_address, from_address], //The chain_caller program permutes the account order in the chain call vec![0], instruction, ) diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index 5ebb6e6..028f8a0 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -24,7 +24,7 @@ fn main() { ChainedCall { program_id, instruction_data: instruction_data.clone(), - account_indices: vec![0, 1], + pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here }; num_chain_calls as usize - 1 ]; @@ -32,7 +32,7 @@ fn main() { chained_call.push(ChainedCall { program_id, instruction_data, - account_indices: vec![1, 0], // <- Account order permutation here + pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here }); write_nssa_outputs_with_chained_call( From 8af6365c5d96dc8216580cde3b33998fe86d0d0b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 24 Nov 2025 08:47:33 -0300 Subject: [PATCH 05/13] noop --- integration_tests/src/test_suite_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 0bbc8b0..1b0f14c 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -1345,7 +1345,7 @@ pub fn prepare_function_map() -> HashMap { #[nssa_integration_test] pub async fn test_pinata() { - info!("########## test_pinata ##########"); + info!("########## test_pinata ###########"); let pinata_addr = PINATA_BASE58; let pinata_prize = 150; let solution = 989106; From 893011e7bfcddb62e0f04dbda9ee17c397061a91 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 24 Nov 2025 10:07:43 -0300 Subject: [PATCH 06/13] hot fix for integration test --- integration_tests/src/test_suite_map.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 1b0f14c..47bea3a 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -1181,8 +1181,7 @@ pub fn prepare_function_map() -> HashMap { let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; println!("Waiting for next blocks to check if continoius run fetch account"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + tokio::time::sleep(Duration::from_secs(8 * TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) .await From d1d22920286439d39476d4547ebd7df543cebd38 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 26 Nov 2025 20:13:23 -0300 Subject: [PATCH 07/13] fmt --- nssa/src/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 90d2a9a..cef7791 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2110,7 +2110,7 @@ pub mod tests { let message = public_transaction::Message::try_new( program.id(), vec![to, from], // The chain_caller program permutes the account order in the chain - // call + // call vec![0], instruction, ) @@ -2148,7 +2148,7 @@ pub mod tests { let message = public_transaction::Message::try_new( program.id(), vec![to, from], // The chain_caller program permutes the account order in the chain - // call + // call vec![0], instruction, ) From d7331455fc5cef688087043681b21324033c63c9 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 27 Nov 2025 04:22:49 +0300 Subject: [PATCH 08/13] feat: add list account subcommand --- integration_tests/src/test_suite_map.rs | 4 ++-- wallet/Cargo.toml | 1 + wallet/src/cli/account.rs | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 2fa8e1d..648e8a5 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -1169,8 +1169,8 @@ pub fn prepare_function_map() -> HashMap { // #[nssa_integration_test] // pub async fn test_success_private_transfer_to_another_owned_account_cont_run_path() { // info!( - // "########## test_success_private_transfer_to_another_owned_account_cont_run_path ##########" - // ); + // "########## test_success_private_transfer_to_another_owned_account_cont_run_path + // ##########" ); // let continious_run_handle = tokio::spawn(wallet::execute_continious_run()); // let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index b04d67e..74eb5bc 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -19,6 +19,7 @@ borsh.workspace = true base58.workspace = true hex = "0.4.3" rand.workspace = true +itertools = "0.14.0" [dependencies.key_protocol] path = "../key_protocol" diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 79371c8..d1e361a 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -1,6 +1,7 @@ use anyhow::Result; use base58::ToBase58; use clap::Subcommand; +use itertools::Itertools as _; use nssa::{Account, AccountId, program::Program}; use serde::Serialize; @@ -83,6 +84,9 @@ pub enum AccountSubcommand { New(NewSubcommand), /// Sync private accounts SyncPrivate {}, + /// List all accounts owned by the wallet + #[command(visible_alias = "ls")] + List {}, } /// Represents generic register CLI subcommand @@ -294,6 +298,23 @@ impl WalletSubcommand for AccountSubcommand { Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block)) } + AccountSubcommand::List {} => { + let user_data = &wallet_core.storage.user_data; + let accounts = user_data + .pub_account_signing_keys + .keys() + .map(|id| format!("Public/{id}")) + .chain( + user_data + .user_private_accounts + .keys() + .map(|id| format!("Private/{id}")), + ) + .format(",\n"); + + println!("{accounts}"); + Ok(SubcommandReturnValue::Empty) + } } } } From 577fad6d5f5ae87cf83e5b0b66ee653d8b29f52e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 09:54:14 -0300 Subject: [PATCH 09/13] refactor call stack execution loop --- nssa/src/public_transaction/transaction.rs | 45 +++++++++++----------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 61c625e..a399ad3 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, - program::{DEFAULT_PROGRAM_ID, validate_execution}, + program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, }; use sha2::{Digest, digest::FixedOutput}; @@ -88,7 +88,7 @@ impl PublicTransaction { } // Build pre_states for execution - let mut input_pre_states: Vec<_> = message + let input_pre_states: Vec<_> = message .account_ids .iter() .map(|account_id| { @@ -102,17 +102,27 @@ impl PublicTransaction { let mut state_diff: HashMap = HashMap::new(); - let mut program_id = message.program_id; - let mut instruction_data = message.instruction_data.clone(); - let mut chained_calls = Vec::new(); + let initial_call = ChainedCall { + program_id: message.program_id, + instruction_data: message.instruction_data.clone(), + pre_states: input_pre_states, + }; + + let mut chained_calls = Vec::from_iter([initial_call]); + let mut chain_calls_counter = 0; + + while let Some(chained_call) = chained_calls.pop() { + if chain_calls_counter > MAX_NUMBER_CHAINED_CALLS { + return Err(NssaError::MaxChainedCallsDepthExceeded); + } - for _i in 0..MAX_NUMBER_CHAINED_CALLS { // Check the `program_id` corresponds to a deployed program - let Some(program) = state.programs().get(&program_id) else { + let Some(program) = state.programs().get(&chained_call.program_id) else { return Err(NssaError::InvalidInput("Unknown program".into())); }; - let mut program_output = program.execute(&input_pre_states, &instruction_data)?; + let mut program_output = + program.execute(&chained_call.pre_states, &chained_call.instruction_data)?; for pre in program_output.pre_states.iter() { let account_id = pre.account_id; @@ -137,7 +147,7 @@ impl PublicTransaction { if !validate_execution( &program_output.pre_states, &program_output.post_states, - program_id, + chained_call.program_id, ) { return Err(NssaError::InvalidProgramBehavior); } @@ -145,7 +155,7 @@ impl PublicTransaction { // The invoked program claims the accounts with default program id. for post in program_output.post_states.iter_mut() { if post.program_owner == DEFAULT_PROGRAM_ID { - post.program_owner = program_id; + post.program_owner = chained_call.program_id; } } @@ -159,21 +169,10 @@ impl PublicTransaction { } chained_calls.extend_from_slice(&program_output.chained_calls); - - if let Some(next_chained_call) = chained_calls.pop() { - program_id = next_chained_call.program_id; - instruction_data = next_chained_call.instruction_data; - input_pre_states = next_chained_call.pre_states; - } else { - break; - }; + chain_calls_counter += 1; } - if chained_calls.is_empty() { - Ok(state_diff) - } else { - Err(NssaError::MaxChainedCallsDepthExceeded) - } + Ok(state_diff) } } From 103332f6cd15131679493f6e0176b05980f9086d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 09:56:52 -0300 Subject: [PATCH 10/13] nit --- nssa/core/src/program.rs | 4 ++-- nssa/src/error.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index f3aaf36..054f993 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -50,12 +50,12 @@ pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec pub fn write_nssa_outputs_with_chained_call( pre_states: Vec, post_states: Vec, - chained_call: Vec, + chained_calls: Vec, ) { let output = ProgramOutput { pre_states, post_states, - chained_calls: chained_call, + chained_calls, }; env::commit(&output); } diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 2299731..45d5310 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -55,6 +55,6 @@ pub enum NssaError { #[error("Program already exists")] ProgramAlreadyExists, - #[error("Chain of calls too long")] + #[error("Chain of calls is too long")] MaxChainedCallsDepthExceeded, } From aba1d844f806c6d174ad2b8899d4639a8c8a4840 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 10:42:58 -0300 Subject: [PATCH 11/13] consume call stack from the other end --- nssa/src/public_transaction/transaction.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index a399ad3..af07895 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap, HashSet, VecDeque}; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, @@ -108,10 +108,10 @@ impl PublicTransaction { pre_states: input_pre_states, }; - let mut chained_calls = Vec::from_iter([initial_call]); + let mut chained_calls = VecDeque::from_iter([initial_call]); let mut chain_calls_counter = 0; - while let Some(chained_call) = chained_calls.pop() { + while let Some(chained_call) = chained_calls.pop_front() { if chain_calls_counter > MAX_NUMBER_CHAINED_CALLS { return Err(NssaError::MaxChainedCallsDepthExceeded); } @@ -168,7 +168,7 @@ impl PublicTransaction { state_diff.insert(pre.account_id, post.clone()); } - chained_calls.extend_from_slice(&program_output.chained_calls); + chained_calls.extend(program_output.chained_calls); chain_calls_counter += 1; } From f46d7ee4268ab18562a1353bbbba9031c0e67a4b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 10:46:35 -0300 Subject: [PATCH 12/13] nit --- nssa/src/public_transaction/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index af07895..bce1703 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -124,7 +124,7 @@ impl PublicTransaction { let mut program_output = program.execute(&chained_call.pre_states, &chained_call.instruction_data)?; - for pre in program_output.pre_states.iter() { + for pre in &program_output.pre_states { let account_id = pre.account_id; // Check that the program output pre_states coinicide with the values in the public // state or with any modifications to those values during the chain of calls. From 409ec199590117bdd0ab9ddd34ccdd9b26c2e954 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 10:53:25 -0300 Subject: [PATCH 13/13] fix concatenation of call stack --- nssa/src/public_transaction/transaction.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index bce1703..f10a266 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -168,7 +168,10 @@ impl PublicTransaction { state_diff.insert(pre.account_id, post.clone()); } - chained_calls.extend(program_output.chained_calls); + for new_call in program_output.chained_calls.into_iter().rev() { + chained_calls.push_front(new_call); + } + chain_calls_counter += 1; }