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::Encode;
34use scale_info::form::PortableForm;
35use std::{
36 fmt::{
37 Display,
38 Write,
39 },
40 str::FromStr,
41};
42use subxt::{
43 self,
44 Config,
45 blocks::ExtrinsicEvents,
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#[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 contract: H160,
73 data: Vec<u8>,
76 topics: Vec<H256>,
79}
80
81impl StaticEvent for ContractEmitted {
82 const PALLET: &'static str = "Revive";
83 const EVENT: &'static str = "ContractEmitted";
84}
85
86#[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 pub deployer: H160,
99 pub contract: H160,
101}
102
103impl StaticEvent for ContractInstantiated {
104 const PALLET: &'static str = "Revive";
105 const EVENT: &'static str = "Instantiated";
106}
107
108#[derive(serde::Serialize)]
110pub struct Field {
111 pub name: String,
113 pub value: Value,
115 #[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#[derive(serde::Serialize)]
132pub struct Event {
133 pub pallet: String,
135 pub name: String,
137 pub fields: Vec<Field>,
139}
140
141#[derive(serde::Serialize)]
143#[allow(dead_code)]
144pub struct Events(Vec<Event>);
145
146#[derive(serde::Serialize)]
148pub struct DisplayEvents(Vec<Event>);
149
150#[allow(clippy::needless_borrows_for_generic_args)]
151impl DisplayEvents {
152 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 contract_emitted = if <ContractEmitted as StaticEvent>::is_event(
187 event.pallet_name(),
188 event.variant_name(),
189 ) {
190 event.as_event::<ContractEmitted>().ok().flatten()
191 } else {
192 None
193 };
194
195 let event_data = &mut event.field_bytes();
196 tracing::debug!("event data: {:?}", hex::encode(&event_data));
197 let mut unnamed_field_name = 0;
198 for field_metadata in event_fields {
199 if let Some(ref ce) = contract_emitted {
200 if field_metadata.name == Some("data".to_string()) {
201 let mut encoded_data =
205 scale::Compact(ce.data.len() as u32).encode();
206 encoded_data.extend_from_slice(&ce.data);
207 let mut data_slice = encoded_data.as_slice();
208 let field = contract_event_vec_field(
209 transcoder,
210 field_metadata,
211 ce.topics.first(),
212 &mut data_slice,
213 field_metadata.name.as_ref().expect("must exist"),
214 )?;
215 event_entry.fields.push(field);
216 } else if field_metadata.name == Some("topics".to_string()) {
217 continue;
220 } else {
221 let field_name = field_metadata
224 .name
225 .as_ref()
226 .map(|s| s.to_string())
227 .unwrap_or_else(|| {
228 let name = unnamed_field_name.to_string();
229 unnamed_field_name += 1;
230 name
231 });
232
233 let decoded_field = events_transcoder.decode(
234 subxt_metadata.types(),
235 field_metadata.ty.id,
236 event_data,
237 )?;
238 let field = Field::new(
239 field_name,
240 decoded_field,
241 field_metadata.type_name.as_ref().map(|s| s.to_string()),
242 );
243 event_entry.fields.push(field);
244 }
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 && let Value::UInt(balance) = field.value
306 {
307 value = BalanceVariant::<E::Balance>::from(
308 balance,
309 Some(token_metadata),
310 )?
311 .to_string();
312 }
313 if field.type_name == Some("H160".to_string()) {
314 if let (Some(start), Some(end)) =
318 (value.find('['), value.find(']'))
319 {
320 let byte_str = &value[start + 1..end];
321 let bytes: Vec<u8> = byte_str
322 .split(", ")
323 .filter_map(|s| s.parse::<u8>().ok())
324 .collect();
325 let h160_value = H160::from_slice(&bytes);
326 value = format!("0x{}", hex::encode(h160_value.as_bytes()));
327 }
328 }
329 let _ = writeln!(
330 out,
331 "{:width$}{}: {}",
332 "",
333 field.name.bright_white(),
334 value,
335 width = event_field_indent,
336 );
337 }
338 }
339 }
340 Ok(out)
341 }
342
343 pub fn to_json(&self) -> Result<String> {
345 Ok(serde_json::to_string_pretty(self)?)
346 }
347}
348
349#[allow(clippy::needless_borrows_for_generic_args)]
352fn contract_event_vec_field(
353 transcoder: Option<&ContractMessageTranscoder>,
354 field_metadata: &scale_info::Field<PortableForm>,
355 event_sig_topic: Option<&H256>,
356 event_data: &mut &[u8],
357 field_name: &String,
358) -> Result<Field> {
359 let event_value = if let Some(transcoder) = transcoder {
360 if let Some(event_sig_topic) = event_sig_topic {
361 match transcoder.decode_contract_event(event_sig_topic, event_data) {
362 Ok(contract_event) => contract_event,
363 Err(err) => {
364 tracing::warn!(
365 "Decoding contract event failed: {:?}. It might have come from another contract.",
366 err
367 );
368 Value::Hex(Hex::from_str(&hex::encode(&event_data))?)
369 }
370 }
371 } else {
372 tracing::info!("Anonymous event not decoded. Data displayed as raw hex.");
373 Value::Hex(Hex::from_str(&hex::encode(event_data))?)
374 }
375 } else {
376 Value::Hex(Hex::from_str(&hex::encode(event_data))?)
377 };
378 Ok(Field::new(
379 field_name.to_string(),
380 event_value,
381 field_metadata.type_name.as_ref().map(|s| s.to_string()),
382 ))
383}
384
385#[cfg(all(test, feature = "std"))]
386#[allow(unused_imports, unexpected_cfgs)]
388mod tests {
389 use super::*;
390 use anyhow::Result;
391 use ink::{
392 metadata::InkProject,
393 prelude::vec::Vec,
394 };
395 use ink_env::Event as _;
396 use scale::Encode;
397 use scale_info::{
398 Field as ScaleField,
399 IntoPortable as _,
400 };
401 use subxt::utils::H256;
402
403 #[allow(clippy::extra_unused_lifetimes, non_local_definitions)]
404 #[ink::contract]
405 pub mod event_contract {
406 #[ink(storage)]
407 pub struct EventHarness {}
408
409 #[ink(event)]
410 pub struct BalanceChanged {
411 pub value: bool,
412 pub amount: u32,
413 }
414
415 impl Default for EventHarness {
416 fn default() -> Self {
417 Self::new()
418 }
419 }
420
421 impl EventHarness {
422 #[ink(constructor)]
423 pub fn new() -> Self {
424 Self {}
425 }
426
427 #[ink(message)]
428 pub fn touch(&self) {}
429 }
430 }
431
432 fn contract_data_field_metadata() -> scale_info::Field<PortableForm> {
433 let meta_field = ScaleField::new(
434 Some("data"),
435 scale_info::MetaType::new::<Vec<u8>>(),
436 Some("Vec<u8>"),
437 vec![],
438 );
439 let mut registry = scale_info::Registry::new();
440 meta_field.into_portable(&mut registry)
441 }
442
443 fn generate_metadata() -> InkProject {
444 unsafe extern "Rust" {
445 fn __ink_generate_metadata() -> InkProject;
446 }
447 unsafe { __ink_generate_metadata() }
448 }
449
450 #[test]
452 fn contract_event_without_transcoder_returns_hex() {
453 let field_meta = contract_data_field_metadata();
454
455 let event_data_bytes = vec![0x04, 0x00, 0x01, 0x02, 0x03];
457 let mut event_data = event_data_bytes.as_slice();
458
459 let result = contract_event_vec_field(
460 None,
461 &field_meta,
462 None,
463 &mut event_data,
464 &"data".to_string(),
465 );
466
467 assert!(result.is_ok(), "decoding without transcoder should succeed");
468 let field = result.unwrap();
469
470 match field.value {
471 Value::Hex(_) => {}
472 other => panic!("expected raw hex fallback, got {other:?}"),
473 }
474 }
475
476 #[test]
478 fn contract_event_with_transcoder_decodes_payload() -> Result<()> {
479 let field_meta = contract_data_field_metadata();
480
481 let metadata = generate_metadata();
482 let transcoder = ContractMessageTranscoder::new(metadata);
483
484 let payload = event_contract::BalanceChanged {
485 value: true,
486 amount: 7u32,
487 };
488 let payload_bytes = payload.encode();
489 let mut encoded_data = scale::Compact(payload_bytes.len() as u32).encode();
490 encoded_data.extend_from_slice(&payload_bytes);
491 let mut data_slice = encoded_data.as_slice();
492
493 let signature_topic_bytes = event_contract::BalanceChanged::SIGNATURE_TOPIC
494 .expect("event has a signature topic");
495 let signature_topic = H256::from(signature_topic_bytes);
496
497 let field = contract_event_vec_field(
498 Some(&transcoder),
499 &field_meta,
500 Some(&signature_topic),
501 &mut data_slice,
502 &"data".to_string(),
503 )?;
504
505 let Value::Map(map) = field.value else {
506 panic!("expected decoded event to be a map");
507 };
508 let decoded_value = map
509 .get_by_str("value")
510 .expect("decoded event contains `value` field");
511 assert_eq!(decoded_value, &Value::Bool(true));
512 let decoded_amount = map
513 .get_by_str("amount")
514 .expect("decoded event contains `amount` field");
515 assert_eq!(decoded_amount, &Value::UInt(7u128));
516
517 Ok(())
518 }
519}