fuel_ethabi/
event.rs

1// Copyright 2015-2020 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Contract event.
10
11use alloc::collections::BTreeMap;
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15use sha3::{Digest, Keccak256};
16
17#[cfg(not(feature = "std"))]
18use crate::no_std_prelude::*;
19use crate::{
20	decode, decode_validate, encode, signature::long_signature, Error, EventParam, Hash, Log, LogParam, ParamType,
21	RawLog, RawTopicFilter, Result, Token, Topic, TopicFilter,
22};
23
24/// Contract event.
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[derive(Clone, Debug, PartialEq)]
27pub struct Event {
28	/// Event name.
29	#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::sanitize_name::deserialize"))]
30	pub name: String,
31	/// Event input.
32	pub inputs: Vec<EventParam>,
33	/// If anonymous, event cannot be found using `from` filter.
34	pub anonymous: bool,
35}
36
37impl Event {
38	/// Returns names of all params.
39	fn params_names(&self) -> Vec<String> {
40		self.inputs.iter().map(|p| p.name.clone()).collect()
41	}
42
43	/// Returns types of all params.
44	fn param_types(&self) -> Vec<ParamType> {
45		self.inputs.iter().map(|p| p.kind.clone()).collect()
46	}
47
48	/// Returns all params of the event.
49	fn indexed_params(&self, indexed: bool) -> Vec<EventParam> {
50		self.inputs.iter().filter(|p| p.indexed == indexed).cloned().collect()
51	}
52
53	/// Event signature
54	pub fn signature(&self) -> Hash {
55		long_signature(&self.name, &self.param_types())
56	}
57
58	/// Creates topic filter
59	pub fn filter(&self, raw: RawTopicFilter) -> Result<TopicFilter> {
60		fn convert_token(token: Token, kind: &ParamType) -> Result<Hash> {
61			if !token.type_check(kind) {
62				return Err(Error::InvalidData);
63			}
64			let encoded = encode(&[token]);
65			if encoded.len() == 32 {
66				let mut data = [0u8; 32];
67				data.copy_from_slice(&encoded);
68				Ok(data.into())
69			} else {
70				Ok(Hash::from_slice(Keccak256::digest(&encoded).as_slice()))
71			}
72		}
73
74		fn convert_topic(topic: Topic<Token>, kind: Option<&ParamType>) -> Result<Topic<Hash>> {
75			match topic {
76				Topic::Any => Ok(Topic::Any),
77				Topic::OneOf(tokens) => match kind {
78					None => Err(Error::InvalidData),
79					Some(kind) => {
80						let topics =
81							tokens.into_iter().map(|token| convert_token(token, kind)).collect::<Result<Vec<_>>>()?;
82						Ok(Topic::OneOf(topics))
83					}
84				},
85				Topic::This(token) => match kind {
86					None => Err(Error::InvalidData),
87					Some(kind) => Ok(Topic::This(convert_token(token, kind)?)),
88				},
89			}
90		}
91
92		let kinds: Vec<_> = self.indexed_params(true).into_iter().map(|param| param.kind).collect();
93		let result = if self.anonymous {
94			TopicFilter {
95				topic0: convert_topic(raw.topic0, kinds.get(0))?,
96				topic1: convert_topic(raw.topic1, kinds.get(1))?,
97				topic2: convert_topic(raw.topic2, kinds.get(2))?,
98				topic3: Topic::Any,
99			}
100		} else {
101			TopicFilter {
102				topic0: Topic::This(self.signature()),
103				topic1: convert_topic(raw.topic0, kinds.get(0))?,
104				topic2: convert_topic(raw.topic1, kinds.get(1))?,
105				topic3: convert_topic(raw.topic2, kinds.get(2))?,
106			}
107		};
108
109		Ok(result)
110	}
111
112	// Converts param types for indexed parameters to bytes32 where appropriate
113	// This applies to strings, arrays, structs and bytes to follow the encoding of
114	// these indexed param types according to
115	// https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters
116	fn convert_topic_param_type(&self, kind: &ParamType) -> ParamType {
117		match kind {
118			ParamType::String
119			| ParamType::Bytes
120			| ParamType::Array(_)
121			| ParamType::FixedArray(_, _)
122			| ParamType::Tuple(_) => ParamType::FixedBytes(32),
123			_ => kind.clone(),
124		}
125	}
126
127	fn parse_log_inner<F: Fn(&[ParamType], &[u8]) -> Result<Vec<Token>>>(&self, log: RawLog, decode: F) -> Result<Log> {
128		let topics = log.topics;
129		let data = log.data;
130		let topics_len = topics.len();
131		// obtains all params info
132		let topic_params = self.indexed_params(true);
133		let data_params = self.indexed_params(false);
134		// then take first topic if event is not anonymous
135		let to_skip = if self.anonymous {
136			0
137		} else {
138			// verify
139			let event_signature = topics.get(0).ok_or(Error::InvalidData)?;
140			if event_signature != &self.signature() {
141				return Err(Error::InvalidData);
142			}
143			1
144		};
145
146		let topic_types =
147			topic_params.iter().map(|p| self.convert_topic_param_type(&p.kind)).collect::<Vec<ParamType>>();
148
149		let flat_topics = topics.into_iter().skip(to_skip).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
150
151		let topic_tokens = decode(&topic_types, &flat_topics)?;
152
153		// topic may be only a 32 bytes encoded token
154		if topic_tokens.len() != topics_len - to_skip {
155			return Err(Error::InvalidData);
156		}
157
158		let topics_named_tokens = topic_params.into_iter().map(|p| p.name).zip(topic_tokens.into_iter());
159
160		let data_types = data_params.iter().map(|p| p.kind.clone()).collect::<Vec<ParamType>>();
161
162		let data_tokens = decode(&data_types, &data)?;
163
164		let data_named_tokens = data_params.into_iter().map(|p| p.name).zip(data_tokens.into_iter());
165
166		let named_tokens = topics_named_tokens.chain(data_named_tokens).collect::<BTreeMap<String, Token>>();
167
168		let decoded_params = self
169			.params_names()
170			.into_iter()
171			.map(|name| LogParam { name: name.clone(), value: named_tokens[&name].clone() })
172			.collect();
173
174		let result = Log { params: decoded_params };
175
176		Ok(result)
177	}
178
179	/// Parses `RawLog` and retrieves all log params from it.
180	/// Checks, that decoded data is exact as input provided
181	pub fn parse_log_validate(&self, log: RawLog) -> Result<Log> {
182		self.parse_log_inner(log, decode_validate)
183	}
184
185	/// Parses `RawLog` and retrieves all log params from it.
186	pub fn parse_log(&self, log: RawLog) -> Result<Log> {
187		self.parse_log_inner(log, decode)
188	}
189}
190
191#[cfg(test)]
192mod tests {
193	use hex_literal::hex;
194
195	#[cfg(not(feature = "std"))]
196	use crate::no_std_prelude::*;
197	use crate::{
198		log::{Log, RawLog},
199		signature::long_signature,
200		token::Token,
201		Event, EventParam, LogParam, ParamType,
202	};
203
204	#[test]
205	fn test_decoding_event() {
206		let event = Event {
207			name: "foo".to_owned(),
208			inputs: vec![
209				EventParam { name: "a".to_owned(), kind: ParamType::Int(256), indexed: false },
210				EventParam { name: "b".to_owned(), kind: ParamType::Int(256), indexed: true },
211				EventParam { name: "c".to_owned(), kind: ParamType::Address, indexed: false },
212				EventParam { name: "d".to_owned(), kind: ParamType::Address, indexed: true },
213				EventParam { name: "e".to_owned(), kind: ParamType::String, indexed: true },
214				EventParam {
215					name: "f".to_owned(),
216					kind: ParamType::Array(Box::new(ParamType::Int(256))),
217					indexed: true,
218				},
219				EventParam {
220					name: "g".to_owned(),
221					kind: ParamType::FixedArray(Box::new(ParamType::Address), 5),
222					indexed: true,
223				},
224			],
225			anonymous: false,
226		};
227
228		let log = RawLog {
229			topics: vec![
230				long_signature(
231					"foo",
232					&[
233						ParamType::Int(256),
234						ParamType::Int(256),
235						ParamType::Address,
236						ParamType::Address,
237						ParamType::String,
238						ParamType::Array(Box::new(ParamType::Int(256))),
239						ParamType::FixedArray(Box::new(ParamType::Address), 5),
240					],
241				),
242				hex!("0000000000000000000000000000000000000000000000000000000000000002").into(),
243				hex!("0000000000000000000000001111111111111111111111111111111111111111").into(),
244				hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into(),
245				hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into(),
246				hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into(),
247			],
248			data: hex!(
249				"
250				0000000000000000000000000000000000000000000000000000000000000003
251				0000000000000000000000002222222222222222222222222222222222222222
252			"
253			)
254			.into(),
255		};
256		let result = event.parse_log(log).unwrap();
257
258		assert_eq!(
259			result,
260			Log {
261				params: [
262					("a", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000003").into()),),
263					("b", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000002").into()),),
264					("c", Token::Address(hex!("2222222222222222222222222222222222222222").into())),
265					("d", Token::Address(hex!("1111111111111111111111111111111111111111").into())),
266					(
267						"e",
268						Token::FixedBytes(
269							hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into()
270						)
271					),
272					(
273						"f",
274						Token::FixedBytes(
275							hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into()
276						)
277					),
278					(
279						"g",
280						Token::FixedBytes(
281							hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into()
282						)
283					),
284				]
285				.iter()
286				.cloned()
287				.map(|(name, value)| LogParam { name: name.to_string(), value })
288				.collect::<Vec<_>>()
289			}
290		);
291	}
292
293	#[test]
294	fn parse_log_whole() {
295		let correct_event = Event {
296			name: "Test".into(),
297			inputs: vec![
298				EventParam {
299					name: "tuple".into(),
300					kind: ParamType::Tuple(vec![ParamType::Address, ParamType::Address]),
301					indexed: false,
302				},
303				EventParam { name: "addr".into(), kind: ParamType::Address, indexed: true },
304			],
305			anonymous: false,
306		};
307		// swap indexed params
308		let mut wrong_event = correct_event.clone();
309		wrong_event.inputs[0].indexed = true;
310		wrong_event.inputs[1].indexed = false;
311
312		let log = RawLog {
313			topics: vec![
314				hex!("cf74b4e62f836eeedcd6f92120ffb5afea90e6fa490d36f8b81075e2a7de0cf7").into(),
315				hex!("0000000000000000000000000000000000000000000000000000000000012321").into(),
316			],
317			data: hex!(
318				"
319			0000000000000000000000000000000000000000000000000000000000012345
320			0000000000000000000000000000000000000000000000000000000000054321
321			"
322			)
323			.into(),
324		};
325
326		assert!(wrong_event.parse_log(log.clone()).is_ok());
327		assert!(wrong_event.parse_log_validate(log.clone()).is_err());
328		assert!(correct_event.parse_log_validate(log).is_ok());
329	}
330}