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}