Skip to main content

sbpf_runtime/syscalls/
mod.rs

1pub mod abort;
2pub mod crypto;
3pub mod log;
4pub mod memory;
5pub mod pda;
6pub mod return_data;
7pub mod sysvar;
8
9use {
10    crate::{
11        config::{ExecutionCost, SysvarContext},
12        cpi::request::{self, CpiRequest},
13        runtime::LogCollector,
14    },
15    sbpf_vm::{
16        compute::ComputeMeter, errors::SbpfVmResult, memory::Memory, syscalls::SyscallHandler,
17    },
18    solana_address::Address,
19};
20
21const ACCOUNT_META_SIZE: u64 = 34;
22const ACCOUNT_INFO_BYTE_SIZE: u64 = 80;
23
24pub struct RuntimeSyscallHandler {
25    pub costs: ExecutionCost,
26    pub program_id: Address,
27    pub sysvars: SysvarContext,
28    pub pending_cpi: Option<CpiRequest>,
29    pub return_data: crate::cpi::ReturnData,
30    pub log_collector: LogCollector,
31}
32
33impl RuntimeSyscallHandler {
34    pub fn new(
35        costs: ExecutionCost,
36        program_id: Address,
37        sysvars: SysvarContext,
38        log_collector: LogCollector,
39    ) -> Self {
40        Self {
41            costs,
42            program_id,
43            sysvars,
44            pending_cpi: None,
45            return_data: None,
46            log_collector,
47        }
48    }
49}
50
51// Consume CUs for CPI.
52fn consume_cpi_compute_units(
53    request: &CpiRequest,
54    compute: &ComputeMeter,
55    costs: &ExecutionCost,
56) -> SbpfVmResult<()> {
57    // Base invoke cost.
58    compute.consume(costs.invoke_units)?;
59
60    // Instruction data and account meta cost.
61    let data_cost = request.data.len() as u64 / costs.cpi_bytes_per_unit;
62    let meta_cost = (request.accounts.len() as u64 * ACCOUNT_META_SIZE) / costs.cpi_bytes_per_unit;
63    compute.consume(data_cost + meta_cost)?;
64
65    // Account info translation cost.
66    let account_info_cost =
67        (request.caller_accounts.len() as u64 * ACCOUNT_INFO_BYTE_SIZE) / costs.cpi_bytes_per_unit;
68    compute.consume(account_info_cost)?;
69
70    // Per-account data cost (skip duplicates).
71    let mut seen = Vec::with_capacity(request.accounts.len());
72    for meta in request.accounts.iter() {
73        if seen.contains(&meta.pubkey) {
74            continue;
75        }
76        seen.push(meta.pubkey);
77        if let Some(caller) = request
78            .caller_accounts
79            .iter()
80            .find(|c| c.pubkey == meta.pubkey)
81        {
82            let acct_cost = caller.data_len / costs.cpi_bytes_per_unit;
83            compute.consume(acct_cost)?;
84        }
85    }
86    Ok(())
87}
88
89impl SyscallHandler for RuntimeSyscallHandler {
90    fn handle(
91        &mut self,
92        name: &str,
93        registers: [u64; 5],
94        memory: &mut Memory,
95        compute: ComputeMeter,
96    ) -> SbpfVmResult<u64> {
97        match name {
98            "sol_log_" => log::sol_log(
99                registers,
100                memory,
101                &compute,
102                &self.costs,
103                &self.log_collector,
104            ),
105            "sol_log_64_" => log::sol_log_64(registers, &compute, &self.costs, &self.log_collector),
106            "sol_log_pubkey" => log::sol_log_pubkey(
107                registers,
108                memory,
109                &compute,
110                &self.costs,
111                &self.log_collector,
112            ),
113            "sol_log_compute_units_" => {
114                log::sol_log_compute_units(&compute, &self.costs, &self.log_collector)
115            }
116            "sol_remaining_compute_units" => {
117                log::sol_remaining_compute_units(&compute, &self.costs)
118            }
119
120            "sol_memcpy_" => memory::sol_memcpy(registers, memory, &compute, &self.costs),
121            "sol_memmove_" => memory::sol_memmove(registers, memory, &compute, &self.costs),
122            "sol_memset_" => memory::sol_memset(registers, memory, &compute, &self.costs),
123            "sol_memcmp_" => memory::sol_memcmp(registers, memory, &compute, &self.costs),
124
125            "abort" => abort::abort(),
126            "sol_panic_" => abort::sol_panic(registers, memory),
127
128            "sol_sha256" => crypto::sol_sha256(registers, memory, &compute, &self.costs),
129            "sol_keccak256" => crypto::sol_keccak256(registers, memory, &compute, &self.costs),
130            "sol_blake3" => crypto::sol_blake3(registers, memory, &compute, &self.costs),
131
132            "sol_create_program_address" => {
133                pda::sol_create_program_address(registers, memory, &compute, &self.costs)
134            }
135            "sol_try_find_program_address" => {
136                pda::sol_try_find_program_address(registers, memory, &compute, &self.costs)
137            }
138
139            "sol_get_clock_sysvar" => sysvar::sol_get_clock_sysvar(
140                registers,
141                memory,
142                &compute,
143                &self.costs,
144                &self.sysvars,
145            ),
146            "sol_get_rent_sysvar" => {
147                sysvar::sol_get_rent_sysvar(registers, memory, &compute, &self.costs, &self.sysvars)
148            }
149            "sol_get_epoch_schedule_sysvar" => sysvar::sol_get_epoch_schedule_sysvar(
150                registers,
151                memory,
152                &compute,
153                &self.costs,
154                &self.sysvars,
155            ),
156            "sol_get_last_restart_slot_sysvar" => sysvar::sol_get_last_restart_slot_sysvar(
157                registers,
158                memory,
159                &compute,
160                &self.costs,
161                &self.sysvars,
162            ),
163
164            "sol_set_return_data" => {
165                let (result, data) = return_data::sol_set_return_data(
166                    registers,
167                    memory,
168                    &compute,
169                    &self.costs,
170                    &self.program_id,
171                )?;
172                self.return_data = data;
173                Ok(result)
174            }
175            "sol_get_return_data" => return_data::sol_get_return_data(
176                registers,
177                memory,
178                &compute,
179                &self.costs,
180                &self.return_data,
181            ),
182
183            "sol_invoke_signed_c" => {
184                let request = request::parse_cpi_c(registers, memory, &self.program_id)?;
185                consume_cpi_compute_units(&request, &compute, &self.costs)?;
186                self.pending_cpi = Some(request);
187                Ok(0)
188            }
189            "sol_invoke_signed_rust" => {
190                let request = request::parse_cpi_rust(registers, memory, &self.program_id)?;
191                consume_cpi_compute_units(&request, &compute, &self.costs)?;
192                self.pending_cpi = Some(request);
193                Ok(0)
194            }
195
196            _ => {
197                compute.consume(self.costs.syscall_base_cost)?;
198                eprintln!("Unknown syscall: {}", name);
199                Ok(0)
200            }
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use {
208        self::test_helpers::{make_memory, meter},
209        super::*,
210        crate::cpi::request::{CallerAccountInfo, CpiAccountMeta},
211        sbpf_vm::errors::SbpfVmError,
212        std::{cell::RefCell, rc::Rc},
213    };
214
215    pub(crate) mod test_helpers {
216        use {
217            crate::config::ExecutionCost,
218            sbpf_vm::{compute::ComputeMeter, memory::Memory},
219        };
220
221        pub fn make_memory() -> Memory {
222            Memory::new(vec![], vec![], 4096, 64 * 1024)
223        }
224
225        pub fn costs() -> ExecutionCost {
226            ExecutionCost::default()
227        }
228
229        pub fn meter(limit: u64) -> ComputeMeter {
230            ComputeMeter::new(limit)
231        }
232    }
233
234    const LIMIT: u64 = 1_000_000;
235
236    fn addr(seed: u8) -> Address {
237        Address::new_from_array([seed; 32])
238    }
239
240    fn meta(pubkey: Address) -> CpiAccountMeta {
241        CpiAccountMeta {
242            pubkey,
243            is_signer: false,
244            is_writable: true,
245        }
246    }
247
248    fn caller(pubkey: Address, data_len: u64) -> CallerAccountInfo {
249        CallerAccountInfo {
250            pubkey,
251            lamports_addr: 0,
252            data_addr: 0,
253            data_len,
254            owner_addr: 0,
255            vm_data_len_addr: 0,
256            is_writable: true,
257        }
258    }
259
260    fn handler() -> RuntimeSyscallHandler {
261        RuntimeSyscallHandler::new(
262            ExecutionCost::default(),
263            addr(7),
264            SysvarContext::default(),
265            Rc::new(RefCell::new(Vec::new())),
266        )
267    }
268
269    #[test]
270    fn consume_cpi_compute_units_dedups_and_charges_data() {
271        let costs = ExecutionCost::default();
272        let a = addr(1);
273        let b = addr(2);
274        let c = addr(3);
275        // `a` is duplicated (should be charged once); `c` has no caller entry.
276        let request = CpiRequest {
277            program_id: addr(9),
278            accounts: vec![meta(a), meta(b), meta(c), meta(a)],
279            data: vec![0u8; 800],
280            caller_accounts: vec![caller(a, 1000), caller(b, 500)],
281            signers: Vec::new(),
282        };
283        let compute = meter(LIMIT);
284        consume_cpi_compute_units(&request, &compute, &costs).unwrap();
285
286        let expected = costs.invoke_units + 3 + 4 + 2;
287        assert_eq!(compute.get_consumed(), expected);
288    }
289
290    #[test]
291    fn consume_cpi_compute_units_no_caller_accounts() {
292        let costs = ExecutionCost::default();
293        let request = CpiRequest {
294            program_id: addr(9),
295            accounts: vec![meta(addr(1)), meta(addr(2))],
296            data: Vec::new(),
297            caller_accounts: Vec::new(),
298            signers: Vec::new(),
299        };
300        let compute = meter(LIMIT);
301        consume_cpi_compute_units(&request, &compute, &costs).unwrap();
302        assert_eq!(compute.get_consumed(), costs.invoke_units);
303    }
304
305    #[test]
306    fn consume_cpi_compute_units_out_of_budget() {
307        let costs = ExecutionCost::default();
308        let request = CpiRequest {
309            program_id: addr(9),
310            accounts: vec![meta(addr(1))],
311            data: Vec::new(),
312            caller_accounts: Vec::new(),
313            signers: Vec::new(),
314        };
315        let compute = ComputeMeter::new(10); // less than invoke_units
316        assert!(consume_cpi_compute_units(&request, &compute, &costs).is_err());
317    }
318
319    #[test]
320    fn handle_unknown_syscall_charges_base_and_returns_zero() {
321        let mut h = handler();
322        let mut memory = make_memory();
323        let compute = meter(LIMIT);
324        let out = h
325            .handle("sol_does_not_exist", [0; 5], &mut memory, compute.clone())
326            .unwrap();
327        assert_eq!(out, 0);
328        assert_eq!(compute.get_consumed(), h.costs.syscall_base_cost);
329    }
330
331    #[test]
332    fn handle_abort_returns_abort_error() {
333        let mut h = handler();
334        let mut memory = make_memory();
335        let compute = meter(LIMIT);
336        let err = h.handle("abort", [0; 5], &mut memory, compute).unwrap_err();
337        assert!(matches!(err, SbpfVmError::Abort));
338    }
339
340    #[test]
341    fn handle_log_64_writes_log() {
342        let mut h = handler();
343        let mut memory = make_memory();
344        let compute = meter(LIMIT);
345        h.handle("sol_log_64_", [1, 2, 3, 4, 5], &mut memory, compute)
346            .unwrap();
347        assert!(!h.log_collector.borrow().is_empty());
348    }
349
350    #[test]
351    fn handle_remaining_compute_units_reports_remaining() {
352        let mut h = handler();
353        let mut memory = make_memory();
354        let compute = meter(LIMIT);
355        let out = h
356            .handle(
357                "sol_remaining_compute_units",
358                [0; 5],
359                &mut memory,
360                compute.clone(),
361            )
362            .unwrap();
363        // Reports the remaining budget after charging its own cost.
364        assert_eq!(out, compute.get_remaining());
365    }
366}