1use {
2 crate::{
3 config::{ExecutionCost, RuntimeConfig, SysvarContext},
4 cpi,
5 elf::load_elf,
6 errors::{RuntimeError, RuntimeResult},
7 serialize,
8 syscalls::RuntimeSyscallHandler,
9 },
10 base64::{Engine, engine::general_purpose::STANDARD as BASE64},
11 sbpf_common::{execute::Vm, instruction::Instruction},
12 sbpf_vm::{
13 compute::ComputeMeter,
14 memory::Memory,
15 vm::{CallFrame, SbpfVm, SbpfVmConfig},
16 },
17 solana_account::Account,
18 solana_address::Address,
19 solana_instruction::{AccountMeta, Instruction as SolanaInstruction},
20 std::{cell::RefCell, collections::HashMap, rc::Rc},
21};
22
23pub type LogCollector = Rc<RefCell<Vec<String>>>;
24
25pub enum ElfSource {
26 Path(String),
27 Bytes(Vec<u8>),
28}
29
30impl From<&str> for ElfSource {
31 fn from(path: &str) -> Self {
32 ElfSource::Path(path.to_string())
33 }
34}
35
36impl From<&[u8]> for ElfSource {
37 fn from(bytes: &[u8]) -> Self {
38 ElfSource::Bytes(bytes.to_vec())
39 }
40}
41
42impl From<Vec<u8>> for ElfSource {
43 fn from(bytes: Vec<u8>) -> Self {
44 ElfSource::Bytes(bytes)
45 }
46}
47
48pub struct ExecutionResult {
49 pub exit_code: Option<u64>,
50 pub compute_units_consumed: u64,
51 pub logs: Vec<String>,
52}
53
54pub struct Runtime {
55 program_id: Address,
56 instructions: Vec<Instruction>,
57 rodata: Vec<u8>,
58 entrypoint: usize,
59 programs: HashMap<Address, Vec<u8>>,
60 config: RuntimeConfig,
61 sysvars: SysvarContext,
62 vm: Option<SbpfVm<RuntimeSyscallHandler>>,
63 accounts: HashMap<Address, Account>,
64 account_metas: Vec<AccountMeta>,
65 pre_lens: Vec<usize>, log_collector: LogCollector,
67}
68
69impl Runtime {
70 pub fn new(
71 program_id: Address,
72 elf: impl Into<ElfSource>,
73 config: RuntimeConfig,
74 ) -> RuntimeResult<Self> {
75 let elf_bytes = match elf.into() {
76 ElfSource::Path(path) => std::fs::read(&path)?,
77 ElfSource::Bytes(bytes) => bytes,
78 };
79
80 let (instructions, rodata, entrypoint) = load_elf(&elf_bytes)?;
81
82 Ok(Self {
83 program_id,
84 instructions,
85 rodata,
86 entrypoint,
87 programs: HashMap::new(),
88 config,
89 sysvars: SysvarContext::default(),
90 vm: None,
91 accounts: HashMap::new(),
92 account_metas: Vec::new(),
93 pre_lens: Vec::new(),
94 log_collector: Rc::new(RefCell::new(Vec::new())),
95 })
96 }
97
98 pub fn add_program(&mut self, program_id: &Address, elf: impl Into<ElfSource>) {
99 let elf_bytes = match elf.into() {
100 ElfSource::Path(path) => std::fs::read(&path).expect("Failed to read ELF"),
101 ElfSource::Bytes(bytes) => bytes,
102 };
103 self.programs.insert(*program_id, elf_bytes);
104 }
105
106 fn setup_vm(
107 &mut self,
108 instruction: &SolanaInstruction,
109 accounts: &[(Address, Account)],
110 ) -> RuntimeResult<()> {
111 for (address, account) in accounts.iter() {
113 self.accounts
114 .entry(*address)
115 .or_insert_with(|| account.clone());
116 }
117 self.account_metas = instruction.accounts.clone();
118
119 let (input, pre_lens, instruction_data_offset) = serialize::serialize_parameters(
120 &self.accounts,
121 &self.account_metas,
122 &instruction.data,
123 &self.program_id,
124 )?;
125
126 let vm_config = SbpfVmConfig {
127 compute_unit_limit: self.config.compute_budget,
128 max_call_depth: self.config.max_call_depth,
129 heap_size: self.config.heap_size,
130 };
131
132 let handler = RuntimeSyscallHandler::new(
133 ExecutionCost::default(),
134 self.program_id,
135 self.sysvars.clone(),
136 self.log_collector.clone(),
137 );
138
139 let mut vm = SbpfVm::new_with_config(
140 self.instructions.clone(),
141 input,
142 self.rodata.clone(),
143 handler,
144 vm_config,
145 );
146 vm.compute_meter = ComputeMeter::new(self.config.compute_budget);
147 vm.set_entrypoint(self.entrypoint);
148 vm.registers[2] = Memory::INPUT_START + instruction_data_offset as u64;
149
150 self.pre_lens = pre_lens;
151 self.vm = Some(vm);
152 Ok(())
153 }
154
155 fn sync_accounts(&mut self) -> RuntimeResult<()> {
156 if let Some(ref vm) = self.vm {
157 serialize::deserialize_parameters(
158 &mut self.accounts,
159 &self.account_metas,
160 &vm.memory.input,
161 &self.pre_lens,
162 &self.program_id,
163 )?;
164 }
165 Ok(())
166 }
167
168 pub fn run(
169 &mut self,
170 instruction: &SolanaInstruction,
171 accounts: &[(Address, Account)],
172 ) -> RuntimeResult<ExecutionResult> {
173 self.log_collector.borrow_mut().clear();
174 self.setup_vm(instruction, accounts)?;
175
176 let pre_lamports: HashMap<Address, u64> = self
178 .account_metas
179 .iter()
180 .filter_map(|meta| {
181 self.accounts
182 .get(&meta.pubkey)
183 .map(|a| (meta.pubkey, a.lamports))
184 })
185 .collect();
186
187 self.log_collector
188 .borrow_mut()
189 .push(format!("Program {} invoke [1]", self.program_id));
190
191 loop {
192 let vm = self.vm.as_mut().unwrap();
193 if let Err(e) = vm.step() {
194 self.log_collector
195 .borrow_mut()
196 .push(format!("Program failed: {}", e));
197 return Err(e.into());
198 }
199
200 if let Some(request) = vm.syscall_handler.pending_cpi.take() {
201 if let Err(e) = self.handle_cpi(request) {
202 self.log_collector
203 .borrow_mut()
204 .push(format!("Program failed: {}", e));
205 return Err(e);
206 }
207 continue;
208 }
209
210 if vm.halted {
211 break;
212 }
213 }
214
215 self.sync_accounts()?;
216
217 let pre_total: u64 = pre_lamports.values().sum();
219 let post_total: u64 = pre_lamports
220 .keys()
221 .filter_map(|pk| self.accounts.get(pk))
222 .map(|a| a.lamports)
223 .sum();
224 if pre_total != post_total {
225 return Err(RuntimeError::UnbalancedInstruction(pre_total, post_total));
226 }
227
228 let vm = self.vm.as_ref().unwrap();
229 let consumed = vm.compute_meter.get_consumed();
230 let exit_code = vm.exit_code;
231
232 if let Some(ref return_data) = vm.syscall_handler.return_data
233 && !return_data.1.is_empty()
234 {
235 self.log_collector.borrow_mut().push(format!(
236 "Program return: {} {}",
237 return_data.0,
238 BASE64.encode(&return_data.1)
239 ));
240 }
241
242 self.log_collector.borrow_mut().push(format!(
243 "Program {} consumed {} of {} compute units",
244 self.program_id, consumed, self.config.compute_budget
245 ));
246
247 if exit_code.unwrap_or(0) == 0 {
248 self.log_collector
249 .borrow_mut()
250 .push(format!("Program {} success", self.program_id));
251 } else {
252 self.log_collector.borrow_mut().push(format!(
253 "Program {} failed: exit code {}",
254 self.program_id,
255 exit_code.unwrap_or(0)
256 ));
257 }
258
259 let logs = self.log_collector.borrow().clone();
260
261 Ok(ExecutionResult {
262 exit_code,
263 compute_units_consumed: consumed,
264 logs,
265 })
266 }
267
268 pub fn prepare(
269 &mut self,
270 instruction: &SolanaInstruction,
271 accounts: &[(Address, Account)],
272 ) -> RuntimeResult<()> {
273 self.log_collector.borrow_mut().clear();
274 self.setup_vm(instruction, accounts)?;
275 self.log_collector
276 .borrow_mut()
277 .push(format!("Program {} invoke [1]", self.program_id));
278 Ok(())
279 }
280
281 pub fn step(&mut self) -> RuntimeResult<()> {
282 let vm = self.vm.as_mut().ok_or(RuntimeError::VmNotPrepared)?;
283 if vm.halted {
284 return Ok(());
285 }
286 if let Err(e) = vm.step() {
287 self.log_collector
288 .borrow_mut()
289 .push(format!("Program failed: {}", e));
290 return Err(e.into());
291 }
292
293 if let Some(request) = vm.syscall_handler.pending_cpi.take()
294 && let Err(e) = self.handle_cpi(request)
295 {
296 self.log_collector
297 .borrow_mut()
298 .push(format!("Program failed: {}", e));
299 return Err(e);
300 }
301
302 let vm_ref = self.vm.as_ref().unwrap();
303 if vm_ref.halted {
304 self.sync_accounts()?;
305
306 let vm = self.vm.as_ref().unwrap();
307 let consumed = vm.compute_meter.get_consumed();
308 let exit_code = vm.exit_code;
309
310 if let Some(ref return_data) = vm.syscall_handler.return_data
311 && !return_data.1.is_empty()
312 {
313 self.log_collector.borrow_mut().push(format!(
314 "Program return: {} {}",
315 return_data.0,
316 BASE64.encode(&return_data.1)
317 ));
318 }
319
320 self.log_collector.borrow_mut().push(format!(
321 "Program {} consumed {} of {} compute units",
322 self.program_id, consumed, self.config.compute_budget
323 ));
324
325 if exit_code.unwrap_or(0) == 0 {
326 self.log_collector
327 .borrow_mut()
328 .push(format!("Program {} success", self.program_id));
329 } else {
330 self.log_collector.borrow_mut().push(format!(
331 "Program {} failed: exit code {}",
332 self.program_id,
333 exit_code.unwrap_or(0)
334 ));
335 }
336 }
337
338 Ok(())
339 }
340
341 fn handle_cpi(&mut self, request: cpi::request::CpiRequest) -> RuntimeResult<()> {
342 let vm = self.vm.as_ref().unwrap();
343 let compute_remaining = self.config.compute_budget - vm.compute_meter.get_consumed();
344
345 cpi::sync::sync_from_caller(&vm.memory, &request.caller_accounts, &mut self.accounts)?;
347
348 let caller_accounts = request.caller_accounts;
349 let cpi_request = cpi::request::CpiRequest {
350 program_id: request.program_id,
351 accounts: request.accounts,
352 data: request.data,
353 caller_accounts: Vec::new(),
354 signers: request.signers,
355 };
356
357 let mut ctx = cpi::CpiContext {
358 request: cpi_request,
359 programs: &self.programs,
360 accounts: &mut self.accounts,
361 config: &self.config,
362 sysvars: &self.sysvars,
363 compute_remaining,
364 cpi_depth: 1,
365 caller_account_metas: &self.account_metas,
366 log_collector: &self.log_collector,
367 };
368
369 let output = cpi::execute_cpi(&mut ctx)?;
370
371 let vm = self.vm.as_mut().unwrap();
372 vm.compute_meter.consume(output.compute_consumed)?;
373 vm.syscall_handler.return_data = output.return_data;
374
375 if output.exit_code != 0 {
376 return Err(RuntimeError::VmError(
377 sbpf_vm::errors::SbpfVmError::SyscallError(format!(
378 "CPI callee returned error: {}",
379 output.exit_code
380 )),
381 ));
382 }
383
384 let vm = self.vm.as_mut().unwrap();
386 cpi::sync::sync_to_caller(&mut vm.memory, &caller_accounts, &self.accounts)?;
387
388 Ok(())
389 }
390
391 pub fn get_pc(&self) -> usize {
392 self.vm.as_ref().map(|vm| vm.pc).unwrap_or(0)
393 }
394
395 pub fn get_registers(&self) -> Option<&[u64; 11]> {
396 self.vm.as_ref().map(|vm| &vm.registers)
397 }
398
399 pub fn current_program_id(&self) -> &Address {
400 &self.program_id
401 }
402
403 pub fn is_halted(&self) -> bool {
404 self.vm.as_ref().map(|vm| vm.halted).unwrap_or(false)
405 }
406
407 pub fn exit_code(&self) -> Option<u64> {
408 self.vm.as_ref().and_then(|vm| vm.exit_code)
409 }
410
411 pub fn compute_units_consumed(&self) -> u64 {
412 self.vm
413 .as_ref()
414 .map(|vm| vm.compute_meter.get_consumed())
415 .unwrap_or(0)
416 }
417
418 pub fn get_account(&self, pubkey: &Address) -> Option<Account> {
419 self.accounts.get(pubkey).cloned()
420 }
421
422 pub fn get_accounts(&self) -> &HashMap<Address, Account> {
423 &self.accounts
424 }
425
426 pub fn get_register(&self, idx: usize) -> Option<u64> {
427 self.vm
428 .as_ref()
429 .and_then(|vm| vm.registers.get(idx).copied())
430 }
431
432 pub fn set_register(&mut self, idx: usize, value: u64) -> RuntimeResult<()> {
433 let vm = self.vm.as_mut().ok_or(RuntimeError::VmNotPrepared)?;
434 if idx >= vm.registers.len() {
435 return Err(RuntimeError::RegisterOutOfRange(idx));
436 }
437 vm.set_register(idx, value);
438 Ok(())
439 }
440
441 pub fn read_memory(&self, addr: u64, size: usize) -> Option<Vec<u8>> {
442 self.vm
443 .as_ref()
444 .and_then(|vm| vm.memory.read_bytes(addr, size).ok().map(|s| s.to_vec()))
445 }
446
447 pub fn get_instruction(&self) -> Option<&Instruction> {
448 let vm = self.vm.as_ref()?;
449 vm.program.get(vm.pc)
450 }
451
452 pub fn get_program(&self) -> &[Instruction] {
453 &self.instructions
454 }
455
456 pub fn get_call_stack(&self) -> Option<&[CallFrame]> {
457 self.vm.as_ref().map(|vm| vm.call_stack.as_slice())
458 }
459
460 pub fn config(&self) -> &RuntimeConfig {
461 &self.config
462 }
463
464 pub fn sysvars(&self) -> &SysvarContext {
465 &self.sysvars
466 }
467
468 pub fn sysvars_mut(&mut self) -> &mut SysvarContext {
469 &mut self.sysvars
470 }
471
472 pub fn log_collector(&self) -> &LogCollector {
473 &self.log_collector
474 }
475
476 pub fn drain_logs(&self) -> Vec<String> {
477 self.log_collector.borrow_mut().drain(..).collect()
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use {super::*, std::path::PathBuf};
484
485 const PROGRAM_ID: Address =
486 Address::from_str_const("22222222222222222222222222222222222222222222");
487
488 fn escrow_elf_path() -> String {
489 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
490 .join("tests/fixtures/libupstream_pinocchio_escrow.so")
491 .to_str()
492 .unwrap()
493 .to_string()
494 }
495
496 fn new_runtime() -> Runtime {
497 Runtime::new(
498 PROGRAM_ID,
499 escrow_elf_path().as_str(),
500 RuntimeConfig::default(),
501 )
502 .unwrap()
503 }
504
505 fn empty_instruction() -> SolanaInstruction {
506 SolanaInstruction {
507 program_id: PROGRAM_ID,
508 accounts: Vec::new(),
509 data: Vec::new(),
510 }
511 }
512
513 #[test]
514 fn elf_source_from_str() {
515 let path = escrow_elf_path();
516 assert!(matches!(ElfSource::from(path.as_str()), ElfSource::Path(_)));
517 }
518
519 #[test]
520 fn elf_source_from_bytes_slice() {
521 let bytes: &[u8] = &[1, 2, 3];
522 assert!(matches!(ElfSource::from(bytes), ElfSource::Bytes(_)));
523 }
524
525 #[test]
526 fn elf_source_from_vec() {
527 assert!(matches!(
528 ElfSource::from(vec![1u8, 2, 3]),
529 ElfSource::Bytes(_)
530 ));
531 }
532
533 #[test]
534 fn new_from_path() {
535 let rt = new_runtime();
536 assert_eq!(*rt.current_program_id(), PROGRAM_ID);
537 assert!(!rt.get_program().is_empty());
538 }
539
540 #[test]
541 fn new_from_bytes() {
542 let bytes = std::fs::read(escrow_elf_path()).unwrap();
543 let rt = Runtime::new(PROGRAM_ID, bytes, RuntimeConfig::default()).unwrap();
544 assert!(!rt.get_program().is_empty());
545 }
546
547 #[test]
548 fn new_bad_path_errors() {
549 match Runtime::new(
550 PROGRAM_ID,
551 "/nonexistent/path/to/program.so",
552 RuntimeConfig::default(),
553 ) {
554 Err(RuntimeError::ElfReadError(_)) => {}
555 other => panic!("expected ElfReadError, got {:?}", other.map(|_| ())),
556 }
557 }
558
559 #[test]
560 fn new_invalid_elf_errors() {
561 match Runtime::new(PROGRAM_ID, vec![0u8; 8], RuntimeConfig::default()) {
562 Err(RuntimeError::ElfReadError(_)) => panic!("expected parse error, got io error"),
563 Err(_) => {}
564 Ok(_) => panic!("expected error for invalid ELF"),
565 }
566 }
567
568 #[test]
569 fn add_program_accepts_path_and_bytes() {
570 let mut rt = new_runtime();
571 rt.add_program(&Address::new_unique(), escrow_elf_path().as_str());
572 let bytes = std::fs::read(escrow_elf_path()).unwrap();
573 rt.add_program(&Address::new_unique(), bytes);
574 }
575
576 #[test]
577 fn getters_before_prepare_are_defaults() {
578 let rt = new_runtime();
579 assert_eq!(rt.get_pc(), 0);
580 assert!(rt.get_registers().is_none());
581 assert!(rt.get_register(2).is_none());
582 assert!(rt.read_memory(Memory::INPUT_START, 8).is_none());
583 assert!(rt.get_instruction().is_none());
584 assert!(rt.get_call_stack().is_none());
585 assert!(!rt.is_halted());
586 assert!(rt.exit_code().is_none());
587 assert_eq!(rt.compute_units_consumed(), 0);
588 assert!(rt.get_account(&PROGRAM_ID).is_none());
589 assert!(rt.get_accounts().is_empty());
590 assert!(rt.drain_logs().is_empty());
591 }
592
593 #[test]
594 fn set_register_before_prepare_errors() {
595 let mut rt = new_runtime();
596 let err = rt.set_register(0, 42).unwrap_err();
597 assert!(matches!(err, RuntimeError::VmNotPrepared));
598 }
599
600 #[test]
601 fn step_before_prepare_errors() {
602 let mut rt = new_runtime();
603 let err = rt.step().unwrap_err();
604 assert!(matches!(err, RuntimeError::VmNotPrepared));
605 }
606
607 #[test]
608 fn config_and_sysvar_accessors() {
609 let mut rt = new_runtime();
610 assert_eq!(
611 rt.config().compute_budget,
612 RuntimeConfig::default().compute_budget
613 );
614 let slot = rt.sysvars().clock.slot;
615 rt.sysvars_mut().clock.slot = slot + 5;
616 assert_eq!(rt.sysvars().clock.slot, slot + 5);
617 assert!(rt.log_collector().borrow().is_empty());
618 }
619
620 #[test]
621 fn prepare_initializes_vm_state() {
622 let mut rt = new_runtime();
623 rt.prepare(&empty_instruction(), &[]).unwrap();
624
625 assert!(rt.get_registers().is_some());
627 assert!(rt.get_call_stack().is_some());
628 assert!(rt.get_instruction().is_some());
629 let r2 = rt.get_register(2).unwrap();
630 assert!(r2 >= Memory::INPUT_START);
631 assert!(rt.read_memory(Memory::INPUT_START, 8).is_some());
632 let logs = rt.log_collector().borrow().clone();
633 assert!(logs.iter().any(|l| l.contains("invoke [1]")));
634 }
635
636 #[test]
637 fn set_register_after_prepare_and_out_of_range() {
638 let mut rt = new_runtime();
639 rt.prepare(&empty_instruction(), &[]).unwrap();
640
641 rt.set_register(3, 0xdead_beef).unwrap();
642 assert_eq!(rt.get_register(3), Some(0xdead_beef));
643
644 let err = rt.set_register(99, 1).unwrap_err();
645 assert!(matches!(err, RuntimeError::RegisterOutOfRange(99)));
646 }
647
648 #[test]
649 fn step_drives_program_to_clean_halt() {
650 let mut rt = new_runtime();
651 rt.prepare(&empty_instruction(), &[]).unwrap();
652
653 let mut steps = 0;
654 while !rt.is_halted() {
655 rt.step().expect("step should not trap on this program");
656 steps += 1;
657 assert!(steps <= 100_000, "program did not terminate");
658 }
659 assert!(steps > 0);
660 assert!(rt.is_halted());
661 assert!(rt.exit_code().is_some());
662 }
663
664 #[test]
665 fn run_returns_ok_with_nonzero_exit_and_logs() {
666 let mut rt = new_runtime();
667 let exec = rt.run(&empty_instruction(), &[]).unwrap();
668
669 assert!(matches!(exec.exit_code, Some(code) if code != 0));
670 assert!(exec.compute_units_consumed > 0);
671
672 assert!(exec.logs.iter().any(|l| l.contains("invoke [1]")));
673 assert!(exec.logs.iter().any(|l| l.contains("consumed")));
674 assert!(exec.logs.iter().any(|l| l.contains("failed: exit code")));
675 }
676}