soroban_env_host/events/
mod.rs

1pub(crate) mod diagnostic;
2mod internal;
3pub(crate) mod system_events;
4pub(crate) use internal::{
5    EventError, InternalDiagnosticArg, InternalDiagnosticEvent, InternalEventsBuffer,
6};
7// expose them as pub use for benches
8use crate::{
9    num::{i256_from_pieces, u256_from_pieces},
10    xdr::{
11        ClaimableBalanceId, ContractEventBody, ContractEventType, ContractExecutable, Hash, PoolId,
12        PublicKey::PublicKeyTypeEd25519, ScAddress, ScContractInstance, ScVal,
13    },
14    Error, Host, HostError, Val, VecObject,
15};
16pub(crate) use internal::{InternalContractEvent, InternalEvent};
17
18/// The external representation of a host event.
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct HostEvent {
21    pub event: crate::xdr::ContractEvent,
22    // failed_call keeps track of if the call this event was emitted in failed
23    pub failed_call: bool,
24}
25
26fn display_address(addr: &ScAddress, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27    match addr {
28        ScAddress::Account(acct) => match &acct.0 {
29            PublicKeyTypeEd25519(e) => {
30                let strkey = stellar_strkey::ed25519::PublicKey(e.0);
31                write!(f, "{}", strkey)
32            }
33        },
34        ScAddress::Contract(hash) => {
35            let strkey = stellar_strkey::Contract(hash.0 .0);
36            write!(f, "{}", strkey)
37        }
38        ScAddress::MuxedAccount(muxed_account) => {
39            let strkey = stellar_strkey::ed25519::MuxedAccount {
40                ed25519: muxed_account.ed25519.0,
41                id: muxed_account.id,
42            };
43            write!(f, "{}", strkey)
44        }
45        // Note, that claimable balance and liquidity pool types can't normally
46        // appear in host, so we have the proper rendering for these here just
47        // for consistency (similar to e.g. non-representable ScVal types).
48        ScAddress::ClaimableBalance(ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(cb_id))) => {
49            let strkey = stellar_strkey::ClaimableBalance::V0(*cb_id);
50            write!(f, "{}", strkey)
51        }
52        ScAddress::LiquidityPool(PoolId(Hash(pool_id))) => {
53            let strkey = stellar_strkey::LiquidityPool(*pool_id);
54            write!(f, "{}", strkey)
55        }
56    }
57}
58
59fn display_scval(scv: &ScVal, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60    match scv {
61        ScVal::Bool(v) => write!(f, "{}", v),
62        ScVal::Void => write!(f, "Void"),
63        ScVal::Error(e) => write!(f, "{:?}", Error::from_scerror(e.clone())),
64        ScVal::U32(v) => write!(f, "{}", v),
65        ScVal::I32(v) => write!(f, "{}", v),
66        ScVal::U64(v) => write!(f, "{}", v),
67        ScVal::I64(v) => write!(f, "{}", v),
68        ScVal::Timepoint(v) => write!(f, "TimePoint({})", v.0),
69        ScVal::Duration(v) => write!(f, "Duration({})", v.0),
70        ScVal::U128(v) => write!(f, "{}", u128::from(v)),
71        ScVal::I128(v) => write!(f, "{}", i128::from(v)),
72        ScVal::U256(v) => write!(
73            f,
74            "{}",
75            u256_from_pieces(v.hi_hi, v.hi_lo, v.lo_hi, v.lo_lo)
76        ),
77        ScVal::I256(v) => write!(
78            f,
79            "{}",
80            i256_from_pieces(v.hi_hi, v.hi_lo, v.lo_hi, v.lo_lo)
81        ),
82        ScVal::Bytes(v) => write!(f, "Bytes({})", v.0),
83        ScVal::String(v) => write!(f, "\"{}\"", v.0),
84        ScVal::Symbol(v) => write!(f, "{}", v.0),
85        ScVal::Vec(None) => write!(f, "[]"),
86        ScVal::Vec(Some(vec)) => {
87            write!(f, "[")?;
88            for (i, e) in vec.0.iter().enumerate() {
89                if i != 0 {
90                    write!(f, ", ")?;
91                }
92                display_scval(e, f)?;
93            }
94            write!(f, "]")
95        }
96        ScVal::Map(None) => write!(f, "{{}}"),
97        ScVal::Map(Some(pairs)) => {
98            write!(f, "{{")?;
99            for (i, e) in pairs.0.iter().enumerate() {
100                if i != 0 {
101                    write!(f, ", ")?;
102                }
103                display_scval(&e.key, f)?;
104                write!(f, ": ")?;
105                display_scval(&e.val, f)?;
106            }
107            write!(f, "}}")
108        }
109        ScVal::Address(addr) => display_address(addr, f),
110        ScVal::LedgerKeyContractInstance => write!(f, "LedgerKeyContractInstance"),
111        ScVal::LedgerKeyNonce(n) => {
112            write!(f, "LedgerKeyNonce({})", n.nonce)
113        }
114        ScVal::ContractInstance(ScContractInstance {
115            executable: ContractExecutable::Wasm(hash),
116            ..
117        }) => {
118            write!(f, "ContractInstance(Wasm({}))", hash)
119        }
120        ScVal::ContractInstance(ScContractInstance {
121            executable: ContractExecutable::StellarAsset,
122            ..
123        }) => write!(f, "ContractInstance(StellarAsset)"),
124    }
125}
126
127impl core::fmt::Display for HostEvent {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        if self.failed_call {
130            write!(f, "[Failed {} Event (not emitted)] ", self.event.type_)?;
131        } else {
132            write!(f, "[{} Event] ", self.event.type_)?;
133        }
134        match &self.event.contract_id {
135            None => (),
136            Some(hash) => {
137                let strkey = stellar_strkey::Contract(hash.0 .0);
138                write!(f, "contract:{}, ", strkey)?
139            }
140        }
141        match &self.event.body {
142            ContractEventBody::V0(ceb) => {
143                write!(f, "topics:[")?;
144
145                let mut is_fn_call = false;
146                for (i, topic) in ceb.topics.iter().enumerate() {
147                    if i != 0 {
148                        write!(f, ", ")?;
149                    }
150
151                    // The second topic of the fn_call event is the contract id as ScBytes,
152                    // but we want to display it as a C key instead, so this block
153                    // tries to deduce if the event is the fn_call event.
154                    if i == 1 && is_fn_call {
155                        if let ScVal::Bytes(bytes) = topic {
156                            let try_convert_to_hash =
157                                TryInto::<[u8; 32]>::try_into(bytes.0.clone());
158                            if let Ok(contract_id) = try_convert_to_hash {
159                                let strkey = stellar_strkey::Contract(contract_id);
160                                write!(f, "{}", strkey)?;
161                                continue;
162                            }
163                        }
164                    }
165
166                    display_scval(topic, f)?;
167
168                    if i == 0 {
169                        if let ScVal::Symbol(first_topic_str) = topic {
170                            if first_topic_str.0.as_slice() == "fn_call".as_bytes() {
171                                is_fn_call = true;
172                            }
173                        }
174                    }
175                }
176                write!(f, "], data:")?;
177                display_scval(&ceb.data, f)
178            }
179        }
180    }
181}
182
183#[test]
184fn host_event_contract_id_is_strkey() {
185    use crate::xdr::{
186        AccountId, ContractEvent, ContractEventBody, ContractEventType, ContractEventV0,
187        ContractId, ExtensionPoint, Hash, PublicKey,
188    };
189    let he = HostEvent {
190        event: ContractEvent {
191            ext: ExtensionPoint::V0,
192            contract_id: Some(ContractId(Hash([0; 32]))),
193            type_: ContractEventType::Diagnostic,
194            body: ContractEventBody::V0(ContractEventV0 {
195                topics: vec![ScVal::Address(ScAddress::Account(AccountId(
196                    PublicKey::PublicKeyTypeEd25519([0; 32].into()),
197                )))]
198                .try_into()
199                .unwrap(),
200                data: ScVal::Void,
201            }),
202        },
203        failed_call: false,
204    };
205    assert_eq!(
206        format!("{}", he),
207        "[Diagnostic Event] contract:CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4, topics:[GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF], data:Void"
208    );
209}
210
211/// The external representation of events in the chronological order.
212#[derive(Clone, Debug, Default)]
213pub struct Events(pub Vec<HostEvent>);
214
215impl Host {
216    pub(crate) fn with_events_mut<F, U>(&self, f: F) -> Result<U, HostError>
217    where
218        F: FnOnce(&mut InternalEventsBuffer) -> Result<U, HostError>,
219    {
220        f(&mut *self.try_borrow_events_mut()?)
221    }
222
223    pub fn get_events(&self) -> Result<Events, HostError> {
224        self.try_borrow_events()?.externalize(self)
225    }
226
227    #[cfg(any(test, feature = "testutils"))]
228    pub fn get_contract_events(&self) -> Result<Events, HostError> {
229        let events = self.get_events()?;
230        Ok(Events(
231            events
232                .0
233                .into_iter()
234                .filter(|e| {
235                    matches!(
236                        e.event.type_,
237                        ContractEventType::Contract | ContractEventType::System
238                    )
239                })
240                .collect(),
241        ))
242    }
243
244    #[cfg(any(test, feature = "testutils"))]
245    pub fn get_diagnostic_events(&self) -> Result<Events, HostError> {
246        self.try_borrow_events()?.externalize_diagnostics(self)
247    }
248
249    // Records a contract event.
250    pub(crate) fn record_contract_event(
251        &self,
252        type_: ContractEventType,
253        topics: VecObject,
254        data: Val,
255    ) -> Result<(), HostError> {
256        let ce = InternalContractEvent {
257            type_,
258            contract_id: self.bytesobj_from_internal_contract_id()?,
259            topics,
260            data,
261        };
262        self.with_events_mut(|events| Ok(events.record(InternalEvent::Contract(ce), self)))?
263    }
264}