diff --git a/Cargo.lock b/Cargo.lock index 57135709..afba784e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9211,6 +9211,7 @@ version = "0.1.0" dependencies = [ "authenticated_transfer_core", "clock_core", + "faucet_core", "nssa_core", "risc0-zkvm", "serde", diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index d9f6f94d..2d8e902c 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/vault.bin b/artifacts/program_methods/vault.bin index 58417603..7f7ce6d6 100644 Binary files a/artifacts/program_methods/vault.bin and b/artifacts/program_methods/vault.bin differ diff --git a/artifacts/test_program_methods/auth_asserting_noop.bin b/artifacts/test_program_methods/auth_asserting_noop.bin index 3293d845..80835971 100644 Binary files a/artifacts/test_program_methods/auth_asserting_noop.bin and b/artifacts/test_program_methods/auth_asserting_noop.bin differ diff --git a/artifacts/test_program_methods/auth_transfer_proxy.bin b/artifacts/test_program_methods/auth_transfer_proxy.bin index a3122134..490223a5 100644 Binary files a/artifacts/test_program_methods/auth_transfer_proxy.bin and b/artifacts/test_program_methods/auth_transfer_proxy.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index 3873ab01..57bb3d77 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index a6fe86e0..c36220de 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin index d285e4c8..a1c94250 100644 Binary files a/artifacts/test_program_methods/changer_claimer.bin and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index 21a5c887..ee9a6276 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/clock_chain_caller.bin b/artifacts/test_program_methods/clock_chain_caller.bin index 10cd6910..ee237e70 100644 Binary files a/artifacts/test_program_methods/clock_chain_caller.bin and b/artifacts/test_program_methods/clock_chain_caller.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 1a4dc474..f559654c 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index 2152e32f..30d44aae 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/faucet_chain_caller.bin b/artifacts/test_program_methods/faucet_chain_caller.bin new file mode 100644 index 00000000..6311ad4b Binary files /dev/null and b/artifacts/test_program_methods/faucet_chain_caller.bin differ diff --git a/artifacts/test_program_methods/flash_swap_callback.bin b/artifacts/test_program_methods/flash_swap_callback.bin index 4a725f3e..8705c5ab 100644 Binary files a/artifacts/test_program_methods/flash_swap_callback.bin and b/artifacts/test_program_methods/flash_swap_callback.bin differ diff --git a/artifacts/test_program_methods/flash_swap_initiator.bin b/artifacts/test_program_methods/flash_swap_initiator.bin index e4b303ee..6fa369f6 100644 Binary files a/artifacts/test_program_methods/flash_swap_initiator.bin and b/artifacts/test_program_methods/flash_swap_initiator.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin index 2b41c197..464af668 100644 Binary files a/artifacts/test_program_methods/malicious_authorization_changer.bin and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/malicious_caller_program_id.bin b/artifacts/test_program_methods/malicious_caller_program_id.bin index 20c03dfa..4c402914 100644 Binary files a/artifacts/test_program_methods/malicious_caller_program_id.bin and b/artifacts/test_program_methods/malicious_caller_program_id.bin differ diff --git a/artifacts/test_program_methods/malicious_self_program_id.bin b/artifacts/test_program_methods/malicious_self_program_id.bin index 0aef2bd7..2ca1fe2d 100644 Binary files a/artifacts/test_program_methods/malicious_self_program_id.bin and b/artifacts/test_program_methods/malicious_self_program_id.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index d6ca6b99..88435296 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index 1c9f6914..2bad082d 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index a8a87da8..286c9028 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index e5659b80..f11876de 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index 023f2e21..e4024958 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/pda_claimer.bin b/artifacts/test_program_methods/pda_claimer.bin index e1bea8f8..7bf306ea 100644 Binary files a/artifacts/test_program_methods/pda_claimer.bin and b/artifacts/test_program_methods/pda_claimer.bin differ diff --git a/artifacts/test_program_methods/pda_fund_spend_proxy.bin b/artifacts/test_program_methods/pda_fund_spend_proxy.bin index 7cd0839a..e167ecc6 100644 Binary files a/artifacts/test_program_methods/pda_fund_spend_proxy.bin and b/artifacts/test_program_methods/pda_fund_spend_proxy.bin differ diff --git a/artifacts/test_program_methods/pinata_cooldown.bin b/artifacts/test_program_methods/pinata_cooldown.bin index 8e3b97c5..fcc0f3fc 100644 Binary files a/artifacts/test_program_methods/pinata_cooldown.bin and b/artifacts/test_program_methods/pinata_cooldown.bin differ diff --git a/artifacts/test_program_methods/private_pda_delegator.bin b/artifacts/test_program_methods/private_pda_delegator.bin index 44d566ec..1c4dd672 100644 Binary files a/artifacts/test_program_methods/private_pda_delegator.bin and b/artifacts/test_program_methods/private_pda_delegator.bin differ diff --git a/artifacts/test_program_methods/private_pda_spender.bin b/artifacts/test_program_methods/private_pda_spender.bin index 70e4c5a0..2db36680 100644 Binary files a/artifacts/test_program_methods/private_pda_spender.bin and b/artifacts/test_program_methods/private_pda_spender.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index 4a47211b..75425bc5 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index 647f86fa..6c98cdf2 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/artifacts/test_program_methods/time_locked_transfer.bin b/artifacts/test_program_methods/time_locked_transfer.bin index 8f423fa7..bfa226fb 100644 Binary files a/artifacts/test_program_methods/time_locked_transfer.bin and b/artifacts/test_program_methods/time_locked_transfer.bin differ diff --git a/artifacts/test_program_methods/two_pda_claimer.bin b/artifacts/test_program_methods/two_pda_claimer.bin index 9d5001f9..8833afb0 100644 Binary files a/artifacts/test_program_methods/two_pda_claimer.bin and b/artifacts/test_program_methods/two_pda_claimer.bin differ diff --git a/artifacts/test_program_methods/validity_window.bin b/artifacts/test_program_methods/validity_window.bin index a0a578e1..37afac43 100644 Binary files a/artifacts/test_program_methods/validity_window.bin and b/artifacts/test_program_methods/validity_window.bin differ diff --git a/artifacts/test_program_methods/validity_window_chain_caller.bin b/artifacts/test_program_methods/validity_window_chain_caller.bin index 326ed7a1..42ef3906 100644 Binary files a/artifacts/test_program_methods/validity_window_chain_caller.bin and b/artifacts/test_program_methods/validity_window_chain_caller.bin differ diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 6175f1a1..21cbfd75 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -67,26 +67,17 @@ impl NSSATransaction { } /// Validates the transaction against the current state and returns the resulting diff - /// without applying it. Rejects transactions that modify clock system accounts and - /// rejects unsafe modifications of the system faucet account. Also rejects direct - /// invocation of the faucet program for user-submitted transactions. + /// without applying it. Rejects transactions that modify clock or faucet system accounts, + /// whether directly or indirectly via chain calls. /// - /// This check is required for all user transactions. Only sequencer transaction may bypass this - /// check. + /// This check is required for all user transactions. Only sequencer transactions may bypass + /// this check. pub fn validate_on_state( &self, state: &V03State, block_id: BlockId, timestamp: Timestamp, ) -> Result { - if let Self::Public(tx) = self - && tx.message().program_id == nssa::program::Program::faucet().id() - { - return Err(nssa::error::NssaError::InvalidInput( - "Transaction invokes restricted faucet program".into(), - )); - } - let diff = match self { Self::Public(tx) => { ValidatedStateDiff::from_public_transaction(tx, state, block_id, timestamp) @@ -111,6 +102,16 @@ impl NSSATransaction { )); } + let faucet_id = nssa::system_faucet_account_id(); + if public_diff + .get(&faucet_id) + .is_some_and(|post| *post != state.get_account_by_id(faucet_id)) + { + return Err(nssa::error::NssaError::InvalidInput( + "Transaction modifies system faucet account".into(), + )); + } + Ok(diff) } diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index 9508ee29..feb5e5e8 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -1,6 +1,7 @@ use std::time::Duration; use anyhow::{Context as _, Result}; +use common::transaction::NSSATransaction; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, private_mention, public_mention, verify_commitment_is_in_state, @@ -623,3 +624,130 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> { Ok(()) } + +#[test] +async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> { + use nssa::{ + EphemeralPublicKey, SharedSecretKey, execute_and_prove, + privacy_preserving_transaction::{self, circuit::ProgramWithDependencies}, + }; + use nssa_core::{InputAccountIdentity, account::AccountWithMetadata}; + + let ctx = TestContext::new().await?; + + let binary = std::fs::read( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../artifacts/test_program_methods/faucet_chain_caller.bin"), + )?; + let deploy_tx = NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new( + nssa::program_deployment_transaction::Message::new(binary.clone()), + )); + ctx.sequencer_client().send_transaction(deploy_tx).await?; + + info!("Waiting for deploy block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let faucet_account_id = nssa::system_faucet_account_id(); + let attacker_id = ctx.existing_public_accounts()[0]; + let faucet_program_id = Program::faucet().id(); + let vault_program_id = Program::vault().id(); + let auth_transfer_program_id = Program::authenticated_transfer_program().id(); + let nsk: nssa_core::NullifierSecretKey = [3; 32]; + let npk = NullifierPublicKey::from(&nsk); + let vpk = Secp256k1Point::from_scalar([4; 32]); + let ssk = SharedSecretKey::new([55; 32], &vpk); + let epk = EphemeralPublicKey::from_scalar([55; 32]); + let attacker_vault_id = { + let seed = vault_core::compute_vault_seed(attacker_id); + AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337) + }; + let amount: u128 = 1; + + let faucet_pre = AccountWithMetadata::new( + ctx.sequencer_client() + .get_account(faucet_account_id) + .await?, + false, + faucet_account_id, + ); + let vault_pda_pre = AccountWithMetadata::new( + ctx.sequencer_client() + .get_account(attacker_vault_id) + .await?, + false, + attacker_vault_id, + ); + + let faucet_chain_caller = Program::new(binary)?; + let program_with_deps = ProgramWithDependencies::new( + faucet_chain_caller, + [ + (faucet_program_id, Program::faucet()), + (vault_program_id, Program::vault()), + ( + auth_transfer_program_id, + Program::authenticated_transfer_program(), + ), + ] + .into(), + ); + + let instruction = + Program::serialize_instruction((faucet_program_id, vault_program_id, attacker_id, amount))?; + + let (output, proof) = execute_and_prove( + vec![faucet_pre, vault_pda_pre], + instruction, + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::PrivatePdaInit { + npk, + ssk, + identifier: 1337, + }, + ], + &program_with_deps, + )?; + + let message = privacy_preserving_transaction::Message::try_from_circuit_output( + vec![faucet_account_id], + vec![], + vec![(npk, vpk, epk)], + output, + )?; + let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]); + let attack_ppt = NSSATransaction::PrivacyPreserving(nssa::PrivacyPreservingTransaction::new( + message, + witness_set, + )); + + let faucet_balance_before = ctx + .sequencer_client() + .get_account_balance(faucet_account_id) + .await?; + let vault_balance_before = ctx + .sequencer_client() + .get_account_balance(attacker_vault_id) + .await?; + + let tx_hash = ctx.sequencer_client().send_transaction(attack_ppt).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let faucet_balance_after = ctx + .sequencer_client() + .get_account_balance(faucet_account_id) + .await?; + let vault_balance_after = ctx + .sequencer_client() + .get_account_balance(attacker_vault_id) + .await?; + let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?; + + assert_eq!(faucet_balance_after, faucet_balance_before); + assert_eq!(vault_balance_after, vault_balance_before); + assert!(tx_on_chain.is_none()); + + Ok(()) +} diff --git a/integration_tests/tests/auth_transfer/public.rs b/integration_tests/tests/auth_transfer/public.rs index 54713f67..72685d0b 100644 --- a/integration_tests/tests/auth_transfer/public.rs +++ b/integration_tests/tests/auth_transfer/public.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{path::PathBuf, time::Duration}; use anyhow::Result; use common::transaction::NSSATransaction; @@ -397,44 +397,6 @@ async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { Ok(()) } -#[test] -async fn can_transfer_funds_to_system_faucet_account() -> Result<()> { - let mut ctx = TestContext::new().await?; - let faucet_account_id = system_faucet_account_id(); - - let sender = ctx.existing_public_accounts()[0]; - let sender_balance_before = ctx.sequencer_client().get_account_balance(sender).await?; - let faucet_balance_before = ctx - .sequencer_client() - .get_account_balance(faucet_account_id) - .await?; - - let amount = 100_u128; - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: public_mention(sender), - to: Some(public_mention(faucet_account_id)), - to_npk: None, - to_vpk: None, - to_identifier: Some(0), - amount, - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let sender_balance_after = ctx.sequencer_client().get_account_balance(sender).await?; - let faucet_balance_after = ctx - .sequencer_client() - .get_account_balance(faucet_account_id) - .await?; - - assert_eq!(sender_balance_after, sender_balance_before - amount); - assert_eq!(faucet_balance_after, faucet_balance_before + amount); - - Ok(()) -} - #[test] async fn cannot_execute_faucet_program() -> Result<()> { let ctx = TestContext::new().await?; @@ -492,3 +454,69 @@ async fn cannot_execute_faucet_program() -> Result<()> { Ok(()) } + +#[test] +async fn user_tx_that_chain_calls_faucet_is_dropped() -> Result<()> { + let ctx = TestContext::new().await?; + + let binary = std::fs::read( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../artifacts/test_program_methods/faucet_chain_caller.bin"), + )?; + let faucet_chain_caller_id = Program::new(binary.clone())?.id(); + let deploy_tx = NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new( + nssa::program_deployment_transaction::Message::new(binary), + )); + ctx.sequencer_client().send_transaction(deploy_tx).await?; + + info!("Waiting for deploy block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let faucet_account_id = system_faucet_account_id(); + let attacker = ctx.existing_public_accounts()[0]; + let faucet_program_id = Program::faucet().id(); + let vault_program_id = Program::vault().id(); + let attacker_vault_id = vault_core::compute_vault_account_id(vault_program_id, attacker); + let amount: u128 = 1; + + let message = public_transaction::Message::try_new( + faucet_chain_caller_id, + vec![faucet_account_id, attacker_vault_id], + vec![], + (faucet_program_id, vault_program_id, attacker, amount), + )?; + let attack_tx = NSSATransaction::Public(nssa::PublicTransaction::new( + message, + nssa::public_transaction::WitnessSet::from_raw_parts(vec![]), + )); + + let faucet_balance_before = ctx + .sequencer_client() + .get_account_balance(faucet_account_id) + .await?; + let vault_balance_before = ctx + .sequencer_client() + .get_account_balance(attacker_vault_id) + .await?; + + let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let faucet_balance_after = ctx + .sequencer_client() + .get_account_balance(faucet_account_id) + .await?; + let vault_balance_after = ctx + .sequencer_client() + .get_account_balance(attacker_vault_id) + .await?; + let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?; + + assert_eq!(faucet_balance_after, faucet_balance_before); + assert_eq!(vault_balance_after, vault_balance_before); + assert!(tx_on_chain.is_none()); + + Ok(()) +} diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs b/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs index aad1bc1c..c65b1d29 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, VecDeque, hash_map::Entry}, + collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, convert::Infallible, }; @@ -49,6 +49,7 @@ pub struct ExecutionState { /// caller-seeds authorization paths to verify /// `AccountId::for_private_pda(program_id, seed, npk, identifier) == pre_state.account_id`. private_pda_npk_by_position: HashMap, + authorized_accounts: HashSet, } impl ExecutionState { @@ -107,6 +108,7 @@ impl ExecutionState { private_pda_bound_positions: HashMap::new(), pda_family_binding: HashMap::new(), private_pda_npk_by_position, + authorized_accounts: HashSet::new(), }; let Some(first_output) = program_outputs.first() else { @@ -246,10 +248,10 @@ impl ExecutionState { program_id: ProgramId, caller_program_id: Option, caller_pda_seeds: &[PdaSeed], - pre_states: Vec, - post_states: Vec, + output_pre_states: Vec, + output_post_states: Vec, ) { - for (pre, mut post) in pre_states.into_iter().zip(post_states) { + for (pre, mut post) in output_pre_states.into_iter().zip(output_post_states) { let pre_account_id = pre.account_id; let pre_is_authorized = pre.is_authorized; let post_states_entry = self.post_states.entry(pre.account_id); @@ -288,6 +290,7 @@ impl ExecutionState { &mut self.pda_family_binding, &mut self.private_pda_bound_positions, &self.private_pda_npk_by_position, + &mut self.authorized_accounts, pre_account_id, pre_state_position, caller_program_id, @@ -491,6 +494,7 @@ fn resolve_authorization_and_record_bindings( pda_family_binding: &mut HashMap<(ProgramId, PdaSeed), AccountId>, private_pda_bound_positions: &mut HashMap, private_pda_npk_by_position: &HashMap, + authorized_accounts: &mut HashSet, pre_account_id: AccountId, pre_state_position: usize, caller_program_id: Option, @@ -525,5 +529,13 @@ fn resolve_authorization_and_record_bindings( } } - previous_is_authorized || matched_caller_seed.is_some() + if authorized_accounts.contains(&pre_account_id) { + return true; + } + + let authorized = previous_is_authorized || matched_caller_seed.is_some(); + if authorized { + authorized_accounts.insert(pre_account_id); + } + authorized } diff --git a/program_methods/guest/src/bin/vault.rs b/program_methods/guest/src/bin/vault.rs index c691e8f6..c56c1a7f 100644 --- a/program_methods/guest/src/bin/vault.rs +++ b/program_methods/guest/src/bin/vault.rs @@ -40,7 +40,7 @@ fn main() { } => { let [sender, recipient_vault] = pre_states .try_into() - .expect("Transfer requires exactly 3 accounts"); + .expect("Transfer requires exactly 2 accounts"); let seed = vault_core::compute_vault_seed(recipient_id); diff --git a/test_program_methods/guest/Cargo.toml b/test_program_methods/guest/Cargo.toml index ca8cdc1d..47ea10e1 100644 --- a/test_program_methods/guest/Cargo.toml +++ b/test_program_methods/guest/Cargo.toml @@ -11,6 +11,7 @@ workspace = true nssa_core.workspace = true authenticated_transfer_core.workspace = true clock_core.workspace = true +faucet_core.workspace = true risc0-zkvm.workspace = true serde = { workspace = true, default-features = false } diff --git a/test_program_methods/guest/src/bin/faucet_chain_caller.rs b/test_program_methods/guest/src/bin/faucet_chain_caller.rs new file mode 100644 index 00000000..2e02982d --- /dev/null +++ b/test_program_methods/guest/src/bin/faucet_chain_caller.rs @@ -0,0 +1,52 @@ +use nssa_core::{ + account::AccountId, + program::{ + AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_nssa_inputs, + }, +}; +use risc0_zkvm::serde::to_vec; + +type Instruction = (ProgramId, ProgramId, AccountId, u128); +// (faucet_program_id, vault_program_id, recipient_id, amount) + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction: (faucet_program_id, vault_program_id, recipient_id, amount), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let post_states: Vec<_> = pre_states + .iter() + .map(|pre| AccountPostState::new(pre.account.clone())) + .collect(); + + assert_eq!(pre_states.len(), 2); + let [faucet_pre, vault_pda_pre] = [pre_states[0].clone(), pre_states[1].clone()]; + + let chained_calls = vec![ChainedCall { + program_id: faucet_program_id, + instruction_data: to_vec(&faucet_core::Instruction::Transfer { + vault_program_id, + recipient_id, + amount, + }) + .unwrap(), + pre_states: vec![faucet_pre, vault_pda_pre], + pda_seeds: vec![], + }]; + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + pre_states, + post_states, + ) + .with_chained_calls(chained_calls) + .write(); +}