1pub mod builtins;
2pub mod request;
3pub mod sync;
4pub mod validate;
5
6use {
7 crate::{
8 config::{ExecutionCost, RuntimeConfig, SysvarContext},
9 elf::load_elf,
10 errors::{RuntimeError, RuntimeResult},
11 runtime::LogCollector,
12 serialize,
13 syscalls::RuntimeSyscallHandler,
14 },
15 base64::{Engine, engine::general_purpose::STANDARD as BASE64},
16 request::CpiRequest,
17 sbpf_vm::{
18 compute::ComputeMeter,
19 memory::Memory,
20 vm::{SbpfVm, SbpfVmConfig},
21 },
22 solana_account::Account,
23 solana_address::Address,
24 solana_instruction::AccountMeta,
25 std::collections::HashMap,
26};
27
28pub type ReturnData = Option<(Address, Vec<u8>)>;
29
30pub struct CpiOutput {
31 pub exit_code: u64,
32 pub return_data: ReturnData,
33 pub compute_consumed: u64,
34}
35
36pub type CpiExecResult = RuntimeResult<CpiOutput>;
37
38pub struct CpiContext<'a> {
39 pub request: CpiRequest,
40 pub programs: &'a HashMap<Address, Vec<u8>>,
41 pub accounts: &'a mut HashMap<Address, Account>,
42 pub config: &'a RuntimeConfig,
43 pub sysvars: &'a SysvarContext,
44 pub compute_remaining: u64,
45 pub cpi_depth: usize,
46 pub caller_account_metas: &'a [AccountMeta],
47 pub log_collector: &'a LogCollector,
48}
49
50pub fn execute_cpi(ctx: &mut CpiContext) -> CpiExecResult {
51 if ctx.cpi_depth >= ctx.config.max_cpi_depth {
52 return Err(RuntimeError::CpiDepthExceeded(ctx.config.max_cpi_depth));
53 }
54
55 validate::check_privileges(&ctx.request, ctx.caller_account_metas)?;
56
57 ctx.log_collector.borrow_mut().push(format!(
58 "Program {} invoke [{}]",
59 ctx.request.program_id,
60 ctx.cpi_depth + 1
61 ));
62
63 if builtins::is_builtin(&ctx.request.program_id) {
64 let mut all_signers = ctx.request.signers.clone();
65 for meta in &ctx.request.accounts {
66 if meta.is_signer && !all_signers.contains(&meta.pubkey) {
67 all_signers.push(meta.pubkey);
68 }
69 }
70 let consumed = builtins::execute_builtin(
71 &ctx.request.program_id,
72 ctx.accounts,
73 &ctx.request,
74 &all_signers,
75 ctx.compute_remaining,
76 )?;
77 ctx.log_collector.borrow_mut().push(format!(
78 "Program {} consumed {} of {} compute units",
79 ctx.request.program_id, consumed, ctx.compute_remaining
80 ));
81 ctx.log_collector
82 .borrow_mut()
83 .push(format!("Program {} success", ctx.request.program_id));
84 return Ok(CpiOutput {
85 exit_code: 0,
86 return_data: None,
87 compute_consumed: consumed,
88 });
89 }
90
91 execute_elf_cpi(ctx)
92}
93
94fn execute_elf_cpi(ctx: &mut CpiContext) -> CpiExecResult {
95 let elf_bytes = ctx
96 .programs
97 .get(&ctx.request.program_id)
98 .ok_or_else(|| RuntimeError::ProgramNotFound(ctx.request.program_id.to_string()))?;
99
100 let (instructions, rodata, entrypoint) = load_elf(elf_bytes)?;
101
102 let account_metas: Vec<AccountMeta> = ctx
103 .request
104 .accounts
105 .iter()
106 .map(|a| AccountMeta {
107 pubkey: a.pubkey,
108 is_signer: a.is_signer,
109 is_writable: a.is_writable,
110 })
111 .collect();
112
113 let (input, pre_lens, instruction_data_offset) = serialize::serialize_parameters(
114 ctx.accounts,
115 &account_metas,
116 &ctx.request.data,
117 &ctx.request.program_id,
118 )?;
119
120 let vm_config = SbpfVmConfig {
121 compute_unit_limit: ctx.compute_remaining,
122 max_call_depth: ctx.config.max_call_depth,
123 heap_size: ctx.config.heap_size,
124 };
125
126 let handler = RuntimeSyscallHandler::new(
127 ExecutionCost::default(),
128 ctx.request.program_id,
129 ctx.sysvars.clone(),
130 ctx.log_collector.clone(),
131 );
132
133 let mut callee_vm = SbpfVm::new_with_config(instructions, input, rodata, handler, vm_config);
134 callee_vm.compute_meter = ComputeMeter::new(ctx.compute_remaining);
135 callee_vm.set_entrypoint(entrypoint);
136 callee_vm.registers[2] = Memory::INPUT_START + instruction_data_offset as u64;
137
138 loop {
139 if let Err(e) = callee_vm.step() {
140 return Err(e.into());
141 }
142
143 if let Some(nested_request) = callee_vm.syscall_handler.pending_cpi.take() {
144 sync::sync_from_caller(
145 &callee_vm.memory,
146 &nested_request.caller_accounts,
147 ctx.accounts,
148 )?;
149
150 let caller_accounts_for_sync = nested_request.caller_accounts;
151 let nested_consumed = callee_vm.compute_meter.get_consumed();
152 let nested_remaining = ctx.compute_remaining.saturating_sub(nested_consumed);
153
154 let nested_cpi_request = CpiRequest {
155 program_id: nested_request.program_id,
156 accounts: nested_request.accounts,
157 data: nested_request.data,
158 caller_accounts: Vec::new(),
159 signers: nested_request.signers,
160 };
161
162 let mut nested_ctx = CpiContext {
163 request: nested_cpi_request,
164 programs: ctx.programs,
165 accounts: ctx.accounts,
166 config: ctx.config,
167 sysvars: ctx.sysvars,
168 compute_remaining: nested_remaining,
169 cpi_depth: ctx.cpi_depth + 1,
170 caller_account_metas: &account_metas,
171 log_collector: ctx.log_collector,
172 };
173
174 let nested_output = execute_cpi(&mut nested_ctx)?;
175
176 callee_vm
177 .compute_meter
178 .consume(nested_output.compute_consumed)?;
179 callee_vm.syscall_handler.return_data = nested_output.return_data;
180
181 if nested_output.exit_code != 0 {
182 let consumed = callee_vm.compute_meter.get_consumed();
183 return Ok(CpiOutput {
184 exit_code: nested_output.exit_code,
185 return_data: callee_vm.syscall_handler.return_data.take(),
186 compute_consumed: consumed,
187 });
188 }
189
190 sync::sync_to_caller(
191 &mut callee_vm.memory,
192 &caller_accounts_for_sync,
193 ctx.accounts,
194 )?;
195 }
196
197 if callee_vm.halted {
198 break;
199 }
200 }
201
202 let exit_code = callee_vm.exit_code.unwrap_or(0);
203 let callee_return_data = callee_vm.syscall_handler.return_data.take();
204 let consumed = callee_vm.compute_meter.get_consumed();
205
206 if exit_code != 0 {
207 ctx.log_collector.borrow_mut().push(format!(
208 "Program {} consumed {} of {} compute units",
209 ctx.request.program_id, consumed, ctx.compute_remaining
210 ));
211 ctx.log_collector.borrow_mut().push(format!(
212 "Program {} failed: exit code {}",
213 ctx.request.program_id, exit_code
214 ));
215 return Ok(CpiOutput {
216 exit_code,
217 return_data: callee_return_data,
218 compute_consumed: consumed,
219 });
220 }
221
222 serialize::deserialize_parameters(
223 ctx.accounts,
224 &account_metas,
225 &callee_vm.memory.input,
226 &pre_lens,
227 &ctx.request.program_id,
228 )?;
229
230 if let Some((ref pid, ref data)) = callee_return_data
231 && !data.is_empty()
232 {
233 ctx.log_collector.borrow_mut().push(format!(
234 "Program return: {} {}",
235 pid,
236 BASE64.encode(data)
237 ));
238 }
239
240 ctx.log_collector.borrow_mut().push(format!(
241 "Program {} consumed {} of {} compute units",
242 ctx.request.program_id, consumed, ctx.compute_remaining
243 ));
244 ctx.log_collector
245 .borrow_mut()
246 .push(format!("Program {} success", ctx.request.program_id));
247
248 Ok(CpiOutput {
249 exit_code: 0,
250 return_data: callee_return_data,
251 compute_consumed: consumed,
252 })
253}
254
255#[cfg(test)]
256mod tests {
257 use {
258 super::*,
259 crate::cpi::request::CpiAccountMeta,
260 solana_system_interface::{instruction::SystemInstruction, program as system_program},
261 std::{cell::RefCell, rc::Rc},
262 };
263
264 fn addr(seed: u8) -> Address {
265 Address::new_from_array([seed; 32])
266 }
267
268 fn log_collector() -> LogCollector {
269 Rc::new(RefCell::new(Vec::new()))
270 }
271
272 fn system_account(lamports: u64) -> Account {
273 Account {
274 lamports,
275 data: vec![],
276 owner: system_program::ID,
277 executable: false,
278 rent_epoch: 0,
279 }
280 }
281
282 #[test]
283 fn execute_cpi_program_not_found() {
284 let config = RuntimeConfig::default();
285 let sysvars = SysvarContext::default();
286 let programs: HashMap<Address, Vec<u8>> = HashMap::new();
287 let mut accounts: HashMap<Address, Account> = HashMap::new();
288 let logs = log_collector();
289
290 let request = CpiRequest {
292 program_id: addr(9),
293 accounts: Vec::new(),
294 data: Vec::new(),
295 caller_accounts: Vec::new(),
296 signers: Vec::new(),
297 };
298 let mut ctx = CpiContext {
299 request,
300 programs: &programs,
301 accounts: &mut accounts,
302 config: &config,
303 sysvars: &sysvars,
304 compute_remaining: 200_000,
305 cpi_depth: 0,
306 caller_account_metas: &[],
307 log_collector: &logs,
308 };
309
310 match execute_cpi(&mut ctx) {
311 Err(RuntimeError::ProgramNotFound(_)) => {}
312 Err(other) => panic!("expected ProgramNotFound, got {other:?}"),
313 Ok(_) => panic!("expected ProgramNotFound error"),
314 }
315 }
316
317 #[test]
318 fn execute_cpi_depth_exceeded() {
319 let config = RuntimeConfig::default();
320 let sysvars = SysvarContext::default();
321 let programs: HashMap<Address, Vec<u8>> = HashMap::new();
322 let mut accounts: HashMap<Address, Account> = HashMap::new();
323 let logs = log_collector();
324
325 let request = CpiRequest {
326 program_id: addr(9),
327 accounts: Vec::new(),
328 data: Vec::new(),
329 caller_accounts: Vec::new(),
330 signers: Vec::new(),
331 };
332 let mut ctx = CpiContext {
333 request,
334 programs: &programs,
335 accounts: &mut accounts,
336 config: &config,
337 sysvars: &sysvars,
338 compute_remaining: 200_000,
339 cpi_depth: config.max_cpi_depth, caller_account_metas: &[],
341 log_collector: &logs,
342 };
343
344 match execute_cpi(&mut ctx) {
345 Err(RuntimeError::CpiDepthExceeded(_)) => {}
346 Err(other) => panic!("expected CpiDepthExceeded, got {other:?}"),
347 Ok(_) => panic!("expected CpiDepthExceeded error"),
348 }
349 }
350
351 #[test]
352 fn execute_cpi_dispatches_system_transfer_builtin() {
353 let config = RuntimeConfig::default();
354 let sysvars = SysvarContext::default();
355 let programs: HashMap<Address, Vec<u8>> = HashMap::new();
356 let logs = log_collector();
357
358 let from = addr(1);
359 let to = addr(2);
360 let mut accounts =
361 HashMap::from([(from, system_account(1_000_000)), (to, system_account(0))]);
362
363 let request = CpiRequest {
364 program_id: system_program::ID,
365 accounts: vec![
366 CpiAccountMeta {
367 pubkey: from,
368 is_signer: true,
369 is_writable: true,
370 },
371 CpiAccountMeta {
372 pubkey: to,
373 is_signer: false,
374 is_writable: true,
375 },
376 ],
377 data: wincode::serialize(&SystemInstruction::Transfer { lamports: 400_000 }).unwrap(),
378 caller_accounts: Vec::new(),
379 signers: Vec::new(),
380 };
381
382 let caller_metas = vec![
383 AccountMeta {
384 pubkey: from,
385 is_signer: true,
386 is_writable: true,
387 },
388 AccountMeta {
389 pubkey: to,
390 is_signer: false,
391 is_writable: true,
392 },
393 ];
394
395 let mut ctx = CpiContext {
396 request,
397 programs: &programs,
398 accounts: &mut accounts,
399 config: &config,
400 sysvars: &sysvars,
401 compute_remaining: 200_000,
402 cpi_depth: 0,
403 caller_account_metas: &caller_metas,
404 log_collector: &logs,
405 };
406
407 let output = execute_cpi(&mut ctx).unwrap();
408 assert_eq!(output.exit_code, 0);
409 assert_eq!(accounts[&from].lamports, 600_000);
410 assert_eq!(accounts[&to].lamports, 400_000);
411 assert!(logs.borrow().iter().any(|l| l.contains("invoke [1]")));
412 assert!(logs.borrow().iter().any(|l| l.contains("success")));
413 }
414}