use std::borrow::Cow; use borsh::{BorshDeserialize, BorshSerialize}; use lee_core::{ account::AccountWithMetadata, program::{InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; use crate::error::LeeError; /// 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, BorshSerialize, BorshDeserialize)] pub struct Program { id: ProgramId, elf: Cow<'static, [u8]>, } impl Program { pub fn new(elf: Cow<'static, [u8]>) -> Result { let binary = risc0_binfmt::ProgramBinary::decode(elf.as_ref()) .map_err(LeeError::InvalidProgramBytecode)?; let id = binary .compute_image_id() .map_err(LeeError::InvalidProgramBytecode)? .into(); Ok(Self { id, elf }) } #[must_use] pub const fn new_unchecked(id: ProgramId, elf: Cow<'static, [u8]>) -> Self { Self { id, elf } } #[must_use] pub const fn id(&self) -> ProgramId { self.id } #[must_use] pub fn elf(&self) -> &[u8] { &self.elf } pub fn serialize_instruction( instruction: T, ) -> Result { to_vec(&instruction).map_err(|e| LeeError::InstructionSerializationError(e.to_string())) } pub(crate) fn execute( &self, caller_program_id: Option, pre_states: &[AccountWithMetadata], instruction_data: &InstructionData, ) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); env_builder.session_limit(Some(MAX_NUM_CYCLES_PUBLIC_EXECUTION)); Self::write_inputs( self.id, caller_program_id, pre_states, instruction_data, &mut env_builder, )?; let env = env_builder.build().unwrap(); // Execute the program (without proving) let executor = default_executor(); let session_info = executor .execute(env, self.elf()) .map_err(|e| LeeError::ProgramExecutionFailed(e.to_string()))?; // Get outputs let program_output = session_info .journal .decode() .map_err(|e| LeeError::ProgramExecutionFailed(e.to_string()))?; Ok(program_output) } /// Writes inputs to `env_builder` in the order expected by the programs. pub(crate) fn write_inputs( program_id: ProgramId, caller_program_id: Option, pre_states: &[AccountWithMetadata], instruction_data: &[u32], env_builder: &mut ExecutorEnvBuilder, ) -> Result<(), LeeError> { env_builder .write(&program_id) .map_err(|e| LeeError::ProgramWriteInputFailed(e.to_string()))?; env_builder .write(&caller_program_id) .map_err(|e| LeeError::ProgramWriteInputFailed(e.to_string()))?; let pre_states = pre_states.to_vec(); env_builder .write(&pre_states) .map_err(|e| LeeError::ProgramWriteInputFailed(e.to_string()))?; env_builder .write(&instruction_data) .map_err(|e| LeeError::ProgramWriteInputFailed(e.to_string()))?; Ok(()) } } #[cfg(test)] mod tests { use lee_core::account::{Account, AccountId, AccountWithMetadata}; use crate::program::Program; #[test] fn program_execution() { let program = crate::test_methods::simple_balance_transfer(); let balance_to_move: u128 = 11_223_344_556_677; let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); let sender = AccountWithMetadata::new( Account { balance: 77_665_544_332_211, ..Account::default() }, true, AccountId::new([0; 32]), ); let recipient = AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); let expected_sender_post = Account { balance: 77_665_544_332_211 - balance_to_move, ..Account::default() }; let expected_recipient_post = Account { balance: balance_to_move, ..Account::default() }; let program_output = program .execute(None, &[sender, recipient], &instruction_data) .unwrap(); let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap(); assert_eq!(sender_post.account(), &expected_sender_post); assert_eq!(recipient_post.account(), &expected_recipient_post); } }