2025-08-06 20:05:04 -03:00
|
|
|
use crate::{
|
2025-08-10 00:53:53 -03:00
|
|
|
address::Address, error::NssaError, program::Program, public_transaction::PublicTransaction,
|
2025-08-06 20:05:04 -03:00
|
|
|
};
|
|
|
|
|
use nssa_core::{
|
|
|
|
|
account::{Account, AccountWithMetadata},
|
2025-08-10 09:57:10 -03:00
|
|
|
program::{ProgramId, validate_execution},
|
2025-08-06 20:05:04 -03:00
|
|
|
};
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
|
2025-08-07 15:19:06 -03:00
|
|
|
pub struct V01State {
|
2025-08-06 20:05:04 -03:00
|
|
|
public_state: HashMap<Address, Account>,
|
2025-08-08 16:19:50 -03:00
|
|
|
builtin_programs: HashMap<ProgramId, Program>,
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl V01State {
|
2025-08-07 15:19:06 -03:00
|
|
|
pub fn new_with_genesis_accounts(initial_data: &[([u8; 32], u128)]) -> 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()
|
2025-08-07 15:19:06 -03:00
|
|
|
.map(|(address_value, 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()
|
|
|
|
|
};
|
2025-08-07 15:19:06 -03:00
|
|
|
let address = Address::new(address_value);
|
|
|
|
|
(address, account)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2025-08-08 16:19:50 -03:00
|
|
|
|
2025-08-10 09:57:10 -03:00
|
|
|
let mut this = Self {
|
2025-08-08 16:19:50 -03:00
|
|
|
public_state,
|
2025-08-10 09:57:10 -03:00
|
|
|
builtin_programs: HashMap::new(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.insert_program(Program::authenticated_transfer_program());
|
|
|
|
|
|
|
|
|
|
this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn insert_program(&mut self, program: Program) {
|
|
|
|
|
self.builtin_programs.insert(program.id(), program);
|
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 = self.execute_and_verify_public_transaction(tx)?;
|
2025-08-06 20:05:04 -03:00
|
|
|
|
|
|
|
|
for (address, post) in state_diff.into_iter() {
|
|
|
|
|
let current_account = self.get_account_by_address_mut(address);
|
|
|
|
|
*current_account = post;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for address in tx.signer_addresses() {
|
|
|
|
|
let current_account = self.get_account_by_address_mut(address);
|
|
|
|
|
current_account.nonce += 1;
|
|
|
|
|
}
|
2025-08-09 20:35:44 -03:00
|
|
|
|
2025-08-06 20:05:04 -03:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account {
|
2025-08-09 19:20:19 -03:00
|
|
|
self.public_state.entry(address).or_default()
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
|
2025-08-07 15:19:06 -03:00
|
|
|
pub fn get_account_by_address(&self, address: &Address) -> Account {
|
2025-08-06 20:05:04 -03:00
|
|
|
self.public_state
|
|
|
|
|
.get(address)
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or(Account::default())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn execute_and_verify_public_transaction(
|
|
|
|
|
&mut self,
|
|
|
|
|
tx: &PublicTransaction,
|
2025-08-09 19:49:07 -03:00
|
|
|
) -> Result<HashMap<Address, Account>, NssaError> {
|
2025-08-06 20:05:04 -03:00
|
|
|
let message = tx.message();
|
|
|
|
|
let witness_set = tx.witness_set();
|
|
|
|
|
|
|
|
|
|
// All addresses must be different
|
|
|
|
|
if message.addresses.iter().collect::<HashSet<_>>().len() != message.addresses.len() {
|
2025-08-09 19:49:07 -03:00
|
|
|
return Err(NssaError::InvalidInput(
|
|
|
|
|
"Duplicate addresses found in message".into(),
|
|
|
|
|
));
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if message.nonces.len() != witness_set.signatures_and_public_keys.len() {
|
2025-08-09 19:49:07 -03:00
|
|
|
return Err(NssaError::InvalidInput(
|
|
|
|
|
"Mismatch between number of nonces and signatures/public keys".into(),
|
|
|
|
|
));
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut authorized_addresses = Vec::new();
|
2025-08-09 20:35:44 -03:00
|
|
|
for ((signature, public_key), nonce) in witness_set.iter_signatures().zip(&message.nonces) {
|
2025-08-06 20:05:04 -03:00
|
|
|
// Check the signature is valid
|
|
|
|
|
if !signature.is_valid_for(message, public_key) {
|
2025-08-09 19:49:07 -03:00
|
|
|
return Err(NssaError::InvalidInput(
|
|
|
|
|
"Invalid signature for given message and public key".into(),
|
|
|
|
|
));
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check the nonce corresponds to the current nonce on the public state.
|
|
|
|
|
let address = Address::from_public_key(public_key);
|
|
|
|
|
let current_nonce = self.get_account_by_address(&address).nonce;
|
|
|
|
|
if current_nonce != *nonce {
|
2025-08-09 19:49:07 -03:00
|
|
|
return Err(NssaError::InvalidInput("Nonce mismatch".into()));
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
authorized_addresses.push(address);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build pre_states for execution
|
|
|
|
|
let pre_states: Vec<_> = message
|
|
|
|
|
.addresses
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|address| AccountWithMetadata {
|
|
|
|
|
account: self.get_account_by_address(address),
|
|
|
|
|
is_authorized: authorized_addresses.contains(address),
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
// Check the `program_id` corresponds to a built-in program
|
|
|
|
|
// Only allowed program so far is the authenticated transfer program
|
2025-08-08 16:19:50 -03:00
|
|
|
let Some(program) = self.builtin_programs.get(&message.program_id) else {
|
2025-08-09 19:49:07 -03:00
|
|
|
return Err(NssaError::InvalidInput("Unknown program".into()));
|
2025-08-08 16:19:50 -03:00
|
|
|
};
|
2025-08-06 20:05:04 -03:00
|
|
|
|
|
|
|
|
// // Execute program
|
2025-08-09 20:16:18 -03:00
|
|
|
let post_states = program.execute(&pre_states, message.instruction_data)?;
|
2025-08-06 20:05:04 -03:00
|
|
|
|
|
|
|
|
// Verify execution corresponds to a well-behaved program.
|
2025-08-10 09:57:10 -03:00
|
|
|
// See the # Programs section for the definition of the `validate_execution` method.
|
|
|
|
|
if !validate_execution(&pre_states, &post_states, message.program_id) {
|
2025-08-09 19:49:07 -03:00
|
|
|
return Err(NssaError::InvalidProgramBehavior);
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
|
2025-08-09 19:20:19 -03:00
|
|
|
Ok(message.addresses.iter().cloned().zip(post_states).collect())
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 00:53:53 -03:00
|
|
|
// Test utils
|
2025-08-06 20:05:04 -03:00
|
|
|
#[cfg(test)]
|
2025-08-10 00:53:53 -03:00
|
|
|
impl V01State {
|
|
|
|
|
/// Include test programs in the builtin programs map
|
|
|
|
|
pub fn with_test_programs(mut self) -> Self {
|
2025-08-10 09:57:10 -03:00
|
|
|
self.insert_program(Program::nonce_changer_program());
|
2025-08-10 10:09:23 -03:00
|
|
|
self.insert_program(Program::extra_output_program());
|
|
|
|
|
self.insert_program(Program::missing_output_program());
|
2025-08-10 10:19:59 -03:00
|
|
|
self.insert_program(Program::program_owner_changer());
|
2025-08-10 11:02:59 -03:00
|
|
|
self.insert_program(Program::simple_balance_transfer());
|
2025-08-10 11:17:15 -03:00
|
|
|
self.insert_program(Program::data_changer());
|
2025-08-10 11:58:25 -03:00
|
|
|
self.insert_program(Program::minter());
|
|
|
|
|
self.insert_program(Program::burner());
|
2025-08-10 10:19:59 -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],
|
|
|
|
|
..Account::default()
|
|
|
|
|
};
|
|
|
|
|
self.public_state.insert(
|
|
|
|
|
Address::new([255; 32]),
|
|
|
|
|
account_with_default_values_except_balance,
|
|
|
|
|
);
|
|
|
|
|
self.public_state.insert(
|
|
|
|
|
Address::new([254; 32]),
|
|
|
|
|
account_with_default_values_except_nonce,
|
|
|
|
|
);
|
|
|
|
|
self.public_state.insert(
|
|
|
|
|
Address::new([253; 32]),
|
|
|
|
|
account_with_default_values_except_data,
|
|
|
|
|
);
|
2025-08-10 00:53:53 -03:00
|
|
|
self
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|
2025-08-10 11:58:25 -03:00
|
|
|
|
|
|
|
|
pub fn with_account_owned_by_burner_program(mut self) -> Self {
|
|
|
|
|
let account = Account {
|
|
|
|
|
program_owner: Program::burner().id(),
|
|
|
|
|
balance: 100,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
self.public_state.insert(Address::new([252; 32]), account);
|
|
|
|
|
self
|
|
|
|
|
}
|
2025-08-06 20:05:04 -03:00
|
|
|
}
|