op_alloy_rpc_types_engine/payload/
v4.rs1use alloc::vec::Vec;
4use alloy_consensus::Block;
5use alloy_eips::{Decodable2718, eip4895::Withdrawal};
6use alloy_primitives::{Address, B256, Bloom, Bytes, U256};
7use alloy_rpc_types_engine::{
8 BlobsBundleV1, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadError,
9};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
16pub struct OpExecutionPayloadV4 {
17 #[cfg_attr(feature = "serde", serde(flatten))]
19 pub payload_inner: ExecutionPayloadV3,
20 pub withdrawals_root: B256,
24}
25
26impl OpExecutionPayloadV4 {
27 pub const fn from_v3_with_withdrawals_root(
32 payload: ExecutionPayloadV3,
33 withdrawals_root: B256,
34 ) -> Self {
35 Self { withdrawals_root, payload_inner: payload }
36 }
37
38 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
45 let mut base_block = self.payload_inner.try_into_block()?;
46
47 base_block.header.withdrawals_root = Some(self.withdrawals_root);
49
50 Ok(base_block)
51 }
52}
53
54#[cfg(feature = "std")]
55impl ssz::Decode for OpExecutionPayloadV4 {
56 fn is_ssz_fixed_len() -> bool {
57 false
58 }
59
60 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
61 let mut builder = ssz::SszDecoderBuilder::new(bytes);
62
63 builder.register_type::<B256>()?;
64 builder.register_type::<Address>()?;
65 builder.register_type::<B256>()?;
66 builder.register_type::<B256>()?;
67 builder.register_type::<Bloom>()?;
68 builder.register_type::<B256>()?;
69 builder.register_type::<u64>()?;
70 builder.register_type::<u64>()?;
71 builder.register_type::<u64>()?;
72 builder.register_type::<u64>()?;
73 builder.register_type::<Bytes>()?;
74 builder.register_type::<U256>()?;
75 builder.register_type::<B256>()?;
76 builder.register_type::<Vec<Bytes>>()?;
77 builder.register_type::<Vec<Withdrawal>>()?;
78 builder.register_type::<u64>()?;
79 builder.register_type::<u64>()?;
80 builder.register_type::<B256>()?;
81
82 let mut decoder = builder.build()?;
83
84 Ok(Self {
85 payload_inner: ExecutionPayloadV3 {
86 payload_inner: ExecutionPayloadV2 {
87 payload_inner: ExecutionPayloadV1 {
88 parent_hash: decoder.decode_next()?,
89 fee_recipient: decoder.decode_next()?,
90 state_root: decoder.decode_next()?,
91 receipts_root: decoder.decode_next()?,
92 logs_bloom: decoder.decode_next()?,
93 prev_randao: decoder.decode_next()?,
94 block_number: decoder.decode_next()?,
95 gas_limit: decoder.decode_next()?,
96 gas_used: decoder.decode_next()?,
97 timestamp: decoder.decode_next()?,
98 extra_data: decoder.decode_next()?,
99 base_fee_per_gas: decoder.decode_next()?,
100 block_hash: decoder.decode_next()?,
101 transactions: decoder.decode_next()?,
102 },
103 withdrawals: decoder.decode_next()?,
104 },
105 blob_gas_used: decoder.decode_next()?,
106 excess_blob_gas: decoder.decode_next()?,
107 },
108 withdrawals_root: decoder.decode_next()?,
109 })
110 }
111}
112
113#[cfg(feature = "std")]
114impl ssz::Encode for OpExecutionPayloadV4 {
115 fn is_ssz_fixed_len() -> bool {
116 false
117 }
118
119 fn ssz_append(&self, buf: &mut Vec<u8>) {
120 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 6
121 + <Address as ssz::Encode>::ssz_fixed_len()
122 + <Bloom as ssz::Encode>::ssz_fixed_len()
123 + <u64 as ssz::Encode>::ssz_fixed_len() * 6
124 + <U256 as ssz::Encode>::ssz_fixed_len()
125 + ssz::BYTES_PER_LENGTH_OFFSET * 3;
126
127 let mut encoder = ssz::SszEncoder::container(buf, offset);
128
129 encoder.append(&self.payload_inner.payload_inner.payload_inner.parent_hash);
130 encoder.append(&self.payload_inner.payload_inner.payload_inner.fee_recipient);
131 encoder.append(&self.payload_inner.payload_inner.payload_inner.state_root);
132 encoder.append(&self.payload_inner.payload_inner.payload_inner.receipts_root);
133 encoder.append(&self.payload_inner.payload_inner.payload_inner.logs_bloom);
134 encoder.append(&self.payload_inner.payload_inner.payload_inner.prev_randao);
135 encoder.append(&self.payload_inner.payload_inner.payload_inner.block_number);
136 encoder.append(&self.payload_inner.payload_inner.payload_inner.gas_limit);
137 encoder.append(&self.payload_inner.payload_inner.payload_inner.gas_used);
138 encoder.append(&self.payload_inner.payload_inner.payload_inner.timestamp);
139 encoder.append(&self.payload_inner.payload_inner.payload_inner.extra_data);
140 encoder.append(&self.payload_inner.payload_inner.payload_inner.base_fee_per_gas);
141 encoder.append(&self.payload_inner.payload_inner.payload_inner.block_hash);
142 encoder.append(&self.payload_inner.payload_inner.payload_inner.transactions);
143 encoder.append(&self.payload_inner.payload_inner.withdrawals);
144 encoder.append(&self.payload_inner.blob_gas_used);
145 encoder.append(&self.payload_inner.excess_blob_gas);
146 encoder.append(&self.withdrawals_root);
147
148 encoder.finalize();
149 }
150
151 fn ssz_bytes_len(&self) -> usize {
152 <ExecutionPayloadV3 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
153 + <B256 as ssz::Encode>::ssz_fixed_len()
154 }
155}
156
157#[derive(Clone, Debug, PartialEq, Eq)]
163#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
164#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
165pub struct OpExecutionPayloadEnvelopeV4 {
166 pub execution_payload: OpExecutionPayloadV4,
168 pub block_value: U256,
170 pub blobs_bundle: BlobsBundleV1,
172 pub should_override_builder: bool,
175 pub parent_beacon_block_root: B256,
177 pub execution_requests: Vec<Bytes>,
181}
182
183#[cfg(test)]
184#[cfg(feature = "serde")]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn serde_roundtrip_execution_payload_envelope_v4() {
190 let response = r#"{"executionPayload":{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0","withdrawalsRoot":"0x123400000000000000000000000000000000000000000000000000000000babe"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]},"shouldOverrideBuilder":false,"parentBeaconBlockRoot":"0xdead00000000000000000000000000000000000000000000000000000000beef","executionRequests":["0xdeadbeef"]}"#;
193 let envelope: OpExecutionPayloadEnvelopeV4 = serde_json::from_str(response).unwrap();
194 assert_eq!(serde_json::to_string(&envelope).unwrap(), response);
195 }
196}