From 6ffcdba8435c330da4ce291a7381dbca4652d445 Mon Sep 17 00:00:00 2001 From: SkandaBhat Date: Wed, 2 Oct 2024 20:49:19 +0530 Subject: [PATCH 1/6] feat: add mev-share sse types --- crates/rpc-types-mev/src/lib.rs | 3 + crates/rpc-types-mev/src/sse.rs | 299 ++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 crates/rpc-types-mev/src/sse.rs diff --git a/crates/rpc-types-mev/src/lib.rs b/crates/rpc-types-mev/src/lib.rs index d2ae96a69a3..1686aa616db 100644 --- a/crates/rpc-types-mev/src/lib.rs +++ b/crates/rpc-types-mev/src/lib.rs @@ -12,6 +12,9 @@ pub use eth_calls::*; mod mev_calls; pub use mev_calls::*; +mod sse; +pub use sse::*; + // types for stats endpoint like flashbots_getUserStats and flashbots_getBundleStats mod stats; pub use stats::*; diff --git a/crates/rpc-types-mev/src/sse.rs b/crates/rpc-types-mev/src/sse.rs new file mode 100644 index 00000000000..9da8045baae --- /dev/null +++ b/crates/rpc-types-mev/src/sse.rs @@ -0,0 +1,299 @@ +//! MEV-share event type bindings + +use alloy_primitives::{hex, Address, Bytes, TxHash, B256, U256}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{array::TryFromSliceError, fmt::LowerHex, ops::Deref}; + +/// SSE event from the MEV-share endpoint +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Event { + /// Transaction or bundle hash. + pub hash: TxHash, + /// Transactions from the event. If the event itself is a transaction, txs will only have one + /// entry. Bundle events may have more. + #[serde(rename = "txs", with = "null_sequence")] + pub transactions: Vec, + /// Event logs emitted by executing the transaction. + #[serde(with = "null_sequence")] + pub logs: Vec, +} + +/// Transaction from the event +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct EventTransaction { + /// Transaction recipient address. + #[serde(skip_serializing_if = "Option::is_none")] + pub to: Option
, + /// 4-byte-function selector + #[serde(rename = "functionSelector")] + #[serde(skip_serializing_if = "Option::is_none")] + pub function_selector: Option, + /// Calldata of the transaction + #[serde(rename = "callData")] + #[serde(skip_serializing_if = "Option::is_none")] + pub calldata: Option, +} + +/// A log produced by a transaction. +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct EventTransactionLog { + /// The address of the contract that emitted the log + pub address: Address, + /// Topics of the log + /// + /// (In solidity: The first topic is the hash of the signature of the event + /// (e.g. `Deposit(address,bytes32,uint256)`), except you declared the event + /// with the anonymous specifier.) + pub topics: Vec, + /// The data of the log + pub data: Bytes, +} + +/// SSE event type of the event history endpoint. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub struct EventHistoryInfo { + pub count: u64, + pub min_block: u64, + pub max_block: u64, + pub min_timestamp: u64, + pub max_timestamp: u64, + pub max_limit: u64, +} + +/// SSE event of the `history` endpoint +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EventHistory { + /// The block number of the event's block. + pub block: u64, + /// The timestamp when the event was emitted. + pub timestamp: u64, + /// Hint for the historic block. + pub hint: Hint, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub struct Hint { + #[serde(with = "null_sequence")] + pub txs: Vec, + pub hash: B256, + #[serde(with = "null_sequence")] + pub logs: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub gas_used: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mev_gas_price: Option, +} + +/// Query params for the `history` endpoint +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +#[allow(missing_docs)] +pub struct EventHistoryParams { + #[serde(skip_serializing_if = "Option::is_none")] + pub block_start: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub block_end: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timestamp_start: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timestamp_end: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option, +} + +#[allow(missing_docs)] +impl EventHistoryParams { + pub fn with_block_start(mut self, block_start: u64) -> Self { + self.block_start = Some(block_start); + self + } + + pub fn with_block_end(mut self, block_end: u64) -> Self { + self.block_end = Some(block_end); + self + } + + pub fn with_block_range(mut self, block_start: u64, block_end: u64) -> Self { + self.block_start = Some(block_start); + self.block_end = Some(block_end); + self + } + + pub fn with_timestamp_start(mut self, timestamp_start: u64) -> Self { + self.timestamp_start = Some(timestamp_start); + self + } + + pub fn with_timestamp_end(mut self, timestamp_end: u64) -> Self { + self.timestamp_end = Some(timestamp_end); + self + } + + pub fn with_timestamp_range(mut self, timestamp_start: u64, timestamp_end: u64) -> Self { + self.timestamp_start = Some(timestamp_start); + self.timestamp_end = Some(timestamp_end); + self + } + + pub fn with_limit(mut self, limit: u64) -> Self { + self.limit = Some(limit); + self + } + + pub fn with_offset(mut self, offset: u64) -> Self { + self.offset = Some(offset); + self + } +} + +/// 4-byte-function selector +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct FunctionSelector(pub [u8; 4]); + +// === impl FunctionSelector === + +impl FunctionSelector { + fn hex_encode(&self) -> String { + hex::encode(self.0.as_ref()) + } +} + +impl Serialize for FunctionSelector { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for FunctionSelector { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let hex_str = String::deserialize(deserializer)?; + let s = hex_str.strip_prefix("0x").unwrap_or(&hex_str); + if s.len() != 8 { + return Err(serde::de::Error::custom(format!( + "Expected 4 byte function selector: {}", + hex_str + ))) + } + + let bytes = hex::decode(s).map_err(serde::de::Error::custom)?; + let selector = + FunctionSelector::try_from(bytes.as_slice()).map_err(serde::de::Error::custom)?; + Ok(selector) + } +} + +impl AsRef<[u8]> for FunctionSelector { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl std::fmt::Debug for FunctionSelector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("FunctionSelector").field(&self.hex_encode()).finish() + } +} + +impl std::fmt::Display for FunctionSelector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", self.hex_encode()) + } +} + +impl LowerHex for FunctionSelector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", self.hex_encode()) + } +} + +impl Deref for FunctionSelector { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + self.as_ref() + } +} + +impl From<[u8; 4]> for FunctionSelector { + fn from(src: [u8; 4]) -> Self { + Self(src) + } +} + +impl<'a> TryFrom<&'a [u8]> for FunctionSelector { + type Error = TryFromSliceError; + + fn try_from(value: &'a [u8]) -> Result { + let sel: [u8; 4] = value.try_into()?; + Ok(Self(sel)) + } +} + +impl PartialEq<[u8; 4]> for FunctionSelector { + fn eq(&self, other: &[u8; 4]) -> bool { + other == &self.0 + } +} + +/// Deserializes missing or null sequences as empty vectors. +mod null_sequence { + use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; + + pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: DeserializeOwned, + { + let s = Option::>::deserialize(deserializer)?.unwrap_or_default(); + Ok(s) + } + + pub(crate) fn serialize(val: &Vec, serializer: S) -> Result + where + T: Serialize, + S: Serializer, + { + if val.is_empty() { + serializer.serialize_none() + } else { + val.serialize(serializer) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_null_txs() { + let s = "{\"hash\":\"0x9d525cbf4ed0cd367df93a685da93da036bf5c6d0d6e9e31945779ddbca31d3b\",\"logs\":[{\"address\":\"0x074201cb10b1efedbd8dec271c37687e1ab5be4e\",\"data\":\"0x\",\"topics\":[\"0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822\",\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"address\":\"0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852\",\"data\":\"0x\",\"topics\":[\"0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822\",\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"txs\":null}"; + let _ev: Event = serde_json::from_str(s).unwrap(); + } + + #[test] + fn test_null_logs() { + let s = "{\"hash\":\"0x8e32bfed609925168302ea538acd839ad34fdcd5d89dff67b19f9717d39abee6\",\"logs\":null,\"txs\":null}"; + let _ev: Event = serde_json::from_str(s).unwrap(); + } + + #[test] + fn test_sample() { + let s = r#"{"hash":"0xc7dc06c994400830054ab815732d91275bc1241f9be62b62b687b7882f19b8d4","logs":null,"txs":[{"to":"0x0000c335bc9d5d1af0402cad63fa7f258363d71a","functionSelector":"0x696d2073","callData":"0x696d20736861726969696969696e67"}]}"#; + let _ev: Event = serde_json::from_str(s).unwrap(); + } +} \ No newline at end of file From 0a16627e08f08ddaa6b45463a804342b1bc62160 Mon Sep 17 00:00:00 2001 From: SkandaBhat Date: Wed, 2 Oct 2024 22:14:03 +0530 Subject: [PATCH 2/6] fix: change to use pub mod instead of exporting on global --- crates/rpc-types-mev/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/rpc-types-mev/src/lib.rs b/crates/rpc-types-mev/src/lib.rs index 1686aa616db..8a5add18506 100644 --- a/crates/rpc-types-mev/src/lib.rs +++ b/crates/rpc-types-mev/src/lib.rs @@ -12,8 +12,7 @@ pub use eth_calls::*; mod mev_calls; pub use mev_calls::*; -mod sse; -pub use sse::*; +pub mod sse; // types for stats endpoint like flashbots_getUserStats and flashbots_getBundleStats mod stats; From 146c2c1ab46044c1f339a244c3232f54dd773c19 Mon Sep 17 00:00:00 2001 From: SkandaBhat Date: Wed, 2 Oct 2024 22:14:15 +0530 Subject: [PATCH 3/6] chore: fmt --- crates/rpc-types-mev/src/sse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc-types-mev/src/sse.rs b/crates/rpc-types-mev/src/sse.rs index 9da8045baae..05b5b3bef70 100644 --- a/crates/rpc-types-mev/src/sse.rs +++ b/crates/rpc-types-mev/src/sse.rs @@ -185,7 +185,7 @@ impl<'de> Deserialize<'de> for FunctionSelector { return Err(serde::de::Error::custom(format!( "Expected 4 byte function selector: {}", hex_str - ))) + ))); } let bytes = hex::decode(s).map_err(serde::de::Error::custom)?; @@ -296,4 +296,4 @@ mod tests { let s = r#"{"hash":"0xc7dc06c994400830054ab815732d91275bc1241f9be62b62b687b7882f19b8d4","logs":null,"txs":[{"to":"0x0000c335bc9d5d1af0402cad63fa7f258363d71a","functionSelector":"0x696d2073","callData":"0x696d20736861726969696969696e67"}]}"#; let _ev: Event = serde_json::from_str(s).unwrap(); } -} \ No newline at end of file +} From d0502a590e6017580c0d53b48cfa3e6558487912 Mon Sep 17 00:00:00 2001 From: SkandaBhat Date: Wed, 2 Oct 2024 22:22:43 +0530 Subject: [PATCH 4/6] chore: fix clippy warnings --- crates/rpc-types-mev/src/sse.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/rpc-types-mev/src/sse.rs b/crates/rpc-types-mev/src/sse.rs index 05b5b3bef70..416ec206718 100644 --- a/crates/rpc-types-mev/src/sse.rs +++ b/crates/rpc-types-mev/src/sse.rs @@ -110,44 +110,44 @@ pub struct EventHistoryParams { #[allow(missing_docs)] impl EventHistoryParams { - pub fn with_block_start(mut self, block_start: u64) -> Self { + pub const fn with_block_start(mut self, block_start: u64) -> Self { self.block_start = Some(block_start); self } - pub fn with_block_end(mut self, block_end: u64) -> Self { + pub const fn with_block_end(mut self, block_end: u64) -> Self { self.block_end = Some(block_end); self } - pub fn with_block_range(mut self, block_start: u64, block_end: u64) -> Self { + pub const fn with_block_range(mut self, block_start: u64, block_end: u64) -> Self { self.block_start = Some(block_start); self.block_end = Some(block_end); self } - pub fn with_timestamp_start(mut self, timestamp_start: u64) -> Self { + pub const fn with_timestamp_start(mut self, timestamp_start: u64) -> Self { self.timestamp_start = Some(timestamp_start); self } - pub fn with_timestamp_end(mut self, timestamp_end: u64) -> Self { + pub const fn with_timestamp_end(mut self, timestamp_end: u64) -> Self { self.timestamp_end = Some(timestamp_end); self } - pub fn with_timestamp_range(mut self, timestamp_start: u64, timestamp_end: u64) -> Self { + pub const fn with_timestamp_range(mut self, timestamp_start: u64, timestamp_end: u64) -> Self { self.timestamp_start = Some(timestamp_start); self.timestamp_end = Some(timestamp_end); self } - pub fn with_limit(mut self, limit: u64) -> Self { + pub const fn with_limit(mut self, limit: u64) -> Self { self.limit = Some(limit); self } - pub fn with_offset(mut self, offset: u64) -> Self { + pub const fn with_offset(mut self, offset: u64) -> Self { self.offset = Some(offset); self } @@ -190,7 +190,7 @@ impl<'de> Deserialize<'de> for FunctionSelector { let bytes = hex::decode(s).map_err(serde::de::Error::custom)?; let selector = - FunctionSelector::try_from(bytes.as_slice()).map_err(serde::de::Error::custom)?; + Self::try_from(bytes.as_slice()).map_err(serde::de::Error::custom)?; Ok(selector) } } From a0b82b15319bae752a3891db47abdfd3070c84b5 Mon Sep 17 00:00:00 2001 From: SkandaBhat Date: Wed, 2 Oct 2024 22:23:48 +0530 Subject: [PATCH 5/6] chore: fmt --- crates/rpc-types-mev/src/sse.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/rpc-types-mev/src/sse.rs b/crates/rpc-types-mev/src/sse.rs index 416ec206718..01624daabdf 100644 --- a/crates/rpc-types-mev/src/sse.rs +++ b/crates/rpc-types-mev/src/sse.rs @@ -189,8 +189,7 @@ impl<'de> Deserialize<'de> for FunctionSelector { } let bytes = hex::decode(s).map_err(serde::de::Error::custom)?; - let selector = - Self::try_from(bytes.as_slice()).map_err(serde::de::Error::custom)?; + let selector = Self::try_from(bytes.as_slice()).map_err(serde::de::Error::custom)?; Ok(selector) } } From 224edab61762bdc3566817a5a62fe77f42a93e0e Mon Sep 17 00:00:00 2001 From: SkandaBhat Date: Wed, 2 Oct 2024 23:20:41 +0530 Subject: [PATCH 6/6] fix: rename sse to mevshare --- crates/rpc-types-mev/src/lib.rs | 2 +- crates/rpc-types-mev/src/{sse.rs => mevshare.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename crates/rpc-types-mev/src/{sse.rs => mevshare.rs} (100%) diff --git a/crates/rpc-types-mev/src/lib.rs b/crates/rpc-types-mev/src/lib.rs index 8a5add18506..7fa37f5f3d0 100644 --- a/crates/rpc-types-mev/src/lib.rs +++ b/crates/rpc-types-mev/src/lib.rs @@ -12,7 +12,7 @@ pub use eth_calls::*; mod mev_calls; pub use mev_calls::*; -pub mod sse; +pub mod mevshare; // types for stats endpoint like flashbots_getUserStats and flashbots_getBundleStats mod stats; diff --git a/crates/rpc-types-mev/src/sse.rs b/crates/rpc-types-mev/src/mevshare.rs similarity index 100% rename from crates/rpc-types-mev/src/sse.rs rename to crates/rpc-types-mev/src/mevshare.rs