pink_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, encode, signature::long_signature, Error, EventParam, Hash, Log, LogParam, ParamType, RawLog,
21	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	/// Parses `RawLog` and retrieves all log params from it.
128	pub fn parse_log(&self, log: RawLog) -> Result<Log> {
129		let topics = log.topics;
130		let data = log.data;
131		let topics_len = topics.len();
132		// obtains all params info
133		let topic_params = self.indexed_params(true);
134		let data_params = self.indexed_params(false);
135		// then take first topic if event is not anonymous
136		let to_skip = if self.anonymous {
137			0
138		} else {
139			// verify
140			let event_signature = topics.get(0).ok_or(Error::InvalidData)?;
141			if event_signature != &self.signature() {
142				return Err(Error::InvalidData);
143			}
144			1
145		};
146
147		let topic_types =
148			topic_params.iter().map(|p| self.convert_topic_param_type(&p.kind)).collect::<Vec<ParamType>>();
149
150		let flat_topics = topics.into_iter().skip(to_skip).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
151
152		let topic_tokens = decode(&topic_types, &flat_topics)?;
153
154		// topic may be only a 32 bytes encoded token
155		if topic_tokens.len() != topics_len - to_skip {
156			return Err(Error::InvalidData);
157		}
158
159		let topics_named_tokens = topic_params.into_iter().map(|p| p.name).zip(topic_tokens.into_iter());
160
161		let data_types = data_params.iter().map(|p| p.kind.clone()).collect::<Vec<ParamType>>();
162
163		let data_tokens = decode(&data_types, &data)?;
164
165		let data_named_tokens = data_params.into_iter().map(|p| p.name).zip(data_tokens.into_iter());
166
167		let named_tokens = topics_named_tokens.chain(data_named_tokens).collect::<BTreeMap<String, Token>>();
168
169		let decoded_params = self
170			.params_names()
171			.into_iter()
172			.map(|name| LogParam { name: name.clone(), value: named_tokens[&name].clone() })
173			.collect();
174
175		let result = Log { params: decoded_params };
176
177		Ok(result)
178	}
179}
180
181#[cfg(test)]
182mod tests {
183	use hex_literal::hex;
184
185	#[cfg(not(feature = "std"))]
186	use crate::no_std_prelude::*;
187	use crate::{
188		log::{Log, RawLog},
189		signature::long_signature,
190		token::Token,
191		Event, EventParam, LogParam, ParamType,
192	};
193
194	#[test]
195	fn test_decoding_event() {
196		let event = Event {
197			name: "foo".to_owned(),
198			inputs: vec![
199				EventParam { name: "a".to_owned(), kind: ParamType::Int(256), indexed: false },
200				EventParam { name: "b".to_owned(), kind: ParamType::Int(256), indexed: true },
201				EventParam { name: "c".to_owned(), kind: ParamType::Address, indexed: false },
202				EventParam { name: "d".to_owned(), kind: ParamType::Address, indexed: true },
203				EventParam { name: "e".to_owned(), kind: ParamType::String, indexed: true },
204				EventParam {
205					name: "f".to_owned(),
206					kind: ParamType::Array(Box::new(ParamType::Int(256))),
207					indexed: true,
208				},
209				EventParam {
210					name: "g".to_owned(),
211					kind: ParamType::FixedArray(Box::new(ParamType::Address), 5),
212					indexed: true,
213				},
214			],
215			anonymous: false,
216		};
217
218		let log = RawLog {
219			topics: vec![
220				long_signature(
221					"foo",
222					&[
223						ParamType::Int(256),
224						ParamType::Int(256),
225						ParamType::Address,
226						ParamType::Address,
227						ParamType::String,
228						ParamType::Array(Box::new(ParamType::Int(256))),
229						ParamType::FixedArray(Box::new(ParamType::Address), 5),
230					],
231				),
232				hex!("0000000000000000000000000000000000000000000000000000000000000002").into(),
233				hex!("0000000000000000000000001111111111111111111111111111111111111111").into(),
234				hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into(),
235				hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into(),
236				hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into(),
237			],
238			data: hex!(
239				"
240				0000000000000000000000000000000000000000000000000000000000000003
241				0000000000000000000000002222222222222222222222222222222222222222
242			"
243			)
244			.into(),
245		};
246		let result = event.parse_log(log).unwrap();
247
248		assert_eq!(
249			result,
250			Log {
251				params: [
252					("a", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000003").into()),),
253					("b", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000002").into()),),
254					("c", Token::Address(hex!("2222222222222222222222222222222222222222").into())),
255					("d", Token::Address(hex!("1111111111111111111111111111111111111111").into())),
256					(
257						"e",
258						Token::FixedBytes(
259							hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into()
260						)
261					),
262					(
263						"f",
264						Token::FixedBytes(
265							hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into()
266						)
267					),
268					(
269						"g",
270						Token::FixedBytes(
271							hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into()
272						)
273					),
274				]
275				.iter()
276				.cloned()
277				.map(|(name, value)| LogParam { name: name.to_string(), value })
278				.collect::<Vec<_>>()
279			}
280		);
281	}
282}