1use serde::{Deserialize, Serialize};
4use serde_with::{serde_as, DisplayFromStr};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct BlockRewardsResponse {
9 #[serde(default)]
11 pub execution_optimistic: bool,
12 #[serde(default)]
14 pub finalized: bool,
15 pub data: BlockRewards,
17}
18
19#[serde_as]
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct BlockRewards {
23 #[serde_as(as = "DisplayFromStr")]
25 pub proposer_index: u64,
26 #[serde_as(as = "DisplayFromStr")]
28 pub total: u64,
29 #[serde_as(as = "DisplayFromStr")]
31 pub attestations: u64,
32 #[serde_as(as = "DisplayFromStr")]
34 pub sync_aggregate: u64,
35 #[serde_as(as = "DisplayFromStr")]
37 pub proposer_slashings: u64,
38 #[serde_as(as = "DisplayFromStr")]
40 pub attester_slashings: u64,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct SyncCommitteeRewardsResponse {
46 #[serde(default)]
48 pub execution_optimistic: bool,
49 #[serde(default)]
51 pub finalized: bool,
52 pub data: Vec<SyncCommitteeReward>,
54}
55
56#[serde_as]
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct SyncCommitteeReward {
60 #[serde_as(as = "DisplayFromStr")]
62 pub validator_index: u64,
63 #[serde_as(as = "DisplayFromStr")]
65 pub reward: i64,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct AttestationRewardsResponse {
71 #[serde(default)]
73 pub execution_optimistic: bool,
74 #[serde(default)]
76 pub finalized: bool,
77 pub data: AttestationRewards,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct AttestationRewards {
84 pub ideal_rewards: Vec<IdealAttestationReward>,
86 pub total_rewards: Vec<TotalAttestationReward>,
88}
89
90#[serde_as]
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct IdealAttestationReward {
94 #[serde_as(as = "DisplayFromStr")]
96 pub effective_balance: u64,
97 #[serde_as(as = "DisplayFromStr")]
99 pub head: u64,
100 #[serde_as(as = "DisplayFromStr")]
102 pub target: u64,
103 #[serde_as(as = "DisplayFromStr")]
105 pub source: u64,
106 #[serde_as(as = "Option<DisplayFromStr>")]
108 #[serde(default)]
109 pub inclusion_delay: Option<u64>,
110 #[serde_as(as = "DisplayFromStr")]
112 pub inactivity: u64,
113}
114
115#[serde_as]
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct TotalAttestationReward {
119 #[serde_as(as = "DisplayFromStr")]
121 pub validator_index: u64,
122 #[serde_as(as = "DisplayFromStr")]
124 pub head: i64,
125 #[serde_as(as = "DisplayFromStr")]
127 pub target: i64,
128 #[serde_as(as = "DisplayFromStr")]
130 pub source: i64,
131 #[serde_as(as = "Option<DisplayFromStr>")]
133 #[serde(default)]
134 pub inclusion_delay: Option<i64>,
135 #[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}