Skip to main content

protobuf_ckb_syscalls/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
4
5pub mod generated {
6    pub mod traces {
7        use once_cell::sync::Lazy;
8        use prost_reflect::DescriptorPool;
9
10        pub static DESCRIPTOR_POOL: Lazy<DescriptorPool> = Lazy::new(|| {
11            DescriptorPool::decode(include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")).as_ref())
12                .unwrap()
13        });
14
15        include!(concat!(env!("OUT_DIR"), "/generated.traces.rs"));
16    }
17}
18
19use crate::generated::traces;
20use alloc::{boxed::Box, collections::VecDeque, vec::Vec};
21use ckb_std::{
22    ckb_constants::{CellField, HeaderField, InputField, Place, Source},
23    syscalls::traits::{Bounds, Error, IoResult, SyscallImpls},
24};
25use ckb_vm::SupportMachine;
26use ckb_vm_fuzzing_utils::{CkbvmRunnerImpls, exit_with_panic, flatten_args};
27use core::ffi::CStr;
28use core::marker::PhantomData;
29use prost::Message;
30use spin::Mutex;
31
32pub const UNEXPECTED: u64 = 19;
33pub const UNEXPECTED_ERROR: Error = Error::Other(UNEXPECTED);
34pub const UNEXPECTED_RESULT: IoResult = IoResult::Error(UNEXPECTED_ERROR);
35
36#[cfg(feature = "std")]
37pub fn entry<F>(data: &[u8], f: F) -> i8
38where
39    F: Fn() -> i8 + std::panic::UnwindSafe,
40{
41    let Some(impls) = ProtobufImpls::new_with_bytes(data) else {
42        return UNEXPECTED as i8;
43    };
44    let (argc, argv) = flatten_args(impls.args());
45    let argv = unsafe { core::slice::from_raw_parts(argv.as_ptr() as *const _, argc) };
46    ckb_vm_fuzzing_utils::entry(impls, f, argv)
47}
48
49pub struct ProtobufImpls {
50    syscalls: Mutex<VecDeque<traces::Syscall>>,
51    args: Vec<Vec<u8>>,
52    debug_printer: Box<dyn Fn(&str) + Send + Sync>,
53}
54
55impl ProtobufImpls {
56    fn new(syscalls: traces::Syscalls) -> Option<Self> {
57        Some(Self {
58            syscalls: Mutex::new(syscalls.syscalls.into()),
59            args: syscalls.args,
60            #[allow(unused_variables)]
61            debug_printer: Box::new(|message| {
62                #[cfg(feature = "print-debug-messages")]
63                eprintln!("Script debug message: {}", message);
64            }),
65        })
66    }
67
68    pub fn new_with_bytes<B: AsRef<[u8]>>(bytes: B) -> Option<Self> {
69        #[cfg(feature = "text-format")]
70        let bytes = {
71            let descriptor = crate::generated::traces::DESCRIPTOR_POOL
72                .get_message_by_name("generated.traces.Syscalls")
73                .expect("extracting protobuf message descriptor");
74            let Ok(s) = str::from_utf8(bytes.as_ref()) else {
75                return None;
76            };
77            let Ok(dmessage) = prost_reflect::DynamicMessage::parse_text_format(descriptor, s) else {
78                return None;
79            };
80            let Ok(message) = dmessage.transcode_to::<traces::Syscalls>() else {
81                return None;
82            };
83            message.encode_to_vec()
84        };
85        traces::Syscalls::decode(bytes.as_ref()).ok().and_then(Self::new)
86    }
87
88    #[cfg(feature = "std")]
89    pub fn new_with_file<P: AsRef<std::path::Path>>(path: P) -> Option<Self> {
90        Self::new_with_bytes(std::fs::read(path).expect("read trace file"))
91    }
92
93    pub fn args(&self) -> &[Vec<u8>] {
94        &self.args
95    }
96
97    fn syscall(&self) -> Option<traces::syscall::Value> {
98        let mut syscalls = self.syscalls.lock();
99        syscalls.pop_front().and_then(|s| s.value)
100    }
101
102    fn io_syscall(&self, buf: &mut [u8], offset: usize, expected_length: Option<usize>) -> IoResult {
103        match self.syscall() {
104            Some(traces::syscall::Value::ReturnWithCode(code)) => {
105                let Ok(e): Result<Error, _> = (code as u64).try_into() else {
106                    return UNEXPECTED_RESULT;
107                };
108                e.into()
109            }
110            Some(traces::syscall::Value::IoData(io_data)) => {
111                if let Some(length) = expected_length {
112                    if offset > length {
113                        return UNEXPECTED_RESULT;
114                    }
115                    if io_data.available_data.len() != length - offset {
116                        return UNEXPECTED_RESULT;
117                    }
118                }
119                let result = if buf.len() > io_data.available_data.len() {
120                    if io_data.additional_length > 0 {
121                        return UNEXPECTED_RESULT;
122                    }
123                    IoResult::FullyLoaded(io_data.available_data.len())
124                } else if (buf.len() < io_data.available_data.len()) || (io_data.additional_length > 0) {
125                    IoResult::PartialLoaded {
126                        loaded: buf.len(),
127                        available: io_data.available_data.len() + io_data.additional_length as usize,
128                    }
129                } else {
130                    // buf.len() == io_data.available_data.len() &&
131                    // io_data.additional_length == 0
132                    IoResult::FullyLoaded(buf.len())
133                };
134                if let Some(read) = result.loaded() {
135                    if read > 0 {
136                        buf[0..read].copy_from_slice(&io_data.available_data[0..read]);
137                    }
138                }
139                result
140            }
141            _ => UNEXPECTED_RESULT,
142        }
143    }
144}
145
146impl SyscallImpls for ProtobufImpls {
147    fn debug(&self, s: &CStr) {
148        (self.debug_printer)(s.to_str().unwrap_or("utf8 error"));
149    }
150
151    fn exit(&self, code: i8) -> ! {
152        exit_with_panic(code);
153    }
154
155    fn load_cell(&self, buf: &mut [u8], offset: usize, _index: usize, _source: Source) -> IoResult {
156        self.io_syscall(buf, offset, None)
157    }
158
159    fn load_cell_by_field(
160        &self,
161        buf: &mut [u8],
162        offset: usize,
163        _index: usize,
164        _source: Source,
165        field: CellField,
166    ) -> IoResult {
167        let expected_length = match field {
168            CellField::Capacity => Some(8),
169            CellField::DataHash => Some(32),
170            CellField::LockHash => Some(32),
171            CellField::TypeHash => Some(32),
172            CellField::OccupiedCapacity => Some(8),
173            _ => None,
174        };
175        self.io_syscall(buf, offset, expected_length)
176    }
177
178    fn load_cell_code(
179        &self,
180        _buf_ptr: *mut u8,
181        _len: usize,
182        _content_offset: usize,
183        _content_size: usize,
184        _index: usize,
185        _source: Source,
186    ) -> Result<(), Error> {
187        panic!("Load cell data as code is not suported!");
188    }
189
190    fn load_cell_data(&self, buf: &mut [u8], offset: usize, _index: usize, _source: Source) -> IoResult {
191        self.io_syscall(buf, offset, None)
192    }
193
194    fn load_header(&self, buf: &mut [u8], offset: usize, _index: usize, _source: Source) -> IoResult {
195        self.io_syscall(buf, offset, None)
196    }
197
198    fn load_header_by_field(
199        &self,
200        buf: &mut [u8],
201        offset: usize,
202        _index: usize,
203        _source: Source,
204        _field: HeaderField,
205    ) -> IoResult {
206        self.io_syscall(buf, offset, Some(8))
207    }
208
209    fn load_input(&self, buf: &mut [u8], offset: usize, _index: usize, _source: Source) -> IoResult {
210        self.io_syscall(buf, offset, None)
211    }
212
213    fn load_input_by_field(
214        &self,
215        buf: &mut [u8],
216        offset: usize,
217        _index: usize,
218        _source: Source,
219        field: InputField,
220    ) -> IoResult {
221        let expected_length = match field {
222            InputField::Since => Some(8),
223            _ => None,
224        };
225        self.io_syscall(buf, offset, expected_length)
226    }
227
228    fn load_script(&self, buf: &mut [u8], offset: usize) -> IoResult {
229        self.io_syscall(buf, offset, None)
230    }
231
232    fn load_script_hash(&self, buf: &mut [u8], offset: usize) -> IoResult {
233        self.io_syscall(buf, offset, Some(32))
234    }
235
236    fn load_transaction(&self, buf: &mut [u8], offset: usize) -> IoResult {
237        self.io_syscall(buf, offset, None)
238    }
239
240    fn load_tx_hash(&self, buf: &mut [u8], offset: usize) -> IoResult {
241        self.io_syscall(buf, offset, Some(32))
242    }
243
244    fn load_witness(&self, buf: &mut [u8], offset: usize, _index: usize, _source: Source) -> IoResult {
245        self.io_syscall(buf, offset, None)
246    }
247
248    fn vm_version(&self) -> u64 {
249        match self.syscall() {
250            Some(traces::syscall::Value::ReturnWithCode(code)) => code as u64,
251            _ => UNEXPECTED,
252        }
253    }
254
255    fn current_cycles(&self) -> u64 {
256        match self.syscall() {
257            Some(traces::syscall::Value::ReturnWithCode(code)) => code as u64,
258            _ => UNEXPECTED,
259        }
260    }
261
262    fn exec(
263        &self,
264        _index: usize,
265        _source: Source,
266        _place: Place,
267        _bounds: Bounds,
268        _argv: &[&CStr],
269    ) -> Result<(), Error> {
270        match self.syscall() {
271            Some(traces::syscall::Value::ReturnWithCode(code)) => {
272                Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
273            }
274            Some(traces::syscall::Value::Terminated(_)) => {
275                self.exit(0);
276            }
277            _ => Err(UNEXPECTED_ERROR),
278        }
279    }
280
281    fn spawn(
282        &self,
283        _index: usize,
284        _source: Source,
285        _place: Place,
286        _bounds: Bounds,
287        _argv: &[&CStr],
288        _inherited_fds: &[u64],
289    ) -> Result<u64, Error> {
290        match self.syscall() {
291            Some(traces::syscall::Value::ReturnWithCode(code)) => {
292                Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
293            }
294            Some(traces::syscall::Value::SuccessOutputData(output)) => Ok(output),
295            _ => Err(UNEXPECTED_ERROR),
296        }
297    }
298
299    fn pipe(&self) -> Result<(u64, u64), Error> {
300        match self.syscall() {
301            Some(traces::syscall::Value::ReturnWithCode(code)) => {
302                Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
303            }
304            Some(traces::syscall::Value::Fds(fds)) => {
305                if fds.fds.len() != 2 {
306                    return Err(UNEXPECTED_ERROR);
307                }
308                Ok((fds.fds[0], fds.fds[1]))
309            }
310            _ => Err(UNEXPECTED_ERROR),
311        }
312    }
313
314    fn inherited_fds(&self, out_fds: &mut [u64]) -> Result<usize, Error> {
315        match self.syscall() {
316            Some(traces::syscall::Value::ReturnWithCode(code)) => {
317                Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
318            }
319            Some(traces::syscall::Value::Fds(fds)) => {
320                let count = core::cmp::min(out_fds.len(), fds.fds.len());
321                out_fds[..count].copy_from_slice(&fds.fds[..count]);
322                Ok(fds.fds.len())
323            }
324            _ => Err(UNEXPECTED_ERROR),
325        }
326    }
327
328    fn read(&self, _fd: u64, buffer: &mut [u8]) -> Result<usize, Error> {
329        match self.syscall() {
330            Some(traces::syscall::Value::ReturnWithCode(code)) => {
331                Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
332            }
333            Some(traces::syscall::Value::IoData(io_data)) => {
334                let read = core::cmp::min(io_data.available_data.len(), buffer.len());
335                buffer[0..read].copy_from_slice(&io_data.available_data[0..read]);
336                Ok(read)
337            }
338            _ => Err(UNEXPECTED_ERROR),
339        }
340    }
341
342    fn write(&self, _fd: u64, _buffer: &[u8]) -> Result<usize, Error> {
343        match self.syscall() {
344            Some(traces::syscall::Value::ReturnWithCode(code)) => {
345                Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
346            }
347            Some(traces::syscall::Value::SuccessOutputData(output)) => Ok(output as usize),
348            _ => Err(UNEXPECTED_ERROR),
349        }
350    }
351
352    fn close(&self, _fd: u64) -> Result<(), Error> {
353        match self.syscall() {
354            Some(traces::syscall::Value::ReturnWithCode(code)) => {
355                if code == 0 {
356                    Ok(())
357                } else {
358                    Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
359                }
360            }
361            _ => Err(UNEXPECTED_ERROR),
362        }
363    }
364
365    fn wait(&self, _pid: u64) -> Result<i8, Error> {
366        match self.syscall() {
367            Some(traces::syscall::Value::ReturnWithCode(code)) => {
368                Err((code as u64).try_into().unwrap_or(UNEXPECTED_ERROR))
369            }
370            Some(traces::syscall::Value::SuccessOutputData(output)) => Ok(output as i8),
371            _ => Err(UNEXPECTED_ERROR),
372        }
373    }
374
375    fn process_id(&self) -> u64 {
376        match self.syscall() {
377            Some(traces::syscall::Value::ReturnWithCode(code)) => code as u64,
378            _ => UNEXPECTED,
379        }
380    }
381
382    fn load_block_extension(&self, buf: &mut [u8], offset: usize, _index: usize, _source: Source) -> IoResult {
383        self.io_syscall(buf, offset, None)
384    }
385}
386
387pub struct ProtobufVmRunnerImpls<M> {
388    inner: ProtobufImpls,
389    _marker: PhantomData<M>,
390}
391
392impl<M> ProtobufVmRunnerImpls<M> {
393    pub fn new_with_bytes<B: AsRef<[u8]>>(bytes: B) -> Option<Self> {
394        ProtobufImpls::new_with_bytes(bytes).map(|inner| Self { inner, _marker: PhantomData })
395    }
396
397    #[cfg(feature = "std")]
398    pub fn new_with_file<P: AsRef<std::path::Path>>(path: P) -> Option<Self> {
399        ProtobufImpls::new_with_file(path).map(|inner| Self { inner, _marker: PhantomData })
400    }
401
402    pub fn args(&self) -> &[Vec<u8>] {
403        self.inner.args()
404    }
405
406    pub fn set_debug_printer<F>(&mut self, f: F)
407    where
408        F: Fn(&str) + Send + Sync + 'static,
409    {
410        self.inner.debug_printer = Box::new(f);
411    }
412}
413
414impl<M> SyscallImpls for ProtobufVmRunnerImpls<M> {
415    fn debug(&self, s: &CStr) {
416        self.inner.debug(s);
417    }
418
419    fn exit(&self, code: i8) -> ! {
420        self.inner.exit(code)
421    }
422
423    fn load_cell(&self, buf: &mut [u8], offset: usize, index: usize, source: Source) -> IoResult {
424        self.inner.load_cell(buf, offset, index, source)
425    }
426
427    fn load_cell_by_field(
428        &self,
429        buf: &mut [u8],
430        offset: usize,
431        index: usize,
432        source: Source,
433        field: CellField,
434    ) -> IoResult {
435        self.inner.load_cell_by_field(buf, offset, index, source, field)
436    }
437
438    fn load_cell_code(
439        &self,
440        buf_ptr: *mut u8,
441        len: usize,
442        content_offset: usize,
443        content_size: usize,
444        index: usize,
445        source: Source,
446    ) -> Result<(), Error> {
447        self.inner.load_cell_code(buf_ptr, len, content_offset, content_size, index, source)
448    }
449
450    fn load_cell_data(&self, buf: &mut [u8], offset: usize, index: usize, source: Source) -> IoResult {
451        self.inner.load_cell_data(buf, offset, index, source)
452    }
453
454    fn load_header(&self, buf: &mut [u8], offset: usize, index: usize, source: Source) -> IoResult {
455        self.inner.load_header(buf, offset, index, source)
456    }
457
458    fn load_header_by_field(
459        &self,
460        buf: &mut [u8],
461        offset: usize,
462        index: usize,
463        source: Source,
464        field: HeaderField,
465    ) -> IoResult {
466        self.inner.load_header_by_field(buf, offset, index, source, field)
467    }
468
469    fn load_input(&self, buf: &mut [u8], offset: usize, index: usize, source: Source) -> IoResult {
470        self.inner.load_input(buf, offset, index, source)
471    }
472
473    fn load_input_by_field(
474        &self,
475        buf: &mut [u8],
476        offset: usize,
477        index: usize,
478        source: Source,
479        field: InputField,
480    ) -> IoResult {
481        self.inner.load_input_by_field(buf, offset, index, source, field)
482    }
483
484    fn load_script(&self, buf: &mut [u8], offset: usize) -> IoResult {
485        self.inner.load_script(buf, offset)
486    }
487
488    fn load_script_hash(&self, buf: &mut [u8], offset: usize) -> IoResult {
489        self.inner.load_script_hash(buf, offset)
490    }
491
492    fn load_transaction(&self, buf: &mut [u8], offset: usize) -> IoResult {
493        self.inner.load_transaction(buf, offset)
494    }
495
496    fn load_tx_hash(&self, buf: &mut [u8], offset: usize) -> IoResult {
497        self.inner.load_tx_hash(buf, offset)
498    }
499
500    fn load_witness(&self, buf: &mut [u8], offset: usize, index: usize, source: Source) -> IoResult {
501        self.inner.load_witness(buf, offset, index, source)
502    }
503
504    fn vm_version(&self) -> u64 {
505        self.inner.vm_version()
506    }
507
508    fn current_cycles(&self) -> u64 {
509        self.inner.current_cycles()
510    }
511
512    fn exec(&self, index: usize, source: Source, place: Place, bounds: Bounds, argv: &[&CStr]) -> Result<(), Error> {
513        self.inner.exec(index, source, place, bounds, argv)
514    }
515
516    fn spawn(
517        &self,
518        index: usize,
519        source: Source,
520        place: Place,
521        bounds: Bounds,
522        argv: &[&CStr],
523        inherited_fds: &[u64],
524    ) -> Result<u64, Error> {
525        self.inner.spawn(index, source, place, bounds, argv, inherited_fds)
526    }
527
528    fn pipe(&self) -> Result<(u64, u64), Error> {
529        self.inner.pipe()
530    }
531
532    fn inherited_fds(&self, out_fds: &mut [u64]) -> Result<usize, Error> {
533        self.inner.inherited_fds(out_fds)
534    }
535
536    fn read(&self, fd: u64, buffer: &mut [u8]) -> Result<usize, Error> {
537        self.inner.read(fd, buffer)
538    }
539
540    fn write(&self, fd: u64, buffer: &[u8]) -> Result<usize, Error> {
541        self.inner.write(fd, buffer)
542    }
543
544    fn close(&self, fd: u64) -> Result<(), Error> {
545        self.inner.close(fd)
546    }
547
548    fn wait(&self, pid: u64) -> Result<i8, Error> {
549        self.inner.wait(pid)
550    }
551
552    fn process_id(&self) -> u64 {
553        self.inner.process_id()
554    }
555
556    fn load_block_extension(&self, buf: &mut [u8], offset: usize, index: usize, source: Source) -> IoResult {
557        self.inner.load_block_extension(buf, offset, index, source)
558    }
559}
560
561impl<M: SupportMachine> CkbvmRunnerImpls<M> for ProtobufVmRunnerImpls<M> {
562    fn fetch_cell_code(
563        &self,
564        _content_offset: usize,
565        content_size: usize,
566        _index: usize,
567        _source: Source,
568    ) -> Result<Vec<u8>, Error> {
569        match self.inner.syscall() {
570            Some(traces::syscall::Value::ReturnWithCode(code)) => {
571                let Ok(e): Result<Error, _> = (code as u64).try_into() else {
572                    return Err(UNEXPECTED_ERROR);
573                };
574                Err(e)
575            }
576            Some(traces::syscall::Value::IoData(io_data)) => {
577                if io_data.additional_length > 0 {
578                    return Err(UNEXPECTED_ERROR);
579                }
580                if io_data.available_data.len() != content_size {
581                    return Err(UNEXPECTED_ERROR);
582                }
583                Ok(io_data.available_data)
584            }
585            _ => Err(UNEXPECTED_ERROR),
586        }
587    }
588}