use alloc::{
borrow::ToOwned,
collections::BTreeMap,
format,
string::String,
sync::Arc,
vec::Vec,
};
use fuel_core_types::{
fuel_asm::RegId,
fuel_tx::PanicReason,
fuel_vm::{
Interpreter,
error::SimpleResult,
interpreter::{
EcalHandler,
Memory,
},
},
};
use parking_lot::Mutex;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogSource {
Predicate(usize),
Script,
}
#[derive(Debug, Clone, Default)]
pub struct EcalLogCollector {
pub enabled: bool,
pub logs: Arc<Mutex<BTreeMap<LogSource, Vec<LogEntry>>>>,
}
#[derive(Debug, Clone)]
pub struct LogEntry {
pub pc: u64,
pub fd: u64,
pub message: String,
}
pub const LOG_SYSCALL: u64 = 1000;
pub const STDOUT: u64 = 1;
pub const STDERR: u64 = 2;
impl EcalHandler for EcalLogCollector {
fn ecal<M, S, Tx, V>(
vm: &mut Interpreter<M, S, Tx, Self, V>,
a: RegId,
b: RegId,
c: RegId,
d: RegId,
) -> SimpleResult<()>
where
M: Memory,
{
if !vm.ecal_state().enabled {
return Err(PanicReason::EcalError.into());
}
let regs = vm.registers();
match regs[a] {
LOG_SYSCALL => {
let predicate_idx = vm.context().predicate().map(|p| p.idx());
let pc = regs[RegId::PC];
let fd = regs[b];
let addr = regs[c];
let size = regs[d];
let bytes = vm.memory().read(addr, size)?;
let message = core::str::from_utf8(bytes)
.map_err(|_| PanicReason::EcalError)?
.to_owned();
let source = match predicate_idx {
Some(idx) => LogSource::Predicate(idx),
None => LogSource::Script,
};
vm.ecal_state_mut()
.logs
.lock()
.entry(source)
.or_default()
.push(LogEntry { pc, fd, message });
}
_ => {
return Err(PanicReason::EcalError.into());
}
};
Ok(())
}
}
impl EcalLogCollector {
pub fn maybe_print_logs(self, span: tracing::Span) {
for (source, logs) in self.logs.lock().iter() {
let logs_string = logs
.iter()
.map(|log| {
format!(
"[{}, pc: {:>10x}] {}: {}",
match source {
LogSource::Predicate(idx) => format!("predicate {idx}"),
LogSource::Script => "script".to_owned(),
},
log.pc,
match log.fd {
1 => "stdout".to_owned(),
2 => "stderr".to_owned(),
other => format!("fd {}", other),
},
log.message
)
})
.collect::<Vec<_>>()
.join("\n");
span.in_scope(|| {
tracing::info!("\n{logs_string}");
});
}
}
}