levana_perpswap_cosmos/
event.rs

1//! Helpers for parsing event data into well typed event data types.
2use std::str::FromStr;
3
4use crate::direction::DirectionToBase;
5use crate::leverage::LeverageToBase;
6use crate::prelude::*;
7use anyhow::Context;
8use cosmwasm_std::Event;
9use serde::de::DeserializeOwned;
10
11use crate::error::{ErrorDomain, ErrorId};
12
13/// Extension trait to add methods to native cosmwasm events
14pub trait CosmwasmEventExt {
15    // these are the only two that require implementation
16    // everything else builds on these
17
18    /// Does the event have the given attribute?
19    fn has_attr(&self, key: &str) -> bool;
20
21    /// Parse the value associated with the key, if it exists
22    fn try_map_attr<B>(&self, key: &str, f: impl Fn(&str) -> B) -> Option<B>;
23
24    /// Parse the value associated with the key as JSON, if it exists
25    fn try_json_attr<B: DeserializeOwned>(&self, key: &str) -> anyhow::Result<Option<B>> {
26        match self.try_map_attr(key, |s| serde_json::from_str(s)) {
27            None => Ok(None),
28            Some(x) => Ok(Some(x?)),
29        }
30    }
31
32    /// Parse the value associated with the key as JSON
33    fn json_attr<B: DeserializeOwned>(&self, key: &str) -> anyhow::Result<B> {
34        self.map_attr_result(key, |s| {
35            serde_json::from_str(s).map_err(anyhow::Error::from)
36        })
37    }
38
39    /// Parse the value associated with the key as a u64
40    fn u64_attr(&self, key: &str) -> anyhow::Result<u64> {
41        self.map_attr_result(key, |s| s.parse().map_err(anyhow::Error::from))
42    }
43
44    /// Parse the value associated with the key as a u64, if it exists
45    fn try_u64_attr(&self, key: &str) -> anyhow::Result<Option<u64>> {
46        match self.try_map_attr(key, |s| s.parse()) {
47            None => Ok(None),
48            Some(x) => Ok(Some(x?)),
49        }
50    }
51
52    /// Parse a timestamp attribute
53    fn timestamp_attr(&self, key: &str) -> anyhow::Result<Timestamp> {
54        self.map_attr_result(key, |s| Timestamp::from_str(s).map_err(|x| x.into()))
55    }
56
57    /// Parse a timestamp attribute, if it exists
58    fn try_timestamp_attr(&self, key: &str) -> anyhow::Result<Option<Timestamp>> {
59        self.try_map_attr(key, |s| Timestamp::from_str(s).map_err(|x| x.into()))
60            .transpose()
61    }
62
63    /// Parse an unsigned decimal attribute
64    fn decimal_attr<T: UnsignedDecimal>(&self, key: &str) -> anyhow::Result<T> {
65        self.map_attr_result(key, |s| {
66            s.parse()
67                .ok()
68                .with_context(|| format!("decimal_attr failed on key {key} and value {s}"))
69        })
70    }
71
72    /// Parse a non-zero (strictly positive) decimal attribute
73    fn non_zero_attr<T: UnsignedDecimal>(&self, key: &str) -> anyhow::Result<NonZero<T>> {
74        self.map_attr_result(key, |s| {
75            s.parse()
76                .ok()
77                .with_context(|| format!("non_zero_attr failed on key {key} and value {s}"))
78        })
79    }
80
81    /// Parse a signed decimal attribute
82    fn signed_attr<T: UnsignedDecimal>(&self, key: &str) -> anyhow::Result<Signed<T>> {
83        self.map_attr_result(key, |s| {
84            s.parse()
85                .ok()
86                .with_context(|| format!("signed_attr failed on key {key} and value {s}"))
87        })
88    }
89
90    /// Parse a signed decimal attribute
91    fn number_attr<T: UnsignedDecimal>(&self, key: &str) -> anyhow::Result<Signed<T>> {
92        self.map_attr_result(key, |s| s.parse())
93    }
94
95    /// Parse an optional signed decimal attribute
96    fn try_number_attr<T: UnsignedDecimal>(&self, key: &str) -> anyhow::Result<Option<Signed<T>>> {
97        self.try_map_attr(key, |s| {
98            s.parse()
99                .ok()
100                .with_context(|| format!("try_number_attr failed parse on key {key} and value {s}"))
101        })
102        .transpose()
103    }
104
105    /// Parse an optional unsigned decimal attribute
106    fn try_decimal_attr<T: UnsignedDecimal>(&self, key: &str) -> anyhow::Result<Option<T>> {
107        self.try_map_attr(key, |s| {
108            s.parse().ok().with_context(|| {
109                format!("try_decimal_attr failed parse on key {key} and value {s}")
110            })
111        })
112        .transpose()
113    }
114
115    /// Parse an optional price
116    fn try_price_base_in_quote(&self, key: &str) -> anyhow::Result<Option<PriceBaseInQuote>> {
117        self.try_map_attr(key, |s| {
118            s.parse().ok().with_context(|| {
119                format!("try_price_base_in_quote failed parse on key {key} and value {s}")
120            })
121        })
122        .transpose()
123    }
124
125    /// Parse a string attribute
126    fn string_attr(&self, key: &str) -> anyhow::Result<String> {
127        self.map_attr_ok(key, |s| s.to_string())
128    }
129
130    /// Parse a bool-as-string attribute
131    fn bool_attr(&self, key: &str) -> anyhow::Result<bool> {
132        self.string_attr(key)
133            .and_then(|s| s.parse::<bool>().map_err(|err| err.into()))
134    }
135
136    /// Parse an attribute with a position direction (to base)
137    fn direction_attr(&self, key: &str) -> anyhow::Result<DirectionToBase> {
138        self.map_attr_result(key, |s| match s {
139            "long" => Ok(DirectionToBase::Long),
140            "short" => Ok(DirectionToBase::Short),
141            _ => Err(anyhow::anyhow!("Invalid direction: {s}")),
142        })
143    }
144
145    /// Parse an attribute with the absolute leverage (to base)
146    fn leverage_to_base_attr(&self, key: &str) -> anyhow::Result<LeverageToBase> {
147        self.map_attr_result(key, LeverageToBase::from_str)
148    }
149
150    /// Parse an optional attribute with the absolute leverage (to base)
151    fn try_leverage_to_base_attr(&self, key: &str) -> anyhow::Result<Option<LeverageToBase>> {
152        self.try_map_attr(key, LeverageToBase::from_str).transpose()
153    }
154
155    /// Parse an address attribute without checking validity
156    fn unchecked_addr_attr(&self, key: &str) -> anyhow::Result<Addr> {
157        self.map_attr_ok(key, |s| Addr::unchecked(s))
158    }
159
160    /// Parse an optional address attribute without checking validity
161    fn try_unchecked_addr_attr(&self, key: &str) -> anyhow::Result<Option<Addr>> {
162        self.try_map_attr(key, |s| Ok(Addr::unchecked(s)))
163            .transpose()
164    }
165
166    /// Require an attribute and apply a function to the raw string value
167    fn map_attr_ok<B>(&self, key: &str, f: impl Fn(&str) -> B) -> anyhow::Result<B> {
168        match self.try_map_attr(key, f) {
169            Some(x) => Ok(x),
170            None => Err(anyhow!(PerpError::new(
171                ErrorId::Any,
172                ErrorDomain::Default,
173                format!("no such key {key}")
174            ))),
175        }
176    }
177
178    /// Require an attribute and try to parse its value with the given function
179    fn map_attr_result<B>(
180        &self,
181        key: &str,
182        f: impl Fn(&str) -> anyhow::Result<B>,
183    ) -> anyhow::Result<B> {
184        // just need to remove the one level of nesting for "no such key"
185        self.map_attr_ok(key, f)?
186    }
187}
188
189impl CosmwasmEventExt for Event {
190    fn has_attr(&self, key: &str) -> bool {
191        self.attributes.iter().any(|a| a.key == key)
192    }
193    fn try_map_attr<B>(&self, key: &str, f: impl Fn(&str) -> B) -> Option<B> {
194        self.attributes.iter().find_map(|a| {
195            if a.key == key {
196                Some(f(a.value.as_str()))
197            } else {
198                None
199            }
200        })
201    }
202}