lssa/nssa/src/program.rs

287 lines
9.2 KiB
Rust
Raw Normal View History

2025-08-09 20:25:58 -03:00
use nssa_core::{
2025-10-29 15:34:11 -03:00
account::AccountWithMetadata,
2025-08-27 16:24:20 -03:00
program::{InstructionData, ProgramId, ProgramOutput},
2025-08-09 20:25:58 -03:00
};
2025-08-27 18:23:56 -03:00
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
2025-08-10 18:59:29 -03:00
use serde::Serialize;
2025-08-09 20:25:58 -03:00
2025-11-26 00:27:20 +03:00
use crate::{
error::NssaError,
program_methods::{AMM_ELF, AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF},
2025-11-26 00:27:20 +03:00
};
2025-08-09 20:25:58 -03:00
/// Maximum number of cycles for a public execution.
/// TODO: Make this variable when fees are implemented
const MAX_NUM_CYCLES_PUBLIC_EXECUTION: u64 = 1024 * 1024 * 32; // 32M cycles
#[derive(Clone, Debug, PartialEq, Eq)]
2025-08-09 20:25:58 -03:00
pub struct Program {
2025-08-13 01:33:11 -03:00
id: ProgramId,
elf: Vec<u8>,
2025-08-09 20:25:58 -03:00
}
impl Program {
pub fn new(bytecode: Vec<u8>) -> Result<Self, NssaError> {
let binary = risc0_binfmt::ProgramBinary::decode(&bytecode)
.map_err(|_| NssaError::InvalidProgramBytecode)?;
let id = binary
.compute_image_id()
.map_err(|_| NssaError::InvalidProgramBytecode)?
.into();
Ok(Self { elf: bytecode, id })
}
2025-08-09 20:25:58 -03:00
pub fn id(&self) -> ProgramId {
self.id
}
2025-08-09 20:35:44 -03:00
pub fn elf(&self) -> &[u8] {
&self.elf
2025-08-18 19:57:21 -03:00
}
2025-08-10 19:53:05 -03:00
pub fn serialize_instruction<T: Serialize>(
2025-08-11 12:07:30 -03:00
instruction: T,
2025-08-10 18:51:55 -03:00
) -> Result<InstructionData, NssaError> {
2025-08-11 19:14:12 -03:00
to_vec(&instruction).map_err(|e| NssaError::InstructionSerializationError(e.to_string()))
2025-08-10 18:51:55 -03:00
}
2025-08-09 20:25:58 -03:00
pub(crate) fn execute(
&self,
pre_states: &[AccountWithMetadata],
2025-08-10 18:51:55 -03:00
instruction_data: &InstructionData,
) -> Result<ProgramOutput, NssaError> {
2025-08-09 20:25:58 -03:00
// Write inputs to the program
let mut env_builder = ExecutorEnv::builder();
env_builder.session_limit(Some(MAX_NUM_CYCLES_PUBLIC_EXECUTION));
2025-09-09 17:03:58 -03:00
Self::write_inputs(pre_states, instruction_data, &mut env_builder)?;
2025-08-09 20:25:58 -03:00
let env = env_builder.build().unwrap();
// Execute the program (without proving)
let executor = default_executor();
let session_info = executor
.execute(env, self.elf())
2025-08-09 20:25:58 -03:00
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
// Get outputs
let program_output = session_info
2025-08-09 20:25:58 -03:00
.journal
.decode()
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
Ok(program_output)
2025-08-14 13:28:23 -03:00
}
2025-08-09 20:25:58 -03:00
/// Writes inputs to `env_builder` in the order expected by the programs
2025-08-18 19:57:21 -03:00
pub(crate) fn write_inputs(
2025-08-09 20:25:58 -03:00
pre_states: &[AccountWithMetadata],
2025-08-10 18:51:55 -03:00
instruction_data: &[u32],
2025-08-09 20:25:58 -03:00
env_builder: &mut ExecutorEnvBuilder,
) -> Result<(), NssaError> {
let pre_states = pre_states.to_vec();
env_builder
2025-09-09 17:03:58 -03:00
.write(&(pre_states, instruction_data))
2025-08-14 13:28:23 -03:00
.map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?;
2025-08-09 20:25:58 -03:00
Ok(())
}
2025-08-10 00:53:53 -03:00
pub fn authenticated_transfer_program() -> Self {
// This unwrap won't panic since the `AUTHENTICATED_TRANSFER_ELF` comes from risc0 build of
// `program_methods`
Self::new(AUTHENTICATED_TRANSFER_ELF.to_vec()).unwrap()
2025-08-10 00:53:53 -03:00
}
2025-09-12 15:18:25 -03:00
pub fn token() -> Self {
// This unwrap won't panic since the `TOKEN_ELF` comes from risc0 build of
// `program_methods`
Self::new(TOKEN_ELF.to_vec()).unwrap()
2025-09-12 15:18:25 -03:00
}
2025-11-14 20:59:42 -05:00
pub fn amm() -> Self {
// This unwrap wont panic since the `AMM_ELF` comes from risc0 build of
// `program_methods`
2025-12-02 15:20:16 -05:00
Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program")
2025-11-14 20:59:42 -05:00
}
2025-08-10 00:53:53 -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-09-04 17:05:12 -03:00
impl Program {
2025-09-04 12:44:22 -03:00
pub fn pinata() -> Self {
// This unwrap won't panic since the `PINATA_ELF` comes from risc0 build of
// `program_methods`
Self::new(PINATA_ELF.to_vec()).unwrap()
2025-09-04 12:44:22 -03:00
}
2025-11-28 11:10:00 -03:00
pub fn pinata_token() -> Self {
use crate::program_methods::PINATA_TOKEN_ELF;
2025-12-02 16:27:22 -03:00
Self::new(PINATA_TOKEN_ELF.to_vec()).expect("Piñata program must be a valid R0BF file")
2025-11-28 11:10:00 -03:00
}
2025-08-10 00:53:53 -03:00
}
2025-08-13 01:33:11 -03:00
#[cfg(test)]
mod tests {
2025-10-29 15:34:11 -03:00
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
2025-08-13 01:33:11 -03:00
2025-11-26 00:27:20 +03:00
use crate::{
program::Program,
program_methods::{
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID,
TOKEN_ELF, TOKEN_ID,
},
};
2025-08-13 01:33:11 -03:00
impl Program {
/// A program that changes the nonce of an account
pub fn nonce_changer_program() -> Self {
use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID};
Program {
id: NONCE_CHANGER_ID,
elf: NONCE_CHANGER_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
/// A program that produces more output accounts than the inputs it received
pub fn extra_output_program() -> Self {
use test_program_methods::{EXTRA_OUTPUT_ELF, EXTRA_OUTPUT_ID};
Program {
id: EXTRA_OUTPUT_ID,
elf: EXTRA_OUTPUT_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
/// A program that produces less output accounts than the inputs it received
pub fn missing_output_program() -> Self {
use test_program_methods::{MISSING_OUTPUT_ELF, MISSING_OUTPUT_ID};
Program {
id: MISSING_OUTPUT_ID,
elf: MISSING_OUTPUT_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
/// A program that changes the program owner of an account to [0, 1, 2, 3, 4, 5, 6, 7]
pub fn program_owner_changer() -> Self {
use test_program_methods::{PROGRAM_OWNER_CHANGER_ELF, PROGRAM_OWNER_CHANGER_ID};
Program {
id: PROGRAM_OWNER_CHANGER_ID,
elf: PROGRAM_OWNER_CHANGER_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
/// A program that transfers balance without caring about authorizations
pub fn simple_balance_transfer() -> Self {
use test_program_methods::{SIMPLE_BALANCE_TRANSFER_ELF, SIMPLE_BALANCE_TRANSFER_ID};
Program {
id: SIMPLE_BALANCE_TRANSFER_ID,
elf: SIMPLE_BALANCE_TRANSFER_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
/// A program that modifies the data of an account
pub fn data_changer() -> Self {
use test_program_methods::{DATA_CHANGER_ELF, DATA_CHANGER_ID};
Program {
id: DATA_CHANGER_ID,
elf: DATA_CHANGER_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
/// A program that mints balance
pub fn minter() -> Self {
use test_program_methods::{MINTER_ELF, MINTER_ID};
Program {
id: MINTER_ID,
elf: MINTER_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
/// A program that burns balance
pub fn burner() -> Self {
use test_program_methods::{BURNER_ELF, BURNER_ID};
Program {
id: BURNER_ID,
elf: BURNER_ELF.to_vec(),
2025-08-13 01:33:11 -03:00
}
}
2025-10-29 15:34:11 -03:00
pub fn chain_caller() -> Self {
use test_program_methods::{CHAIN_CALLER_ELF, CHAIN_CALLER_ID};
Program {
id: CHAIN_CALLER_ID,
elf: CHAIN_CALLER_ELF.to_vec(),
}
}
pub fn claimer() -> Self {
use test_program_methods::{CLAIMER_ELF, CLAIMER_ID};
Program {
id: CLAIMER_ID,
elf: CLAIMER_ELF.to_vec(),
}
}
pub fn modified_transfer_program() -> Self {
use test_program_methods::MODIFIED_TRANSFER_ELF;
// This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of
// `program_methods`
Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap()
}
2025-08-13 01:33:11 -03:00
}
#[test]
fn test_program_execution() {
let program = Program::simple_balance_transfer();
let balance_to_move: u128 = 11223344556677;
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
2025-09-11 16:37:28 -03:00
let sender = AccountWithMetadata::new(
Account {
2025-08-13 01:33:11 -03:00
balance: 77665544332211,
..Account::default()
},
2025-09-11 16:37:28 -03:00
true,
2025-09-12 09:18:40 -03:00
AccountId::new([0; 32]),
2025-09-11 16:37:28 -03:00
);
let recipient =
2025-09-12 09:18:40 -03:00
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
2025-08-13 01:33:11 -03:00
let expected_sender_post = Account {
balance: 77665544332211 - balance_to_move,
..Account::default()
};
let expected_recipient_post = Account {
balance: balance_to_move,
..Account::default()
};
let program_output = program
2025-09-09 17:03:58 -03:00
.execute(&[sender, recipient], &instruction_data)
2025-08-13 01:33:11 -03:00
.unwrap();
let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap();
2025-12-04 16:26:40 -03:00
assert_eq!(sender_post.account(), &expected_sender_post);
assert_eq!(recipient_post.account(), &expected_recipient_post);
2025-08-13 01:33:11 -03:00
}
#[test]
fn test_builtin_programs() {
let auth_transfer_program = Program::authenticated_transfer_program();
let token_program = Program::token();
let pinata_program = Program::pinata();
assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID);
assert_eq!(auth_transfer_program.elf, AUTHENTICATED_TRANSFER_ELF);
assert_eq!(token_program.id, TOKEN_ID);
assert_eq!(token_program.elf, TOKEN_ELF);
assert_eq!(pinata_program.id, PINATA_ID);
assert_eq!(pinata_program.elf, PINATA_ELF);
}
2025-08-13 01:33:11 -03:00
}