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