ethabi_next/
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 serde::Deserialize;
12use sha3::{Digest, Keccak256};
13use std::collections::HashMap;
14
15use crate::{
16	decode, encode, signature::long_signature, Error, EventParam, Hash, Log, LogParam, ParamType, RawLog,
17	RawTopicFilter, Result, Token, Topic, TopicFilter,
18};
19
20/// Contract event.
21#[derive(Clone, Debug, PartialEq, Deserialize)]
22pub struct Event {
23	/// Event name.
24	pub name: String,
25	/// Event input.
26	pub inputs: Vec<EventParam>,
27	/// If anonymous, event cannot be found using `from` filter.
28	pub anonymous: bool,
29}
30
31impl Event {
32	/// Returns names of all params.
33	fn params_names(&self) -> Vec<String> {
34		self.inputs.iter().map(|p| p.name.clone()).collect()
35	}
36
37	/// Returns types of all params.
38	fn param_types(&self) -> Vec<ParamType> {
39		self.inputs.iter().map(|p| p.kind.clone()).collect()
40	}
41
42	/// Returns all params of the event.
43	fn indexed_params(&self, indexed: bool) -> Vec<EventParam> {
44		self.inputs.iter().filter(|p| p.indexed == indexed).cloned().collect()
45	}
46
47	/// Event signature
48	pub fn signature(&self) -> Hash {
49		long_signature(&self.name, &self.param_types())
50	}
51
52	/// Creates topic filter
53	pub fn filter(&self, raw: RawTopicFilter) -> Result<TopicFilter> {
54		fn convert_token(token: Token, kind: &ParamType) -> Result<Hash> {
55			if !token.type_check(kind) {
56				return Err(Error::InvalidData);
57			}
58			let encoded = encode(&[token]);
59			if encoded.len() == 32 {
60				let mut data = [0u8; 32];
61				data.copy_from_slice(&encoded);
62				Ok(data.into())
63			} else {
64				Ok(Hash::from_slice(Keccak256::digest(&encoded).as_slice()))
65			}
66		}
67
68		fn convert_topic(topic: Topic<Token>, kind: Option<&ParamType>) -> Result<Topic<Hash>> {
69			match topic {
70				Topic::Any => Ok(Topic::Any),
71				Topic::OneOf(tokens) => match kind {
72					None => Err(Error::InvalidData),
73					Some(kind) => {
74						let topics =
75							tokens.into_iter().map(|token| convert_token(token, kind)).collect::<Result<Vec<_>>>()?;
76						Ok(Topic::OneOf(topics))
77					}
78				},
79				Topic::This(token) => match kind {
80					None => Err(Error::InvalidData),
81					Some(kind) => Ok(Topic::This(convert_token(token, kind)?)),
82				},
83			}
84		}
85
86		let kinds: Vec<_> = self.indexed_params(true).into_iter().map(|param| param.kind).collect();
87		let result = if self.anonymous {
88			TopicFilter {
89				topic0: convert_topic(raw.topic0, kinds.get(0))?,
90				topic1: convert_topic(raw.topic1, kinds.get(1))?,
91				topic2: convert_topic(raw.topic2, kinds.get(2))?,
92				topic3: Topic::Any,
93			}
94		} else {
95			TopicFilter {
96				topic0: Topic::This(self.signature()),
97				topic1: convert_topic(raw.topic0, kinds.get(0))?,
98				topic2: convert_topic(raw.topic1, kinds.get(1))?,
99				topic3: convert_topic(raw.topic2, kinds.get(2))?,
100			}
101		};
102
103		Ok(result)
104	}
105
106	// Converts param types for indexed parameters to bytes32 where appropriate
107	// This applies to strings, arrays, structs and bytes to follow the encoding of
108	// these indexed param types according to
109	// https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters
110	fn convert_topic_param_type(&self, kind: &ParamType) -> ParamType {
111		match kind {
112			ParamType::String
113			| ParamType::Bytes
114			| ParamType::Array(_)
115			| ParamType::FixedArray(_, _)
116			| ParamType::Tuple(_) => ParamType::FixedBytes(32),
117			_ => kind.clone(),
118		}
119	}
120
121	/// Parses `RawLog` and retrieves all log params from it.
122	pub fn parse_log(&self, log: RawLog) -> Result<Log> {
123		let topics = log.topics;
124		let data = log.data;
125		let topics_len = topics.len();
126		// obtains all params info
127		let topic_params = self.indexed_params(true);
128		let data_params = self.indexed_params(false);
129		// then take first topic if event is not anonymous
130		let to_skip = if self.anonymous {
131			0
132		} else {
133			// verify
134			let event_signature = topics.get(0).ok_or(Error::InvalidData)?;
135			if event_signature != &self.signature() {
136				return Err(Error::InvalidData);
137			}
138			1
139		};
140
141		let topic_types =
142			topic_params.iter().map(|p| self.convert_topic_param_type(&p.kind)).collect::<Vec<ParamType>>();
143
144		let flat_topics = topics.into_iter().skip(to_skip).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
145
146		let topic_tokens = decode(&topic_types, &flat_topics)?;
147
148		// topic may be only a 32 bytes encoded token
149		if topic_tokens.len() != topics_len - to_skip {
150			return Err(Error::InvalidData);
151		}
152
153		let topics_named_tokens = topic_params.into_iter().map(|p| p.name).zip(topic_tokens.into_iter());
154
155		let data_types = data_params.iter().map(|p| p.kind.clone()).collect::<Vec<ParamType>>();
156
157		let data_tokens = decode(&data_types, &data)?;
158
159		let data_named_tokens = data_params.into_iter().map(|p| p.name).zip(data_tokens.into_iter());
160
161		let named_tokens = topics_named_tokens.chain(data_named_tokens).collect::<HashMap<String, Token>>();
162
163		let decoded_params = self
164			.params_names()
165			.into_iter()
166			.map(|name| LogParam { name: name.clone(), value: named_tokens[&name].clone() })
167			.collect();
168
169		let result = Log { params: decoded_params };
170
171		Ok(result)
172	}
173}
174
175#[cfg(test)]
176mod tests {
177	use crate::{
178		log::{Log, RawLog},
179		signature::long_signature,
180		token::Token,
181		Event, EventParam, LogParam, ParamType,
182	};
183	use hex_literal::hex;
184
185	#[test]
186	fn test_decoding_event() {
187		let event = Event {
188			name: "foo".to_owned(),
189			inputs: vec![
190				EventParam { name: "a".to_owned(), kind: ParamType::Int(256), indexed: false },
191				EventParam { name: "b".to_owned(), kind: ParamType::Int(256), indexed: true },
192				EventParam { name: "c".to_owned(), kind: ParamType::Address, indexed: false },
193				EventParam { name: "d".to_owned(), kind: ParamType::Address, indexed: true },
194				EventParam { name: "e".to_owned(), kind: ParamType::String, indexed: true },
195				EventParam {
196					name: "f".to_owned(),
197					kind: ParamType::Array(Box::new(ParamType::Int(256))),
198					indexed: true,
199				},
200				EventParam {
201					name: "g".to_owned(),
202					kind: ParamType::FixedArray(Box::new(ParamType::Address), 5),
203					indexed: true,
204				},
205			],
206			anonymous: false,
207		};
208
209		let log = RawLog {
210			topics: vec![
211				long_signature(
212					"foo",
213					&[
214						ParamType::Int(256),
215						ParamType::Int(256),
216						ParamType::Address,
217						ParamType::Address,
218						ParamType::String,
219						ParamType::Array(Box::new(ParamType::Int(256))),
220						ParamType::FixedArray(Box::new(ParamType::Address), 5),
221					],
222				),
223				hex!("0000000000000000000000000000000000000000000000000000000000000002").into(),
224				hex!("0000000000000000000000001111111111111111111111111111111111111111").into(),
225				hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into(),
226				hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into(),
227				hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into(),
228			],
229			data: hex!(
230				"
231				0000000000000000000000000000000000000000000000000000000000000003
232				0000000000000000000000002222222222222222222222222222222222222222
233			"
234			)
235			.into(),
236		};
237		let result = event.parse_log(log).unwrap();
238
239		assert_eq!(
240			result,
241			Log {
242				params: [
243					("a", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000003").into()),),
244					("b", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000002").into()),),
245					("c", Token::Address(hex!("2222222222222222222222222222222222222222").into())),
246					("d", Token::Address(hex!("1111111111111111111111111111111111111111").into())),
247					(
248						"e",
249						Token::FixedBytes(
250							hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into()
251						)
252					),
253					(
254						"f",
255						Token::FixedBytes(
256							hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into()
257						)
258					),
259					(
260						"g",
261						Token::FixedBytes(
262							hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into()
263						)
264					),
265				]
266				.iter()
267				.cloned()
268				.map(|(name, value)| LogParam { name: name.to_string(), value })
269				.collect::<Vec<_>>()
270			}
271		);
272	}
273}