1use fuel_vm::{
2 interpreter::EcalHandler,
3 prelude::{Interpreter, RegId},
4};
5
6pub 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 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#[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 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 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}