alloy_eips/
eip4895.rs

1//! [EIP-4895] Withdrawal type and serde helpers.
2//!
3//! [EIP-4895]: https://eips.ethereum.org/EIPS/eip-4895
4
5use alloc::vec::Vec;
6use alloy_primitives::{Address, U256};
7use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
8use derive_more::derive::{AsRef, Deref, DerefMut, From, IntoIterator};
9
10/// Multiplier for converting gwei to wei.
11pub const GWEI_TO_WEI: u64 = 1_000_000_000;
12
13/// Withdrawal represents a validator withdrawal from the consensus layer.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
15#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
18pub struct Withdrawal {
19    /// Monotonically increasing identifier issued by consensus layer.
20    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
21    pub index: u64,
22    /// Index of validator associated with withdrawal.
23    #[cfg_attr(
24        feature = "serde",
25        serde(with = "alloy_serde::quantity", rename = "validatorIndex")
26    )]
27    pub validator_index: u64,
28    /// Target address for withdrawn ether.
29    pub address: Address,
30    /// Value of the withdrawal in gwei.
31    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
32    pub amount: u64,
33}
34
35impl Withdrawal {
36    /// Return the withdrawal amount in wei.
37    pub fn amount_wei(&self) -> U256 {
38        U256::from(self.amount) * U256::from(GWEI_TO_WEI)
39    }
40}
41
42/// Represents a collection of Withdrawals.
43#[derive(
44    Debug,
45    Clone,
46    PartialEq,
47    Eq,
48    Default,
49    Hash,
50    From,
51    AsRef,
52    Deref,
53    DerefMut,
54    IntoIterator,
55    RlpEncodableWrapper,
56    RlpDecodableWrapper,
57)]
58#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60pub struct Withdrawals(pub Vec<Withdrawal>);
61
62impl Withdrawals {
63    /// Create a new Withdrawals instance.
64    pub const fn new(withdrawals: Vec<Withdrawal>) -> Self {
65        Self(withdrawals)
66    }
67
68    /// Calculate the total size, including capacity, of the Withdrawals.
69    #[inline]
70    pub fn total_size(&self) -> usize {
71        self.0.capacity() * core::mem::size_of::<Withdrawal>()
72    }
73
74    /// Calculate a heuristic for the in-memory size of the [Withdrawals].
75    #[inline]
76    pub fn size(&self) -> usize {
77        self.0.len() * core::mem::size_of::<Withdrawal>()
78    }
79
80    /// Get an iterator over the Withdrawals.
81    pub fn iter(&self) -> core::slice::Iter<'_, Withdrawal> {
82        self.0.iter()
83    }
84
85    /// Get a mutable iterator over the Withdrawals.
86    pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Withdrawal> {
87        self.0.iter_mut()
88    }
89
90    /// Convert [Self] into raw vec of withdrawals.
91    pub fn into_inner(self) -> Vec<Withdrawal> {
92        self.0
93    }
94}
95
96impl<'a> IntoIterator for &'a Withdrawals {
97    type Item = &'a Withdrawal;
98    type IntoIter = core::slice::Iter<'a, Withdrawal>;
99    fn into_iter(self) -> Self::IntoIter {
100        self.iter()
101    }
102}
103
104impl<'a> IntoIterator for &'a mut Withdrawals {
105    type Item = &'a mut Withdrawal;
106    type IntoIter = core::slice::IterMut<'a, Withdrawal>;
107
108    fn into_iter(self) -> Self::IntoIter {
109        self.iter_mut()
110    }
111}
112
113#[cfg(all(test, feature = "serde"))]
114mod tests {
115    use super::*;
116    use core::str::FromStr;
117
118    // <https://github.com/paradigmxyz/reth/issues/1614>
119    #[test]
120    fn test_withdrawal_serde_roundtrip() {
121        let input = r#"[{"index":"0x0","validatorIndex":"0x0","address":"0x0000000000000000000000000000000000001000","amount":"0x1"},{"index":"0x1","validatorIndex":"0x1","address":"0x0000000000000000000000000000000000001001","amount":"0x1"},{"index":"0x2","validatorIndex":"0x2","address":"0x0000000000000000000000000000000000001002","amount":"0x1"},{"index":"0x3","validatorIndex":"0x3","address":"0x0000000000000000000000000000000000001003","amount":"0x1"},{"index":"0x4","validatorIndex":"0x4","address":"0x0000000000000000000000000000000000001004","amount":"0x1"},{"index":"0x5","validatorIndex":"0x5","address":"0x0000000000000000000000000000000000001005","amount":"0x1"},{"index":"0x6","validatorIndex":"0x6","address":"0x0000000000000000000000000000000000001006","amount":"0x1"},{"index":"0x7","validatorIndex":"0x7","address":"0x0000000000000000000000000000000000001007","amount":"0x1"},{"index":"0x8","validatorIndex":"0x8","address":"0x0000000000000000000000000000000000001008","amount":"0x1"},{"index":"0x9","validatorIndex":"0x9","address":"0x0000000000000000000000000000000000001009","amount":"0x1"},{"index":"0xa","validatorIndex":"0xa","address":"0x000000000000000000000000000000000000100a","amount":"0x1"},{"index":"0xb","validatorIndex":"0xb","address":"0x000000000000000000000000000000000000100b","amount":"0x1"},{"index":"0xc","validatorIndex":"0xc","address":"0x000000000000000000000000000000000000100c","amount":"0x1"},{"index":"0xd","validatorIndex":"0xd","address":"0x000000000000000000000000000000000000100d","amount":"0x1"},{"index":"0xe","validatorIndex":"0xe","address":"0x000000000000000000000000000000000000100e","amount":"0x1"},{"index":"0xf","validatorIndex":"0xf","address":"0x000000000000000000000000000000000000100f","amount":"0x1"}]"#;
122
123        // With a vector of withdrawals.
124        let withdrawals: Vec<Withdrawal> = serde_json::from_str(input).unwrap();
125        let s = serde_json::to_string(&withdrawals).unwrap();
126        assert_eq!(input, s);
127
128        // With a Withdrawals struct.
129        let withdrawals: Withdrawals = serde_json::from_str(input).unwrap();
130        let s = serde_json::to_string(&withdrawals).unwrap();
131        assert_eq!(input, s);
132    }
133
134    #[test]
135    fn test_withdrawal_amount_wei() {
136        let withdrawal =
137            Withdrawal { index: 1, validator_index: 2, address: Address::random(), amount: 454456 };
138
139        // Assert that the amount_wei method returns the correct value
140        assert_eq!(withdrawal.amount_wei(), U256::from_str("0x19d5348723000").unwrap());
141    }
142}