Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: move mev.rs from reth to rpc-types-mev #970

Merged
merged 11 commits into from Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ alloy-rpc-types-anvil = { version = "0.1", path = "crates/rpc-types-anvil", defa
alloy-rpc-types-beacon = { version = "0.1", path = "crates/rpc-types-beacon", default-features = false }
alloy-rpc-types-engine = { version = "0.1", path = "crates/rpc-types-engine", default-features = false }
alloy-rpc-types-eth = { version = "0.1", path = "crates/rpc-types-eth", default-features = false }
alloy-rpc-types-mev = { version = "0.1", path = "crates/rpc-types-mev", default-features = false }
alloy-rpc-types-trace = { version = "0.1", path = "crates/rpc-types-trace", default-features = false }
alloy-rpc-types-txpool = { version = "0.1", path = "crates/rpc-types-txpool", default-features = false }
alloy-rpc-types = { version = "0.1", path = "crates/rpc-types", default-features = false }
Expand Down
27 changes: 27 additions & 0 deletions crates/rpc-types-mev/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "alloy-rpc-types-mev"
description = "Types for the MEV JSON-RPC namespace"

version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
exclude.workspace = true

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
alloy-eips = { workspace = true, features = ["serde"] }
alloy-primitives.workspace = true
alloy-serde.workspace = true

serde.workspace = true
serde_json.workspace = true

[lints]
workspace = true
3 changes: 3 additions & 0 deletions crates/rpc-types-mev/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# alloy-rpc-types-mev

Types for the MEV bundle JSON-RPC namespace.
175 changes: 175 additions & 0 deletions crates/rpc-types-mev/src/bundle_stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use crate::{ConsideredByBuildersAt, SealedByBuildersAt};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

// TODO(@optimiz-r): Revisit after <https://github.com/flashbots/flashbots-docs/issues/424> is closed.
/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getbundlestatsv2>
///
/// Timestamp format: "2022-10-06T21:36:06.322Z"
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum BundleStats {
/// The relayer has not yet seen the bundle.
#[default]
Unknown,
/// The relayer has seen the bundle, but has not simulated it yet.
Seen(StatsSeen),
/// The relayer has seen the bundle and has simulated it.
Simulated(StatsSimulated),
}

impl Serialize for BundleStats {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Unknown => serde_json::json!({"isSimulated": false}).serialize(serializer),
Self::Seen(stats) => stats.serialize(serializer),
Self::Simulated(stats) => stats.serialize(serializer),
}
}
}

impl<'de> Deserialize<'de> for BundleStats {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let map = serde_json::Map::deserialize(deserializer)?;

if map.get("receivedAt").is_none() {
Ok(Self::Unknown)
} else if map["isSimulated"] == false {
StatsSeen::deserialize(serde_json::Value::Object(map))
.map(BundleStats::Seen)
.map_err(serde::de::Error::custom)
} else {
StatsSimulated::deserialize(serde_json::Value::Object(map))
.map(BundleStats::Simulated)
.map_err(serde::de::Error::custom)
}
}
}

/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getbundlestatsv2>
///
/// Timestamp format: "2022-10-06T21:36:06.322Z
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatsSeen {
/// boolean representing if this searcher has a high enough reputation to be in the high
/// priority queue
pub is_high_priority: bool,
/// representing whether the bundle gets simulated. All other fields will be omitted except
/// simulated field if API didn't receive bundle
pub is_simulated: bool,
/// time at which the bundle API received the bundle
pub received_at: String,
}

/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getbundlestatsv2>
///
/// Timestamp format: "2022-10-06T21:36:06.322Z
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatsSimulated {
/// boolean representing if this searcher has a high enough reputation to be in the high
/// priority queue
pub is_high_priority: bool,
/// representing whether the bundle gets simulated. All other fields will be omitted except
/// simulated field if API didn't receive bundle
pub is_simulated: bool,
/// time at which the bundle gets simulated
pub simulated_at: String,
/// time at which the bundle API received the bundle
pub received_at: String,
/// indicates time at which each builder selected the bundle to be included in the target
/// block
#[serde(default = "Vec::new")]
pub considered_by_builders_at: Vec<ConsideredByBuildersAt>,
/// indicates time at which each builder sealed a block containing the bundle
#[serde(default = "Vec::new")]
pub sealed_by_builders_at: Vec<SealedByBuildersAt>,
}

#[cfg(test)]
mod tests {
use super::*;

use crate::SealedByBuildersAt;

#[test]
fn can_serialize_deserialize_bundle_stats() {
let fixtures = [
(
r#"{
"isSimulated": false
}"#,
BundleStats::Unknown,
),
(
r#"{
"isHighPriority": false,
"isSimulated": false,
"receivedAt": "476190476193"
}"#,
BundleStats::Seen(StatsSeen {
is_high_priority: false,
is_simulated: false,
received_at: "476190476193".to_string(),
}),
),
(
r#"{
"isHighPriority": true,
"isSimulated": true,
"simulatedAt": "111",
"receivedAt": "222",
"consideredByBuildersAt":[],
"sealedByBuildersAt": [
{
"pubkey": "333",
"timestamp": "444"
},
{
"pubkey": "555",
"timestamp": "666"
}
]
}"#,
BundleStats::Simulated(StatsSimulated {
is_high_priority: true,
is_simulated: true,
simulated_at: String::from("111"),
received_at: String::from("222"),
considered_by_builders_at: vec![],
sealed_by_builders_at: vec![
SealedByBuildersAt {
pubkey: String::from("333"),
timestamp: String::from("444"),
},
SealedByBuildersAt {
pubkey: String::from("555"),
timestamp: String::from("666"),
},
],
}),
),
];

let strip_whitespaces =
|input: &str| input.chars().filter(|&c| !c.is_whitespace()).collect::<String>();

for (serialized, deserialized) in fixtures {
// Check de-serialization
let deserialized_expected = serde_json::from_str::<BundleStats>(serialized).unwrap();
assert_eq!(deserialized, deserialized_expected);

// Check serialization
let serialized_expected = &serde_json::to_string(&deserialized).unwrap();
assert_eq!(strip_whitespaces(serialized), strip_whitespaces(serialized_expected));
}
}
}
Loading