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}