1use std::collections::BTreeMap;
13
14use serde::{Deserialize, Serialize};
15use serde_json::{json, Value};
16use soldb_core::{SoldbError, SoldbResult};
17
18use crate::abi::keccak256;
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct EventParam {
22 #[serde(default)]
23 pub name: String,
24 #[serde(rename = "type")]
25 pub ty: String,
26 #[serde(default)]
27 pub indexed: bool,
28 #[serde(default, skip_serializing_if = "Vec::is_empty")]
29 pub components: Vec<EventParam>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct EventAbi {
34 pub name: String,
35 #[serde(default)]
36 pub inputs: Vec<EventParam>,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub struct DecodedEventArg {
41 pub name: String,
42 #[serde(rename = "type")]
43 pub ty: String,
44 pub value: Value,
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct DecodedEvent {
49 pub event: String,
50 pub signature: String,
51 pub contract_name: Option<String>,
52 pub args: Vec<DecodedEventArg>,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct EventRegistryEntry {
57 pub contract_name: Option<String>,
58 pub topic: String,
59 pub signature: String,
60 pub event: EventAbi,
61}
62
63#[derive(Debug, Clone, Default, PartialEq, Eq)]
64pub struct EventRegistry {
65 entries: BTreeMap<String, EventRegistryEntry>,
66}
67
68impl EventRegistry {
69 pub fn insert(&mut self, contract_name: Option<String>, event: EventAbi) -> SoldbResult<()> {
70 let signature = event_signature(&event);
71 let topic = event_topic(&event);
72 self.entries.insert(
73 topic.clone(),
74 EventRegistryEntry {
75 contract_name,
76 topic,
77 signature,
78 event,
79 },
80 );
81 Ok(())
82 }
83
84 pub fn is_empty(&self) -> bool {
85 self.entries.is_empty()
86 }
87
88 pub fn decode_log(&self, topics: &[String], data: &str) -> Option<DecodedEvent> {
89 let topic = topics.first()?.to_ascii_lowercase();
90 let entry = self.entries.get(&topic)?;
91 decode_event(entry, topics, data).ok()
92 }
93}
94
95pub fn parse_event_abis(input: &str) -> SoldbResult<Vec<EventAbi>> {
96 let value = serde_json::from_str::<Value>(input)
97 .map_err(|error| SoldbError::Message(format!("Invalid ABI JSON: {error}")))?;
98 let items = if let Some(items) = value.as_array() {
99 items
100 } else {
101 value.get("abi").and_then(Value::as_array).ok_or_else(|| {
102 SoldbError::Message("ABI JSON must be an array or contain an 'abi' array".to_owned())
103 })?
104 };
105
106 let mut events = Vec::new();
107 for item in items {
108 if item.get("type").and_then(Value::as_str) != Some("event") {
109 continue;
110 }
111 let Some(name) = item.get("name").and_then(Value::as_str) else {
112 continue;
113 };
114 let inputs = item
115 .get("inputs")
116 .and_then(Value::as_array)
117 .map(|inputs| {
118 inputs
119 .iter()
120 .filter_map(parse_event_param)
121 .collect::<Vec<_>>()
122 })
123 .unwrap_or_default();
124 events.push(EventAbi {
125 name: name.to_owned(),
126 inputs,
127 });
128 }
129
130 Ok(events)
131}
132
133pub fn event_signature(event: &EventAbi) -> String {
134 let input_types = event
135 .inputs
136 .iter()
137 .map(canonical_event_type)
138 .collect::<Vec<_>>()
139 .join(",");
140 format!("{}({input_types})", event.name)
141}
142
143pub fn event_topic(event: &EventAbi) -> String {
144 let signature = event_signature(event);
145 format!("0x{}", bytes_to_hex(&keccak256(signature.as_bytes())))
146}
147
148fn parse_event_param(value: &Value) -> Option<EventParam> {
149 let components = value
150 .get("components")
151 .and_then(Value::as_array)
152 .map(|components| {
153 components
154 .iter()
155 .filter_map(parse_event_param)
156 .collect::<Vec<_>>()
157 })
158 .unwrap_or_default();
159
160 Some(EventParam {
161 name: value
162 .get("name")
163 .and_then(Value::as_str)
164 .unwrap_or_default()
165 .to_owned(),
166 ty: value.get("type")?.as_str()?.to_owned(),
167 indexed: value
168 .get("indexed")
169 .and_then(Value::as_bool)
170 .unwrap_or(false),
171 components,
172 })
173}
174
175fn decode_event(
176 entry: &EventRegistryEntry,
177 topics: &[String],
178 data: &str,
179) -> SoldbResult<DecodedEvent> {
180 let data_bytes = parse_hex_data(data)?;
181 let mut next_topic = 1;
182 let mut next_data_offset = 0;
183 let mut args = Vec::with_capacity(entry.event.inputs.len());
184
185 for input in &entry.event.inputs {
186 let value = if input.indexed {
187 let topic = topics.get(next_topic).ok_or_else(|| {
188 SoldbError::Message(format!("Missing indexed topic for {}", input.name))
189 })?;
190 next_topic += 1;
191 decode_indexed_topic(input, topic)?
192 } else {
193 let head_words = event_param_head_words(input)?;
194 let value = decode_param_at(input, &data_bytes, next_data_offset, 0)?;
195 next_data_offset += head_words * 32;
196 value
197 };
198 args.push(DecodedEventArg {
199 name: input.name.clone(),
200 ty: canonical_event_type(input),
201 value,
202 });
203 }
204
205 Ok(DecodedEvent {
206 event: entry.event.name.clone(),
207 signature: entry.signature.clone(),
208 contract_name: entry.contract_name.clone(),
209 args,
210 })
211}
212
213fn canonical_event_type(input: &EventParam) -> String {
214 if let Some(suffix) = input.ty.strip_prefix("tuple") {
215 let components = input
216 .components
217 .iter()
218 .map(canonical_event_type)
219 .collect::<Vec<_>>()
220 .join(",");
221 return format!("({components}){suffix}");
222 }
223 input.ty.clone()
224}
225
226fn decode_indexed_topic(input: &EventParam, topic: &str) -> SoldbResult<Value> {
227 if indexed_param_is_hashed(input) {
228 let topic = normalize_word(topic)?;
229 return Ok(json!(format!("0x{topic}")));
230 }
231 decode_static_word(&input.ty, topic)
232}
233
234fn indexed_param_is_hashed(input: &EventParam) -> bool {
235 input.ty == "string"
236 || input.ty == "bytes"
237 || parse_array_type(&input.ty).is_some()
238 || is_tuple_param(input)
239}
240
241fn decode_param_at(
242 input: &EventParam,
243 data: &[u8],
244 head_offset: usize,
245 base_offset: usize,
246) -> SoldbResult<Value> {
247 if event_param_is_dynamic(input)? {
248 let relative_offset = read_usize_word(data, head_offset, &input.name)?;
249 let offset = checked_offset(base_offset, relative_offset, &input.name)?;
250 return decode_dynamic_param(input, data, offset);
251 }
252 decode_static_param(input, data, head_offset)
253}
254
255fn decode_dynamic_param(input: &EventParam, data: &[u8], offset: usize) -> SoldbResult<Value> {
256 if input.ty == "string" {
257 let bytes = read_dynamic_bytes(data, offset, &input.name)?;
258 let value = String::from_utf8(bytes).map_err(|error| {
259 SoldbError::Message(format!(
260 "Invalid UTF-8 event string {}: {error}",
261 input.name
262 ))
263 })?;
264 return Ok(json!(value));
265 }
266 if input.ty == "bytes" {
267 let bytes = read_dynamic_bytes(data, offset, &input.name)?;
268 return Ok(json!(format!("0x{}", bytes_to_hex(&bytes))));
269 }
270
271 if let Some((base_type, fixed_len)) = parse_array_type(&input.ty) {
272 let base = array_element_param(input, base_type);
273 let (len, heads_offset, offsets_base) = if let Some(len) = fixed_len {
274 (len, offset, offset)
275 } else {
276 let len = read_usize_word(data, offset, &input.name)?;
277 let heads_offset = checked_offset(offset, 32, &input.name)?;
278 (len, heads_offset, heads_offset)
279 };
280 return decode_array_items(&base, len, data, heads_offset, offsets_base);
281 }
282
283 if is_tuple_param(input) {
284 return decode_tuple_components(&input.components, data, offset);
285 }
286
287 Err(SoldbError::Message(format!(
288 "Dynamic event ABI type '{}' is not supported",
289 input.ty
290 )))
291}
292
293fn decode_static_param(input: &EventParam, data: &[u8], offset: usize) -> SoldbResult<Value> {
294 if let Some((base_type, Some(len))) = parse_array_type(&input.ty) {
295 let base = array_element_param(input, base_type);
296 return decode_array_items(&base, len, data, offset, offset);
297 }
298
299 if is_tuple_param(input) {
300 return decode_tuple_components(&input.components, data, offset);
301 }
302
303 let word = read_word_hex(data, offset)?;
304 decode_static_word(&input.ty, &word)
305}
306
307fn decode_tuple_components(
308 components: &[EventParam],
309 data: &[u8],
310 tuple_offset: usize,
311) -> SoldbResult<Value> {
312 let mut offset = tuple_offset;
313 let mut values = Vec::with_capacity(components.len());
314 for component in components {
315 let head_words = event_param_head_words(component)?;
316 values.push(decode_param_at(component, data, offset, tuple_offset)?);
317 offset = advance_offset(offset, head_words, &component.name)?;
318 }
319 Ok(Value::Array(values))
320}
321
322fn decode_array_items(
323 base: &EventParam,
324 len: usize,
325 data: &[u8],
326 heads_offset: usize,
327 offsets_base: usize,
328) -> SoldbResult<Value> {
329 let base_head_words = event_param_head_words(base)?;
330 let element_head = base_head_words
334 .checked_mul(32)
335 .filter(|size| *size > 0)
336 .ok_or_else(|| {
337 SoldbError::Message(format!(
338 "Event array element '{}' has no decodable width",
339 base.ty
340 ))
341 })?;
342 let available = data.len().saturating_sub(heads_offset);
343 if element_head
344 .checked_mul(len)
345 .is_none_or(|required| required > available)
346 {
347 return Err(SoldbError::Message(format!(
348 "Event array length {len} exceeds the {available} bytes of remaining event data"
349 )));
350 }
351
352 let mut offset = heads_offset;
353 let mut values = Vec::with_capacity(len);
354 for _ in 0..len {
355 values.push(decode_param_at(base, data, offset, offsets_base)?);
356 offset = advance_offset(offset, base_head_words, &base.name)?;
357 }
358 Ok(Value::Array(values))
359}
360
361fn event_param_head_words(input: &EventParam) -> SoldbResult<usize> {
362 if event_param_is_dynamic(input)? {
363 return Ok(1);
364 }
365
366 if let Some((base_type, Some(len))) = parse_array_type(&input.ty) {
367 let base = array_element_param(input, base_type);
368 return Ok(event_param_head_words(&base)? * len);
369 }
370
371 if is_tuple_param(input) {
372 return input.components.iter().try_fold(0, |total, component| {
373 Ok(total + event_param_head_words(component)?)
374 });
375 }
376
377 Ok(1)
378}
379
380fn event_param_is_dynamic(input: &EventParam) -> SoldbResult<bool> {
381 if input.ty == "string" || input.ty == "bytes" {
382 return Ok(true);
383 }
384
385 if let Some((base_type, fixed_len)) = parse_array_type(&input.ty) {
386 if fixed_len.is_none() {
387 return Ok(true);
388 }
389 let base = array_element_param(input, base_type);
390 return event_param_is_dynamic(&base);
391 }
392
393 if is_tuple_param(input) {
394 return input
395 .components
396 .iter()
397 .try_fold(false, |dynamic, component| {
398 Ok(dynamic || event_param_is_dynamic(component)?)
399 });
400 }
401
402 Ok(false)
403}
404
405fn array_element_param(input: &EventParam, base_type: &str) -> EventParam {
406 EventParam {
407 name: input.name.clone(),
408 ty: base_type.to_owned(),
409 indexed: false,
410 components: input.components.clone(),
411 }
412}
413
414fn parse_array_type(arg_type: &str) -> Option<(&str, Option<usize>)> {
415 if !arg_type.ends_with(']') {
416 return None;
417 }
418 let open = arg_type.rfind('[')?;
419 let base_type = arg_type[..open].trim();
420 if base_type.is_empty() {
421 return None;
422 }
423
424 let length = &arg_type[open + 1..arg_type.len() - 1];
425 if length.is_empty() {
426 return Some((base_type, None));
427 }
428 Some((base_type, Some(length.parse::<usize>().ok()?)))
429}
430
431fn is_tuple_param(input: &EventParam) -> bool {
432 input.ty == "tuple" || input.ty.starts_with("tuple[")
433}
434
435fn read_dynamic_bytes(data: &[u8], offset: usize, label: &str) -> SoldbResult<Vec<u8>> {
436 let len = read_usize_word(data, offset, label)?;
437 let start = checked_offset(offset, 32, label)?;
440 let end = checked_offset(start, len, label)?;
441 data.get(start..end).map(<[u8]>::to_vec).ok_or_else(|| {
442 SoldbError::Message(format!(
443 "Event dynamic bytes for {label} exceed data length"
444 ))
445 })
446}
447
448fn checked_offset(base: usize, delta: usize, label: &str) -> SoldbResult<usize> {
450 base.checked_add(delta).ok_or_else(|| {
451 SoldbError::Message(format!(
452 "Event offset for {label} overflows the address space"
453 ))
454 })
455}
456
457fn advance_offset(offset: usize, words: usize, label: &str) -> SoldbResult<usize> {
459 let advance = words.checked_mul(32).ok_or_else(|| {
460 SoldbError::Message(format!(
461 "Event head width for {label} overflows the address space"
462 ))
463 })?;
464 checked_offset(offset, advance, label)
465}
466
467fn read_usize_word(data: &[u8], offset: usize, label: &str) -> SoldbResult<usize> {
468 let word = read_word(data, offset)?;
469 if word[..24].iter().any(|byte| *byte != 0) {
470 return Err(SoldbError::Message(format!(
471 "Event offset or length for {label} does not fit usize"
472 )));
473 }
474 let low = u64::from_be_bytes(word[24..32].try_into().expect("8-byte slice"));
475 usize::try_from(low).map_err(|error| {
476 SoldbError::Message(format!(
477 "Event offset or length for {label} does not fit usize: {error}"
478 ))
479 })
480}
481
482fn read_word_hex(data: &[u8], offset: usize) -> SoldbResult<String> {
483 Ok(bytes_to_hex(read_word(data, offset)?))
484}
485
486fn read_word(data: &[u8], offset: usize) -> SoldbResult<&[u8]> {
487 let end = checked_offset(offset, 32, "ABI word")?;
488 data.get(offset..end)
489 .ok_or_else(|| SoldbError::Message("Event data is missing an ABI word".to_owned()))
490}
491
492fn parse_hex_data(data: &str) -> SoldbResult<Vec<u8>> {
493 let data = data.trim_start_matches("0x");
494 if data.is_empty() {
495 return Ok(Vec::new());
496 }
497 if !data.len().is_multiple_of(2) || !data.bytes().all(|byte| byte.is_ascii_hexdigit()) {
498 return Err(SoldbError::Message("Event data is not hex".to_owned()));
499 }
500
501 data.as_bytes()
502 .chunks(2)
503 .map(|chunk| {
504 let pair = std::str::from_utf8(chunk)
505 .map_err(|error| SoldbError::Message(format!("Invalid event data: {error}")))?;
506 u8::from_str_radix(pair, 16)
507 .map_err(|error| SoldbError::Message(format!("Invalid event data: {error}")))
508 })
509 .collect()
510}
511
512fn decode_static_word(arg_type: &str, word: &str) -> SoldbResult<Value> {
513 let word = normalize_word(word)?;
514 if arg_type == "address" {
515 return Ok(json!(format!("0x{}", &word[24..64])));
516 }
517 if arg_type == "bool" {
518 return Ok(json!(u128::from_str_radix(&word, 16).unwrap_or(0) != 0));
519 }
520 if arg_type.starts_with("uint") {
521 let value = u128::from_str_radix(&word, 16)
522 .map_err(|error| SoldbError::Message(format!("Invalid uint event word: {error}")))?;
523 if let Ok(value) = u64::try_from(value) {
524 return Ok(json!(value));
525 }
526 return Ok(json!(value.to_string()));
527 }
528 if arg_type == "bytes32" {
529 return Ok(json!(format!("0x{word}")));
530 }
531
532 Ok(json!(format!("0x{word}")))
533}
534
535fn normalize_word(word: &str) -> SoldbResult<String> {
536 let word = word.trim_start_matches("0x");
537 if word.len() > 64 || !word.bytes().all(|byte| byte.is_ascii_hexdigit()) {
538 return Err(SoldbError::Message(format!(
539 "Invalid ABI event word: {word}"
540 )));
541 }
542 Ok(format!("{word:0>64}").to_ascii_lowercase())
543}
544
545fn bytes_to_hex(bytes: &[u8]) -> String {
546 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
547}
548
549#[cfg(test)]
550mod tests {
551 use super::{event_signature, event_topic, parse_event_abis, EventRegistry};
552 use serde_json::json;
553
554 const BALANCE_UPDATED_ABI: &str = r#"[
555 {
556 "type": "event",
557 "name": "BalanceUpdated",
558 "inputs": [
559 {"name": "user", "type": "address", "indexed": true},
560 {"name": "newBalance", "type": "uint256", "indexed": false}
561 ]
562 },
563 {"type": "function", "name": "ignored", "inputs": []}
564 ]"#;
565
566 const COMPLEX_EVENT_ABI: &str = r#"[
567 {
568 "type": "event",
569 "name": "Complex",
570 "inputs": [
571 {"name": "sender", "type": "address", "indexed": true},
572 {"name": "message", "type": "string", "indexed": false},
573 {"name": "payload", "type": "bytes", "indexed": false},
574 {"name": "values", "type": "uint256[]", "indexed": false},
575 {"name": "labels", "type": "string[2]", "indexed": false},
576 {
577 "name": "item",
578 "type": "tuple",
579 "indexed": false,
580 "components": [
581 {"name": "count", "type": "uint256"},
582 {"name": "note", "type": "string"}
583 ]
584 },
585 {
586 "name": "nested",
587 "type": "tuple",
588 "indexed": false,
589 "components": [
590 {"name": "count", "type": "uint256"},
591 {
592 "name": "inner",
593 "type": "tuple",
594 "components": [
595 {"name": "target", "type": "address"},
596 {"name": "label", "type": "string"}
597 ]
598 }
599 ]
600 },
601 {"name": "tag", "type": "string", "indexed": true}
602 ]
603 }
604 ]"#;
605
606 const BLOB_ABI: &str = r#"[
607 {
608 "type": "event",
609 "name": "Blob",
610 "inputs": [{"name": "payload", "type": "bytes", "indexed": false}]
611 }
612 ]"#;
613
614 const NUMBERS_ABI: &str = r#"[
615 {
616 "type": "event",
617 "name": "Numbers",
618 "inputs": [{"name": "values", "type": "uint256[]", "indexed": false}]
619 }
620 ]"#;
621
622 fn registry_for(abi: &str) -> (EventRegistry, String) {
623 let events = parse_event_abis(abi).expect("parse abi");
624 let topic = event_topic(&events[0]);
625 let mut registry = EventRegistry::default();
626 registry
627 .insert(None, events[0].clone())
628 .expect("insert event");
629 (registry, topic)
630 }
631
632 #[test]
633 fn rejects_dynamic_length_that_overflows_the_offset() {
634 let (registry, topic) = registry_for(BLOB_ABI);
635 let data = format!("0x{:064x}{}{}", 32u64, "0".repeat(48), "f".repeat(16));
639
640 assert!(registry.decode_log(&[topic], &data).is_none());
641 }
642
643 #[test]
644 fn rejects_array_length_larger_than_the_event_data() {
645 let (registry, topic) = registry_for(NUMBERS_ABI);
646 let data = format!("0x{:064x}{:064x}", 32u64, 1u64 << 40);
649
650 assert!(registry.decode_log(&[topic], &data).is_none());
651 }
652
653 #[test]
654 fn decodes_a_well_formed_dynamic_array() {
655 let (registry, topic) = registry_for(NUMBERS_ABI);
656 let data = format!("0x{:064x}{:064x}{:064x}{:064x}", 32u64, 2u64, 7u64, 8u64);
657
658 let decoded = registry
659 .decode_log(&[topic], &data)
660 .expect("decode dynamic array");
661
662 assert_eq!(decoded.args[0].value, json!([7, 8]));
663 }
664
665 #[test]
666 fn parses_event_abis_from_array() {
667 let events = parse_event_abis(BALANCE_UPDATED_ABI).expect("parse abi");
668
669 assert_eq!(events.len(), 1);
670 assert_eq!(
671 event_signature(&events[0]),
672 "BalanceUpdated(address,uint256)"
673 );
674 assert_eq!(event_topic(&events[0]).len(), 66);
675 }
676
677 #[test]
678 fn decodes_static_event_arguments() {
679 let event = parse_event_abis(BALANCE_UPDATED_ABI)
680 .expect("parse abi")
681 .remove(0);
682 let topic = event_topic(&event);
683 let mut registry = EventRegistry::default();
684 registry
685 .insert(Some("TestContract".to_owned()), event)
686 .expect("insert event");
687
688 let user = format!(
689 "0x{}f39fd6e51aad88f6f4ce6ab8827279cfffb92266",
690 "0".repeat(24)
691 );
692 let data = format!("0x{:064x}", 99);
693 let decoded = registry
694 .decode_log(&[topic, user], &data)
695 .expect("decode event");
696
697 assert_eq!(decoded.contract_name.as_deref(), Some("TestContract"));
698 assert_eq!(decoded.signature, "BalanceUpdated(address,uint256)");
699 assert_eq!(
700 decoded.args[0].value,
701 "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
702 );
703 assert_eq!(decoded.args[1].value, 99);
704 }
705
706 #[test]
707 fn decodes_dynamic_arrays_and_tuple_event_arguments() {
708 let event = parse_event_abis(COMPLEX_EVENT_ABI)
709 .expect("parse abi")
710 .remove(0);
711 assert_eq!(
712 event_signature(&event),
713 "Complex(address,string,bytes,uint256[],string[2],(uint256,string),(uint256,(address,string)),string)"
714 );
715
716 let topic = event_topic(&event);
717 let mut registry = EventRegistry::default();
718 registry.insert(None, event).expect("insert event");
719
720 let sender = format!(
721 "0x{}f39fd6e51aad88f6f4ce6ab8827279cfffb92266",
722 "0".repeat(24)
723 );
724 let indexed_tag_hash = format!("0x{}", "aa".repeat(32));
725 let data = concat!(
726 "0x",
727 "00000000000000000000000000000000000000000000000000000000000000c0",
728 "0000000000000000000000000000000000000000000000000000000000000100",
729 "0000000000000000000000000000000000000000000000000000000000000140",
730 "00000000000000000000000000000000000000000000000000000000000001a0",
731 "0000000000000000000000000000000000000000000000000000000000000260",
732 "00000000000000000000000000000000000000000000000000000000000002e0",
733 "0000000000000000000000000000000000000000000000000000000000000005",
734 "68656c6c6f000000000000000000000000000000000000000000000000000000",
735 "0000000000000000000000000000000000000000000000000000000000000002",
736 "0102000000000000000000000000000000000000000000000000000000000000",
737 "0000000000000000000000000000000000000000000000000000000000000002",
738 "0000000000000000000000000000000000000000000000000000000000000007",
739 "0000000000000000000000000000000000000000000000000000000000000008",
740 "0000000000000000000000000000000000000000000000000000000000000040",
741 "0000000000000000000000000000000000000000000000000000000000000080",
742 "0000000000000000000000000000000000000000000000000000000000000001",
743 "6100000000000000000000000000000000000000000000000000000000000000",
744 "0000000000000000000000000000000000000000000000000000000000000002",
745 "6262000000000000000000000000000000000000000000000000000000000000",
746 "0000000000000000000000000000000000000000000000000000000000000005",
747 "0000000000000000000000000000000000000000000000000000000000000040",
748 "0000000000000000000000000000000000000000000000000000000000000002",
749 "6869000000000000000000000000000000000000000000000000000000000000",
750 "0000000000000000000000000000000000000000000000000000000000000009",
751 "0000000000000000000000000000000000000000000000000000000000000040",
752 "0000000000000000000000000000000000000000000000000000000000000002",
753 "0000000000000000000000000000000000000000000000000000000000000040",
754 "0000000000000000000000000000000000000000000000000000000000000006",
755 "6e65737465640000000000000000000000000000000000000000000000000000"
756 );
757
758 let decoded = registry
759 .decode_log(&[topic, sender, indexed_tag_hash.clone()], data)
760 .expect("decode dynamic event");
761
762 assert_eq!(
763 decoded.args[0].value,
764 "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
765 );
766 assert_eq!(decoded.args[1].value, "hello");
767 assert_eq!(decoded.args[2].value, "0x0102");
768 assert_eq!(decoded.args[3].value, json!([7, 8]));
769 assert_eq!(decoded.args[4].value, json!(["a", "bb"]));
770 assert_eq!(decoded.args[5].value, json!([5, "hi"]));
771 assert_eq!(
772 decoded.args[6].value,
773 json!([9, ["0x0000000000000000000000000000000000000002", "nested"]])
774 );
775 assert_eq!(decoded.args[7].value, indexed_tag_hash);
776 }
777
778 #[test]
779 fn parses_event_abis_from_compiler_json_object() {
780 let wrapped = format!(r#"{{"abi": {BALANCE_UPDATED_ABI}}}"#);
781 let events = parse_event_abis(&wrapped).expect("parse wrapped abi");
782
783 assert_eq!(events.len(), 1);
784 assert_eq!(events[0].inputs[0].name, "user");
785 }
786}