forc_test/
ecal.rs

1use fuel_vm::{
2    interpreter::EcalHandler,
3    prelude::{Interpreter, RegId},
4};
5
6// ssize_t write(int fd, const void buf[.count], size_t count);
7pub const WRITE_SYSCALL: u64 = 1000;
8
9#[derive(Debug, Clone)]
10pub enum Syscall {
11    Write { fd: u64, bytes: Vec<u8> },
12    Unknown { ra: u64, rb: u64, rc: u64, rd: u64 },
13}
14
15impl Syscall {
16    pub fn apply(&self) {
17        match self {
18            Syscall::Write { fd, bytes } => {
19                let s = std::str::from_utf8(bytes.as_slice()).unwrap();
20
21                use std::io::Write;
22                use std::os::fd::FromRawFd;
23
24                let mut f = unsafe { std::fs::File::from_raw_fd(*fd as i32) };
25                write!(&mut f, "{}", s).unwrap();
26
27                // Dont close the fd
28                std::mem::forget(f);
29            }
30            Syscall::Unknown { ra, rb, rc, rd } => {
31                println!("Unknown ecal: {} {} {} {}", ra, rb, rc, rd);
32            }
33        }
34    }
35}
36
37/// Handle VM `ecal` as syscalls.
38///
39/// The application of the syscalls can be turned off,
40/// guaranteeing total isolation from the outside world.
41///
42/// Capture of the syscalls can be turned on, allowing
43/// its application even after the VM is not running anymore.
44///
45/// Supported syscalls:
46/// 1000 - write(fd: u64, buf: raw_ptr, count: u64) -> u64
47#[derive(Debug, Clone)]
48pub struct EcalSyscallHandler {
49    pub apply: bool,
50    pub capture: bool,
51    pub captured: Vec<Syscall>,
52}
53
54impl Default for EcalSyscallHandler {
55    fn default() -> Self {
56        Self::only_capturing()
57    }
58}
59
60impl EcalSyscallHandler {
61    pub fn only_capturing() -> Self {
62        Self {
63            apply: false,
64            capture: true,
65            captured: vec![],
66        }
67    }
68
69    pub fn only_applying() -> Self {
70        Self {
71            apply: true,
72            capture: false,
73            captured: vec![],
74        }
75    }
76
77    pub fn clear(&mut self) {
78        self.captured.clear();
79    }
80}
81
82impl EcalHandler for EcalSyscallHandler {
83    fn ecal<M, S, Tx>(
84        vm: &mut Interpreter<M, S, Tx, Self>,
85        a: RegId,
86        b: RegId,
87        c: RegId,
88        d: RegId,
89    ) -> fuel_vm::error::SimpleResult<()>
90    where
91        M: fuel_vm::prelude::Memory,
92    {
93        let regs = vm.registers();
94        let syscall = match regs[a.to_u8() as usize] {
95            WRITE_SYSCALL => {
96                let fd = regs[b.to_u8() as usize];
97                let addr = regs[c.to_u8() as usize];
98                let count = regs[d.to_u8() as usize];
99                let bytes = vm.memory().read(addr, count).unwrap().to_vec();
100                Syscall::Write { fd, bytes }
101            }
102            _ => {
103                let ra = regs[a.to_u8() as usize];
104                let rb = regs[b.to_u8() as usize];
105                let rc = regs[c.to_u8() as usize];
106                let rd = regs[d.to_u8() as usize];
107                Syscall::Unknown { ra, rb, rc, rd }
108            }
109        };
110
111        let s = vm.ecal_state_mut();
112
113        if s.apply {
114            syscall.apply();
115        }
116
117        if s.capture {
118            s.captured.push(syscall);
119        }
120
121        Ok(())
122    }
123}
124
125#[test]
126fn ok_capture_ecals() {
127    use fuel_vm::fuel_asm::op::*;
128    use fuel_vm::prelude::*;
129    let vm: Interpreter<MemoryInstance, MemoryStorage, Script, EcalSyscallHandler> = <_>::default();
130
131    let test_input = "Hello, WriteSyscall!";
132    let script_data: Vec<u8> = test_input.bytes().collect();
133    let script = vec![
134        movi(0x20, WRITE_SYSCALL as u32),
135        gtf_args(0x10, 0x00, GTFArgs::ScriptData),
136        movi(0x21, script_data.len().try_into().unwrap()),
137        ecal(0x20, 0x1, 0x10, 0x21),
138        ret(RegId::ONE),
139    ]
140    .into_iter()
141    .collect();
142
143    // Execute transaction
144    let mut client = MemoryClient::from_txtor(vm.into());
145    let tx = TransactionBuilder::script(script, script_data)
146        .script_gas_limit(1_000_000)
147        .add_fee_input()
148        .finalize()
149        .into_checked(Default::default(), &ConsensusParameters::standard())
150        .expect("failed to generate a checked tx");
151    let _ = client.transact(tx);
152
153    // Verify
154    let t: Transactor<MemoryInstance, MemoryStorage, Script, EcalSyscallHandler> = client.into();
155    let syscalls = t.interpreter().ecal_state().captured.clone();
156
157    assert_eq!(syscalls.len(), 1);
158    assert!(
159        matches!(&syscalls[0], Syscall::Write { fd: 1, bytes } if std::str::from_utf8(bytes).unwrap() == test_input)
160    );
161}