Skip to main content

sbpf_runtime/cpi/
mod.rs

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}