soroban_env_host/events/
diagnostic.rs

1use std::rc::Rc;
2
3use crate::{
4    events::{
5        internal::{InternalDiagnosticArg, InternalDiagnosticEvent},
6        InternalEvent, InternalEventsBuffer,
7    },
8    host::metered_clone::{MeteredAlloc, MeteredClone, MeteredContainer, MeteredIterator},
9    xdr::{Hash, ScBytes, ScString, ScVal, StringM},
10    Error, Host, HostError, Symbol, SymbolSmall, Val,
11};
12
13#[derive(Clone, Default)]
14pub enum DiagnosticLevel {
15    #[default]
16    None,
17    Debug,
18}
19
20impl Host {
21    fn record_diagnostic_event(
22        &self,
23        contract_id: Option<Hash>,
24        topics: Vec<InternalDiagnosticArg>,
25        args: Vec<InternalDiagnosticArg>,
26    ) -> Result<(), HostError> {
27        self.with_debug_mode(|| {
28            #[cfg(any(test, feature = "recording_mode"))]
29            if *self.try_borrow_suppress_diagnostic_events()? {
30                return Ok(());
31            }
32
33            let de = Rc::metered_new(
34                InternalDiagnosticEvent {
35                    contract_id,
36                    topics,
37                    args,
38                },
39                self,
40            )?;
41            self.with_events_mut(|events| events.record(InternalEvent::Diagnostic(de), self))
42        });
43        Ok(())
44    }
45
46    pub(crate) fn log_diagnostics(&self, msg: &str, args: &[Val]) {
47        self.with_debug_mode(|| {
48            let calling_contract = self.get_current_contract_id_opt_internal()?;
49            let log_sym = SymbolSmall::try_from_str("log")?;
50            Vec::<InternalDiagnosticArg>::charge_bulk_init_cpy(1, self)?;
51            let topics = vec![InternalDiagnosticArg::HostVal(log_sym.to_val())];
52            let msg = ScVal::String(ScString::from(StringM::try_from(
53                self.metered_slice_to_vec(msg.as_bytes())?,
54            )?));
55            let args: Vec<_> = std::iter::once(InternalDiagnosticArg::XdrVal(msg))
56                .chain(args.iter().map(|rv| InternalDiagnosticArg::HostVal(*rv)))
57                .metered_collect(self)?;
58            self.record_diagnostic_event(calling_contract, topics, args)
59        })
60    }
61
62    pub(crate) fn record_err_diagnostics(
63        &self,
64        events: &mut InternalEventsBuffer,
65        error: Error,
66        msg: &str,
67        args: &[Val],
68    ) {
69        self.with_debug_mode(|| {
70            #[cfg(any(test, feature = "recording_mode"))]
71            if *self.try_borrow_suppress_diagnostic_events()? {
72                return Ok(());
73            }
74            let error_sym = SymbolSmall::try_from_str("error")?;
75            let contract_id = self.get_current_contract_id_opt_internal()?;
76            Vec::<InternalDiagnosticArg>::charge_bulk_init_cpy(2, self)?;
77            let topics = vec![
78                InternalDiagnosticArg::HostVal(error_sym.to_val()),
79                InternalDiagnosticArg::HostVal(error.to_val()),
80            ];
81            let msg = ScVal::String(ScString::from(StringM::try_from(
82                self.metered_slice_to_vec(msg.as_bytes())?,
83            )?));
84            let args: Vec<_> = std::iter::once(InternalDiagnosticArg::XdrVal(msg))
85                .chain(args.iter().map(|rv| InternalDiagnosticArg::HostVal(*rv)))
86                .metered_collect(self)?;
87
88            // We do the event-recording ourselves here rather than calling
89            // self.record_system_debug_contract_event because we can/should
90            // only be called with an already-borrowed events buffer (to
91            // insulate against double-faulting).
92            let ce = Rc::metered_new(
93                InternalDiagnosticEvent {
94                    contract_id,
95                    topics,
96                    args,
97                },
98                self,
99            )?;
100            events.record(InternalEvent::Diagnostic(ce), self)
101        })
102    }
103
104    // Emits an event with topic = ["fn_call", called_contract_id,
105    // function_name] and data = [arg1, args2, ...]. Should called prior to
106    // opening a frame for the next call so the calling contract can be inferred
107    // correctly
108    pub(crate) fn fn_call_diagnostics(
109        &self,
110        called_contract_id: &Hash,
111        func: &Symbol,
112        args: &[Val],
113    ) {
114        self.with_debug_mode(|| {
115            let calling_contract = self.get_current_contract_id_opt_internal()?;
116            Vec::<InternalDiagnosticArg>::charge_bulk_init_cpy(3, self)?;
117            let topics = vec![
118                InternalDiagnosticArg::HostVal(SymbolSmall::try_from_str("fn_call")?.into()),
119                InternalDiagnosticArg::XdrVal(ScVal::Bytes(ScBytes::try_from(
120                    self.metered_slice_to_vec(called_contract_id.as_slice())?,
121                )?)),
122                InternalDiagnosticArg::HostVal(func.into()),
123            ];
124            let args = args
125                .iter()
126                .map(|rv| InternalDiagnosticArg::HostVal(*rv))
127                .metered_collect(self)?;
128            self.record_diagnostic_event(calling_contract, topics, args)
129        })
130    }
131
132    // Emits an event with topic = ["fn_return", function_name] and
133    // data = [return_val]
134    pub(crate) fn fn_return_diagnostics(&self, contract_id: &Hash, func: &Symbol, res: &Val) {
135        self.with_debug_mode(|| {
136            Vec::<InternalDiagnosticArg>::charge_bulk_init_cpy(2, self)?;
137            let topics = vec![
138                InternalDiagnosticArg::HostVal(SymbolSmall::try_from_str("fn_return")?.into()),
139                InternalDiagnosticArg::HostVal(func.into()),
140            ];
141            Vec::<InternalDiagnosticArg>::charge_bulk_init_cpy(1, self)?;
142            let args = vec![InternalDiagnosticArg::HostVal(*res)];
143            self.record_diagnostic_event(Some(contract_id.metered_clone(self)?), topics, args)
144        })
145    }
146}