layer_climb_core/
events.rs

1mod constants;
2mod ibc;
3pub use constants::*;
4pub use ibc::*;
5
6use crate::prelude::*;
7
8// this wrapper (with From impls for standard event sources) helps to abstract over events to aid filtering etc.
9// it is slightly opinionated in that it will search for events with the wasm- prefix if the exact type is not found
10// also, it has attribute iterators to preserve performance with references
11pub enum CosmosTxEvents<'a> {
12    TxResponseRef(&'a layer_climb_proto::abci::TxResponse),
13    TxResponseOwned(Box<layer_climb_proto::abci::TxResponse>),
14    // I think this is from RPC...
15    Tendermint2ListRef(&'a [tendermint::abci::Event]),
16    Tendermint2ListOwned(Box<Vec<tendermint::abci::Event>>),
17    CosmWasmRef(&'a [cosmwasm_std::Event]),
18    CosmWasmOwned(Box<Vec<cosmwasm_std::Event>>),
19}
20
21impl<'a> From<&'a layer_climb_proto::abci::TxResponse> for CosmosTxEvents<'a> {
22    fn from(resp: &'a layer_climb_proto::abci::TxResponse) -> Self {
23        CosmosTxEvents::TxResponseRef(resp)
24    }
25}
26
27impl From<layer_climb_proto::abci::TxResponse> for CosmosTxEvents<'static> {
28    fn from(resp: layer_climb_proto::abci::TxResponse) -> Self {
29        CosmosTxEvents::TxResponseOwned(Box::new(resp))
30    }
31}
32
33impl<'a> From<&'a [tendermint::abci::Event]> for CosmosTxEvents<'a> {
34    fn from(events: &'a [tendermint::abci::Event]) -> Self {
35        CosmosTxEvents::Tendermint2ListRef(events)
36    }
37}
38
39impl From<Vec<cosmwasm_std::Event>> for CosmosTxEvents<'static> {
40    fn from(events: Vec<cosmwasm_std::Event>) -> Self {
41        CosmosTxEvents::CosmWasmOwned(Box::new(events))
42    }
43}
44
45impl<'a> From<&'a [cosmwasm_std::Event]> for CosmosTxEvents<'a> {
46    fn from(events: &'a [cosmwasm_std::Event]) -> Self {
47        CosmosTxEvents::CosmWasmRef(events)
48    }
49}
50
51impl From<Vec<tendermint::abci::Event>> for CosmosTxEvents<'static> {
52    fn from(events: Vec<tendermint::abci::Event>) -> Self {
53        CosmosTxEvents::Tendermint2ListOwned(Box::new(events))
54    }
55}
56
57// Local event type to allow efficient lazy converting until after filtering/finding
58#[derive(Clone)]
59pub enum Event<'a> {
60    String(&'a layer_climb_proto::abci::StringEvent),
61    // TODO - can we get rid of one of these?
62    Tendermint(&'a layer_climb_proto::tendermint::Event),
63    // I think this is from RPC...
64    Tendermint2(&'a tendermint::abci::Event),
65    CosmWasm(&'a cosmwasm_std::Event),
66}
67
68impl<'a> From<&'a layer_climb_proto::abci::StringEvent> for Event<'a> {
69    fn from(event: &'a layer_climb_proto::abci::StringEvent) -> Self {
70        Event::String(event)
71    }
72}
73
74impl<'a> From<&'a layer_climb_proto::tendermint::Event> for Event<'a> {
75    fn from(event: &'a layer_climb_proto::tendermint::Event) -> Self {
76        Event::Tendermint(event)
77    }
78}
79
80impl<'a> From<&'a tendermint::abci::Event> for Event<'a> {
81    fn from(event: &'a tendermint::abci::Event) -> Self {
82        Event::Tendermint2(event)
83    }
84}
85
86impl<'a> From<&'a cosmwasm_std::Event> for Event<'a> {
87    fn from(event: &'a cosmwasm_std::Event) -> Self {
88        Event::CosmWasm(event)
89    }
90}
91
92impl<'a> From<Event<'a>> for cosmwasm_std::Event {
93    fn from(event: Event<'a>) -> cosmwasm_std::Event {
94        cosmwasm_std::Event::new(event.ty()).add_attributes(event.attributes())
95    }
96}
97
98impl std::fmt::Debug for Event<'_> {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        if f.alternate() {
101            match self {
102                Event::String(e) => write!(f, "{e:#?}"),
103                Event::Tendermint(e) => write!(f, "{e:#?}"),
104                Event::Tendermint2(e) => write!(f, "{e:#?}"),
105                Event::CosmWasm(e) => write!(f, "{e:#?}"),
106            }
107        } else {
108            match self {
109                Event::String(e) => write!(f, "{e:?}"),
110                Event::Tendermint(e) => write!(f, "{e:?}"),
111                Event::Tendermint2(e) => write!(f, "{e:?}"),
112                Event::CosmWasm(e) => write!(f, "{e:?}"),
113            }
114        }
115    }
116}
117
118pub enum Attribute<'a> {
119    String(&'a layer_climb_proto::abci::Attribute),
120    // TODO - can we get rid of one of these?
121    Tendermint(&'a layer_climb_proto::tendermint::EventAttribute),
122    // I think this is from RPC...
123    Tendermint2(&'a tendermint::abci::EventAttribute),
124    CosmWasm(&'a cosmwasm_std::Attribute),
125}
126
127impl<'a> From<Attribute<'a>> for cosmwasm_std::Attribute {
128    fn from(attr: Attribute<'a>) -> Self {
129        cosmwasm_std::Attribute {
130            key: attr.key().to_string(),
131            value: attr.value().to_string(),
132        }
133    }
134}
135
136impl<'a> Event<'a> {
137    pub fn ty(&self) -> &str {
138        match self {
139            Event::String(e) => &e.r#type,
140            Event::Tendermint(e) => &e.r#type,
141            Event::Tendermint2(e) => &e.kind,
142            Event::CosmWasm(e) => &e.ty,
143        }
144    }
145
146    pub fn attributes(&self) -> Box<dyn Iterator<Item = Attribute<'a>> + 'a> {
147        match self {
148            Event::String(e) => Box::new(e.attributes.iter().map(Attribute::String)),
149            Event::Tendermint(e) => Box::new(e.attributes.iter().map(Attribute::Tendermint)),
150            Event::Tendermint2(e) => Box::new(e.attributes.iter().map(Attribute::Tendermint2)),
151            Event::CosmWasm(e) => Box::new(e.attributes.iter().map(Attribute::CosmWasm)),
152        }
153    }
154
155    pub fn is_type(&self, ty: &str) -> bool {
156        let self_ty = self.ty();
157
158        if self_ty == ty {
159            true
160        } else {
161            self_ty == format!("wasm-{ty}")
162        }
163    }
164}
165
166impl Attribute<'_> {
167    pub fn key(&self) -> &str {
168        match self {
169            Attribute::String(a) => &a.key,
170            Attribute::Tendermint(a) => &a.key,
171            Attribute::Tendermint2(a) => a.key_str().unwrap(),
172            Attribute::CosmWasm(a) => &a.key,
173        }
174    }
175
176    pub fn value(&self) -> &str {
177        match self {
178            Attribute::String(a) => &a.value,
179            Attribute::Tendermint(a) => &a.value,
180            Attribute::Tendermint2(a) => a.value_str().unwrap(),
181            Attribute::CosmWasm(a) => &a.value,
182        }
183    }
184}
185
186impl<'a> CosmosTxEvents<'a> {
187    pub fn events_iter(&'a self) -> Box<dyn Iterator<Item = Event<'a>> + 'a> {
188        match &self {
189            Self::TxResponseRef(resp) => {
190                if resp.logs.len() > 1 {
191                    Box::new(
192                        resp.logs
193                            .iter()
194                            .flat_map(|log| log.events.iter().map(Event::String)),
195                    )
196                } else {
197                    Box::new(resp.events.iter().map(Event::Tendermint))
198                }
199            }
200            Self::TxResponseOwned(resp) => {
201                if resp.logs.len() > 1 {
202                    Box::new(
203                        resp.logs
204                            .iter()
205                            .flat_map(|log| log.events.iter().map(Event::String)),
206                    )
207                } else {
208                    Box::new(resp.events.iter().map(Event::Tendermint))
209                }
210            }
211            Self::Tendermint2ListRef(events) => Box::new(events.iter().map(Event::Tendermint2)),
212            Self::Tendermint2ListOwned(events) => Box::new(events.iter().map(Event::Tendermint2)),
213            Self::CosmWasmRef(events) => Box::new(events.iter().map(Event::CosmWasm)),
214            Self::CosmWasmOwned(events) => Box::new(events.iter().map(Event::CosmWasm)),
215        }
216    }
217
218    pub fn filter_events_by_type<'b: 'a>(
219        &'a self,
220        ty: &'b str,
221    ) -> impl Iterator<Item = Event<'a>> + 'a {
222        self.events_iter().filter(move |e| e.is_type(ty))
223    }
224
225    pub fn filter_events_by_attr_key<'b: 'a>(
226        &'a self,
227        ty: &'b str,
228        key: &'b str,
229    ) -> impl Iterator<Item = Event<'a>> + 'a {
230        self.events_iter().filter(move |e| {
231            if e.is_type(ty) {
232                e.attributes().any(|a| a.key() == key)
233            } else {
234                false
235            }
236        })
237    }
238
239    pub fn filter_attrs<'b: 'a>(
240        &'a self,
241        ty: &'b str,
242        key: &'b str,
243    ) -> impl Iterator<Item = Attribute<'a>> + 'a {
244        self.events_iter().filter_map(move |e| {
245            if e.is_type(ty) {
246                e.attributes().find(|a| a.key() == key)
247            } else {
248                None
249            }
250        })
251    }
252
253    pub fn filter_map_attrs<'b: 'a, T, F>(
254        &'a self,
255        ty: &'b str,
256        key: &'b str,
257        f: F,
258    ) -> impl Iterator<Item = T> + 'a
259    where
260        F: Clone + Fn(Attribute<'a>) -> Option<T> + 'a,
261        T: 'static,
262    {
263        self.events_iter().filter_map(move |e| {
264            if e.is_type(ty) {
265                e.attributes().find(|a| a.key() == key).and_then(f.clone())
266            } else {
267                None
268            }
269        })
270    }
271
272    pub fn event_first_by_type<'b: 'a>(&'a self, ty: &'b str) -> Result<Event<'a>> {
273        self.filter_events_by_type(ty)
274            .next()
275            .ok_or_else(|| anyhow!("couldn't find event for {}", ty))
276    }
277
278    pub fn event_first_by_attr_key<'b: 'a>(
279        &'a self,
280        ty: &'b str,
281        key: &'b str,
282    ) -> Result<Event<'a>> {
283        self.filter_events_by_attr_key(ty, key)
284            .next()
285            .ok_or_else(|| anyhow!("couldn't find event for {}.{}", ty, key))
286    }
287
288    pub fn attr_first<'b: 'a>(&'a self, ty: &'b str, key: &'b str) -> Result<Attribute<'a>> {
289        self.filter_attrs(ty, key)
290            .next()
291            .ok_or_else(|| anyhow!("couldn't find event attribute for {}.{}", ty, key))
292    }
293
294    pub fn map_attr_first<'b: 'a, T, F>(&'a self, ty: &'b str, key: &'b str, f: F) -> Result<T>
295    where
296        F: Clone + Fn(Attribute<'a>) -> Option<T> + 'a,
297        T: 'static,
298    {
299        self.filter_map_attrs(ty, key, f)
300            .next()
301            .ok_or_else(|| anyhow!("couldn't find attribute for {}.{}", ty, key))
302    }
303
304    pub fn event_last_by_type<'b: 'a>(&'a self, ty: &'b str) -> Result<Event<'a>> {
305        self.filter_events_by_type(ty)
306            .last()
307            .ok_or_else(|| anyhow!("couldn't find event for {}", ty))
308    }
309
310    pub fn event_last_by_attr_key<'b: 'a>(
311        &'a self,
312        ty: &'b str,
313        key: &'b str,
314    ) -> Result<Event<'a>> {
315        self.filter_events_by_attr_key(ty, key)
316            .last()
317            .ok_or_else(|| anyhow!("couldn't find event for {}.{}", ty, key))
318    }
319
320    pub fn attr_last<'b: 'a>(&'a self, ty: &'b str, key: &'b str) -> Result<Attribute<'a>> {
321        self.filter_attrs(ty, key)
322            .last()
323            .ok_or_else(|| anyhow!("couldn't find event attribute for {}.{}", ty, key))
324    }
325
326    pub fn map_attr_last<'b: 'a, T, F>(&'a self, ty: &'b str, key: &'b str, f: F) -> Result<T>
327    where
328        F: Clone + Fn(Attribute<'a>) -> Option<T> + 'a,
329        T: 'static,
330    {
331        self.filter_map_attrs(ty, key, f)
332            .last()
333            .ok_or_else(|| anyhow!("couldn't find attribute for {}.{}", ty, key))
334    }
335}