@@ -73,18 +73,29 @@ impl DynSolEvent {
7373 }
7474 }
7575
76+ let not_anonymous = !self . is_anonymous ( ) as usize ;
7677 let indexed = self
7778 . indexed
7879 . iter ( )
79- . zip ( topics. by_ref ( ) . take ( self . indexed . len ( ) ) )
80- . map ( |( ty, topic) | {
81- let value = ty. decode_event_topic ( topic) ;
82- Ok ( value)
80+ . enumerate ( )
81+ . map ( |( i, ty) | match topics. next ( ) {
82+ Some ( topic) => Ok ( ty. decode_event_topic ( topic) ) ,
83+ // Ran out of topics: report the count we actually have (event hash + `i`
84+ // indexed topics consumed so far) rather than letting the body decode below
85+ // fail with a confusing `Overrun`.
86+ None => Err ( Error :: TopicLengthMismatch {
87+ expected : num_topics,
88+ actual : not_anonymous + i,
89+ } ) ,
8390 } )
8491 . collect :: < Result < _ > > ( ) ?;
8592
86- let body = self . body . abi_decode_sequence ( data) ?. into_fixed_seq ( ) . expect ( "body is a tuple" ) ;
87-
93+ // Validate the topic count before decoding the body. A log whose topic count does not
94+ // match this event (e.g. an ERC-721 `Transfer` decoded against the ERC-20 `Transfer`
95+ // ABI, which shares the same `topic0`) must surface as `TopicLengthMismatch`, not as an
96+ // `Overrun` from trying to read body words that the extra topics displaced. The early
97+ // `size_hint` check above only fires for exact-size iterators, so this also covers
98+ // iterators (`filter`/`map`/streaming) that don't report an exact length.
8899 let remaining = topics. count ( ) ;
89100 if remaining > 0 {
90101 return Err ( Error :: TopicLengthMismatch {
@@ -93,6 +104,8 @@ impl DynSolEvent {
93104 } ) ;
94105 }
95106
107+ let body = self . body . abi_decode_sequence ( data) ?. into_fixed_seq ( ) . expect ( "body is a tuple" ) ;
108+
96109 Ok ( DecodedEvent { selector : self . topic_0 , indexed, body } )
97110 }
98111
@@ -215,4 +228,57 @@ mod test {
215228 let encoded = decoded. encode_log_data ( ) ;
216229 assert_eq ! ( encoded, log) ;
217230 }
231+
232+ // ERC-20 and ERC-721 `Transfer` share the same `topic0`, so a topic-only filter matches
233+ // both. Decoding an ERC-721 transfer (4 topics, empty data) against the ERC-20 ABI used to
234+ // fail with a confusing `Overrun` when the topic iterator didn't report an exact size,
235+ // because the body was decoded before the topic count was validated. See alloy#2243.
236+ #[ test]
237+ fn topic_count_is_validated_before_body ( ) {
238+ let t0 = b256 ! ( "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" ) ;
239+ // event Transfer(address indexed from, address indexed to, uint256 value)
240+ let event = DynSolEvent {
241+ topic_0 : Some ( t0) ,
242+ indexed : vec ! [ DynSolType :: Address , DynSolType :: Address ] ,
243+ body : DynSolType :: Tuple ( vec ! [ DynSolType :: Uint ( 256 ) ] ) ,
244+ } ;
245+
246+ // ERC-721-style log: 4 topics (sig + from + to + tokenId), empty data.
247+ let topics = [
248+ t0,
249+ b256 ! ( "0x000000000000000000000000d9a442856c234a39a81a089c06451ebaa4306a72" ) ,
250+ b256 ! ( "0x0000000000000000000000002a3dd3eb832af982ec71669e178424b10dca2ede" ) ,
251+ b256 ! ( "0x0000000000000000000000000000000000000000000000000000000000000290" ) ,
252+ ] ;
253+
254+ // Exact-size iterator: caught by the up-front `size_hint` check.
255+ let err = event. decode_log_parts ( topics. iter ( ) . copied ( ) , & [ ] ) . unwrap_err ( ) ;
256+ assert ! (
257+ matches!( err, Error :: TopicLengthMismatch { expected: 3 , actual: 4 } ) ,
258+ "exact iterator: {err:?}"
259+ ) ;
260+
261+ // Non-exact iterator (e.g. `filter`): previously returned `Overrun`.
262+ let err = event. decode_log_parts ( topics. iter ( ) . copied ( ) . filter ( |_| true ) , & [ ] ) . unwrap_err ( ) ;
263+ assert ! (
264+ matches!( err, Error :: TopicLengthMismatch { expected: 3 , actual: 4 } ) ,
265+ "non-exact iterator: {err:?}"
266+ ) ;
267+ }
268+
269+ #[ test]
270+ fn too_few_topics_is_a_length_mismatch ( ) {
271+ let t0 = b256 ! ( "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" ) ;
272+ let event = DynSolEvent {
273+ topic_0 : Some ( t0) ,
274+ indexed : vec ! [ DynSolType :: Address , DynSolType :: Address ] ,
275+ body : DynSolType :: Tuple ( vec ! [ DynSolType :: Uint ( 256 ) ] ) ,
276+ } ;
277+
278+ // Only sig + one indexed topic, supplied via a non-exact iterator.
279+ let topics =
280+ [ t0, b256 ! ( "0x000000000000000000000000d9a442856c234a39a81a089c06451ebaa4306a72" ) ] ;
281+ let err = event. decode_log_parts ( topics. iter ( ) . copied ( ) . filter ( |_| true ) , & [ ] ) . unwrap_err ( ) ;
282+ assert ! ( matches!( err, Error :: TopicLengthMismatch { expected: 3 , actual: 2 } ) , "{err:?}" ) ;
283+ }
218284}
0 commit comments