Skip to main content

alloy_rpc_types_beacon/
rewards.rs

1//! Types for the beacon rewards endpoints.
2
3use serde::{Deserialize, Serialize};
4use serde_with::{serde_as, DisplayFromStr};
5
6/// Response from the [`/eth/v1/beacon/rewards/blocks/{block_id}`](https://ethereum.github.io/beacon-APIs/#/Rewards/getBlockRewards) endpoint.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct BlockRewardsResponse {
9    /// Whether the response references an unverified execution payload.
10    #[serde(default)]
11    pub execution_optimistic: bool,
12    /// Whether the response references the finalized history of the chain.
13    #[serde(default)]
14    pub finalized: bool,
15    /// Block rewards data.
16    pub data: BlockRewards,
17}
18
19/// Rewards info for a single block.
20#[serde_as]
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct BlockRewards {
23    /// Proposer index of the block.
24    #[serde_as(as = "DisplayFromStr")]
25    pub proposer_index: u64,
26    /// Total block reward in Gwei.
27    #[serde_as(as = "DisplayFromStr")]
28    pub total: u64,
29    /// Attestation reward in Gwei.
30    #[serde_as(as = "DisplayFromStr")]
31    pub attestations: u64,
32    /// Sync aggregate reward in Gwei.
33    #[serde_as(as = "DisplayFromStr")]
34    pub sync_aggregate: u64,
35    /// Proposer slashings reward in Gwei.
36    #[serde_as(as = "DisplayFromStr")]
37    pub proposer_slashings: u64,
38    /// Attester slashings reward in Gwei.
39    #[serde_as(as = "DisplayFromStr")]
40    pub attester_slashings: u64,
41}
42
43/// Response from the [`/eth/v1/beacon/rewards/sync_committee/{block_id}`](https://ethereum.github.io/beacon-APIs/#/Rewards/getSyncCommitteeRewards) endpoint.
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct SyncCommitteeRewardsResponse {
46    /// Whether the response references an unverified execution payload.
47    #[serde(default)]
48    pub execution_optimistic: bool,
49    /// Whether the response references the finalized history of the chain.
50    #[serde(default)]
51    pub finalized: bool,
52    /// List of validator sync committee rewards.
53    pub data: Vec<SyncCommitteeReward>,
54}
55
56/// Reward for a single validator from sync committee participation.
57#[serde_as]
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct SyncCommitteeReward {
60    /// Validator index.
61    #[serde_as(as = "DisplayFromStr")]
62    pub validator_index: u64,
63    /// Sync committee reward in Gwei (can be negative).
64    #[serde_as(as = "DisplayFromStr")]
65    pub reward: i64,
66}
67
68/// Response from the [`/eth/v1/beacon/rewards/attestations/{epoch}`](https://ethereum.github.io/beacon-APIs/#/Rewards/getAttestationsRewards) endpoint.
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct AttestationRewardsResponse {
71    /// Whether the response references an unverified execution payload.
72    #[serde(default)]
73    pub execution_optimistic: bool,
74    /// Whether the response references the finalized history of the chain.
75    #[serde(default)]
76    pub finalized: bool,
77    /// Attestation rewards data.
78    pub data: AttestationRewards,
79}
80
81/// Attestation rewards broken down into ideal and total rewards.
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct AttestationRewards {
84    /// Ideal rewards for each effective balance.
85    pub ideal_rewards: Vec<IdealAttestationReward>,
86    /// Total rewards for each validator.
87    pub total_rewards: Vec<TotalAttestationReward>,
88}
89
90/// Ideal attestation reward for a given effective balance.
91#[serde_as]
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct IdealAttestationReward {
94    /// Effective balance in Gwei.
95    #[serde_as(as = "DisplayFromStr")]
96    pub effective_balance: u64,
97    /// Ideal head reward in Gwei.
98    #[serde_as(as = "DisplayFromStr")]
99    pub head: u64,
100    /// Ideal target reward in Gwei.
101    #[serde_as(as = "DisplayFromStr")]
102    pub target: u64,
103    /// Ideal source reward in Gwei.
104    #[serde_as(as = "DisplayFromStr")]
105    pub source: u64,
106    /// Ideal inclusion delay reward in Gwei (Phase0 only).
107    #[serde_as(as = "Option<DisplayFromStr>")]
108    #[serde(default)]
109    pub inclusion_delay: Option<u64>,
110    /// Ideal inactivity reward in Gwei.
111    #[serde_as(as = "DisplayFromStr")]
112    pub inactivity: u64,
113}
114
115/// Total attestation reward for a single validator.
116#[serde_as]
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct TotalAttestationReward {
119    /// Validator index.
120    #[serde_as(as = "DisplayFromStr")]
121    pub validator_index: u64,
122    /// Head reward in Gwei (can be negative).
123    #[serde_as(as = "DisplayFromStr")]
124    pub head: i64,
125    /// Target reward in Gwei (can be negative).
126    #[serde_as(as = "DisplayFromStr")]
127    pub target: i64,
128    /// Source reward in Gwei (can be negative).
129    #[serde_as(as = "DisplayFromStr")]
130    pub source: i64,
131    /// Inclusion delay reward in Gwei (Phase0 only, can be negative).
132    #[serde_as(as = "Option<DisplayFromStr>")]
133    #[serde(default)]
134    pub inclusion_delay: Option<i64>,
135    /// Inactivity reward in Gwei (can be negative).
136    #[serde_as(as = "DisplayFromStr")]
137    pub inactivity: i64,
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn serde_block_rewards_response() {
146        let s = r#"{
147            "execution_optimistic": false,
148            "finalized": true,
149            "data": {
150                "proposer_index": "123",
151                "total": "456",
152                "attestations": "100",
153                "sync_aggregate": "200",
154                "proposer_slashings": "50",
155                "attester_slashings": "106"
156            }
157        }"#;
158
159        let response: BlockRewardsResponse = serde_json::from_str(s).unwrap();
160
161        assert!(!response.execution_optimistic);
162        assert!(response.finalized);
163        assert_eq!(response.data.proposer_index, 123);
164        assert_eq!(response.data.total, 456);
165        assert_eq!(response.data.attestations, 100);
166        assert_eq!(response.data.sync_aggregate, 200);
167        assert_eq!(response.data.proposer_slashings, 50);
168        assert_eq!(response.data.attester_slashings, 106);
169
170        let roundtrip = serde_json::to_string(&response).unwrap();
171        let deserialized: BlockRewardsResponse = serde_json::from_str(&roundtrip).unwrap();
172        assert_eq!(response, deserialized);
173    }
174
175    #[test]
176    fn serde_block_rewards_response_defaults() {
177        let s = r#"{
178            "data": {
179                "proposer_index": "0",
180                "total": "0",
181                "attestations": "0",
182                "sync_aggregate": "0",
183                "proposer_slashings": "0",
184                "attester_slashings": "0"
185            }
186        }"#;
187
188        let response: BlockRewardsResponse = serde_json::from_str(s).unwrap();
189
190        assert!(!response.execution_optimistic);
191        assert!(!response.finalized);
192    }
193
194    #[test]
195    fn serde_sync_committee_rewards_response() {
196        let s = r#"{
197            "execution_optimistic": true,
198            "finalized": false,
199            "data": [
200                {
201                    "validator_index": "1",
202                    "reward": "2000"
203                },
204                {
205                    "validator_index": "2",
206                    "reward": "-500"
207                }
208            ]
209        }"#;
210
211        let response: SyncCommitteeRewardsResponse = serde_json::from_str(s).unwrap();
212
213        assert!(response.execution_optimistic);
214        assert!(!response.finalized);
215        assert_eq!(response.data.len(), 2);
216        assert_eq!(response.data[0].validator_index, 1);
217        assert_eq!(response.data[0].reward, 2000);
218        assert_eq!(response.data[1].validator_index, 2);
219        assert_eq!(response.data[1].reward, -500);
220
221        let roundtrip = serde_json::to_string(&response).unwrap();
222        let deserialized: SyncCommitteeRewardsResponse = serde_json::from_str(&roundtrip).unwrap();
223        assert_eq!(response, deserialized);
224    }
225
226    #[test]
227    fn serde_attestation_rewards_response() {
228        let s = r#"{
229            "execution_optimistic": false,
230            "finalized": true,
231            "data": {
232                "ideal_rewards": [
233                    {
234                        "effective_balance": "32000000000",
235                        "head": "2500",
236                        "target": "5000",
237                        "source": "3000",
238                        "inclusion_delay": "1500",
239                        "inactivity": "0"
240                    }
241                ],
242                "total_rewards": [
243                    {
244                        "validator_index": "10",
245                        "head": "2500",
246                        "target": "-1000",
247                        "source": "3000",
248                        "inclusion_delay": "-200",
249                        "inactivity": "0"
250                    }
251                ]
252            }
253        }"#;
254
255        let response: AttestationRewardsResponse = serde_json::from_str(s).unwrap();
256
257        assert!(!response.execution_optimistic);
258        assert!(response.finalized);
259
260        let ideal = &response.data.ideal_rewards[0];
261        assert_eq!(ideal.effective_balance, 32000000000);
262        assert_eq!(ideal.head, 2500);
263        assert_eq!(ideal.target, 5000);
264        assert_eq!(ideal.source, 3000);
265        assert_eq!(ideal.inclusion_delay, Some(1500));
266        assert_eq!(ideal.inactivity, 0);
267
268        let total = &response.data.total_rewards[0];
269        assert_eq!(total.validator_index, 10);
270        assert_eq!(total.head, 2500);
271        assert_eq!(total.target, -1000);
272        assert_eq!(total.source, 3000);
273        assert_eq!(total.inclusion_delay, Some(-200));
274        assert_eq!(total.inactivity, 0);
275
276        let roundtrip = serde_json::to_string(&response).unwrap();
277        let deserialized: AttestationRewardsResponse = serde_json::from_str(&roundtrip).unwrap();
278        assert_eq!(response, deserialized);
279    }
280
281    #[test]
282    fn serde_attestation_rewards_without_inclusion_delay() {
283        let s = r#"{
284            "execution_optimistic": false,
285            "finalized": false,
286            "data": {
287                "ideal_rewards": [
288                    {
289                        "effective_balance": "32000000000",
290                        "head": "2500",
291                        "target": "5000",
292                        "source": "3000",
293                        "inactivity": "0"
294                    }
295                ],
296                "total_rewards": [
297                    {
298                        "validator_index": "10",
299                        "head": "2500",
300                        "target": "-1000",
301                        "source": "3000",
302                        "inactivity": "0"
303                    }
304                ]
305            }
306        }"#;
307
308        let response: AttestationRewardsResponse = serde_json::from_str(s).unwrap();
309
310        assert_eq!(response.data.ideal_rewards[0].inclusion_delay, None);
311        assert_eq!(response.data.total_rewards[0].inclusion_delay, None);
312
313        let roundtrip = serde_json::to_string(&response).unwrap();
314        let deserialized: AttestationRewardsResponse = serde_json::from_str(&roundtrip).unwrap();
315        assert_eq!(response, deserialized);
316    }
317}