From f9e178b14f6dd91b936038c785fe8c161b7d3113 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 28 Jun 2022 20:32:03 +0200 Subject: [PATCH 1/6] feat(core): add OtherFields type --- ethers-core/src/types/mod.rs | 3 + ethers-core/src/types/other.rs | 121 +++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 ethers-core/src/types/other.rs diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs index e4e6a799d..c57017279 100644 --- a/ethers-core/src/types/mod.rs +++ b/ethers-core/src/types/mod.rs @@ -63,3 +63,6 @@ pub use proof::*; mod fee; pub use fee::*; + +mod other; +pub use other::OtherFields; diff --git a/ethers-core/src/types/other.rs b/ethers-core/src/types/other.rs new file mode 100644 index 000000000..451a3d8b0 --- /dev/null +++ b/ethers-core/src/types/other.rs @@ -0,0 +1,121 @@ +//! Support for capturing other fields +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::BTreeMap, ops::Deref}; + +/// A type that is supposed to capture additional fields that are not native to ethereum but included in ethereum adjacent networks, for example fields the [optimism `eth_getTransactionByHash` request](https://docs.alchemy.com/alchemy/apis/optimism/eth-gettransactionbyhash) returns additional fields that this type will capture +/// +/// This type is supposed to be used with [`#[serde(flatten)`](https://serde.rs/field-attrs.html#flatten) +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct OtherFields { + /// Contains all unknown fields + inner: BTreeMap, +} + +// === impl OtherFields === + +impl OtherFields { + /// Returns the deserialized value of the field, if it exists. + /// Deserializes the value with the given closure + /// + /// ``` + /// # use ethers_core::types::{OtherFields, U64}; + /// fn d(other: OtherFields) { + /// let l1_block_number = other.get_with("l1BlockNumber", |value| serde_json::from_value::(value)).unwrap().unwrap(); + /// # } + /// ``` + pub fn get_with(&self, key: impl AsRef, with: F) -> Option + where + V: DeserializeOwned, + F: FnOnce(serde_json::Value) -> V, + { + self.inner.get(key.as_ref()).cloned().map(with) + } + + /// Returns the deserialized value of the field, if it exists + /// + /// ``` + /// # use ethers_core::types::{OtherFields, U64}; + /// fn d(other: OtherFields) { + /// let l1_block_number = other.get_deserialized::("l1BlockNumber").unwrap().unwrap(); + /// # } + /// ``` + pub fn get_deserialized( + &self, + key: impl AsRef, + ) -> Option> { + self.inner.get(key.as_ref()).cloned().map(serde_json::from_value) + } + + /// Removes the deserialized value of the field, if it exists + /// + /// ``` + /// # use ethers_core::types::{OtherFields, U64}; + /// fn d(mut other: OtherFields) { + /// let l1_block_number = other.remove_deserialized::("l1BlockNumber").unwrap().unwrap(); + /// assert!(!other.contains_key("l1BlockNumber")); + /// # } + /// ``` + /// + /// **Note:** this will also remove the value if deserializing it resulted in an error + pub fn remove_deserialized( + &mut self, + key: impl AsRef, + ) -> Option> { + self.inner.remove(key.as_ref()).map(serde_json::from_value) + } + + /// Removes the deserialized value of the field, if it exists. + /// Deserializes the value with the given closure + /// + /// ``` + /// # use ethers_core::types::{OtherFields, U64}; + /// fn d(mut other: OtherFields) { + /// let l1_block_number = other.remove_with("l1BlockNumber", |value| serde_json::from_value::(value)).unwrap().unwrap(); + /// # } + /// ``` + /// **Note:** this will also remove the value if deserializing it resulted in an error + pub fn remove_with(&mut self, key: impl AsRef, with: F) -> Option + where + V: DeserializeOwned, + F: FnOnce(serde_json::Value) -> V, + { + self.inner.remove(key.as_ref()).map(with) + } + + /// Removes the deserialized value of the field, if it exists and also returns the key + /// + /// ``` + /// # use ethers_core::types::{OtherFields, U64}; + /// fn d(mut other: OtherFields) { + /// let (key, l1_block_number_result) = other.remove_entry_deserialized::("l1BlockNumber").unwrap(); + /// let l1_block_number = l1_block_number_result.unwrap(); + /// assert!(!other.contains_key("l1BlockNumber")); + /// # } + /// ``` + /// + /// **Note:** this will also remove the value if deserializing it resulted in an error + pub fn remove_entry_deserialized( + &mut self, + key: impl AsRef, + ) -> Option<(String, serde_json::Result)> { + self.inner + .remove_entry(key.as_ref()) + .map(|(key, value)| (key, serde_json::from_value(value))) + } +} + +impl Deref for OtherFields { + type Target = BTreeMap; + + #[inline] + fn deref(&self) -> &BTreeMap { + self.as_ref() + } +} + +impl AsRef> for OtherFields { + fn as_ref(&self) -> &BTreeMap { + &self.inner + } +} From f9f85d4e28ef8cc621304adfd6e3aae35834aae3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 28 Jun 2022 20:58:49 +0200 Subject: [PATCH 2/6] feat: capture unknown fields --- ethers-core/src/types/block.rs | 9 ++++ ethers-core/src/types/other.rs | 46 ++++++++++++++++++- ethers-core/src/types/transaction/response.rs | 5 ++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs index e2bfb6681..bfdcc8a2e 100644 --- a/ethers-core/src/types/block.rs +++ b/ethers-core/src/types/block.rs @@ -96,6 +96,11 @@ pub struct Block { #[cfg_attr(docsrs, doc(cfg(feature = "celo")))] #[serde(rename = "epochSnarkData", default)] pub epoch_snark_data: Option, + + /// Captures unknown fields such as additional fields used by L2s + #[cfg(not(feature = "celo"))] + #[serde(flatten)] + pub other: crate::types::OtherFields, } /// Error returned by [`Block::time`]. @@ -203,6 +208,7 @@ impl Block { mix_hash, nonce, base_fee_per_gas, + other, .. } = self; Block { @@ -228,6 +234,7 @@ impl Block { nonce, base_fee_per_gas, transactions, + other, } } @@ -305,6 +312,7 @@ impl From> for Block { mix_hash, nonce, base_fee_per_gas, + other, } = full; Block { hash, @@ -329,6 +337,7 @@ impl From> for Block { nonce, base_fee_per_gas, transactions: transactions.iter().map(|tx| tx.hash).collect(), + other, } } diff --git a/ethers-core/src/types/other.rs b/ethers-core/src/types/other.rs index 451a3d8b0..ab7d6ac45 100644 --- a/ethers-core/src/types/other.rs +++ b/ethers-core/src/types/other.rs @@ -1,11 +1,12 @@ //! Support for capturing other fields use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Map; use std::{collections::BTreeMap, ops::Deref}; /// A type that is supposed to capture additional fields that are not native to ethereum but included in ethereum adjacent networks, for example fields the [optimism `eth_getTransactionByHash` request](https://docs.alchemy.com/alchemy/apis/optimism/eth-gettransactionbyhash) returns additional fields that this type will capture /// /// This type is supposed to be used with [`#[serde(flatten)`](https://serde.rs/field-attrs.html#flatten) -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default)] #[serde(transparent)] pub struct OtherFields { /// Contains all unknown fields @@ -103,6 +104,31 @@ impl OtherFields { .remove_entry(key.as_ref()) .map(|(key, value)| (key, serde_json::from_value(value))) } + + /// Deserialized this type into another container type + /// + /// ``` + /// use ethers_core::types::{Address, OtherFields, U64}; + /// use serde::Deserialize; + /// # fn d(mut other: OtherFields) { + /// + /// /// Additional Optimism transaction fields + /// #[derive(Deserialize)] + /// #[serde(rename_all = "camelCase")] + /// struct OptimismExtraFields { + /// pub l1_tx_origin : Option
, + /// pub l1_timestamp : U64, + /// pub l1_block_number : U64, + /// } + /// + /// let optimism: OptimismExtraFields = other.deserialize_into().unwrap(); + /// # } + /// ``` + pub fn deserialize_into(self) -> serde_json::Result { + let mut map = Map::with_capacity(self.inner.len()); + map.extend(self); + serde_json::from_value(serde_json::Value::Object(map)) + } } impl Deref for OtherFields { @@ -119,3 +145,21 @@ impl AsRef> for OtherFields { &self.inner } } + +impl IntoIterator for OtherFields { + type Item = (String, serde_json::Value); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl<'a> IntoIterator for &'a OtherFields { + type Item = (&'a String, &'a serde_json::Value); + type IntoIter = std::collections::btree_map::Iter<'a, String, serde_json::Value>; + + fn into_iter(self) -> Self::IntoIter { + self.as_ref().iter() + } +} diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs index a50aa8381..55b43955b 100644 --- a/ethers-core/src/types/transaction/response.rs +++ b/ethers-core/src/types/transaction/response.rs @@ -113,6 +113,11 @@ pub struct Transaction { #[serde(rename = "chainId", default, skip_serializing_if = "Option::is_none")] pub chain_id: Option, + + /// Captures unknown fields such as additional fields used by L2s + #[cfg(not(feature = "celo"))] + #[serde(flatten)] + pub other: crate::types::OtherFields, } impl Transaction { From 260c84e52b2f7bebac79150ce7c18944810c81dc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 28 Jun 2022 21:02:09 +0200 Subject: [PATCH 3/6] chore: update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36f206a73..784586571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Unreleased +- capture unknown fields in `Block` and `Transaction` type via new `OtherFields` type [#1423](https://github.com/gakonst/ethers-rs/pull/1423) - Methods like `set_to()` from `TypedTransaction` can be chained - Use H64 for Block Nonce [#1396](https://github.com/gakonst/ethers-rs/pull/1396) - Add `as_*_mut` methods on `TypedTransaction` From ba2544f11a81351e147eb87baaf78f040eed8f9d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 28 Jun 2022 21:06:24 +0200 Subject: [PATCH 4/6] test: update tests --- ethers-core/src/types/transaction/response.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs index 55b43955b..90d532de0 100644 --- a/ethers-core/src/types/transaction/response.rs +++ b/ethers-core/src/types/transaction/response.rs @@ -555,6 +555,7 @@ mod tests { 16, ) .unwrap(), + other: Default::default(), }; println!("0x{}", hex::encode(&tx.rlp())); assert_eq!( @@ -598,6 +599,7 @@ mod tests { 16, ) .unwrap(), + other: Default::default(), }; println!("0x{}", hex::encode(&tx.rlp())); assert_eq!( @@ -631,7 +633,8 @@ mod tests { chain_id: Some(U256::from(1)), access_list: None, max_fee_per_gas: None, - max_priority_fee_per_gas: None + max_priority_fee_per_gas: None, + other: Default::default() }; assert_eq!( tx.rlp(), @@ -677,6 +680,7 @@ mod tests { max_priority_fee_per_gas: Some(1500000000.into()), max_fee_per_gas: Some(1500000009.into()), chain_id: Some(5.into()), + other: Default::default(), }; assert_eq!( tx.rlp(), @@ -722,6 +726,7 @@ mod tests { max_priority_fee_per_gas: Some(1500000000.into()), max_fee_per_gas: Some(1500000009.into()), chain_id: Some(5.into()), + other: Default::default(), }; let rlp_bytes = hex::decode("02f86f05418459682f008459682f098301a0cf9411d7c2ab0d4aa26b7d8502f6a7ef6844908495c28084e5225381c001a01a8d7bef47f6155cbdf13d57107fc577fd52880fa2862b1a50d47641f8839419a03279bbf73fde76de83440d04b9d97f3809fec8617d3557ee40ac3e0edc391514").unwrap(); @@ -767,6 +772,7 @@ mod tests { max_priority_fee_per_gas: Some(1500000000.into()), max_fee_per_gas: Some(1500000009.into()), chain_id: Some(5.into()), + other: Default::default(), }; assert_eq!(tx.hash, tx.hash()); From da9cbc082f9d857591167c2cb74af8f5644eb49b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 28 Jun 2022 21:27:53 +0200 Subject: [PATCH 5/6] docs: fix doc tests --- ethers-core/src/types/other.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ethers-core/src/types/other.rs b/ethers-core/src/types/other.rs index ab7d6ac45..b528636ad 100644 --- a/ethers-core/src/types/other.rs +++ b/ethers-core/src/types/other.rs @@ -27,7 +27,6 @@ impl OtherFields { /// ``` pub fn get_with(&self, key: impl AsRef, with: F) -> Option where - V: DeserializeOwned, F: FnOnce(serde_json::Value) -> V, { self.inner.get(key.as_ref()).cloned().map(with) @@ -38,7 +37,7 @@ impl OtherFields { /// ``` /// # use ethers_core::types::{OtherFields, U64}; /// fn d(other: OtherFields) { - /// let l1_block_number = other.get_deserialized::("l1BlockNumber").unwrap().unwrap(); + /// let l1_block_number: U64 = other.get_deserialized("l1BlockNumber").unwrap().unwrap(); /// # } /// ``` pub fn get_deserialized( @@ -53,7 +52,7 @@ impl OtherFields { /// ``` /// # use ethers_core::types::{OtherFields, U64}; /// fn d(mut other: OtherFields) { - /// let l1_block_number = other.remove_deserialized::("l1BlockNumber").unwrap().unwrap(); + /// let l1_block_number: U64 = other.remove_deserialized("l1BlockNumber").unwrap().unwrap(); /// assert!(!other.contains_key("l1BlockNumber")); /// # } /// ``` @@ -72,13 +71,12 @@ impl OtherFields { /// ``` /// # use ethers_core::types::{OtherFields, U64}; /// fn d(mut other: OtherFields) { - /// let l1_block_number = other.remove_with("l1BlockNumber", |value| serde_json::from_value::(value)).unwrap().unwrap(); + /// let l1_block_number: U64 = other.remove_with("l1BlockNumber", |value| serde_json::from_value(value)).unwrap().unwrap(); /// # } /// ``` /// **Note:** this will also remove the value if deserializing it resulted in an error pub fn remove_with(&mut self, key: impl AsRef, with: F) -> Option where - V: DeserializeOwned, F: FnOnce(serde_json::Value) -> V, { self.inner.remove(key.as_ref()).map(with) From 8b1e73077a3a6a3452c46fe40b5b0b33945580c4 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 28 Jun 2022 21:33:14 +0200 Subject: [PATCH 6/6] fix: make docs compile on stable --- ethers-core/src/types/other.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-core/src/types/other.rs b/ethers-core/src/types/other.rs index b528636ad..abb570d3e 100644 --- a/ethers-core/src/types/other.rs +++ b/ethers-core/src/types/other.rs @@ -87,7 +87,7 @@ impl OtherFields { /// ``` /// # use ethers_core::types::{OtherFields, U64}; /// fn d(mut other: OtherFields) { - /// let (key, l1_block_number_result) = other.remove_entry_deserialized::("l1BlockNumber").unwrap(); + /// let (key, l1_block_number_result) : (_, serde_json::Result) = other.remove_entry_deserialized("l1BlockNumber").unwrap(); /// let l1_block_number = l1_block_number_result.unwrap(); /// assert!(!other.contains_key("l1BlockNumber")); /// # }