lssa/nssa/core/src/program.rs

151 lines
4.6 KiB
Rust
Raw Normal View History

use std::collections::HashSet;
2025-09-11 15:49:54 -03:00
use crate::account::{Account, AccountWithMetadata};
2025-08-10 18:51:55 -03:00
use risc0_zkvm::serde::Deserializer;
use risc0_zkvm::{DeserializeOwned, guest::env};
2025-08-14 14:09:04 -03:00
use serde::{Deserialize, Serialize};
2025-08-06 20:05:04 -03:00
pub type ProgramId = [u32; 8];
2025-08-10 18:51:55 -03:00
pub type InstructionData = Vec<u32>;
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
2025-11-07 20:42:00 -03:00
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
2025-08-06 20:05:04 -03:00
2025-08-14 14:30:04 -03:00
pub struct ProgramInput<T> {
pub pre_states: Vec<AccountWithMetadata>,
pub instruction: T,
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct ChainedCall {
pub program_id: ProgramId,
pub instruction_data: InstructionData,
pub account_indices: Vec<usize>,
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
2025-08-14 14:09:04 -03:00
pub struct ProgramOutput {
2025-11-18 01:38:47 -03:00
pub instruction_data: InstructionData,
2025-08-14 14:09:04 -03:00
pub pre_states: Vec<AccountWithMetadata>,
pub post_states: Vec<Account>,
2025-10-29 15:34:11 -03:00
pub chained_call: Option<ChainedCall>,
2025-08-14 14:09:04 -03:00
}
2025-11-18 01:38:47 -03:00
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionData) {
2025-08-10 18:51:55 -03:00
let pre_states: Vec<AccountWithMetadata> = env::read();
2025-09-08 19:29:56 -03:00
let instruction_words: InstructionData = env::read();
let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap();
2025-11-18 01:38:47 -03:00
(
ProgramInput {
pre_states,
instruction,
},
instruction_words,
)
2025-08-10 18:51:55 -03:00
}
2025-08-14 14:09:04 -03:00
2025-11-18 01:38:47 -03:00
pub fn write_nssa_outputs(
instruction_data: InstructionData,
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<Account>,
) {
2025-08-14 14:09:04 -03:00
let output = ProgramOutput {
2025-11-18 01:38:47 -03:00
instruction_data,
2025-08-14 14:09:04 -03:00
pre_states,
post_states,
2025-10-29 15:34:11 -03:00
chained_call: None,
};
env::commit(&output);
}
pub fn write_nssa_outputs_with_chained_call(
2025-11-18 01:38:47 -03:00
instruction_data: InstructionData,
2025-10-29 15:34:11 -03:00
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<Account>,
chained_call: Option<ChainedCall>,
) {
let output = ProgramOutput {
2025-11-18 01:38:47 -03:00
instruction_data,
2025-10-29 15:34:11 -03:00
pre_states,
post_states,
chained_call,
2025-08-14 14:09:04 -03:00
};
env::commit(&output);
}
2025-08-06 20:05:04 -03:00
/// Validates well-behaved program execution
///
/// # Parameters
/// - `pre_states`: The list of input accounts, each annotated with authorization metadata.
/// - `post_states`: The list of resulting accounts after executing the program logic.
/// - `executing_program_id`: The identifier of the program that was executed.
2025-08-10 09:57:10 -03:00
pub fn validate_execution(
2025-08-06 20:05:04 -03:00
pre_states: &[AccountWithMetadata],
post_states: &[Account],
executing_program_id: ProgramId,
2025-08-09 19:49:07 -03:00
) -> bool {
// 1. Check account ids are all different
if !validate_uniqueness_of_account_ids(pre_states) {
return false;
}
// 2. Lengths must match
2025-08-06 20:05:04 -03:00
if pre_states.len() != post_states.len() {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
for (pre, post) in pre_states.iter().zip(post_states) {
// 3. Nonce must remain unchanged
2025-08-06 20:05:04 -03:00
if pre.account.nonce != post.nonce {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
// 4. Program ownership changes are not allowed
2025-09-15 16:22:48 -03:00
if pre.account.program_owner != post.program_owner {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
2025-09-15 16:22:48 -03:00
let account_program_owner = pre.account.program_owner;
// 5. Decreasing balance only allowed if owned by executing program
2025-09-15 16:22:48 -03:00
if post.balance < pre.account.balance && account_program_owner != executing_program_id {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
// 6. Data changes only allowed if owned by executing program or if account pre state has
2025-09-15 16:22:48 -03:00
// default values
2025-08-06 20:05:04 -03:00
if pre.account.data != post.data
2025-09-15 16:22:48 -03:00
&& pre.account != Account::default()
&& account_program_owner != executing_program_id
2025-08-06 20:05:04 -03:00
{
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
// 7. If a post state has default program owner, the pre state must have been a default account
if post.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
return false;
}
2025-08-06 20:05:04 -03:00
}
// 8. Total balance is preserved
2025-08-06 20:05:04 -03:00
let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum();
let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum();
if total_balance_pre_states != total_balance_post_states {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
2025-08-09 19:49:07 -03:00
true
2025-08-06 20:05:04 -03:00
}
fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> bool {
let number_of_accounts = pre_states.len();
let number_of_account_ids = pre_states
.iter()
2025-11-22 17:48:29 -03:00
.map(|account| &account.account_id)
.collect::<HashSet<_>>()
.len();
number_of_accounts == number_of_account_ids
}