blueprint_evm_extra/extract/
event.rs

1//! Event extractors for EVM
2//!
3//! Simple extractors for EVM events and logs, following the same pattern as
4//! the Substrate implementation.
5
6use alloy_rpc_types::Log;
7use alloy_sol_types::SolEvent;
8use blueprint_core::{
9    __composite_rejection as composite_rejection, __define_rejection as define_rejection,
10    __impl_deref as impl_deref, __impl_deref_vec as impl_deref_vec, __impl_from as impl_from,
11    FromJobCallParts, job::call::Parts as JobCallParts,
12};
13
14/// Extracts all events from the current block
15#[derive(Debug, Clone)]
16pub struct BlockEvents(pub Vec<Log>);
17
18impl_deref!(BlockEvents: Vec<Log>);
19impl_from!(Vec<Log>, BlockEvents);
20
21define_rejection! {
22    #[body = "No events found in the extensions"]
23    /// This rejection is used to indicate that no events were found in the extensions.
24    /// This should never happen, but it's here to be safe.
25    pub struct MissingBlockEvents;
26}
27
28impl TryFrom<&mut JobCallParts> for BlockEvents {
29    type Error = MissingBlockEvents;
30
31    fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
32        let events = parts
33            .extensions
34            .get::<Vec<Log>>()
35            .ok_or(MissingBlockEvents)?;
36        Ok(BlockEvents(events.clone()))
37    }
38}
39
40impl<Ctx> FromJobCallParts<Ctx> for BlockEvents
41where
42    Ctx: Send + Sync,
43{
44    type Rejection = MissingBlockEvents;
45
46    async fn from_job_call_parts(
47        parts: &mut JobCallParts,
48        _: &Ctx,
49    ) -> Result<Self, Self::Rejection> {
50        Self::try_from(parts)
51    }
52}
53
54/// Extracts events of type T from the current block
55#[derive(Debug)]
56pub struct Events<T>(pub Vec<T>);
57
58impl_deref_vec!(Events);
59
60define_rejection! {
61    #[body = "Failed to decode events"]
62    /// This rejection is used to indicate that the events could not be decoded.
63    pub struct EventDecodingError;
64}
65
66composite_rejection! {
67    /// Rejection for event extractor
68    pub enum EventRejection {
69        MissingBlockEvents,
70        EventDecodingError,
71    }
72}
73
74impl<T> TryFrom<&mut JobCallParts> for Events<T>
75where
76    T: SolEvent + Clone,
77{
78    type Error = EventRejection;
79
80    fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
81        let logs = parts
82            .extensions
83            .get::<Vec<Log>>()
84            .ok_or(MissingBlockEvents)?;
85
86        let events = logs
87            .iter()
88            .filter(|log| T::SIGNATURE_HASH == log.topics()[0])
89            .filter_map(|log| T::decode_log(&log.inner, true).ok())
90            .map(|event| event.data)
91            .collect();
92
93        Ok(Events(events))
94    }
95}
96
97impl<Ctx, T> FromJobCallParts<Ctx> for Events<T>
98where
99    Ctx: Send + Sync,
100    T: SolEvent + Clone + Send + Sync,
101{
102    type Rejection = EventRejection;
103
104    async fn from_job_call_parts(
105        parts: &mut JobCallParts,
106        _: &Ctx,
107    ) -> Result<Self, Self::Rejection> {
108        Self::try_from(parts)
109    }
110}
111
112/// Extracts the first event of type T from the current block
113#[derive(Debug, Clone)]
114pub struct FirstEvent<T>(pub T);
115
116impl_deref!(FirstEvent);
117
118impl<T: SolEvent + Clone> TryFrom<&mut JobCallParts> for FirstEvent<T> {
119    type Error = EventRejection;
120
121    fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
122        let logs = parts
123            .extensions
124            .get::<Vec<Log>>()
125            .ok_or(MissingBlockEvents)?;
126
127        let first_t = logs
128            .iter()
129            .find_map(|log| {
130                if Some(&T::SIGNATURE_HASH) == log.topic0() {
131                    T::decode_log(&log.inner, true).ok()
132                } else {
133                    None
134                }
135            })
136            .ok_or(EventDecodingError)?;
137
138        Ok(FirstEvent(first_t.data))
139    }
140}
141
142impl<Ctx, T> FromJobCallParts<Ctx> for FirstEvent<T>
143where
144    Ctx: Send + Sync,
145    T: SolEvent + Clone + Send + Sync,
146{
147    type Rejection = EventRejection;
148
149    async fn from_job_call_parts(
150        parts: &mut JobCallParts,
151        _: &Ctx,
152    ) -> Result<Self, Self::Rejection> {
153        Self::try_from(parts)
154    }
155}
156
157/// Extracts the last event of type T from the current block
158#[derive(Debug, Clone)]
159pub struct LastEvent<T>(pub T);
160
161impl_deref!(LastEvent);
162
163impl<T: SolEvent + Clone> TryFrom<&mut JobCallParts> for LastEvent<T> {
164    type Error = EventRejection;
165
166    fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
167        let logs = parts
168            .extensions
169            .get::<Vec<Log>>()
170            .ok_or(MissingBlockEvents)?;
171
172        let last_t = logs
173            .iter()
174            .rev()
175            .find_map(|log| {
176                if Some(&T::SIGNATURE_HASH) == log.topic0() {
177                    T::decode_log(&log.inner, true).ok()
178                } else {
179                    None
180                }
181            })
182            .ok_or(EventDecodingError)?;
183
184        Ok(LastEvent(last_t.data))
185    }
186}
187
188impl<Ctx, T> FromJobCallParts<Ctx> for LastEvent<T>
189where
190    Ctx: Send + Sync,
191    T: SolEvent + Clone + Send + Sync,
192{
193    type Rejection = EventRejection;
194
195    async fn from_job_call_parts(
196        parts: &mut JobCallParts,
197        _: &Ctx,
198    ) -> Result<Self, Self::Rejection> {
199        Self::try_from(parts)
200    }
201}