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    blocks::ExtrinsicEvents,
44    events::StaticEvent,
45    ext::{
46        scale_decode::{
47            self,
48            IntoVisitor,
49        },
50        scale_encode,
51    },
52    Config,
53};
54
55/// A custom event emitted by the contract.
56#[derive(
57    scale::Decode,
58    scale::Encode,
59    scale_decode::DecodeAsType,
60    scale_encode::EncodeAsType,
61    Debug,
62)]
63#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
64#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
65pub struct ContractEmitted<AccountId> {
66    pub contract: AccountId,
67    pub data: Vec<u8>,
68}
69
70impl<AccountId> StaticEvent for ContractEmitted<AccountId>
71where
72    AccountId: IntoVisitor,
73{
74    const PALLET: &'static str = "Contracts";
75    const EVENT: &'static str = "ContractEmitted";
76}
77
78/// A contract was successfully instantiated.
79#[derive(
80    Debug,
81    scale::Decode,
82    scale::Encode,
83    scale_decode::DecodeAsType,
84    scale_encode::EncodeAsType,
85)]
86#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
87#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
88pub struct ContractInstantiated<AccountId> {
89    /// Account id of the deployer.
90    pub deployer: AccountId,
91    /// Account id where the contract was instantiated to.
92    pub contract: AccountId,
93}
94
95impl<AccountId> StaticEvent for ContractInstantiated<AccountId>
96where
97    AccountId: IntoVisitor,
98{
99    const PALLET: &'static str = "Contracts";
100    const EVENT: &'static str = "Instantiated";
101}
102
103/// An event triggered by either the `instantiate_with_code` or the `upload_code` call.
104#[derive(
105    Debug,
106    scale::Decode,
107    scale::Encode,
108    scale_decode::DecodeAsType,
109    scale_encode::EncodeAsType,
110)]
111#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
112#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
113pub struct CodeStored<Hash> {
114    /// Hash under which the contract code was stored.
115    pub code_hash: Hash,
116}
117
118impl<Hash> StaticEvent for CodeStored<Hash>
119where
120    Hash: IntoVisitor,
121{
122    const PALLET: &'static str = "Contracts";
123    const EVENT: &'static str = "CodeStored";
124}
125
126/// An event triggered by the `remove_code` call.
127#[derive(
128    Debug,
129    scale::Decode,
130    scale::Encode,
131    scale_decode::DecodeAsType,
132    scale_encode::EncodeAsType,
133)]
134#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
135#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
136pub struct CodeRemoved<Hash, AccountId, Balance> {
137    pub code_hash: Hash,
138    pub deposit_released: Balance,
139    pub remover: AccountId,
140}
141
142impl<Hash, Balance, AccountId> StaticEvent for CodeRemoved<Hash, AccountId, Balance>
143where
144    Hash: IntoVisitor,
145    Balance: IntoVisitor,
146    AccountId: IntoVisitor,
147{
148    const PALLET: &'static str = "Contracts";
149    const EVENT: &'static str = "CodeRemoved";
150}
151
152/// Field that represent data of an event from invoking a contract extrinsic.
153#[derive(serde::Serialize)]
154pub struct Field {
155    /// name of a field
156    pub name: String,
157    /// value of a field
158    pub value: Value,
159    /// The name of a type as defined in the pallet Source Code
160    #[serde(skip_serializing)]
161    pub type_name: Option<String>,
162}
163
164impl Field {
165    pub fn new(name: String, value: Value, type_name: Option<String>) -> Self {
166        Field {
167            name,
168            value,
169            type_name,
170        }
171    }
172}
173
174/// An event produced from invoking a contract extrinsic.
175#[derive(serde::Serialize)]
176pub struct Event {
177    /// name of a pallet
178    pub pallet: String,
179    /// name of the event
180    pub name: String,
181    /// data associated with the event
182    pub fields: Vec<Field>,
183}
184
185/// Events produced from invoking a contract extrinsic.
186#[derive(serde::Serialize)]
187#[allow(dead_code)]
188pub struct Events(Vec<Event>);
189
190/// Displays events produced from invoking a contract extrinsic.
191#[derive(serde::Serialize)]
192pub struct DisplayEvents(Vec<Event>);
193
194#[allow(clippy::needless_borrows_for_generic_args)]
195impl DisplayEvents {
196    /// Parses events and returns an object which can be serialised
197    pub fn from_events<C: Config, E: Environment>(
198        result: &ExtrinsicEvents<C>,
199        transcoder: Option<&ContractMessageTranscoder>,
200        subxt_metadata: &subxt::Metadata,
201    ) -> Result<DisplayEvents>
202    where
203        C::AccountId: IntoVisitor,
204    {
205        let mut events: Vec<Event> = vec![];
206
207        let events_transcoder = TranscoderBuilder::new(subxt_metadata.types())
208            .with_default_custom_type_transcoders()
209            .done();
210
211        for event in result.iter() {
212            let event = event?;
213            tracing::debug!(
214                "displaying event {}:{}",
215                event.pallet_name(),
216                event.variant_name()
217            );
218
219            let event_metadata = event.event_metadata();
220            let event_fields = &event_metadata.variant.fields;
221
222            let mut event_entry = Event {
223                pallet: event.pallet_name().to_string(),
224                name: event.variant_name().to_string(),
225                fields: vec![],
226            };
227
228            let event_data = &mut event.field_bytes();
229            let event_sig_topic = event.topics().iter().next();
230            let mut unnamed_field_name = 0;
231            for field_metadata in event_fields {
232                if <ContractEmitted<C::AccountId> as StaticEvent>::is_event(
233                    event.pallet_name(),
234                    event.variant_name(),
235                ) && field_metadata.name == Some("data".to_string())
236                {
237                    tracing::debug!("event data: {:?}", hex::encode(&event_data));
238                    let field = contract_event_data_field::<C>(
239                        transcoder,
240                        field_metadata,
241                        event_sig_topic,
242                        event_data,
243                    )?;
244                    event_entry.fields.push(field);
245                } else {
246                    let field_name = field_metadata
247                        .name
248                        .as_ref()
249                        .map(|s| s.to_string())
250                        .unwrap_or_else(|| {
251                            let name = unnamed_field_name.to_string();
252                            unnamed_field_name += 1;
253                            name
254                        });
255
256                    let decoded_field = events_transcoder.decode(
257                        subxt_metadata.types(),
258                        field_metadata.ty.id,
259                        event_data,
260                    )?;
261                    let field = Field::new(
262                        field_name,
263                        decoded_field,
264                        field_metadata.type_name.as_ref().map(|s| s.to_string()),
265                    );
266                    event_entry.fields.push(field);
267                }
268            }
269            events.push(event_entry);
270        }
271
272        Ok(DisplayEvents(events))
273    }
274
275    /// Displays events in a human readable format
276    pub fn display_events<E: Environment>(
277        &self,
278        verbosity: Verbosity,
279        token_metadata: &TokenMetadata,
280    ) -> Result<String>
281    where
282        E::Balance: Display + From<u128>,
283    {
284        let event_field_indent: usize = DEFAULT_KEY_COL_WIDTH - 3;
285        let mut out = format!(
286            "{:>width$}\n",
287            "Events".bright_purple().bold(),
288            width = DEFAULT_KEY_COL_WIDTH
289        );
290        for event in &self.0 {
291            let _ = writeln!(
292                out,
293                "{:>width$} {} ➜ {}",
294                "Event".bright_green().bold(),
295                event.pallet.bright_white(),
296                event.name.bright_white().bold(),
297                width = DEFAULT_KEY_COL_WIDTH
298            );
299
300            for field in &event.fields {
301                if verbosity.is_verbose() {
302                    let mut value: String = field.value.to_string();
303                    if field.type_name == Some("T::Balance".to_string())
304                        || field.type_name == Some("BalanceOf<T>".to_string())
305                    {
306                        if let Value::UInt(balance) = field.value {
307                            value = BalanceVariant::<E::Balance>::from(
308                                balance,
309                                Some(token_metadata),
310                            )?
311                            .to_string();
312                        }
313                    }
314                    let _ = writeln!(
315                        out,
316                        "{:width$}{}: {}",
317                        "",
318                        field.name.bright_white(),
319                        value,
320                        width = event_field_indent,
321                    );
322                }
323            }
324        }
325        Ok(out)
326    }
327
328    /// Returns an event result in json format
329    pub fn to_json(&self) -> Result<String> {
330        Ok(serde_json::to_string_pretty(self)?)
331    }
332}
333
334/// Construct the contract event data field, attempting to decode the event using the
335/// [`ContractMessageTranscoder`] if available.
336#[allow(clippy::needless_borrows_for_generic_args)]
337fn contract_event_data_field<C: Config>(
338    transcoder: Option<&ContractMessageTranscoder>,
339    field_metadata: &scale_info::Field<PortableForm>,
340    event_sig_topic: Option<&C::Hash>,
341    event_data: &mut &[u8],
342) -> Result<Field> {
343    let event_value = if let Some(transcoder) = transcoder {
344        if let Some(event_sig_topic) = event_sig_topic {
345            match transcoder.decode_contract_event(event_sig_topic, event_data) {
346                Ok(contract_event) => contract_event,
347                Err(err) => {
348                    tracing::warn!(
349                        "Decoding contract event failed: {:?}. It might have come from another contract.",
350                        err
351                    );
352                    Value::Hex(Hex::from_str(&hex::encode(&event_data))?)
353                }
354            }
355        } else {
356            tracing::info!("Anonymous event not decoded. Data displayed as raw hex.");
357            Value::Hex(Hex::from_str(&hex::encode(event_data))?)
358        }
359    } else {
360        Value::Hex(Hex::from_str(&hex::encode(event_data))?)
361    };
362    Ok(Field::new(
363        String::from("data"),
364        event_value,
365        field_metadata.type_name.as_ref().map(|s| s.to_string()),
366    ))
367}