contract_extrinsics/
events.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17use super::{
18    BalanceVariant,
19    TokenMetadata,
20};
21use crate::DEFAULT_KEY_COL_WIDTH;
22use colored::Colorize as _;
23use contract_build::Verbosity;
24use contract_transcode::{
25    ContractMessageTranscoder,
26    Hex,
27    TranscoderBuilder,
28    Value,
29};
30
31use anyhow::Result;
32use ink_env::Environment;
33use scale_info::form::PortableForm;
34use std::{
35    fmt::{
36        Display,
37        Write,
38    },
39    str::FromStr,
40};
41use subxt::{
42    self,
43    Config,
44    blocks::ExtrinsicEvents,
45    config::HashFor,
46    events::StaticEvent,
47    ext::{
48        scale_decode::{
49            self,
50            IntoVisitor,
51        },
52        scale_encode,
53    },
54    utils::{
55        H160,
56        H256,
57    },
58};
59
60/// A custom event emitted by the contract.
61#[derive(
62    scale::Decode,
63    scale::Encode,
64    scale_decode::DecodeAsType,
65    scale_encode::EncodeAsType,
66    Debug,
67)]
68#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
69#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
70pub struct ContractEmitted {
71    /// The contract that emitted the event.
72    contract: H160,
73    /// Data supplied by the contract. Metadata generated during contract compilation
74    /// is needed to decode it.
75    data: Vec<u8>,
76    // A list of topics used to index the event.
77    // Number of topics is capped by [`limits::NUM_EVENT_TOPICS`].
78    topics: Vec<H256>,
79}
80
81impl StaticEvent for ContractEmitted {
82    const PALLET: &'static str = "Revive";
83    const EVENT: &'static str = "ContractEmitted";
84}
85
86/// Contract deployed by deployer at the specified address.
87#[derive(
88    scale::Decode,
89    scale::Encode,
90    scale_decode::DecodeAsType,
91    scale_encode::EncodeAsType,
92    Debug,
93)]
94#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
95#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
96pub struct ContractInstantiated {
97    /// Address of the deployer.
98    pub deployer: H160,
99    /// Address where the contract was instantiated to.
100    pub contract: H160,
101}
102
103impl StaticEvent for ContractInstantiated {
104    const PALLET: &'static str = "Revive";
105    const EVENT: &'static str = "Instantiated";
106}
107
108/// Field that represent data of an event from invoking a contract extrinsic.
109#[derive(serde::Serialize)]
110pub struct Field {
111    /// name of a field
112    pub name: String,
113    /// value of a field
114    pub value: Value,
115    /// The name of a type as defined in the pallet Source Code
116    #[serde(skip_serializing)]
117    pub type_name: Option<String>,
118}
119
120impl Field {
121    pub fn new(name: String, value: Value, type_name: Option<String>) -> Self {
122        Field {
123            name,
124            value,
125            type_name,
126        }
127    }
128}
129
130/// An event produced from invoking a contract extrinsic.
131#[derive(serde::Serialize)]
132pub struct Event {
133    /// name of a pallet
134    pub pallet: String,
135    /// name of the event
136    pub name: String,
137    /// data associated with the event
138    pub fields: Vec<Field>,
139}
140
141/// Events produced from invoking a contract extrinsic.
142#[derive(serde::Serialize)]
143#[allow(dead_code)]
144pub struct Events(Vec<Event>);
145
146/// Displays events produced from invoking a contract extrinsic.
147#[derive(serde::Serialize)]
148pub struct DisplayEvents(Vec<Event>);
149
150#[allow(clippy::needless_borrows_for_generic_args)]
151impl DisplayEvents {
152    /// Parses events and returns an object which can be serialised
153    pub fn from_events<C: Config, E: Environment>(
154        result: &ExtrinsicEvents<C>,
155        transcoder: Option<&ContractMessageTranscoder>,
156        subxt_metadata: &subxt::Metadata,
157    ) -> Result<DisplayEvents>
158    where
159        C::AccountId: IntoVisitor,
160    {
161        let mut events: Vec<Event> = vec![];
162
163        let events_transcoder = TranscoderBuilder::new(subxt_metadata.types())
164            .with_default_custom_type_transcoders()
165            .done();
166
167        for event in result.iter() {
168            let event = event?;
169            tracing::debug!(
170                "displaying event {}:{}",
171                event.pallet_name(),
172                event.variant_name()
173            );
174
175            let event_metadata = event.event_metadata();
176            let event_fields = &event_metadata.variant.fields;
177
178            let mut event_entry = Event {
179                pallet: event.pallet_name().to_string(),
180                name: event.variant_name().to_string(),
181                fields: vec![],
182            };
183
184            let event_data = &mut event.field_bytes();
185            let event_sig_topic = event.topics().iter().next();
186            let mut unnamed_field_name = 0;
187            for field_metadata in event_fields {
188                if <ContractEmitted as StaticEvent>::is_event(
189                    event.pallet_name(),
190                    event.variant_name(),
191                ) && (field_metadata.name == Some("data".to_string())
192                    || field_metadata.name == Some("topics".to_string()))
193                {
194                    tracing::debug!("event data: {:?}", hex::encode(&event_data));
195                    let field = contract_event_vec_field::<C>(
196                        transcoder,
197                        field_metadata,
198                        event_sig_topic,
199                        event_data,
200                        field_metadata.name.as_ref().expect("must exist"),
201                    )?;
202                    event_entry.fields.push(field);
203                } else {
204                    let field_name = field_metadata
205                        .name
206                        .as_ref()
207                        .map(|s| s.to_string())
208                        .unwrap_or_else(|| {
209                            let name = unnamed_field_name.to_string();
210                            unnamed_field_name += 1;
211                            name
212                        });
213
214                    let decoded_field = events_transcoder.decode(
215                        subxt_metadata.types(),
216                        field_metadata.ty.id,
217                        event_data,
218                    )?;
219                    let field = Field::new(
220                        field_name,
221                        decoded_field,
222                        field_metadata.type_name.as_ref().map(|s| s.to_string()),
223                    );
224                    event_entry.fields.push(field);
225                }
226            }
227            events.push(event_entry);
228        }
229
230        Ok(DisplayEvents(events))
231    }
232
233    /// Displays events in a human readable format
234    pub fn display_events<E: Environment>(
235        &self,
236        verbosity: Verbosity,
237        token_metadata: &TokenMetadata,
238    ) -> Result<String>
239    where
240        E::Balance: Display + From<u128>,
241    {
242        let event_field_indent: usize = DEFAULT_KEY_COL_WIDTH - 3;
243        let mut out = format!(
244            "{:>width$}\n",
245            "Events".bright_purple().bold(),
246            width = DEFAULT_KEY_COL_WIDTH
247        );
248        for event in &self.0 {
249            let _ = writeln!(
250                out,
251                "{:>width$} {} ➜ {}",
252                "Event".bright_green().bold(),
253                event.pallet.bright_white(),
254                event.name.bright_white().bold(),
255                width = DEFAULT_KEY_COL_WIDTH
256            );
257
258            for field in &event.fields {
259                if verbosity.is_verbose() {
260                    let mut value: String = field.value.to_string();
261                    if (field.type_name == Some("T::Balance".to_string())
262                        || field.type_name == Some("BalanceOf<T>".to_string()))
263                        && let Value::UInt(balance) = field.value
264                    {
265                        value = BalanceVariant::<E::Balance>::from(
266                            balance,
267                            Some(token_metadata),
268                        )?
269                        .to_string();
270                    }
271                    if field.type_name == Some("H160".to_string()) {
272                        // Value is in the format `H160([bytes])`.
273                        // Extract the byte array between the brackets and convert it to a
274                        // hexadecimal string.
275                        if let (Some(start), Some(end)) =
276                            (value.find('['), value.find(']'))
277                        {
278                            let byte_str = &value[start + 1..end];
279                            let bytes: Vec<u8> = byte_str
280                                .split(", ")
281                                .filter_map(|s| s.parse::<u8>().ok())
282                                .collect();
283                            let h160_value = H160::from_slice(&bytes);
284                            value = format!("0x{}", hex::encode(h160_value.as_bytes()));
285                        }
286                    }
287                    let _ = writeln!(
288                        out,
289                        "{:width$}{}: {}",
290                        "",
291                        field.name.bright_white(),
292                        value,
293                        width = event_field_indent,
294                    );
295                }
296            }
297        }
298        Ok(out)
299    }
300
301    /// Returns an event result in json format
302    pub fn to_json(&self) -> Result<String> {
303        Ok(serde_json::to_string_pretty(self)?)
304    }
305}
306
307/// Construct the contract event data field, attempting to decode the event using the
308/// [`ContractMessageTranscoder`] if available.
309#[allow(clippy::needless_borrows_for_generic_args)]
310fn contract_event_vec_field<C: Config>(
311    transcoder: Option<&ContractMessageTranscoder>,
312    field_metadata: &scale_info::Field<PortableForm>,
313    event_sig_topic: Option<&HashFor<C>>,
314    event_data: &mut &[u8],
315    field_name: &String,
316) -> Result<Field> {
317    let event_value = if let Some(transcoder) = transcoder {
318        if let Some(event_sig_topic) = event_sig_topic {
319            match transcoder.decode_contract_event(event_sig_topic, event_data) {
320                Ok(contract_event) => contract_event,
321                Err(err) => {
322                    tracing::warn!(
323                        "Decoding contract event failed: {:?}. It might have come from another contract.",
324                        err
325                    );
326                    Value::Hex(Hex::from_str(&hex::encode(&event_data))?)
327                }
328            }
329        } else {
330            tracing::info!("Anonymous event not decoded. Data displayed as raw hex.");
331            Value::Hex(Hex::from_str(&hex::encode(event_data))?)
332        }
333    } else {
334        Value::Hex(Hex::from_str(&hex::encode(event_data))?)
335    };
336    Ok(Field::new(
337        field_name.to_string(),
338        event_value,
339        field_metadata.type_name.as_ref().map(|s| s.to_string()),
340    ))
341}