lssa/nssa/src/state.rs

4146 lines
160 KiB
Rust
Raw Normal View History

2025-11-26 00:27:20 +03:00
use std::collections::{HashMap, HashSet};
use nssa_core::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
account::{Account, AccountId},
program::ProgramId,
};
2025-08-06 20:05:04 -03:00
use crate::{
2025-09-30 15:30:28 -03:00
error::NssaError, merkle_tree::MerkleTree,
privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program,
program_deployment_transaction::ProgramDeploymentTransaction,
public_transaction::PublicTransaction,
2025-08-06 20:05:04 -03:00
};
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
2025-08-25 07:44:56 -03:00
pub(crate) struct CommitmentSet {
merkle_tree: MerkleTree,
commitments: HashMap<Commitment, usize>,
2025-08-27 16:24:20 -03:00
root_history: HashSet<CommitmentSetDigest>,
2025-08-25 07:44:56 -03:00
}
impl CommitmentSet {
pub(crate) fn digest(&self) -> CommitmentSetDigest {
2025-08-25 07:44:56 -03:00
self.merkle_tree.root()
}
/// Queries the `CommitmentSet` for a membership proof of commitment
2025-08-27 16:24:20 -03:00
pub fn get_proof_for(&self, commitment: &Commitment) -> Option<MembershipProof> {
2025-08-25 07:44:56 -03:00
let index = *self.commitments.get(commitment)?;
2025-08-27 16:24:20 -03:00
self.merkle_tree
2025-08-25 07:44:56 -03:00
.get_authentication_path_for(index)
2025-08-27 16:24:20 -03:00
.map(|path| (index, path))
}
2025-08-18 14:28:26 -03:00
/// Inserts a list of commitments to the `CommitmentSet`.
2025-08-22 13:42:37 -03:00
pub(crate) fn extend(&mut self, commitments: &[Commitment]) {
2025-08-25 07:44:56 -03:00
for commitment in commitments.iter().cloned() {
let index = self.merkle_tree.insert(commitment.to_byte_array());
self.commitments.insert(commitment, index);
}
2025-08-25 09:22:59 -03:00
self.root_history.insert(self.digest());
}
2025-08-18 14:28:26 -03:00
fn contains(&self, commitment: &Commitment) -> bool {
2025-08-25 07:44:56 -03:00
self.commitments.contains_key(commitment)
}
/// Initializes an empty `CommitmentSet` with a given capacity.
2025-09-05 16:38:34 -04:00
/// If the capacity is not a power_of_two, then capacity is taken
2025-09-05 16:21:46 -04:00
/// to be the next power_of_two.
2025-08-25 07:44:56 -03:00
pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet {
Self {
merkle_tree: MerkleTree::with_capacity(capacity),
commitments: HashMap::new(),
2025-08-25 09:22:59 -03:00
root_history: HashSet::new(),
2025-08-25 07:44:56 -03:00
}
2025-08-18 14:28:26 -03:00
}
}
2025-08-18 07:39:41 -03:00
type NullifierSet = HashSet<Nullifier>;
2025-08-06 20:05:04 -03:00
2025-10-16 16:24:18 -03:00
pub struct V02State {
public_state: HashMap<AccountId, Account>,
2025-08-27 16:24:20 -03:00
private_state: (CommitmentSet, NullifierSet),
2025-10-14 17:15:04 -03:00
programs: HashMap<ProgramId, Program>,
2025-08-06 20:05:04 -03:00
}
2025-10-16 16:24:18 -03:00
impl V02State {
2025-09-18 15:59:17 +03:00
pub fn new_with_genesis_accounts(
initial_data: &[(AccountId, u128)],
2025-09-18 15:59:17 +03:00
initial_commitments: &[nssa_core::Commitment],
) -> Self {
2025-08-10 00:53:53 -03:00
let authenticated_transfer_program = Program::authenticated_transfer_program();
2025-08-07 15:19:06 -03:00
let public_state = initial_data
2025-08-09 19:20:19 -03:00
.iter()
.copied()
.map(|(account_id, balance)| {
2025-08-09 18:40:32 -03:00
let account = Account {
2025-08-09 19:20:19 -03:00
balance,
2025-08-10 00:53:53 -03:00
program_owner: authenticated_transfer_program.id(),
2025-08-09 18:40:32 -03:00
..Account::default()
};
(account_id, account)
2025-08-07 15:19:06 -03:00
})
.collect();
2025-09-18 15:59:17 +03:00
let mut private_state = CommitmentSet::with_capacity(32);
private_state.extend(&[DUMMY_COMMITMENT]);
2025-09-18 15:59:17 +03:00
private_state.extend(initial_commitments);
2025-08-10 09:57:10 -03:00
let mut this = Self {
public_state,
2025-09-18 15:59:17 +03:00
private_state: (private_state, NullifierSet::new()),
2025-10-14 17:15:04 -03:00
programs: HashMap::new(),
2025-08-10 09:57:10 -03:00
};
this.insert_program(Program::authenticated_transfer_program());
2025-09-12 15:18:25 -03:00
this.insert_program(Program::token());
2025-12-17 14:21:36 +02:00
this.insert_program(Program::amm());
2025-08-10 09:57:10 -03:00
this
}
2025-08-12 21:27:27 -03:00
pub(crate) fn insert_program(&mut self, program: Program) {
2025-10-14 17:15:04 -03:00
self.programs.insert(program.id(), program);
2025-08-07 15:19:06 -03:00
}
2025-08-09 19:49:07 -03:00
pub fn transition_from_public_transaction(
&mut self,
tx: &PublicTransaction,
) -> Result<(), NssaError> {
let state_diff = tx.validate_and_produce_public_state_diff(self)?;
2025-08-06 20:05:04 -03:00
for (account_id, post) in state_diff.into_iter() {
let current_account = self.get_account_by_id_mut(account_id);
2025-08-06 20:05:04 -03:00
*current_account = post;
}
for account_id in tx.signer_account_ids() {
let current_account = self.get_account_by_id_mut(account_id);
2025-08-06 20:05:04 -03:00
current_account.nonce += 1;
}
2025-08-09 20:35:44 -03:00
2025-08-06 20:05:04 -03:00
Ok(())
}
pub fn transition_from_privacy_preserving_transaction(
&mut self,
tx: &PrivacyPreservingTransaction,
) -> Result<(), NssaError> {
// 1. Verify the transaction satisfies acceptance criteria
let public_state_diff = tx.validate_and_produce_public_state_diff(self)?;
let message = tx.message();
// 2. Add new commitments
2025-08-21 10:19:08 -03:00
self.private_state.0.extend(&message.new_commitments);
// 3. Add new nullifiers
2025-08-25 09:22:59 -03:00
let new_nullifiers = message
.new_nullifiers
.iter()
.cloned()
.map(|(nullifier, _)| nullifier)
.collect::<Vec<Nullifier>>();
self.private_state.1.extend(new_nullifiers);
// 4. Update public accounts
for (account_id, post) in public_state_diff.into_iter() {
let current_account = self.get_account_by_id_mut(account_id);
*current_account = post;
}
2025-12-11 08:59:28 -05:00
// 5. Increment nonces for public signers
for account_id in tx.signer_account_ids() {
let current_account = self.get_account_by_id_mut(account_id);
current_account.nonce += 1;
}
Ok(())
}
pub fn transition_from_program_deployment_transaction(
&mut self,
tx: &ProgramDeploymentTransaction,
) -> Result<(), NssaError> {
let program = tx.validate_and_produce_public_state_diff(self)?;
self.insert_program(program);
Ok(())
}
fn get_account_by_id_mut(&mut self, account_id: AccountId) -> &mut Account {
self.public_state.entry(account_id).or_default()
2025-08-06 20:05:04 -03:00
}
pub fn get_account_by_id(&self, account_id: &AccountId) -> Account {
2025-08-06 20:05:04 -03:00
self.public_state
.get(account_id)
2025-08-06 20:05:04 -03:00
.cloned()
.unwrap_or(Account::default())
}
2025-08-27 16:24:20 -03:00
pub fn get_proof_for_commitment(&self, commitment: &Commitment) -> Option<MembershipProof> {
self.private_state.0.get_proof_for(commitment)
}
pub(crate) fn programs(&self) -> &HashMap<ProgramId, Program> {
2025-10-14 17:15:04 -03:00
&self.programs
2025-08-06 20:05:04 -03:00
}
pub fn commitment_set_digest(&self) -> CommitmentSetDigest {
self.private_state.0.digest()
}
pub(crate) fn check_commitments_are_new(
&self,
new_commitments: &[Commitment],
) -> Result<(), NssaError> {
2025-08-18 14:28:26 -03:00
for commitment in new_commitments.iter() {
if self.private_state.0.contains(commitment) {
return Err(NssaError::InvalidInput(
"Commitment already seen".to_string(),
));
}
}
Ok(())
}
2025-08-25 09:22:59 -03:00
pub(crate) fn check_nullifiers_are_valid(
&self,
2025-08-25 09:22:59 -03:00
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
) -> Result<(), NssaError> {
2025-08-25 09:22:59 -03:00
for (nullifier, digest) in new_nullifiers.iter() {
2025-08-18 14:28:26 -03:00
if self.private_state.1.contains(nullifier) {
return Err(NssaError::InvalidInput(
"Nullifier already seen".to_string(),
));
}
2025-08-25 09:22:59 -03:00
if !self.private_state.0.root_history.contains(digest) {
return Err(NssaError::InvalidInput(
"Unrecognized commitment set digest".to_string(),
));
}
2025-08-18 14:28:26 -03:00
}
Ok(())
}
2025-08-06 20:05:04 -03:00
}
2025-08-13 01:33:11 -03:00
2025-09-05 23:45:44 -03:00
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
2025-10-16 16:24:18 -03:00
impl V02State {
pub fn add_pinata_program(&mut self, account_id: AccountId) {
2025-09-04 17:05:12 -03:00
self.insert_program(Program::pinata());
self.public_state.insert(
account_id,
2025-09-04 17:05:12 -03:00
Account {
program_owner: Program::pinata().id(),
balance: 1500,
// Difficulty: 3
data: vec![3; 33].try_into().expect("should fit"),
2025-09-04 17:05:12 -03:00
nonce: 0,
},
);
}
2025-11-28 11:10:00 -03:00
pub fn add_pinata_token_program(&mut self, account_id: AccountId) {
self.insert_program(Program::pinata_token());
self.public_state.insert(
account_id,
Account {
program_owner: Program::pinata_token().id(),
// Difficulty: 3
data: vec![3; 33].try_into().expect("should fit"),
2025-11-28 11:10:00 -03:00
..Account::default()
},
);
}
2025-09-04 17:05:12 -03:00
}
2025-08-13 01:33:11 -03:00
#[cfg(test)]
2025-08-22 12:29:45 -03:00
pub mod tests {
2025-08-13 01:33:11 -03:00
use std::collections::HashMap;
2025-11-26 00:27:20 +03:00
use nssa_core::{
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
2025-11-26 00:27:20 +03:00
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
program::{PdaSeed, ProgramId},
2025-11-26 00:27:20 +03:00
};
2025-08-13 01:33:11 -03:00
use crate::{
PublicKey, PublicTransaction, V02State,
error::NssaError,
2025-09-02 12:38:31 -03:00
execute_and_prove,
privacy_preserving_transaction::{
2025-11-22 16:39:56 -03:00
PrivacyPreservingTransaction,
circuit::{self, ProgramWithDependencies},
message::Message,
witness_set::WitnessSet,
2025-12-09 14:42:58 -05:00
},
program::Program,
2025-11-22 16:39:56 -03:00
public_transaction,
2025-12-09 14:42:58 -05:00
signature::PrivateKey,
state::MAX_NUMBER_CHAINED_CALLS,
2025-08-13 01:33:11 -03:00
};
2025-08-26 14:53:02 -03:00
2025-08-13 01:33:11 -03:00
fn transfer_transaction(
from: AccountId,
2025-08-13 01:33:11 -03:00
from_key: PrivateKey,
nonce: u128,
to: AccountId,
2025-08-13 01:33:11 -03:00
balance: u128,
) -> PublicTransaction {
let account_ids = vec![from, to];
2025-08-13 01:33:11 -03:00
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();
2025-08-13 01:33:11 -03:00
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
PublicTransaction::new(message, witness_set)
}
#[test]
fn test_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));
2025-08-13 13:42:00 +03:00
let initial_data = [(addr1, 100u128), (addr2, 151u128)];
2025-09-12 17:13:37 -03:00
let authenticated_transfers_program = Program::authenticated_transfer_program();
2025-08-13 01:33:11 -03:00
let expected_public_state = {
let mut this = HashMap::new();
this.insert(
addr1,
Account {
balance: 100,
2025-09-12 17:13:37 -03:00
program_owner: authenticated_transfers_program.id(),
2025-08-13 01:33:11 -03:00
..Account::default()
},
);
this.insert(
addr2,
Account {
balance: 151,
2025-09-12 17:13:37 -03:00
program_owner: authenticated_transfers_program.id(),
2025-08-13 01:33:11 -03:00
..Account::default()
},
);
this
};
let expected_builtin_programs = {
let mut this = HashMap::new();
2025-09-12 17:13:37 -03:00
this.insert(
authenticated_transfers_program.id(),
authenticated_transfers_program,
);
this.insert(Program::token().id(), Program::token());
2025-08-13 01:33:11 -03:00
this
};
2025-10-16 16:24:18 -03:00
let state = V02State::new_with_genesis_accounts(&initial_data, &[]);
2025-08-13 01:33:11 -03:00
assert_eq!(state.public_state, expected_public_state);
2025-10-14 17:15:04 -03:00
assert_eq!(state.programs, expected_builtin_programs);
2025-08-13 01:33:11 -03:00
}
#[test]
fn test_insert_program() {
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&[], &[]);
2025-08-13 01:33:11 -03:00
let program_to_insert = Program::simple_balance_transfer();
let program_id = program_to_insert.id();
2025-10-14 17:15:04 -03:00
assert!(!state.programs.contains_key(&program_id));
2025-08-13 01:33:11 -03:00
state.insert_program(program_to_insert);
2025-10-14 17:15:04 -03:00
assert!(state.programs.contains_key(&program_id));
2025-08-13 01:33:11 -03:00
}
#[test]
fn test_get_account_by_account_id_non_default_account() {
2025-08-13 01:33:11 -03:00
let key = PrivateKey::try_new([1; 32]).unwrap();
let account_id = AccountId::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(account_id, 100u128)];
2025-10-16 16:24:18 -03:00
let state = V02State::new_with_genesis_accounts(&initial_data, &[]);
let expected_account = state.public_state.get(&account_id).unwrap();
2025-08-13 01:33:11 -03:00
let account = state.get_account_by_id(&account_id);
2025-08-13 01:33:11 -03:00
assert_eq!(&account, expected_account);
}
#[test]
fn test_get_account_by_account_id_default_account() {
let addr2 = AccountId::new([0; 32]);
2025-10-16 16:24:18 -03:00
let state = V02State::new_with_genesis_accounts(&[], &[]);
2025-08-13 01:33:11 -03:00
let expected_account = Account::default();
let account = state.get_account_by_id(&addr2);
2025-08-13 01:33:11 -03:00
assert_eq!(account, expected_account);
}
#[test]
fn test_builtin_programs_getter() {
2025-10-16 16:24:18 -03:00
let state = V02State::new_with_genesis_accounts(&[], &[]);
2025-08-13 01:33:11 -03:00
let builtin_programs = state.programs();
2025-08-13 01:33:11 -03:00
2025-10-14 17:15:04 -03:00
assert_eq!(builtin_programs, &state.programs);
2025-08-13 01:33:11 -03:00
}
#[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)];
2025-10-16 16:24:18 -03:00
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());
2025-08-13 01:33:11 -03:00
let balance_to_move = 5;
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(from, key, 0, to, balance_to_move);
2025-08-13 01:33:11 -03:00
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);
2025-08-13 01:33:11 -03:00
}
#[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)];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]);
let from = account_id;
2025-08-13 01:33:11 -03:00
let from_key = key;
let to = AccountId::new([2; 32]);
2025-08-13 01:33:11 -03:00
let balance_to_move = 101;
assert!(state.get_account_by_id(&from).balance < balance_to_move);
2025-08-13 01:33:11 -03:00
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
2025-08-13 01:33:11 -03:00
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);
2025-08-13 01:33:11 -03:00
}
#[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)];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]);
let from = account_id2;
2025-08-13 01:33:11 -03:00
let from_key = key2;
let to = account_id1;
assert_ne!(state.get_account_by_id(&to), Account::default());
2025-08-13 01:33:11 -03:00
let balance_to_move = 8;
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
2025-08-13 01:33:11 -03:00
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);
2025-08-13 01:33:11 -03:00
}
#[test]
fn transition_from_sequence_of_authenticated_transfer_program_invocations() {
2025-08-13 01:33:11 -03:00
let key1 = PrivateKey::try_new([8; 32]).unwrap();
let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1));
2025-08-13 01:33:11 -03:00
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)];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]);
let account_id3 = AccountId::new([3; 32]);
2025-08-13 01:33:11 -03:00
let balance_to_move = 5;
let tx = transfer_transaction(account_id1, key1, 0, account_id2, balance_to_move);
2025-08-13 01:33:11 -03:00
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);
2025-08-13 01:33:11 -03:00
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);
2025-08-13 01:33:11 -03:00
}
2025-10-16 16:24:18 -03:00
impl V02State {
pub fn force_insert_account(&mut self, account_id: AccountId, account: Account) {
self.public_state.insert(account_id, account);
2025-08-13 01:55:56 -03:00
}
2025-08-13 01:33:11 -03:00
/// Include test programs in the builtin programs map
pub fn with_test_programs(mut self) -> Self {
self.insert_program(Program::nonce_changer_program());
self.insert_program(Program::extra_output_program());
self.insert_program(Program::missing_output_program());
self.insert_program(Program::program_owner_changer());
self.insert_program(Program::simple_balance_transfer());
self.insert_program(Program::data_changer());
self.insert_program(Program::minter());
self.insert_program(Program::burner());
2025-10-29 15:34:11 -03:00
self.insert_program(Program::chain_caller());
2025-11-20 21:02:18 -05:00
self.insert_program(Program::amm());
self.insert_program(Program::claimer());
2025-08-13 01:33:11 -03:00
self
}
pub fn with_non_default_accounts_but_default_program_owners(mut self) -> Self {
let account_with_default_values_except_balance = Account {
balance: 100,
..Account::default()
};
let account_with_default_values_except_nonce = Account {
nonce: 37,
..Account::default()
};
let account_with_default_values_except_data = Account {
data: vec![0xca, 0xfe].try_into().unwrap(),
2025-08-13 01:33:11 -03:00
..Account::default()
};
self.force_insert_account(
AccountId::new([255; 32]),
2025-08-13 01:33:11 -03:00
account_with_default_values_except_balance,
);
self.force_insert_account(
AccountId::new([254; 32]),
2025-08-13 01:33:11 -03:00
account_with_default_values_except_nonce,
);
self.force_insert_account(
AccountId::new([253; 32]),
2025-08-13 01:33:11 -03:00
account_with_default_values_except_data,
);
self
}
pub fn with_account_owned_by_burner_program(mut self) -> Self {
let account = Account {
program_owner: Program::burner().id(),
balance: 100,
..Default::default()
};
self.force_insert_account(AccountId::new([252; 32]), account);
2025-08-13 01:33:11 -03:00
self
}
2025-08-22 12:29:45 -03:00
pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self {
2025-08-27 16:24:20 -03:00
let commitment = Commitment::new(&keys.npk(), account);
self.private_state.0.extend(&[commitment]);
2025-08-22 12:29:45 -03:00
self
}
2025-08-13 01:33:11 -03:00
}
#[test]
fn test_program_should_fail_if_modifies_nonces() {
let initial_data = [(AccountId::new([1; 32]), 100)];
2025-09-18 15:59:17 +03:00
let mut state =
2025-10-16 16:24:18 -03:00
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let account_ids = vec![AccountId::new([1; 32])];
2025-08-13 01:33:11 -03:00
let program_id = Program::nonce_changer_program().id();
let message =
public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap();
2025-08-13 01:33:11 -03:00
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 = [(AccountId::new([1; 32]), 100)];
2025-09-18 15:59:17 +03:00
let mut state =
2025-10-16 16:24:18 -03:00
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let account_ids = vec![AccountId::new([1; 32])];
2025-08-13 01:33:11 -03:00
let program_id = Program::extra_output_program().id();
let message =
public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap();
2025-08-13 01:33:11 -03:00
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_with_missing_output_accounts() {
let initial_data = [(AccountId::new([1; 32]), 100)];
2025-09-18 15:59:17 +03:00
let mut state =
2025-10-16 16:24:18 -03:00
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let account_ids = vec![AccountId::new([1; 32]), AccountId::new([2; 32])];
2025-08-13 01:33:11 -03:00
let program_id = Program::missing_output_program().id();
let message =
public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap();
2025-08-13 01:33:11 -03:00
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_modifies_program_owner_with_only_non_default_program_owner() {
let initial_data = [(AccountId::new([1; 32]), 0)];
2025-09-18 15:59:17 +03:00
let mut state =
2025-10-16 16:24:18 -03:00
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);
2025-11-26 00:27:20 +03:00
// Assert the target account only differs from the default account in the program owner
// field
2025-08-13 01:33:11 -03:00
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();
2025-08-13 01:33:11 -03:00
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_modifies_program_owner_with_only_non_default_balance() {
let initial_data = [];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[])
2025-08-13 01:33:11 -03:00
.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);
2025-08-13 01:33:11 -03:00
// 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();
2025-08-13 01:33:11 -03:00
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_modifies_program_owner_with_only_non_default_nonce() {
let initial_data = [];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[])
2025-08-13 01:33:11 -03:00
.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);
2025-08-13 01:33:11 -03:00
// 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();
2025-08-13 01:33:11 -03:00
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_modifies_program_owner_with_only_non_default_data() {
let initial_data = [];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[])
2025-08-13 01:33:11 -03:00
.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);
2025-08-13 01:33:11 -03:00
// 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();
2025-08-13 01:33:11 -03:00
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_transfers_balance_from_non_owned_account() {
let initial_data = [(AccountId::new([1; 32]), 100)];
2025-09-18 15:59:17 +03:00
let mut state =
2025-10-16 16:24:18 -03:00
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]);
2025-08-13 01:33:11 -03:00
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,
2025-08-13 01:33:11 -03:00
program_id
);
let message = public_transaction::Message::try_new(
program_id,
vec![sender_account_id, receiver_account_id],
2025-08-13 01:33:11 -03:00
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 test_program_should_fail_if_modifies_data_of_non_owned_account() {
let initial_data = [];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[])
2025-09-12 17:13:37 -03:00
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let account_id = AccountId::new([255; 32]);
2025-08-13 01:33:11 -03:00
let program_id = Program::data_changer().id();
assert_ne!(state.get_account_by_id(&account_id), Account::default());
2025-08-13 01:33:11 -03:00
assert_ne!(
state.get_account_by_id(&account_id).program_owner,
2025-08-13 01:33:11 -03:00
program_id
);
let message =
public_transaction::Message::try_new(program_id, vec![account_id], vec![], vec![0])
.unwrap();
2025-08-13 01:33:11 -03:00
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_does_not_preserve_total_balance_by_minting() {
let initial_data = [];
2025-09-18 15:59:17 +03:00
let mut state =
2025-10-16 16:24:18 -03:00
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let account_id = AccountId::new([1; 32]);
2025-08-13 01:33:11 -03:00
let program_id = Program::minter().id();
let message =
public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap();
2025-08-13 01:33:11 -03:00
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_does_not_preserve_total_balance_by_burning() {
let initial_data = [];
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[])
2025-08-13 01:33:11 -03:00
.with_test_programs()
.with_account_owned_by_burner_program();
let program_id = Program::burner().id();
let account_id = AccountId::new([252; 32]);
2025-08-13 01:33:11 -03:00
assert_eq!(
state.get_account_by_id(&account_id).program_owner,
2025-08-13 01:33:11 -03:00
program_id
);
let balance_to_burn: u128 = 1;
assert!(state.get_account_by_id(&account_id).balance > balance_to_burn);
2025-08-13 01:33:11 -03:00
let message = public_transaction::Message::try_new(
program_id,
vec![account_id],
2025-08-13 01:33:11 -03:00
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)));
}
2025-08-22 13:42:37 -03:00
pub struct TestPublicKeys {
pub signing_key: PrivateKey,
2025-08-22 12:29:45 -03:00
}
impl TestPublicKeys {
pub fn account_id(&self) -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&self.signing_key))
2025-08-22 12:29:45 -03:00
}
}
fn test_public_account_keys_1() -> TestPublicKeys {
TestPublicKeys {
signing_key: PrivateKey::try_new([37; 32]).unwrap(),
}
}
2025-08-22 13:42:37 -03:00
pub struct TestPrivateKeys {
pub nsk: NullifierSecretKey,
2025-09-15 14:04:49 +03:00
pub isk: Scalar,
2025-08-22 12:29:45 -03:00
}
impl TestPrivateKeys {
2025-08-22 13:42:37 -03:00
pub fn npk(&self) -> NullifierPublicKey {
2025-08-22 12:29:45 -03:00
NullifierPublicKey::from(&self.nsk)
}
pub fn ivk(&self) -> IncomingViewingPublicKey {
IncomingViewingPublicKey::from_scalar(self.isk)
}
2025-08-22 12:29:45 -03:00
}
2025-08-22 13:42:37 -03:00
pub fn test_private_account_keys_1() -> TestPrivateKeys {
2025-08-22 12:29:45 -03:00
TestPrivateKeys {
nsk: [13; 32],
isk: [31; 32],
2025-08-22 12:29:45 -03:00
}
}
2025-08-22 13:42:37 -03:00
pub fn test_private_account_keys_2() -> TestPrivateKeys {
2025-08-22 12:29:45 -03:00
TestPrivateKeys {
nsk: [38; 32],
isk: [83; 32],
2025-08-22 12:29:45 -03:00
}
}
fn shielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
sender_keys: &TestPublicKeys,
recipient_keys: &TestPrivateKeys,
balance_to_move: u128,
2025-10-16 16:24:18 -03:00
state: &V02State,
) -> PrivacyPreservingTransaction {
2025-09-11 16:37:28 -03:00
let sender = AccountWithMetadata::new(
state.get_account_by_id(&sender_keys.account_id()),
2025-09-11 16:37:28 -03:00
true,
sender_keys.account_id(),
2025-09-11 16:37:28 -03:00
);
let sender_nonce = sender.account.nonce;
2025-08-21 15:52:35 -03:00
2025-09-11 16:37:28 -03:00
let recipient = AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
let esk = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk());
let epk = EphemeralPublicKey::from_scalar(esk);
let (output, proof) = circuit::execute_and_prove(
&[sender, recipient],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[0, 2],
&[0xdeadbeef],
&[(recipient_keys.npk(), shared_secret)],
&[],
2025-11-20 19:25:56 -03:00
&Program::authenticated_transfer_program().into(),
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![sender_keys.account_id()],
vec![sender_nonce],
2025-09-01 18:12:13 -03:00
vec![(recipient_keys.npk(), recipient_keys.ivk(), epk)],
output,
2025-08-26 14:53:02 -03:00
)
.unwrap();
2025-08-22 12:29:45 -03:00
let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]);
PrivacyPreservingTransaction::new(message, witness_set)
}
fn private_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
sender_keys: &TestPrivateKeys,
sender_private_account: &Account,
2025-08-22 12:29:45 -03:00
recipient_keys: &TestPrivateKeys,
balance_to_move: u128,
new_nonces: [Nonce; 2],
2025-10-16 16:24:18 -03:00
state: &V02State,
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
2025-08-27 16:24:20 -03:00
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
2025-09-11 16:37:28 -03:00
let sender_pre =
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
let recipient_pre =
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
let esk_1 = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk());
let epk_1 = EphemeralPublicKey::from_scalar(esk_1);
let esk_2 = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk());
let epk_2 = EphemeralPublicKey::from_scalar(esk_2);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 2],
&new_nonces,
&[
(sender_keys.npk(), shared_secret_1),
(recipient_keys.npk(), shared_secret_2),
],
&[(
2025-08-22 12:29:45 -03:00
sender_keys.nsk,
2025-08-27 16:24:20 -03:00
state.get_proof_for_commitment(&sender_commitment).unwrap(),
)],
2025-11-20 19:25:56 -03:00
&program.into(),
)
.unwrap();
2025-09-01 18:12:13 -03:00
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![
(sender_keys.npk(), sender_keys.ivk(), epk_1),
(recipient_keys.npk(), recipient_keys.ivk(), epk_2),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
PrivacyPreservingTransaction::new(message, witness_set)
}
fn deshielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
sender_keys: &TestPrivateKeys,
sender_private_account: &Account,
recipient_account_id: &AccountId,
balance_to_move: u128,
new_nonce: Nonce,
2025-10-16 16:24:18 -03:00
state: &V02State,
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
2025-08-27 16:24:20 -03:00
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
2025-09-11 16:37:28 -03:00
let sender_pre =
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
let recipient_pre = AccountWithMetadata::new(
state.get_account_by_id(recipient_account_id),
2025-09-11 16:37:28 -03:00
false,
*recipient_account_id,
2025-09-11 16:37:28 -03:00
);
let esk = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk());
let epk = EphemeralPublicKey::from_scalar(esk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 0],
&[new_nonce],
&[(sender_keys.npk(), shared_secret)],
&[(
2025-08-22 12:29:45 -03:00
sender_keys.nsk,
2025-08-27 16:24:20 -03:00
state.get_proof_for_commitment(&sender_commitment).unwrap(),
)],
2025-11-20 19:25:56 -03:00
&program.into(),
)
.unwrap();
2025-09-01 18:12:13 -03:00
let message = Message::try_from_circuit_output(
vec![*recipient_account_id],
2025-09-01 18:12:13 -03:00
vec![],
vec![(sender_keys.npk(), sender_keys.ivk(), epk)],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
PrivacyPreservingTransaction::new(message, witness_set)
}
#[test]
fn test_transition_from_privacy_preserving_transaction_shielded() {
2025-08-22 12:29:45 -03:00
let sender_keys = test_public_account_keys_1();
let recipient_keys = test_private_account_keys_1();
let mut state =
V02State::new_with_genesis_accounts(&[(sender_keys.account_id(), 200)], &[]);
2025-08-22 12:29:45 -03:00
let balance_to_move = 37;
let tx = shielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
&sender_keys,
&recipient_keys,
balance_to_move,
&state,
);
let expected_sender_post = {
let mut this = state.get_account_by_id(&sender_keys.account_id());
this.balance -= balance_to_move;
this.nonce += 1;
this
};
let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap();
assert!(!state.private_state.0.contains(&expected_new_commitment));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
let sender_post = state.get_account_by_id(&sender_keys.account_id());
assert_eq!(sender_post, expected_sender_post);
assert!(state.private_state.0.contains(&expected_new_commitment));
assert_eq!(
state.get_account_by_id(&sender_keys.account_id()).balance,
2025-08-22 12:29:45 -03:00
200 - balance_to_move
);
}
#[test]
fn test_transition_from_privacy_preserving_transaction_private() {
2025-08-22 12:29:45 -03:00
let sender_keys = test_private_account_keys_1();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
data: Data::default(),
2025-08-22 12:29:45 -03:00
};
let recipient_keys = test_private_account_keys_2();
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&[], &[])
2025-08-22 12:29:45 -03:00
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let tx = private_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
&sender_keys,
&sender_private_account,
2025-08-22 12:29:45 -03:00
&recipient_keys,
balance_to_move,
2025-08-22 12:29:45 -03:00
[0xcafecafe, 0xfecafeca],
&state,
);
let expected_new_commitment_1 = Commitment::new(
2025-08-22 12:29:45 -03:00
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
2025-08-22 12:29:45 -03:00
nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move,
data: Data::default(),
},
);
2025-08-22 12:29:45 -03:00
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let expected_new_nullifier =
Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk);
let expected_new_commitment_2 = Commitment::new(
2025-08-22 12:29:45 -03:00
&recipient_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
2025-08-22 12:29:45 -03:00
nonce: 0xfecafeca,
balance: balance_to_move,
..Account::default()
},
);
let previous_public_state = state.public_state.clone();
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment_1));
assert!(!state.private_state.0.contains(&expected_new_commitment_2));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
assert_eq!(state.public_state, previous_public_state);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment_1));
assert!(state.private_state.0.contains(&expected_new_commitment_2));
assert!(state.private_state.1.contains(&expected_new_nullifier));
}
#[test]
fn test_transition_from_privacy_preserving_transaction_deshielded() {
2025-08-22 12:29:45 -03:00
let sender_keys = test_private_account_keys_1();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
data: Data::default(),
2025-08-22 12:29:45 -03:00
};
let recipient_keys = test_public_account_keys_1();
let recipient_initial_balance = 400;
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(
&[(recipient_keys.account_id(), recipient_initial_balance)],
2025-09-18 15:59:17 +03:00
&[],
)
2025-08-22 12:29:45 -03:00
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let expected_recipient_post = {
let mut this = state.get_account_by_id(&recipient_keys.account_id());
this.balance += balance_to_move;
this
};
let tx = deshielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
&sender_keys,
&sender_private_account,
&recipient_keys.account_id(),
balance_to_move,
2025-08-22 12:29:45 -03:00
0xcafecafe,
&state,
);
let expected_new_commitment = Commitment::new(
2025-08-22 12:29:45 -03:00
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
2025-08-22 12:29:45 -03:00
nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move,
data: Data::default(),
},
);
2025-08-22 12:29:45 -03:00
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let expected_new_nullifier =
Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
let recipient_post = state.get_account_by_id(&recipient_keys.account_id());
assert_eq!(recipient_post, expected_recipient_post);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment));
assert!(state.private_state.1.contains(&expected_new_nullifier));
assert_eq!(
2025-08-22 12:29:45 -03:00
state
.get_account_by_id(&recipient_keys.account_id())
2025-08-22 12:29:45 -03:00
.balance,
recipient_initial_balance + balance_to_move
);
}
2025-09-02 12:38:31 -03:00
#[test]
fn test_burner_program_should_fail_in_privacy_preserving_circuit() {
2025-09-02 14:09:50 -03:00
let program = Program::burner();
let public_account = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
2025-09-02 12:38:31 -03:00
balance: 100,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
2025-09-02 12:38:31 -03:00
2025-09-02 14:09:50 -03:00
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(10u128).unwrap(),
&[0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 14:09:50 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_minter_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::minter();
let public_account = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
2025-09-02 12:38:31 -03:00
let result = execute_and_prove(
2025-09-02 14:09:50 -03:00
&[public_account],
2025-09-02 12:38:31 -03:00
&Program::serialize_instruction(10u128).unwrap(),
2025-09-02 14:09:50 -03:00
&[0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 14:09:50 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::nonce_changer_program();
let public_account = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
2025-09-02 14:09:50 -03:00
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(()).unwrap(),
&[0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 14:09:50 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() {
let program = Program::data_changer();
let public_account = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
2025-09-02 14:09:50 -03:00
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(vec![0]).unwrap(),
2025-09-02 14:09:50 -03:00
&[0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 14:09:50 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_data_changer_program_should_fail_for_too_large_data_in_privacy_preserving_circuit() {
let program = Program::data_changer();
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let large_data: Vec<u8> = vec![0; nssa_core::account::data::DATA_MAX_LENGTH_IN_BYTES + 1];
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(large_data).unwrap(),
&[0],
&[],
&[],
&[],
&program.to_owned().into(),
);
assert!(matches!(result, Err(NssaError::ProgramProveFailed(_))));
}
2025-09-02 14:09:50 -03:00
#[test]
fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::extra_output_program();
let public_account = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
2025-09-02 14:09:50 -03:00
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(()).unwrap(),
&[0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 14:09:50 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::missing_output_program();
let public_account_1 = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([1; 32]),
);
2025-09-02 14:09:50 -03:00
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(()).unwrap(),
&[0, 0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 14:09:50 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() {
let program = Program::program_owner_changer();
let public_account = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
2025-09-02 14:09:50 -03:00
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(()).unwrap(),
&[0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 14:09:50 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 100,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
2025-09-02 14:09:50 -03:00
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([1; 32]),
);
2025-09-02 14:09:50 -03:00
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[0, 0],
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-02 12:38:31 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
true,
AccountId::new([1; 32]),
);
// Setting only one visibility mask for a circuit execution with two pre_state accounts.
let visibility_mask = [0];
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&visibility_mask,
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
2025-09-03 16:25:02 -03:00
#[test]
fn test_circuit_fails_if_insufficient_nonces_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 =
2025-10-03 18:31:56 -03:00
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
2025-09-03 16:25:02 -03:00
// Setting only one nonce for an execution with two private accounts.
let private_account_nonces = [0xdeadbeef1];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&private_account_nonces,
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_insufficient_keys_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
2025-09-03 16:25:02 -03:00
// Setting only one key for an execution with two private accounts.
let private_account_keys = [(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
)];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&private_account_keys,
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_insufficient_auth_keys_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 =
2025-10-03 18:31:56 -03:00
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
2025-09-03 16:25:02 -03:00
// Setting no auth key for an execution with one non default private accounts.
let private_account_auth = [];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&private_account_auth,
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_invalid_auth_keys_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 =
2025-10-03 18:31:56 -03:00
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
2025-09-03 16:25:02 -03:00
let private_account_keys = [
// First private account is the sender
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
// Second private account is the recipient
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
];
let private_account_auth = [
// Setting the recipient key to authorize the sender.
// This should be set to the sender private account in
// a normal circumstance. The recipient can't authorize this.
(recipient_keys.nsk, (0, vec![])),
];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&private_account_keys,
&private_account_auth,
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
// Non default balance
balance: 1,
..Account::default()
},
false,
2025-10-03 18:31:56 -03:00
&recipient_keys.npk(),
);
2025-09-03 16:25:02 -03:00
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided()
{
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
// Non default program_owner
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
..Account::default()
},
false,
2025-10-03 18:31:56 -03:00
&recipient_keys.npk(),
);
2025-09-03 16:25:02 -03:00
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
// Non default data
data: b"hola mundo".to_vec().try_into().unwrap(),
2025-09-03 16:25:02 -03:00
..Account::default()
},
false,
2025-10-03 18:31:56 -03:00
&recipient_keys.npk(),
);
2025-09-03 16:25:02 -03:00
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
// Non default nonce
nonce: 0xdeadbeef,
..Account::default()
},
false,
2025-10-03 18:31:56 -03:00
&recipient_keys.npk(),
);
2025-09-03 16:25:02 -03:00
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized()
{
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 = AccountWithMetadata::new(
Account::default(),
2025-09-03 16:25:02 -03:00
// This should be set to false in normal circumstances
true,
2025-10-03 18:31:56 -03:00
&recipient_keys.npk(),
);
2025-09-03 16:25:02 -03:00
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_with_invalid_visibility_mask_value() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:25:02 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let public_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
2025-09-03 16:25:02 -03:00
let visibility_mask = [0, 3];
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&visibility_mask,
&[],
&[],
&[],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:25:02 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
2025-09-03 16:44:55 -03:00
#[test]
fn test_circuit_should_fail_with_too_many_nonces() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:44:55 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 =
2025-10-03 18:31:56 -03:00
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
2025-09-03 16:44:55 -03:00
// Setting three new private account nonces for a circuit execution with only two private
// accounts.
let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&private_account_nonces,
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:44:55 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_with_too_many_private_account_keys() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:44:55 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 =
2025-10-03 18:31:56 -03:00
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
2025-09-03 16:44:55 -03:00
// Setting three private account keys for a circuit execution with only two private
// accounts.
let private_account_keys = [
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
(
sender_keys.npk(),
SharedSecretKey::new(&[57; 32], &sender_keys.ivk()),
),
];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&private_account_keys,
&[(sender_keys.nsk, (0, vec![]))],
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:44:55 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_with_too_many_private_account_auth_keys() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata::new(
Account {
2025-09-03 16:44:55 -03:00
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
2025-10-03 18:31:56 -03:00
&sender_keys.npk(),
);
let private_account_2 =
2025-10-03 18:31:56 -03:00
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
2025-09-03 16:44:55 -03:00
// Setting two private account keys for a circuit execution with only one non default
// private account (visibility mask equal to 1 means that auth keys are expected).
let visibility_mask = [1, 2];
let private_account_auth = [
(sender_keys.nsk, (0, vec![])),
(recipient_keys.nsk, (1, vec![])),
];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&visibility_mask,
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&private_account_auth,
2025-11-20 19:25:56 -03:00
&program.into(),
2025-09-03 16:44:55 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_private_accounts_can_only_be_initialized_once() {
let sender_keys = test_private_account_keys_1();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
data: Data::default(),
};
let recipient_keys = test_private_account_keys_2();
2025-10-16 16:24:18 -03:00
let mut state = V02State::new_with_genesis_accounts(&[], &[])
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let tx = private_balance_transfer_for_tests(
&sender_keys,
&sender_private_account,
&recipient_keys,
balance_to_move,
[0xcafecafe, 0xfecafeca],
&state,
);
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100 - balance_to_move,
nonce: 0xcafecafe,
data: Data::default(),
};
let tx = private_balance_transfer_for_tests(
&sender_keys,
&sender_private_account,
&recipient_keys,
balance_to_move,
[0x1234, 0x5678],
&state,
);
let result = state.transition_from_privacy_preserving_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
let NssaError::InvalidInput(error_message) = result.err().unwrap() else {
panic!("Incorrect message error");
};
let expected_error_message = "Nullifier already seen".to_string();
assert_eq!(error_message, expected_error_message);
}
2025-10-03 18:31:56 -03:00
#[test]
fn test_circuit_should_fail_if_there_are_repeated_ids() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
&sender_keys.npk(),
);
let visibility_mask = [1, 1];
let private_account_auth = [
(sender_keys.nsk, (1, vec![])),
(sender_keys.nsk, (1, vec![])),
];
let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.ivk());
let result = execute_and_prove(
&[private_account_1.clone(), private_account_1],
&Program::serialize_instruction(100u128).unwrap(),
&visibility_mask,
&[0xdeadbeef1, 0xdeadbeef2],
&[
2025-12-16 14:05:34 +02:00
(sender_keys.npk(), shared_secret),
2025-10-03 18:31:56 -03:00
(sender_keys.npk(), shared_secret),
],
&private_account_auth,
2025-11-20 19:25:56 -03:00
&program.into(),
2025-10-03 18:31:56 -03:00
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
2025-10-29 15:34:11 -03:00
#[test]
fn test_claiming_mechanism() {
let program = Program::authenticated_transfer_program();
let key = PrivateKey::try_new([1; 32]).unwrap();
let account_id = AccountId::from(&PublicKey::new_from_private_key(&key));
let initial_balance = 100;
let initial_data = [(account_id, initial_balance)];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let from = account_id;
let from_key = key;
let to = AccountId::new([2; 32]);
let amount: u128 = 37;
// Check the recipient is an uninitialized account
assert_eq!(state.get_account_by_id(&to), Account::default());
let expected_recipient_post = Account {
program_owner: program.id(),
balance: amount,
..Account::default()
};
let message =
public_transaction::Message::try_new(program.id(), vec![from, to], vec![0], amount)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let recipient_post = state.get_account_by_id(&to);
assert_eq!(recipient_post, expected_recipient_post);
}
2025-10-29 15:34:11 -03:00
#[test]
2025-11-20 01:40:05 -03:00
fn test_public_chained_call() {
2025-10-29 15:34:11 -03:00
let program = Program::chain_caller();
let key = PrivateKey::try_new([1; 32]).unwrap();
let from = AccountId::from(&PublicKey::new_from_private_key(&key));
let to = AccountId::new([2; 32]);
let initial_balance = 1000;
let initial_data = [(from, initial_balance), (to, 0)];
2025-10-29 15:34:11 -03:00
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let from_key = key;
let amount: u128 = 37;
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
amount,
Program::authenticated_transfer_program().id(),
2,
None,
);
2025-10-29 15:34:11 -03:00
let expected_to_post = Account {
2025-11-17 15:43:01 -03:00
program_owner: Program::authenticated_transfer_program().id(),
2025-11-12 19:08:46 -03:00
balance: amount * 2, // The `chain_caller` chains the program twice
..Account::default()
};
2025-10-29 15:34:11 -03:00
let message = public_transaction::Message::try_new(
program.id(),
vec![to, from], // The chain_caller program permutes the account order in the chain
2025-11-26 20:13:23 -03:00
// call
2025-10-29 15:34:11 -03:00
vec![0],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let from_post = state.get_account_by_id(&from);
let to_post = state.get_account_by_id(&to);
2025-11-12 19:08:46 -03:00
// The `chain_caller` program calls the program twice
assert_eq!(from_post.balance, initial_balance - 2 * amount);
assert_eq!(to_post, expected_to_post);
2025-10-29 15:34:11 -03:00
}
#[test]
fn test_execution_fails_if_chained_calls_exceeds_depth() {
let program = Program::chain_caller();
let key = PrivateKey::try_new([1; 32]).unwrap();
let from = AccountId::from(&PublicKey::new_from_private_key(&key));
let to = AccountId::new([2; 32]);
let initial_balance = 100;
let initial_data = [(from, initial_balance), (to, 0)];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let from_key = key;
let amount: u128 = 0;
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
amount,
Program::authenticated_transfer_program().id(),
MAX_NUMBER_CHAINED_CALLS as u32 + 1,
None,
);
let message = public_transaction::Message::try_new(
program.id(),
vec![to, from], // The chain_caller program permutes the account order in the chain
2025-11-26 20:13:23 -03:00
// 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)
));
}
2025-11-20 21:02:18 -05:00
// TODO: repeated code needs to be cleaned up
// from token.rs (also repeated in amm.rs)
2025-11-20 21:02:18 -05:00
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
#[allow(unused)]
2025-11-20 21:02:18 -05:00
const TOKEN_HOLDING_TYPE: u8 = 1;
const TOKEN_HOLDING_DATA_SIZE: usize = 49;
struct TokenDefinition {
account_type: u8,
name: [u8; 6],
total_supply: u128,
}
2025-11-20 21:02:18 -05:00
struct TokenHolding {
account_type: u8,
definition_id: AccountId,
balance: u128,
2025-11-20 21:02:18 -05:00
}
impl TokenDefinition {
2025-12-15 18:55:38 -05:00
fn into_data(self) -> Data {
let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE];
bytes[0] = self.account_type;
bytes[1..7].copy_from_slice(&self.name);
bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes());
2025-12-15 18:55:38 -05:00
bytes
.to_vec()
.try_into()
.expect("23 bytes should fit into Data")
2025-11-20 21:02:18 -05:00
}
}
impl TokenHolding {
fn into_data(self) -> Data {
let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE];
bytes[0] = self.account_type;
bytes[1..33].copy_from_slice(&self.definition_id.to_bytes());
bytes[33..].copy_from_slice(&self.balance.to_le_bytes());
2025-12-15 18:55:38 -05:00
bytes
.to_vec()
.try_into()
.expect("33 bytes should fit into Data")
2025-11-20 21:02:18 -05:00
}
}
// TODO repeated code should ultimately be removed;
2025-12-09 14:42:58 -05:00
fn compute_pool_pda(
amm_program_id: ProgramId,
definition_token_a_id: AccountId,
definition_token_b_id: AccountId,
) -> AccountId {
AccountId::from((
&amm_program_id,
&compute_pool_pda_seed(definition_token_a_id, definition_token_b_id),
))
}
fn compute_pool_pda_seed(
definition_token_a_id: AccountId,
definition_token_b_id: AccountId,
) -> PdaSeed {
use risc0_zkvm::sha::{Impl, Sha256};
let mut i: usize = 0;
let (token_1, token_2) = loop {
if definition_token_a_id.value()[i] > definition_token_b_id.value()[i] {
2025-12-16 14:05:34 +02:00
let token_1 = definition_token_a_id;
let token_2 = definition_token_b_id;
2025-12-09 14:42:58 -05:00
break (token_1, token_2);
} else if definition_token_a_id.value()[i] < definition_token_b_id.value()[i] {
2025-12-16 14:05:34 +02:00
let token_1 = definition_token_b_id;
let token_2 = definition_token_a_id;
2025-12-09 14:42:58 -05:00
break (token_1, token_2);
}
2025-11-25 23:06:47 -05:00
2025-12-09 14:42:58 -05:00
if i == 32 {
panic!("Definitions match");
} else {
i += 1;
}
};
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(&token_1.to_bytes());
bytes[32..].copy_from_slice(&token_2.to_bytes());
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
PdaSeed::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Hash output must be exactly 32 bytes long"),
)
}
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
fn compute_vault_pda(
amm_program_id: ProgramId,
pool_id: AccountId,
definition_token_id: AccountId,
) -> AccountId {
AccountId::from((
&amm_program_id,
&compute_vault_pda_seed(pool_id, definition_token_id),
))
}
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed {
use risc0_zkvm::sha::{Impl, Sha256};
2025-12-09 14:42:58 -05:00
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(&pool_id.to_bytes());
bytes[32..].copy_from_slice(&definition_token_id.to_bytes());
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
PdaSeed::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Hash output must be exactly 32 bytes long"),
)
}
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId {
AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id)))
}
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed {
use risc0_zkvm::sha::{Impl, Sha256};
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(&pool_id.to_bytes());
bytes[32..].copy_from_slice(&[0; 32]);
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
PdaSeed::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Hash output must be exactly 32 bytes long"),
)
}
const POOL_DEFINITION_DATA_SIZE: usize = 225;
2025-12-08 22:05:51 -05:00
#[derive(Default)]
2025-12-09 14:42:58 -05:00
struct PoolDefinition {
definition_token_a_id: AccountId,
definition_token_b_id: AccountId,
vault_a_id: AccountId,
vault_b_id: AccountId,
2025-12-09 14:42:58 -05:00
liquidity_pool_id: AccountId,
liquidity_pool_supply: u128,
reserve_a: u128,
reserve_b: u128,
fees: u128,
active: bool,
}
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
impl PoolDefinition {
fn into_data(self) -> Data {
2025-12-09 14:42:58 -05:00
let mut bytes = [0; POOL_DEFINITION_DATA_SIZE];
bytes[0..32].copy_from_slice(&self.definition_token_a_id.to_bytes());
bytes[32..64].copy_from_slice(&self.definition_token_b_id.to_bytes());
bytes[64..96].copy_from_slice(&self.vault_a_id.to_bytes());
bytes[96..128].copy_from_slice(&self.vault_b_id.to_bytes());
2025-12-09 14:42:58 -05:00
bytes[128..160].copy_from_slice(&self.liquidity_pool_id.to_bytes());
bytes[160..176].copy_from_slice(&self.liquidity_pool_supply.to_le_bytes());
bytes[176..192].copy_from_slice(&self.reserve_a.to_le_bytes());
bytes[192..208].copy_from_slice(&self.reserve_b.to_le_bytes());
bytes[208..224].copy_from_slice(&self.fees.to_le_bytes());
bytes[224] = self.active as u8;
bytes
.to_vec()
.try_into()
.expect("225 bytes should fit into Data")
}
2025-12-16 14:05:34 +02:00
#[allow(unused)]
fn parse(data: &[u8]) -> Option<Self> {
if data.len() != POOL_DEFINITION_DATA_SIZE {
None
} else {
let definition_token_a_id = AccountId::new(data[0..32].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token A definition"));
let definition_token_b_id = AccountId::new(data[32..64].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Vault B definition"));
let vault_a_id = AccountId::new(data[64..96].try_into().expect(
"Parse data: The AMM program must be provided a valid AccountId for Vault A",
));
let vault_b_id = AccountId::new(data[96..128].try_into().expect(
"Parse data: The AMM program must be provided a valid AccountId for Vault B",
));
let liquidity_pool_id = AccountId::new(data[128..160].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token liquidity pool definition"));
let liquidity_pool_supply = u128::from_le_bytes(data[160..176].try_into().expect(
"Parse data: The AMM program must be provided a valid u128 for liquidity cap",
));
let reserve_a = u128::from_le_bytes(data[176..192].try_into().expect("Parse data: The AMM program must be provided a valid u128 for reserve A balance"));
let reserve_b = u128::from_le_bytes(data[192..208].try_into().expect("Parse data: The AMM program must be provided a valid u128 for reserve B balance"));
let fees =
u128::from_le_bytes(data[208..224].try_into().expect(
"Parse data: The AMM program must be provided a valid u128 for fees",
));
let active = match data[224] {
0 => false,
1 => true,
_ => panic!(
"Parse data: The AMM program must be provided a valid bool for active"
),
};
Some(Self {
definition_token_a_id,
definition_token_b_id,
vault_a_id,
vault_b_id,
liquidity_pool_id,
liquidity_pool_supply,
reserve_a,
reserve_b,
fees,
active,
})
}
2025-12-09 14:42:58 -05:00
}
2025-12-06 14:52:18 -05:00
}
2025-12-06 14:52:18 -05:00
enum AccountsEnum {
2025-12-07 20:34:26 -05:00
UserTokenAHolding,
UserTokenBHolding,
UserTokenLPHolding,
PoolDefinitionInit,
TokenADefinitionAcc,
TokenBDefinitionAcc,
TokenLPDefinitionAcc,
VaultAInit,
VaultBInit,
VaultASwap1,
VaultBSwap1,
UserTokenAHoldingSwap1,
UserTokenBHoldingSwap1,
PoolDefinitionSwap1,
VaultASwap2,
VaultBSwap2,
UserTokenAHoldingSwap2,
UserTokenBHoldingSwap2,
PoolDefinitionSwap2,
VaultAAdd,
VaultBAdd,
UserTokenAHoldingAdd,
2025-12-09 14:42:58 -05:00
UserTokenBHoldingAdd,
UserTokenLPHoldingAdd,
2025-12-07 20:34:26 -05:00
PoolDefinitionAdd,
TokenLPDefinitionAdd,
VaultARemove,
VaultBRemove,
UserTokenAHoldingRemove,
2025-12-09 14:42:58 -05:00
UserTokenBHoldingRemove,
UserTokenLPHoldingRemove,
2025-12-07 20:34:26 -05:00
PoolDefinitionRemove,
TokenLPDefinitionRemove,
2025-12-08 22:05:51 -05:00
VaultAInitInactive,
VaultBInitInactive,
TokenLPDefinitionInitInactive,
PoolDefinitionInactive,
2025-12-09 14:42:58 -05:00
UserTokenAHoldingNewInit,
UserTokenBHoldingNewInit,
UserTokenLPHoldingNewInit,
TokenLPDefinitionNewInit,
PoolDefinitionNewInit,
UserTokenLPHoldingInitZero,
2025-12-06 14:52:18 -05:00
}
enum BalancesEnum {
2025-12-07 20:34:26 -05:00
UserTokenAHoldingInit,
UserTokenBHoldingInit,
UserTokenLPHoldingInit,
VaultABalanceInit,
VaultBBalanceInit,
PoolLPSupplyInit,
TokenASupply,
TokenBSupply,
TokenLPSupply,
RemoveLP,
RemoveMinAmountA,
RemoveMinAmountB,
AddMinAmountLP,
AddMaxAmountA,
AddMaxAmountB,
SwapAmountIn,
SwapMinAmountOUt,
VaultABalanceSwap1,
VaultBBalanceSwap1,
UserTokenAHoldingSwap1,
UserTokenBHoldingSwap1,
VaultABalanceSwap2,
VaultBBalanceSwap2,
UserTokenAHoldingSwap2,
UserTokenBHoldingSwap2,
VaultABalanceAdd,
VaultBBalanceAdd,
UserTokenAHoldingAdd,
UserTokenBHoldingAdd,
UserTokenLPHoldingAdd,
TokenLPSupplyAdd,
VaultABalanceRemove,
VaultBBalanceRemove,
UserTokenAHoldingRemove,
UserTokenBHoldingRemove,
UserTokenLPHoldingRemove,
TokenLPSupplyRemove,
2025-12-09 14:42:58 -05:00
UserTokenAHoldingNewDef,
UserTokenBHoldingNewDef,
2025-12-06 14:52:18 -05:00
}
2025-12-16 14:05:34 +02:00
#[allow(clippy::enum_variant_names)]
2025-12-06 14:52:18 -05:00
enum IdEnum {
2025-12-07 20:34:26 -05:00
PoolDefinitionId,
TokenLPDefinitionId,
TokenADefinitionId,
TokenBDefinitionId,
UserTokenAId,
UserTokenBId,
UserTokenLPId,
VaultAId,
VaultBId,
2025-12-06 14:52:18 -05:00
}
2025-12-16 14:05:34 +02:00
#[allow(clippy::enum_variant_names)]
2025-12-06 14:52:18 -05:00
enum PrivateKeysEnum {
2025-12-07 20:34:26 -05:00
UserTokenAKey,
UserTokenBKey,
UserTokenLPKey,
2025-12-06 14:52:18 -05:00
}
fn helper_balances_constructor(selection: BalancesEnum) -> u128 {
match selection {
2025-12-07 20:34:26 -05:00
BalancesEnum::UserTokenAHoldingInit => 10_000,
BalancesEnum::UserTokenBHoldingInit => 10_000,
BalancesEnum::UserTokenLPHoldingInit => 2_000,
BalancesEnum::VaultABalanceInit => 5_000,
BalancesEnum::VaultBBalanceInit => 2_500,
BalancesEnum::PoolLPSupplyInit => 5_000,
BalancesEnum::TokenASupply => 100_000,
BalancesEnum::TokenBSupply => 100_000,
BalancesEnum::TokenLPSupply => 5_000,
BalancesEnum::RemoveLP => 1_000,
BalancesEnum::RemoveMinAmountA => 500,
BalancesEnum::RemoveMinAmountB => 500,
BalancesEnum::AddMinAmountLP => 1_000,
BalancesEnum::AddMaxAmountA => 2_000,
BalancesEnum::AddMaxAmountB => 1_000,
BalancesEnum::SwapAmountIn => 1_000,
BalancesEnum::SwapMinAmountOUt => 200,
BalancesEnum::VaultABalanceSwap1 => 3_572,
BalancesEnum::VaultBBalanceSwap1 => 3_500,
BalancesEnum::UserTokenAHoldingSwap1 => 11_428,
BalancesEnum::UserTokenBHoldingSwap1 => 9_000,
BalancesEnum::VaultABalanceSwap2 => 6_000,
BalancesEnum::VaultBBalanceSwap2 => 2_084,
BalancesEnum::UserTokenAHoldingSwap2 => 9_000,
BalancesEnum::UserTokenBHoldingSwap2 => 10_416,
BalancesEnum::VaultABalanceAdd => 7_000,
BalancesEnum::VaultBBalanceAdd => 3_500,
BalancesEnum::UserTokenAHoldingAdd => 8_000,
BalancesEnum::UserTokenBHoldingAdd => 9_000,
BalancesEnum::UserTokenLPHoldingAdd => 4_000,
BalancesEnum::TokenLPSupplyAdd => 7_000,
BalancesEnum::VaultABalanceRemove => 4_000,
BalancesEnum::VaultBBalanceRemove => 2_000,
BalancesEnum::UserTokenAHoldingRemove => 11_000,
BalancesEnum::UserTokenBHoldingRemove => 10_500,
BalancesEnum::UserTokenLPHoldingRemove => 1_000,
BalancesEnum::TokenLPSupplyRemove => 4_000,
2025-12-09 14:42:58 -05:00
BalancesEnum::UserTokenAHoldingNewDef => 5_000,
BalancesEnum::UserTokenBHoldingNewDef => 7_500,
2025-12-06 14:52:18 -05:00
}
}
2025-11-24 19:44:08 -05:00
2025-12-06 14:52:18 -05:00
fn helper_private_keys_constructor(selection: PrivateKeysEnum) -> PrivateKey {
match selection {
2025-12-09 14:42:58 -05:00
PrivateKeysEnum::UserTokenAKey => {
PrivateKey::try_new([31; 32]).expect("Keys constructor expects valid private key")
}
PrivateKeysEnum::UserTokenBKey => {
PrivateKey::try_new([32; 32]).expect("Keys constructor expects valid private key")
}
PrivateKeysEnum::UserTokenLPKey => {
PrivateKey::try_new([33; 32]).expect("Keys constructor expects valid private key")
}
2025-12-06 14:52:18 -05:00
}
}
2025-12-06 14:52:18 -05:00
fn helper_id_constructor(selection: IdEnum) -> AccountId {
match selection {
2025-12-08 22:05:51 -05:00
IdEnum::PoolDefinitionId => compute_pool_pda(
2025-12-09 14:42:58 -05:00
Program::amm().id(),
helper_id_constructor(IdEnum::TokenADefinitionId),
helper_id_constructor(IdEnum::TokenBDefinitionId),
),
2025-12-08 22:05:51 -05:00
IdEnum::VaultAId => compute_vault_pda(
2025-12-09 14:42:58 -05:00
Program::amm().id(),
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::TokenADefinitionId),
),
2025-12-08 22:05:51 -05:00
IdEnum::VaultBId => compute_vault_pda(
2025-12-09 14:42:58 -05:00
Program::amm().id(),
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::TokenBDefinitionId),
),
2025-12-08 22:05:51 -05:00
IdEnum::TokenLPDefinitionId => compute_liquidity_token_pda(
2025-12-09 14:42:58 -05:00
Program::amm().id(),
helper_id_constructor(IdEnum::PoolDefinitionId),
),
IdEnum::TokenADefinitionId => AccountId::new([3; 32]),
IdEnum::TokenBDefinitionId => AccountId::new([4; 32]),
IdEnum::UserTokenAId => AccountId::from(&PublicKey::new_from_private_key(
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenAKey),
)),
IdEnum::UserTokenBId => AccountId::from(&PublicKey::new_from_private_key(
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenBKey),
)),
IdEnum::UserTokenLPId => AccountId::from(&PublicKey::new_from_private_key(
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenLPKey),
)),
2025-12-06 14:52:18 -05:00
}
}
2025-12-06 14:52:18 -05:00
fn helper_account_constructor(selection: AccountsEnum) -> Account {
match selection {
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenAHolding => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenAHoldingInit),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenBHolding => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenBHoldingInit),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::PoolDefinitionInit => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::amm().id(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
definition_token_a_id: helper_id_constructor(IdEnum::TokenADefinitionId),
definition_token_b_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-15 18:55:38 -05:00
vault_a_id: helper_id_constructor(IdEnum::VaultAId),
vault_b_id: helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
liquidity_pool_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
liquidity_pool_supply: helper_balances_constructor(
BalancesEnum::PoolLPSupplyInit,
2025-12-06 14:52:18 -05:00
),
2025-12-09 14:42:58 -05:00
reserve_a: helper_balances_constructor(BalancesEnum::VaultABalanceInit),
reserve_b: helper_balances_constructor(BalancesEnum::VaultBBalanceInit),
fees: 0u128,
active: true,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
AccountsEnum::TokenADefinitionAcc => Account {
program_owner: Program::token().id(),
balance: 0u128,
data: TokenDefinition::into_data(TokenDefinition {
account_type: 0u8,
name: [1u8; 6],
total_supply: helper_balances_constructor(BalancesEnum::TokenASupply),
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::TokenBDefinitionAcc => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenDefinition::into_data(TokenDefinition {
account_type: 0u8,
name: [1u8; 6],
total_supply: helper_balances_constructor(BalancesEnum::TokenBSupply),
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::TokenLPDefinitionAcc => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenDefinition::into_data(TokenDefinition {
account_type: 0u8,
name: [1u8; 6],
total_supply: helper_balances_constructor(BalancesEnum::TokenLPSupply),
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultAInit => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultABalanceInit),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultBInit => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultBBalanceInit),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenLPHolding => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenLPHoldingInit),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultASwap1 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultABalanceSwap1),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultBSwap1 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultBBalanceSwap1),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::PoolDefinitionSwap1 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::amm().id(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
definition_token_a_id: helper_id_constructor(IdEnum::TokenADefinitionId),
definition_token_b_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-15 18:55:38 -05:00
vault_a_id: helper_id_constructor(IdEnum::VaultAId),
vault_b_id: helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
liquidity_pool_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
liquidity_pool_supply: helper_balances_constructor(
BalancesEnum::PoolLPSupplyInit,
),
reserve_a: helper_balances_constructor(BalancesEnum::VaultABalanceSwap1),
reserve_b: helper_balances_constructor(BalancesEnum::VaultBBalanceSwap1),
fees: 0u128,
active: true,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenAHoldingSwap1 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenAHoldingSwap1),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenBHoldingSwap1 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenBHoldingSwap1),
}),
nonce: 1,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultASwap2 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultABalanceSwap2),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultBSwap2 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultBBalanceSwap2),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::PoolDefinitionSwap2 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::amm().id(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
definition_token_a_id: helper_id_constructor(IdEnum::TokenADefinitionId),
definition_token_b_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-15 18:55:38 -05:00
vault_a_id: helper_id_constructor(IdEnum::VaultAId),
vault_b_id: helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
liquidity_pool_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
liquidity_pool_supply: helper_balances_constructor(
BalancesEnum::PoolLPSupplyInit,
),
reserve_a: helper_balances_constructor(BalancesEnum::VaultABalanceSwap2),
reserve_b: helper_balances_constructor(BalancesEnum::VaultBBalanceSwap2),
fees: 0u128,
active: true,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenAHoldingSwap2 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenAHoldingSwap2),
}),
nonce: 1,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenBHoldingSwap2 => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenBHoldingSwap2),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultAAdd => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultABalanceAdd),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultBAdd => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultBBalanceAdd),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::PoolDefinitionAdd => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::amm().id(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
definition_token_a_id: helper_id_constructor(IdEnum::TokenADefinitionId),
definition_token_b_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-15 18:55:38 -05:00
vault_a_id: helper_id_constructor(IdEnum::VaultAId),
vault_b_id: helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
liquidity_pool_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
liquidity_pool_supply: helper_balances_constructor(
BalancesEnum::TokenLPSupplyAdd,
),
reserve_a: helper_balances_constructor(BalancesEnum::VaultABalanceAdd),
reserve_b: helper_balances_constructor(BalancesEnum::VaultBBalanceAdd),
fees: 0u128,
active: true,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenAHoldingAdd => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenAHoldingAdd),
}),
nonce: 1,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenBHoldingAdd => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenBHoldingAdd),
}),
nonce: 1,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenLPHoldingAdd => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenLPHoldingAdd),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::TokenLPDefinitionAdd => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenDefinition::into_data(TokenDefinition {
account_type: 0u8,
name: [1u8; 6],
total_supply: helper_balances_constructor(BalancesEnum::TokenLPSupplyAdd),
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultARemove => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultABalanceRemove),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::VaultBRemove => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::VaultBBalanceRemove),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::PoolDefinitionRemove => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::amm().id(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
definition_token_a_id: helper_id_constructor(IdEnum::TokenADefinitionId),
definition_token_b_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-15 18:55:38 -05:00
vault_a_id: helper_id_constructor(IdEnum::VaultAId),
vault_b_id: helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
liquidity_pool_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
liquidity_pool_supply: helper_balances_constructor(
BalancesEnum::TokenLPSupplyRemove,
),
reserve_a: helper_balances_constructor(BalancesEnum::VaultABalanceRemove),
reserve_b: helper_balances_constructor(BalancesEnum::VaultBBalanceRemove),
fees: 0u128,
active: true,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenAHoldingRemove => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenAHoldingRemove),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenBHoldingRemove => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenBHoldingRemove),
}),
nonce: 0,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::UserTokenLPHoldingRemove => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenLPHoldingRemove),
}),
nonce: 1,
},
2025-12-07 20:34:26 -05:00
AccountsEnum::TokenLPDefinitionRemove => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenDefinition::into_data(TokenDefinition {
account_type: 0u8,
name: [1u8; 6],
total_supply: helper_balances_constructor(BalancesEnum::TokenLPSupplyRemove),
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-08 22:05:51 -05:00
AccountsEnum::TokenLPDefinitionInitInactive => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenDefinition::into_data(TokenDefinition {
account_type: 0u8,
name: [1u8; 6],
total_supply: 0,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
2025-12-08 22:05:51 -05:00
AccountsEnum::VaultAInitInactive => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: 0,
}),
nonce: 0,
},
2025-12-08 22:05:51 -05:00
AccountsEnum::VaultBInitInactive => Account {
2025-12-09 14:42:58 -05:00
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: 0,
}),
nonce: 0,
},
AccountsEnum::PoolDefinitionInactive => Account {
program_owner: Program::amm().id(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
definition_token_a_id: helper_id_constructor(IdEnum::TokenADefinitionId),
definition_token_b_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-15 18:55:38 -05:00
vault_a_id: helper_id_constructor(IdEnum::VaultAId),
vault_b_id: helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
liquidity_pool_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
liquidity_pool_supply: 0,
reserve_a: 0,
reserve_b: 0,
fees: 0u128,
active: false,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
AccountsEnum::UserTokenAHoldingNewInit => Account {
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenADefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenAHoldingNewDef),
}),
nonce: 1,
},
AccountsEnum::UserTokenBHoldingNewInit => Account {
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenBHoldingNewDef),
}),
nonce: 1,
},
AccountsEnum::UserTokenLPHoldingNewInit => Account {
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
balance: helper_balances_constructor(BalancesEnum::UserTokenAHoldingNewDef),
}),
nonce: 0,
},
AccountsEnum::TokenLPDefinitionNewInit => Account {
program_owner: Program::token().id(),
balance: 0u128,
data: TokenDefinition::into_data(TokenDefinition {
account_type: 0u8,
name: [1u8; 6],
total_supply: helper_balances_constructor(BalancesEnum::VaultABalanceInit),
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
AccountsEnum::PoolDefinitionNewInit => Account {
program_owner: Program::amm().id(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
definition_token_a_id: helper_id_constructor(IdEnum::TokenADefinitionId),
definition_token_b_id: helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-15 18:55:38 -05:00
vault_a_id: helper_id_constructor(IdEnum::VaultAId),
vault_b_id: helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
liquidity_pool_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
liquidity_pool_supply: helper_balances_constructor(
BalancesEnum::UserTokenAHoldingNewDef,
),
reserve_a: helper_balances_constructor(BalancesEnum::VaultABalanceInit),
reserve_b: helper_balances_constructor(BalancesEnum::VaultBBalanceInit),
fees: 0u128,
active: true,
2025-12-16 14:05:34 +02:00
}),
2025-12-09 14:42:58 -05:00
nonce: 0,
},
AccountsEnum::UserTokenLPHoldingInitZero => Account {
program_owner: Program::token().id(),
balance: 0u128,
data: TokenHolding::into_data(TokenHolding {
account_type: 1u8,
definition_id: helper_id_constructor(IdEnum::TokenLPDefinitionId),
balance: 0,
}),
nonce: 0,
},
2025-12-06 14:52:18 -05:00
}
}
2025-12-06 14:52:18 -05:00
fn amm_state_constructor() -> V02State {
let initial_data = [];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::PoolDefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::PoolDefinitionInit),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::TokenADefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::TokenADefinitionAcc),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::TokenBDefinitionAcc),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::TokenLPDefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::TokenLPDefinitionAcc),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::UserTokenAId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::UserTokenAHolding),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::UserTokenBId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::UserTokenBHolding),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::UserTokenLPId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::UserTokenLPHolding),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::VaultAId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::VaultAInit),
2025-12-06 14:52:18 -05:00
);
state.force_insert_account(
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::VaultBInit),
2025-12-06 14:52:18 -05:00
);
2025-11-25 23:06:47 -05:00
2025-12-06 14:52:18 -05:00
state
}
2025-11-25 23:06:47 -05:00
2025-12-08 22:05:51 -05:00
fn amm_state_constructor_for_new_def() -> V02State {
let initial_data = [];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
state.force_insert_account(
helper_id_constructor(IdEnum::TokenADefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::TokenADefinitionAcc),
2025-12-08 22:05:51 -05:00
);
state.force_insert_account(
helper_id_constructor(IdEnum::TokenBDefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::TokenBDefinitionAcc),
2025-12-08 22:05:51 -05:00
);
state.force_insert_account(
helper_id_constructor(IdEnum::UserTokenAId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::UserTokenAHolding),
2025-12-08 22:05:51 -05:00
);
state.force_insert_account(
helper_id_constructor(IdEnum::UserTokenBId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::UserTokenBHolding),
2025-12-08 22:05:51 -05:00
);
state
}
#[test]
fn test_simple_amm_remove() {
2025-12-06 14:52:18 -05:00
let mut state = amm_state_constructor();
2025-11-25 23:06:47 -05:00
let mut instruction: Vec<u8> = Vec::new();
instruction.push(3);
2025-12-09 14:42:58 -05:00
instruction
.extend_from_slice(&helper_balances_constructor(BalancesEnum::RemoveLP).to_le_bytes());
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::RemoveMinAmountA).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::RemoveMinAmountB).to_le_bytes(),
);
2025-11-25 23:06:47 -05:00
let message = public_transaction::Message::try_new(
Program::amm().id(),
vec![
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::VaultAId),
helper_id_constructor(IdEnum::VaultBId),
helper_id_constructor(IdEnum::TokenLPDefinitionId),
helper_id_constructor(IdEnum::UserTokenAId),
helper_id_constructor(IdEnum::UserTokenBId),
helper_id_constructor(IdEnum::UserTokenLPId),
],
2025-12-08 22:05:51 -05:00
vec![0],
2025-11-25 23:06:47 -05:00
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
2025-12-09 14:42:58 -05:00
&[&helper_private_keys_constructor(
PrivateKeysEnum::UserTokenLPKey,
)],
2025-11-25 23:06:47 -05:00
);
let tx = PublicTransaction::new(message, witness_set);
2025-11-25 23:06:47 -05:00
state.transition_from_public_transaction(&tx).unwrap();
2025-12-07 20:34:26 -05:00
let pool_post = state.get_account_by_id(&helper_id_constructor(IdEnum::PoolDefinitionId));
let vault_a_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultAId));
let vault_b_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultBId));
2025-12-09 14:42:58 -05:00
let token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::TokenLPDefinitionId));
let user_token_a_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenAId));
let user_token_b_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenBId));
let user_token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenLPId));
2025-12-07 20:34:26 -05:00
let expected_pool = helper_account_constructor(AccountsEnum::PoolDefinitionRemove);
let expected_vault_a = helper_account_constructor(AccountsEnum::VaultARemove);
let expected_vault_b = helper_account_constructor(AccountsEnum::VaultBRemove);
let expected_token_lp = helper_account_constructor(AccountsEnum::TokenLPDefinitionRemove);
2025-12-09 14:42:58 -05:00
let expected_user_token_a =
helper_account_constructor(AccountsEnum::UserTokenAHoldingRemove);
let expected_user_token_b =
helper_account_constructor(AccountsEnum::UserTokenBHoldingRemove);
let expected_user_token_lp =
helper_account_constructor(AccountsEnum::UserTokenLPHoldingRemove);
2025-12-07 20:34:26 -05:00
assert!(pool_post == expected_pool);
assert!(vault_a_post == expected_vault_a);
assert!(vault_b_post == expected_vault_b);
assert!(token_lp_post == expected_token_lp);
assert!(user_token_a_post == expected_user_token_a);
assert!(user_token_b_post == expected_user_token_b);
assert!(user_token_lp_post == expected_user_token_lp);
}
2025-12-08 22:05:51 -05:00
#[test]
2025-12-09 14:42:58 -05:00
fn test_simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
2025-12-08 22:05:51 -05:00
let mut state = amm_state_constructor_for_new_def();
2025-12-09 14:42:58 -05:00
// Uninitialized in constructor
2025-12-08 22:05:51 -05:00
state.force_insert_account(
helper_id_constructor(IdEnum::VaultAId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::VaultAInitInactive),
2025-12-08 22:05:51 -05:00
);
state.force_insert_account(
helper_id_constructor(IdEnum::VaultBId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::VaultBInitInactive),
2025-12-08 22:05:51 -05:00
);
state.force_insert_account(
helper_id_constructor(IdEnum::PoolDefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::PoolDefinitionInactive),
2025-12-08 22:05:51 -05:00
);
state.force_insert_account(
helper_id_constructor(IdEnum::TokenLPDefinitionId),
2025-12-09 14:42:58 -05:00
helper_account_constructor(AccountsEnum::TokenLPDefinitionInitInactive),
2025-12-08 22:05:51 -05:00
);
2025-12-09 14:42:58 -05:00
let mut instruction: Vec<u8> = Vec::new();
instruction.push(0);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::VaultABalanceInit).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::VaultBBalanceInit).to_le_bytes(),
);
let amm_program_u8: [u8; 32] = bytemuck::cast(Program::amm().id());
instruction.extend_from_slice(&amm_program_u8);
2025-12-08 22:05:51 -05:00
2025-12-09 14:42:58 -05:00
let message = public_transaction::Message::try_new(
Program::amm().id(),
vec![
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::VaultAId),
helper_id_constructor(IdEnum::VaultBId),
helper_id_constructor(IdEnum::TokenLPDefinitionId),
helper_id_constructor(IdEnum::UserTokenAId),
helper_id_constructor(IdEnum::UserTokenBId),
helper_id_constructor(IdEnum::UserTokenLPId),
],
vec![0, 0],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenAKey),
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenBKey),
],
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let pool_post = state.get_account_by_id(&helper_id_constructor(IdEnum::PoolDefinitionId));
let vault_a_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultAId));
let vault_b_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultBId));
let token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::TokenLPDefinitionId));
let user_token_a_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenAId));
let user_token_b_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenBId));
let user_token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenLPId));
let expected_pool = helper_account_constructor(AccountsEnum::PoolDefinitionNewInit);
let expected_vault_a = helper_account_constructor(AccountsEnum::VaultAInit);
let expected_vault_b = helper_account_constructor(AccountsEnum::VaultBInit);
let expected_token_lp = helper_account_constructor(AccountsEnum::TokenLPDefinitionNewInit);
let expected_user_token_a =
helper_account_constructor(AccountsEnum::UserTokenAHoldingNewInit);
let expected_user_token_b =
helper_account_constructor(AccountsEnum::UserTokenBHoldingNewInit);
let expected_user_token_lp =
helper_account_constructor(AccountsEnum::UserTokenLPHoldingNewInit);
assert!(pool_post == expected_pool);
assert!(vault_a_post == expected_vault_a);
assert!(vault_b_post == expected_vault_b);
assert!(token_lp_post == expected_token_lp);
assert!(user_token_a_post == expected_user_token_a);
assert!(user_token_b_post == expected_user_token_b);
assert!(user_token_lp_post == expected_user_token_lp);
}
#[test]
fn test_simple_amm_new_definition_inactive_initialized_pool_init_user_lp() {
let mut state = amm_state_constructor_for_new_def();
// Uninitialized in constructor
state.force_insert_account(
helper_id_constructor(IdEnum::VaultAId),
helper_account_constructor(AccountsEnum::VaultAInitInactive),
);
state.force_insert_account(
helper_id_constructor(IdEnum::VaultBId),
helper_account_constructor(AccountsEnum::VaultBInitInactive),
);
state.force_insert_account(
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_account_constructor(AccountsEnum::PoolDefinitionInactive),
);
state.force_insert_account(
helper_id_constructor(IdEnum::TokenLPDefinitionId),
helper_account_constructor(AccountsEnum::TokenLPDefinitionInitInactive),
);
state.force_insert_account(
helper_id_constructor(IdEnum::UserTokenLPId),
helper_account_constructor(AccountsEnum::UserTokenLPHoldingInitZero),
);
2025-12-08 22:05:51 -05:00
let mut instruction: Vec<u8> = Vec::new();
instruction.push(0);
2025-12-09 14:42:58 -05:00
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::VaultABalanceInit).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::VaultBBalanceInit).to_le_bytes(),
);
2025-12-08 22:05:51 -05:00
let amm_program_u8: [u8; 32] = bytemuck::cast(Program::amm().id());
instruction.extend_from_slice(&amm_program_u8);
let message = public_transaction::Message::try_new(
Program::amm().id(),
vec![
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::VaultAId),
helper_id_constructor(IdEnum::VaultBId),
helper_id_constructor(IdEnum::TokenLPDefinitionId),
helper_id_constructor(IdEnum::UserTokenAId),
helper_id_constructor(IdEnum::UserTokenBId),
helper_id_constructor(IdEnum::UserTokenLPId),
],
vec![0, 0],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenAKey),
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenBKey),
],
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
2025-12-09 14:42:58 -05:00
2025-12-08 22:05:51 -05:00
let pool_post = state.get_account_by_id(&helper_id_constructor(IdEnum::PoolDefinitionId));
let vault_a_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultAId));
let vault_b_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultBId));
2025-12-09 14:42:58 -05:00
let token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::TokenLPDefinitionId));
let user_token_a_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenAId));
let user_token_b_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenBId));
let user_token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenLPId));
let expected_pool = helper_account_constructor(AccountsEnum::PoolDefinitionNewInit);
let expected_vault_a = helper_account_constructor(AccountsEnum::VaultAInit);
let expected_vault_b = helper_account_constructor(AccountsEnum::VaultBInit);
let expected_token_lp = helper_account_constructor(AccountsEnum::TokenLPDefinitionNewInit);
let expected_user_token_a =
helper_account_constructor(AccountsEnum::UserTokenAHoldingNewInit);
let expected_user_token_b =
helper_account_constructor(AccountsEnum::UserTokenBHoldingNewInit);
let expected_user_token_lp =
helper_account_constructor(AccountsEnum::UserTokenLPHoldingNewInit);
2025-12-08 22:05:51 -05:00
assert!(pool_post == expected_pool);
assert!(vault_a_post == expected_vault_a);
assert!(vault_b_post == expected_vault_b);
assert!(token_lp_post == expected_token_lp);
assert!(user_token_a_post == expected_user_token_a);
assert!(user_token_b_post == expected_user_token_b);
2025-12-09 14:42:58 -05:00
assert!(user_token_lp_post == expected_user_token_lp);
2025-12-08 22:05:51 -05:00
}
2025-12-09 14:42:58 -05:00
#[test]
fn test_simple_amm_new_definition_uninitialized_pool() {
let mut state = amm_state_constructor_for_new_def();
// Uninitialized in constructor
state.force_insert_account(
helper_id_constructor(IdEnum::VaultAId),
helper_account_constructor(AccountsEnum::VaultAInitInactive),
);
state.force_insert_account(
helper_id_constructor(IdEnum::VaultBId),
helper_account_constructor(AccountsEnum::VaultBInitInactive),
);
let mut instruction: Vec<u8> = Vec::new();
instruction.push(0);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::VaultABalanceInit).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::VaultBBalanceInit).to_le_bytes(),
);
let amm_program_u8: [u8; 32] = bytemuck::cast(Program::amm().id());
instruction.extend_from_slice(&amm_program_u8);
let message = public_transaction::Message::try_new(
Program::amm().id(),
vec![
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::VaultAId),
helper_id_constructor(IdEnum::VaultBId),
helper_id_constructor(IdEnum::TokenLPDefinitionId),
helper_id_constructor(IdEnum::UserTokenAId),
helper_id_constructor(IdEnum::UserTokenBId),
helper_id_constructor(IdEnum::UserTokenLPId),
],
vec![0, 0],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenAKey),
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenBKey),
],
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let pool_post = state.get_account_by_id(&helper_id_constructor(IdEnum::PoolDefinitionId));
let vault_a_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultAId));
let vault_b_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultBId));
let token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::TokenLPDefinitionId));
let user_token_a_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenAId));
let user_token_b_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenBId));
let user_token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenLPId));
let expected_pool = helper_account_constructor(AccountsEnum::PoolDefinitionNewInit);
let expected_vault_a = helper_account_constructor(AccountsEnum::VaultAInit);
let expected_vault_b = helper_account_constructor(AccountsEnum::VaultBInit);
let expected_token_lp = helper_account_constructor(AccountsEnum::TokenLPDefinitionNewInit);
let expected_user_token_a =
helper_account_constructor(AccountsEnum::UserTokenAHoldingNewInit);
let expected_user_token_b =
helper_account_constructor(AccountsEnum::UserTokenBHoldingNewInit);
let expected_user_token_lp =
helper_account_constructor(AccountsEnum::UserTokenLPHoldingNewInit);
assert!(pool_post == expected_pool);
assert!(vault_a_post == expected_vault_a);
assert!(vault_b_post == expected_vault_b);
assert!(token_lp_post == expected_token_lp);
assert!(user_token_a_post == expected_user_token_a);
assert!(user_token_b_post == expected_user_token_b);
assert!(user_token_lp_post == expected_user_token_lp);
}
2025-12-08 22:05:51 -05:00
#[test]
fn test_simple_amm_add() {
2025-12-06 14:52:18 -05:00
let mut state = amm_state_constructor();
let mut instruction: Vec<u8> = Vec::new();
instruction.push(2);
2025-12-09 14:42:58 -05:00
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::AddMinAmountLP).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::AddMaxAmountA).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::AddMaxAmountB).to_le_bytes(),
);
2025-11-25 23:06:47 -05:00
let message = public_transaction::Message::try_new(
Program::amm().id(),
vec![
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::VaultAId),
helper_id_constructor(IdEnum::VaultBId),
helper_id_constructor(IdEnum::TokenLPDefinitionId),
helper_id_constructor(IdEnum::UserTokenAId),
helper_id_constructor(IdEnum::UserTokenBId),
helper_id_constructor(IdEnum::UserTokenLPId),
],
2025-12-09 14:42:58 -05:00
vec![0, 0],
2025-11-25 23:06:47 -05:00
instruction,
)
.unwrap();
2025-11-25 23:06:47 -05:00
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[
2025-12-07 20:34:26 -05:00
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenAKey),
&helper_private_keys_constructor(PrivateKeysEnum::UserTokenBKey),
],
2025-11-25 23:06:47 -05:00
);
2025-11-25 23:06:47 -05:00
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
2025-12-07 20:34:26 -05:00
let pool_post = state.get_account_by_id(&helper_id_constructor(IdEnum::PoolDefinitionId));
let vault_a_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultAId));
let vault_b_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultBId));
2025-12-09 14:42:58 -05:00
let token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::TokenLPDefinitionId));
let user_token_a_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenAId));
let user_token_b_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenBId));
let user_token_lp_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenLPId));
2025-12-07 20:34:26 -05:00
let expected_pool = helper_account_constructor(AccountsEnum::PoolDefinitionAdd);
let expected_vault_a = helper_account_constructor(AccountsEnum::VaultAAdd);
let expected_vault_b = helper_account_constructor(AccountsEnum::VaultBAdd);
let expected_token_lp = helper_account_constructor(AccountsEnum::TokenLPDefinitionAdd);
let expected_user_token_a = helper_account_constructor(AccountsEnum::UserTokenAHoldingAdd);
let expected_user_token_b = helper_account_constructor(AccountsEnum::UserTokenBHoldingAdd);
2025-12-09 14:42:58 -05:00
let expected_user_token_lp =
helper_account_constructor(AccountsEnum::UserTokenLPHoldingAdd);
2025-12-06 14:52:18 -05:00
assert!(pool_post == expected_pool);
assert!(vault_a_post == expected_vault_a);
assert!(vault_b_post == expected_vault_b);
2025-12-06 14:52:18 -05:00
assert!(token_lp_post == expected_token_lp);
assert!(user_token_a_post == expected_user_token_a);
assert!(user_token_b_post == expected_user_token_b);
assert!(user_token_lp_post == expected_user_token_lp);
}
2025-12-06 14:52:18 -05:00
#[test]
fn test_simple_amm_swap_1() {
2025-12-06 14:52:18 -05:00
let mut state = amm_state_constructor();
2025-11-25 23:06:47 -05:00
let mut instruction: Vec<u8> = Vec::new();
instruction.push(1);
2025-12-09 14:42:58 -05:00
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::SwapAmountIn).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::SwapMinAmountOUt).to_le_bytes(),
);
instruction
.extend_from_slice(&helper_id_constructor(IdEnum::TokenBDefinitionId).to_bytes());
2025-11-25 23:06:47 -05:00
let message = public_transaction::Message::try_new(
Program::amm().id(),
vec![
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::VaultAId),
helper_id_constructor(IdEnum::VaultBId),
helper_id_constructor(IdEnum::UserTokenAId),
helper_id_constructor(IdEnum::UserTokenBId),
2025-11-25 23:06:47 -05:00
],
2025-12-08 22:05:51 -05:00
vec![0],
2025-11-25 23:06:47 -05:00
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
2025-12-09 14:42:58 -05:00
&[&helper_private_keys_constructor(
PrivateKeysEnum::UserTokenBKey,
)],
2025-11-25 23:06:47 -05:00
);
2025-11-25 23:06:47 -05:00
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
2025-12-09 14:42:58 -05:00
2025-12-07 20:34:26 -05:00
let pool_post = state.get_account_by_id(&helper_id_constructor(IdEnum::PoolDefinitionId));
let vault_a_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultAId));
let vault_b_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultBId));
2025-12-09 14:42:58 -05:00
let user_token_a_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenAId));
let user_token_b_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenBId));
2025-11-25 23:06:47 -05:00
2025-12-07 20:34:26 -05:00
let expected_pool = helper_account_constructor(AccountsEnum::PoolDefinitionSwap1);
let expected_vault_a = helper_account_constructor(AccountsEnum::VaultASwap1);
let expected_vault_b = helper_account_constructor(AccountsEnum::VaultBSwap1);
2025-12-09 14:42:58 -05:00
let expected_user_token_a =
helper_account_constructor(AccountsEnum::UserTokenAHoldingSwap1);
let expected_user_token_b =
helper_account_constructor(AccountsEnum::UserTokenBHoldingSwap1);
2025-11-25 23:06:47 -05:00
2025-12-06 14:52:18 -05:00
assert!(pool_post == expected_pool);
assert!(vault_a_post == expected_vault_a);
assert!(vault_b_post == expected_vault_b);
2025-12-06 14:52:18 -05:00
assert!(user_token_a_post == expected_user_token_a);
assert!(user_token_b_post == expected_user_token_b);
}
2025-12-06 14:52:18 -05:00
#[test]
fn test_simple_amm_swap_2() {
2025-12-06 14:52:18 -05:00
let mut state = amm_state_constructor();
2025-11-25 23:06:47 -05:00
let mut instruction: Vec<u8> = Vec::new();
instruction.push(1);
2025-12-09 14:42:58 -05:00
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::SwapAmountIn).to_le_bytes(),
);
instruction.extend_from_slice(
&helper_balances_constructor(BalancesEnum::SwapMinAmountOUt).to_le_bytes(),
);
instruction
.extend_from_slice(&helper_id_constructor(IdEnum::TokenADefinitionId).to_bytes());
2025-11-25 23:06:47 -05:00
let message = public_transaction::Message::try_new(
Program::amm().id(),
vec![
2025-12-07 20:34:26 -05:00
helper_id_constructor(IdEnum::PoolDefinitionId),
helper_id_constructor(IdEnum::VaultAId),
helper_id_constructor(IdEnum::VaultBId),
helper_id_constructor(IdEnum::UserTokenAId),
helper_id_constructor(IdEnum::UserTokenBId),
],
2025-12-08 22:05:51 -05:00
vec![0],
2025-11-25 23:06:47 -05:00
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
2025-12-09 14:42:58 -05:00
&[&helper_private_keys_constructor(
PrivateKeysEnum::UserTokenAKey,
)],
2025-11-25 23:06:47 -05:00
);
2025-11-25 23:06:47 -05:00
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
2025-12-09 14:42:58 -05:00
2025-12-07 20:34:26 -05:00
let pool_post = state.get_account_by_id(&helper_id_constructor(IdEnum::PoolDefinitionId));
let vault_a_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultAId));
let vault_b_post = state.get_account_by_id(&helper_id_constructor(IdEnum::VaultBId));
2025-12-09 14:42:58 -05:00
let user_token_a_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenAId));
let user_token_b_post =
state.get_account_by_id(&helper_id_constructor(IdEnum::UserTokenBId));
2025-11-25 23:06:47 -05:00
2025-12-07 20:34:26 -05:00
let expected_pool = helper_account_constructor(AccountsEnum::PoolDefinitionSwap2);
let expected_vault_a = helper_account_constructor(AccountsEnum::VaultASwap2);
let expected_vault_b = helper_account_constructor(AccountsEnum::VaultBSwap2);
2025-12-09 14:42:58 -05:00
let expected_user_token_a =
helper_account_constructor(AccountsEnum::UserTokenAHoldingSwap2);
let expected_user_token_b =
helper_account_constructor(AccountsEnum::UserTokenBHoldingSwap2);
2025-12-06 14:52:18 -05:00
assert!(pool_post == expected_pool);
assert!(vault_a_post == expected_vault_a);
assert!(vault_b_post == expected_vault_b);
2025-12-06 14:52:18 -05:00
assert!(user_token_a_post == expected_user_token_a);
assert!(user_token_b_post == expected_user_token_b);
}
2025-12-06 14:52:18 -05:00
#[test]
fn test_execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() {
let chain_caller = Program::chain_caller();
let pda_seed = PdaSeed::new([37; 32]);
let from = AccountId::from((&chain_caller.id(), &pda_seed));
let to = AccountId::new([2; 32]);
let initial_balance = 1000;
let initial_data = [(from, initial_balance), (to, 0)];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let amount: u128 = 58;
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
amount,
Program::authenticated_transfer_program().id(),
1,
Some(pda_seed),
);
let expected_to_post = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: amount, // The `chain_caller` chains the program twice
..Account::default()
};
let message = public_transaction::Message::try_new(
chain_caller.id(),
vec![to, from], // The chain_caller program permutes the account order in the chain
// call
vec![],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let from_post = state.get_account_by_id(&from);
let to_post = state.get_account_by_id(&to);
assert_eq!(from_post.balance, initial_balance - amount);
assert_eq!(to_post, expected_to_post);
}
2025-12-03 17:06:09 -03:00
#[test]
fn test_claiming_mechanism_within_chain_call() {
// This test calls the authenticated transfer program through the chain_caller program.
// The transfer is made from an initialized sender to an uninitialized recipient. And
// it is expected that the recipient account is claimed by the authenticated transfer
// program and not the chained_caller program.
let chain_caller = Program::chain_caller();
let auth_transfer = Program::authenticated_transfer_program();
let key = PrivateKey::try_new([1; 32]).unwrap();
let account_id = AccountId::from(&PublicKey::new_from_private_key(&key));
let initial_balance = 100;
let initial_data = [(account_id, initial_balance)];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let from = account_id;
let from_key = key;
let to = AccountId::new([2; 32]);
let amount: u128 = 37;
// Check the recipient is an uninitialized account
assert_eq!(state.get_account_by_id(&to), Account::default());
let expected_to_post = Account {
// The expected program owner is the authenticated transfer program
program_owner: auth_transfer.id(),
balance: amount,
..Account::default()
};
// The transaction executes the chain_caller program, which internally calls the
// authenticated_transfer program
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
amount,
Program::authenticated_transfer_program().id(),
1,
None,
);
2025-12-03 17:06:09 -03:00
let message = public_transaction::Message::try_new(
chain_caller.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);
state.transition_from_public_transaction(&tx).unwrap();
let from_post = state.get_account_by_id(&from);
let to_post = state.get_account_by_id(&to);
assert_eq!(from_post.balance, initial_balance - amount);
assert_eq!(to_post, expected_to_post);
}
2025-11-28 11:10:00 -03:00
2025-11-20 01:40:05 -03:00
#[test]
fn test_private_chained_call() {
2025-11-22 17:48:29 -03:00
// Arrange
2025-11-20 19:25:56 -03:00
let chain_caller = Program::chain_caller();
let auth_transfers = Program::authenticated_transfer_program();
2025-11-20 01:40:05 -03:00
let from_keys = test_private_account_keys_1();
2025-11-20 19:25:56 -03:00
let to_keys = test_private_account_keys_2();
2025-11-20 01:40:05 -03:00
let initial_balance = 100;
let from_account = AccountWithMetadata::new(
Account {
2025-11-20 19:25:56 -03:00
program_owner: auth_transfers.id(),
2025-11-20 01:40:05 -03:00
balance: initial_balance,
..Account::default()
},
true,
&from_keys.npk(),
);
2025-11-20 19:25:56 -03:00
let to_account = AccountWithMetadata::new(
Account {
program_owner: auth_transfers.id(),
..Account::default()
},
true,
&to_keys.npk(),
);
2025-11-22 17:48:29 -03:00
2025-11-20 01:40:05 -03:00
let from_commitment = Commitment::new(&from_keys.npk(), &from_account.account);
2025-11-20 19:25:56 -03:00
let to_commitment = Commitment::new(&to_keys.npk(), &to_account.account);
2025-11-22 17:48:29 -03:00
let mut state = V02State::new_with_genesis_accounts(
2025-11-20 19:25:56 -03:00
&[],
&[from_commitment.clone(), to_commitment.clone()],
)
.with_test_programs();
2025-11-20 01:40:05 -03:00
let amount: u128 = 37;
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
amount,
Program::authenticated_transfer_program().id(),
1,
None,
);
2025-11-20 01:40:05 -03:00
let from_esk = [3; 32];
let from_ss = SharedSecretKey::new(&from_esk, &from_keys.ivk());
2025-11-22 17:48:29 -03:00
let from_epk = EphemeralPublicKey::from_scalar(from_esk);
2025-11-20 01:40:05 -03:00
2025-11-20 19:25:56 -03:00
let to_esk = [3; 32];
2025-11-20 01:40:05 -03:00
let to_ss = SharedSecretKey::new(&to_esk, &to_keys.ivk());
2025-11-22 17:48:29 -03:00
let to_epk = EphemeralPublicKey::from_scalar(to_esk);
2025-11-25 15:03:17 -03:00
2025-11-20 19:25:56 -03:00
let mut dependencies = HashMap::new();
2025-11-20 01:40:05 -03:00
2025-11-20 19:25:56 -03:00
dependencies.insert(auth_transfers.id(), auth_transfers);
let program_with_deps = ProgramWithDependencies::new(chain_caller, dependencies);
2025-11-22 17:48:29 -03:00
let from_new_nonce = 0xdeadbeef1;
let to_new_nonce = 0xdeadbeef2;
2025-11-22 17:48:29 -03:00
let from_expected_post = Account {
balance: initial_balance - amount,
nonce: from_new_nonce,
..from_account.account.clone()
};
let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post);
let to_expected_post = Account {
balance: amount,
nonce: to_new_nonce,
..to_account.account.clone()
};
let to_expected_commitment = Commitment::new(&to_keys.npk(), &to_expected_post);
// Act
let (output, proof) = execute_and_prove(
2025-11-20 19:25:56 -03:00
&[to_account, from_account],
2025-11-20 01:40:05 -03:00
&Program::serialize_instruction(instruction).unwrap(),
2025-11-20 19:25:56 -03:00
&[1, 1],
&[from_new_nonce, to_new_nonce],
&[(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)],
2025-11-20 19:25:56 -03:00
&[
(
from_keys.nsk,
state.get_proof_for_commitment(&from_commitment).unwrap(),
),
(
to_keys.nsk,
state.get_proof_for_commitment(&to_commitment).unwrap(),
),
2025-11-20 19:25:56 -03:00
],
&program_with_deps,
2025-11-20 01:40:05 -03:00
)
.unwrap();
2025-11-20 19:25:56 -03:00
2025-11-22 17:48:29 -03:00
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![
(to_keys.npk(), to_keys.ivk(), to_epk),
(from_keys.npk(), from_keys.ivk(), from_epk),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
state
.transition_from_privacy_preserving_transaction(&transaction)
.unwrap();
// Assert
assert!(
state
.get_proof_for_commitment(&from_expected_commitment)
.is_some()
);
assert!(
state
.get_proof_for_commitment(&to_expected_commitment)
.is_some()
);
}
2025-11-28 11:10:00 -03:00
#[test]
fn test_pda_mechanism_with_pinata_token_program() {
let pinata_token = Program::pinata_token();
let token = Program::token();
let pinata_definition_id = AccountId::new([1; 32]);
let pinata_token_definition_id = AccountId::new([2; 32]);
// Total supply of pinata token will be in an account under a PDA.
let pinata_token_holding_id = AccountId::from((&pinata_token.id(), &PdaSeed::new([0; 32])));
let winner_token_holding_id = AccountId::new([3; 32]);
let mut expected_winner_account_data = [0; 49];
expected_winner_account_data[0] = 1;
expected_winner_account_data[1..33].copy_from_slice(pinata_token_definition_id.value());
expected_winner_account_data[33..].copy_from_slice(&150u128.to_le_bytes());
let expected_winner_token_holding_post = Account {
program_owner: token.id(),
data: expected_winner_account_data.to_vec().try_into().unwrap(),
2025-11-28 11:10:00 -03:00
..Account::default()
};
let mut state = V02State::new_with_genesis_accounts(&[], &[]);
state.add_pinata_token_program(pinata_definition_id);
// Execution of the token program to create new token for the pinata token
// definition and supply accounts
let total_supply: u128 = 10_000_000;
// instruction: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction: [u8; 23] = [0; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(b"PINATA");
let message = public_transaction::Message::try_new(
token.id(),
vec![pinata_token_definition_id, pinata_token_holding_id],
vec![],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
// Execution of the token program transfer just to initialize the winner token account
let mut instruction: [u8; 23] = [0; 23];
instruction[0] = 2;
let message = public_transaction::Message::try_new(
token.id(),
vec![pinata_token_definition_id, winner_token_holding_id],
vec![],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
// Submit a solution to the pinata program to claim the prize
let solution: u128 = 989106;
let message = public_transaction::Message::try_new(
pinata_token.id(),
vec![
pinata_definition_id,
pinata_token_holding_id,
winner_token_holding_id,
],
vec![],
solution,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let winner_token_holding_post = state.get_account_by_id(&winner_token_holding_id);
assert_eq!(
winner_token_holding_post,
expected_winner_token_holding_post
);
2025-11-28 11:10:00 -03:00
}
#[test]
fn test_claiming_mechanism_cannot_claim_initialied_accounts() {
let claimer = Program::claimer();
let mut state = V02State::new_with_genesis_accounts(&[], &[]).with_test_programs();
let account_id = AccountId::new([2; 32]);
// Insert an account with non-default program owner
state.force_insert_account(
account_id,
Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
..Account::default()
},
);
let message =
public_transaction::Message::try_new(claimer.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)))
}
/// This test ensures that even if a malicious program tries to perform overflow of balances
/// it will not be able to break the balance validation.
#[test]
fn test_malicious_program_cannot_break_balance_validation() {
let sender_key = PrivateKey::try_new([37; 32]).unwrap();
let sender_id = AccountId::from(&PublicKey::new_from_private_key(&sender_key));
let sender_init_balance: u128 = 10;
let recipient_key = PrivateKey::try_new([42; 32]).unwrap();
let recipient_id = AccountId::from(&PublicKey::new_from_private_key(&recipient_key));
let recipient_init_balance: u128 = 10;
let mut state = V02State::new_with_genesis_accounts(
&[
(sender_id, sender_init_balance),
(recipient_id, recipient_init_balance),
],
&[],
);
state.insert_program(Program::modified_transfer_program());
let balance_to_move: u128 = 4;
let sender =
AccountWithMetadata::new(state.get_account_by_id(&sender_id.clone()), true, sender_id);
let sender_nonce = sender.account.nonce;
let _recipient =
AccountWithMetadata::new(state.get_account_by_id(&recipient_id), false, sender_id);
let message = public_transaction::Message::try_new(
Program::modified_transfer_program().id(),
vec![sender_id, recipient_id],
vec![sender_nonce],
balance_to_move,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]);
let tx = PublicTransaction::new(message, witness_set);
let res = state.transition_from_public_transaction(&tx);
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
let sender_post = state.get_account_by_id(&sender_id);
let recipient_post = state.get_account_by_id(&recipient_id);
let expected_sender_post = {
let mut this = state.get_account_by_id(&sender_id);
this.balance = sender_init_balance;
this.nonce = 0;
this
};
let expected_recipient_post = {
let mut this = state.get_account_by_id(&sender_id);
this.balance = recipient_init_balance;
this.nonce = 0;
this
};
assert!(expected_sender_post == sender_post);
assert!(expected_recipient_post == recipient_post);
}
2025-08-13 01:33:11 -03:00
}