alloy_rpc_types_mev/
stats.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
use crate::{u256_numeric_string, ConsideredByBuildersAt, SealedByBuildersAt};

use alloy_primitives::U256;
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>,
}

/// Response for `flashbots_getUserStatsV2` represents stats for a searcher.
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getuserstatsv2>
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserStats {
    /// Represents whether this searcher has a high enough reputation to be in the high priority
    /// queue.
    pub is_high_priority: bool,
    /// The total amount paid to validators over all time.
    #[serde(with = "u256_numeric_string")]
    pub all_time_validator_payments: U256,
    /// The total amount of gas simulated across all bundles submitted to Flashbots.
    /// This is the actual gas used in simulations, not gas limit.
    #[serde(with = "u256_numeric_string")]
    pub all_time_gas_simulated: U256,
    /// The total amount paid to validators the last 7 days.
    #[serde(with = "u256_numeric_string")]
    pub last_7d_validator_payments: U256,
    /// The total amount of gas simulated across all bundles submitted to Flashbots in the last 7
    /// days. This is the actual gas used in simulations, not gas limit.
    #[serde(with = "u256_numeric_string")]
    pub last_7d_gas_simulated: U256,
    /// The total amount paid to validators the last day.
    #[serde(with = "u256_numeric_string")]
    pub last_1d_validator_payments: U256,
    /// The total amount of gas simulated across all bundles submitted to Flashbots in the last
    /// day. This is the actual gas used in simulations, not gas limit.
    #[serde(with = "u256_numeric_string")]
    pub last_1d_gas_simulated: U256,
}

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

    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));
        }
    }
}