1use 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#[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#[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 pub deployer: AccountId,
91 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#[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 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#[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#[derive(serde::Serialize)]
154pub struct Field {
155 pub name: String,
157 pub value: Value,
159 #[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#[derive(serde::Serialize)]
176pub struct Event {
177 pub pallet: String,
179 pub name: String,
181 pub fields: Vec<Field>,
183}
184
185#[derive(serde::Serialize)]
187#[allow(dead_code)]
188pub struct Events(Vec<Event>);
189
190#[derive(serde::Serialize)]
192pub struct DisplayEvents(Vec<Event>);
193
194#[allow(clippy::needless_borrows_for_generic_args)]
195impl DisplayEvents {
196 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 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 pub fn to_json(&self) -> Result<String> {
330 Ok(serde_json::to_string_pretty(self)?)
331 }
332}
333
334#[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}