1use bytes::Bytes;
11use libssz::{SszDecode, SszEncode};
12use libssz_derive::{HashTreeRoot, SszDecode, SszEncode};
13use libssz_merkle::{
14 HashTreeRoot, Node, Sha256Hasher, merkleize_progressive, mix_in_active_fields,
15};
16use libssz_types::{ProgressiveList, SszList, SszVector};
17
18use super::requests::EncodedRequests;
19
20const MAX_EXTRA_DATA_BYTES: usize = 32;
28
29const DEPOSIT_REQUEST_TYPE: u8 = 0x00;
32const WITHDRAWAL_REQUEST_TYPE: u8 = 0x01;
33const CONSOLIDATION_REQUEST_TYPE: u8 = 0x02;
34const BUILDER_DEPOSIT_REQUEST_TYPE: u8 = 0x03;
35const BUILDER_EXIT_REQUEST_TYPE: u8 = 0x04;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[repr(transparent)]
46pub struct Bytes20(pub [u8; 20]);
47
48impl SszEncode for Bytes20 {
49 fn is_fixed_size() -> bool {
50 true
51 }
52 fn fixed_size() -> usize {
53 20
54 }
55 fn encoded_len(&self) -> usize {
56 20
57 }
58 fn ssz_append(&self, buf: &mut Vec<u8>) {
59 self.0.ssz_append(buf);
60 }
61}
62
63impl SszDecode for Bytes20 {
64 fn is_fixed_size() -> bool {
65 true
66 }
67 fn fixed_size() -> usize {
68 20
69 }
70 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, libssz::DecodeError> {
71 <[u8; 20]>::from_ssz_bytes(bytes).map(Self)
72 }
73}
74
75impl HashTreeRoot for Bytes20 {
76 fn hash_tree_root(&self, _hasher: &impl Sha256Hasher) -> libssz_merkle::Node {
77 let mut node = [0u8; 32];
78 node[..20].copy_from_slice(&self.0);
79 node
80 }
81}
82
83impl From<[u8; 20]> for Bytes20 {
84 fn from(bytes: [u8; 20]) -> Self {
85 Self(bytes)
86 }
87}
88
89impl From<Bytes20> for [u8; 20] {
90 fn from(b: Bytes20) -> Self {
91 b.0
92 }
93}
94
95pub const BYTES_PER_LOGS_BLOOM: usize = 256;
102
103pub type LogsBloom = SszVector<u8, BYTES_PER_LOGS_BLOOM>;
105
106#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
110pub struct Withdrawal {
111 pub index: u64,
112 pub validator_index: u64,
113 pub address: Bytes20,
114 pub amount: u64,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
119pub struct DepositRequest {
120 pub pubkey: [u8; 48],
121 pub withdrawal_credentials: [u8; 32],
122 pub amount: u64,
123 pub signature: [u8; 96],
124 pub index: u64,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
129pub struct WithdrawalRequest {
130 pub source_address: Bytes20,
131 pub validator_pubkey: [u8; 48],
132 pub amount: u64,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
137pub struct ConsolidationRequest {
138 pub source_address: Bytes20,
139 pub source_pubkey: [u8; 48],
140 pub target_pubkey: [u8; 48],
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
145pub struct BuilderDepositRequest {
146 pub pubkey: [u8; 48],
147 pub withdrawal_credentials: [u8; 32],
148 pub amount: u64,
149 pub signature: [u8; 96],
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
154pub struct BuilderExitRequest {
155 pub source_address: Bytes20,
156 pub pubkey: [u8; 48],
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)]
169pub struct ExecutionPayload {
170 pub parent_hash: [u8; 32],
171 pub fee_recipient: Bytes20,
172 pub state_root: [u8; 32],
173 pub receipts_root: [u8; 32],
174 pub logs_bloom: LogsBloom,
175 pub prev_randao: [u8; 32],
176 pub block_number: u64,
177 pub gas_limit: u64,
178 pub gas_used: u64,
179 pub timestamp: u64,
180 pub extra_data: SszList<u8, MAX_EXTRA_DATA_BYTES>,
181 pub base_fee_per_gas: [u8; 32],
183 pub block_hash: [u8; 32],
184 pub transactions: ProgressiveList<ProgressiveList<u8>>,
185 pub withdrawals: ProgressiveList<Withdrawal>,
186 pub blob_gas_used: u64,
187 pub excess_blob_gas: u64,
188 pub block_access_list: ProgressiveList<u8>,
190 pub slot_number: u64,
194}
195
196fn progressive_container_root<const N: usize>(
207 hasher: &impl Sha256Hasher,
208 field_roots: [Node; N],
209) -> Node {
210 let root = merkleize_progressive(hasher, &field_roots);
211 mix_in_active_fields(hasher, &root, &[true; N])
212}
213
214impl HashTreeRoot for ExecutionPayload {
215 fn hash_tree_root(&self, hasher: &impl Sha256Hasher) -> Node {
216 let Self {
221 parent_hash,
222 fee_recipient,
223 state_root,
224 receipts_root,
225 logs_bloom,
226 prev_randao,
227 block_number,
228 gas_limit,
229 gas_used,
230 timestamp,
231 extra_data,
232 base_fee_per_gas,
233 block_hash,
234 transactions,
235 withdrawals,
236 blob_gas_used,
237 excess_blob_gas,
238 block_access_list,
239 slot_number,
240 } = self;
241 progressive_container_root(
242 hasher,
243 [
244 parent_hash.hash_tree_root(hasher),
245 fee_recipient.hash_tree_root(hasher),
246 state_root.hash_tree_root(hasher),
247 receipts_root.hash_tree_root(hasher),
248 logs_bloom.hash_tree_root(hasher),
249 prev_randao.hash_tree_root(hasher),
250 block_number.hash_tree_root(hasher),
251 gas_limit.hash_tree_root(hasher),
252 gas_used.hash_tree_root(hasher),
253 timestamp.hash_tree_root(hasher),
254 extra_data.hash_tree_root(hasher),
255 base_fee_per_gas.hash_tree_root(hasher),
256 block_hash.hash_tree_root(hasher),
257 transactions.hash_tree_root(hasher),
258 withdrawals.hash_tree_root(hasher),
259 blob_gas_used.hash_tree_root(hasher),
260 excess_blob_gas.hash_tree_root(hasher),
261 block_access_list.hash_tree_root(hasher),
262 slot_number.hash_tree_root(hasher),
263 ],
264 )
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)]
276pub struct ExecutionRequests {
277 pub deposits: ProgressiveList<DepositRequest>,
278 pub withdrawals: ProgressiveList<WithdrawalRequest>,
279 pub consolidations: ProgressiveList<ConsolidationRequest>,
280 pub builder_deposits: ProgressiveList<BuilderDepositRequest>,
281 pub builder_exits: ProgressiveList<BuilderExitRequest>,
282}
283
284impl ExecutionRequests {
285 pub fn to_encoded_requests(&self) -> Vec<EncodedRequests> {
292 fn encode<T: SszEncode>(
293 type_byte: u8,
294 items: impl IntoIterator<Item = T>,
295 ) -> EncodedRequests {
296 let mut buf = Vec::new();
297 buf.push(type_byte);
298 for item in items {
299 item.ssz_append(&mut buf);
300 }
301 EncodedRequests(Bytes::from(buf))
302 }
303
304 vec![
305 encode(DEPOSIT_REQUEST_TYPE, self.deposits.iter().cloned()),
306 encode(WITHDRAWAL_REQUEST_TYPE, self.withdrawals.iter().cloned()),
307 encode(
308 CONSOLIDATION_REQUEST_TYPE,
309 self.consolidations.iter().cloned(),
310 ),
311 encode(
312 BUILDER_DEPOSIT_REQUEST_TYPE,
313 self.builder_deposits.iter().cloned(),
314 ),
315 encode(
316 BUILDER_EXIT_REQUEST_TYPE,
317 self.builder_exits.iter().cloned(),
318 ),
319 ]
320 }
321}
322
323impl HashTreeRoot for ExecutionRequests {
324 fn hash_tree_root(&self, hasher: &impl Sha256Hasher) -> Node {
325 let Self {
327 deposits,
328 withdrawals,
329 consolidations,
330 builder_deposits,
331 builder_exits,
332 } = self;
333 progressive_container_root(
334 hasher,
335 [
336 deposits.hash_tree_root(hasher),
337 withdrawals.hash_tree_root(hasher),
338 consolidations.hash_tree_root(hasher),
339 builder_deposits.hash_tree_root(hasher),
340 builder_exits.hash_tree_root(hasher),
341 ],
342 )
343 }
344}
345
346#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
351pub struct NewPayloadRequest {
352 pub execution_payload: ExecutionPayload,
353 pub versioned_hashes: ProgressiveList<[u8; 32]>,
354 pub parent_beacon_block_root: [u8; 32],
355 pub execution_requests: ExecutionRequests,
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
363pub struct PublicInput {
364 pub new_payload_request_root: [u8; 32],
365}
366
367impl NewPayloadRequest {
368 pub fn public_input(&self, hasher: &impl Sha256Hasher) -> PublicInput {
371 PublicInput {
372 new_payload_request_root: self.hash_tree_root(hasher),
373 }
374 }
375}
376
377const MAX_BYTES_PER_WITNESS_NODE: usize = 1_048_576; const MAX_BYTES_PER_CODE: usize = 16_777_216; const MAX_WITNESS_HEADERS: usize = 256;
395const MAX_BYTES_PER_HEADER: usize = 1_024; const PUBLIC_KEY_BYTES: usize = 65;
399
400pub const STATELESS_INPUT_SCHEMA_ID: u16 = 0x1501;
424
425pub const STATELESS_INPUT_SCHEMA_ID_SIZE: usize = 2;
427
428pub type SszPublicKeys = ProgressiveList<SszVector<u8, PUBLIC_KEY_BYTES>>;
433
434#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
445pub struct SszExecutionWitness {
446 pub state: ProgressiveList<SszList<u8, MAX_BYTES_PER_WITNESS_NODE>>,
447 pub codes: ProgressiveList<SszList<u8, MAX_BYTES_PER_CODE>>,
448 pub headers: SszList<SszList<u8, MAX_BYTES_PER_HEADER>, MAX_WITNESS_HEADERS>,
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
456pub struct SszStatelessInput {
457 pub new_payload_request: NewPayloadRequest,
458 pub witness: SszExecutionWitness,
459 pub chain_id: u64,
463 pub public_keys: SszPublicKeys,
464}
465
466#[derive(Debug, Clone, Default, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
468pub struct SszStatelessValidationResult {
469 pub new_payload_request_root: [u8; 32],
470 pub successful_validation: bool,
471 pub chain_id: u64,
474 pub schema_id: u16,
478}
479
480impl SszExecutionWitness {
483 pub fn codes_as_vecs(&self) -> Vec<Vec<u8>> {
485 self.codes
486 .iter()
487 .map(|c| c.iter().copied().collect())
488 .collect()
489 }
490
491 pub fn headers_as_vecs(&self) -> Vec<Vec<u8>> {
493 self.headers
494 .iter()
495 .map(|h| h.iter().copied().collect())
496 .collect()
497 }
498
499 pub fn state_as_vecs(&self) -> Vec<Vec<u8>> {
501 self.state
502 .iter()
503 .map(|n| n.iter().copied().collect())
504 .collect()
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511 use libssz_merkle::Sha2Hasher;
512
513 const HASHER: Sha2Hasher = Sha2Hasher;
514
515 fn sample_payload() -> ExecutionPayload {
518 ExecutionPayload {
519 parent_hash: [1u8; 32],
520 fee_recipient: Bytes20([2u8; 20]),
521 state_root: [3u8; 32],
522 receipts_root: [4u8; 32],
523 logs_bloom: vec![0u8; 256].try_into().expect("logs_bloom length"),
524 prev_randao: [5u8; 32],
525 block_number: 42,
526 gas_limit: 30_000_000,
527 gas_used: 21_000,
528 timestamp: 1_700_000_000,
529 extra_data: vec![0xAB, 0xCD].try_into().expect("extra_data fits"),
530 base_fee_per_gas: {
531 let mut b = [0u8; 32];
532 b[0] = 7; b
534 },
535 block_hash: [6u8; 32],
536 transactions: vec![
537 vec![0xDE, 0xAD, 0xBE, 0xEF]
538 .try_into()
539 .expect("tx bytes fit"),
540 ]
541 .try_into()
542 .expect("txs fit"),
543 withdrawals: vec![Withdrawal {
544 index: 0,
545 validator_index: 1,
546 address: Bytes20([7u8; 20]),
547 amount: 1_000_000,
548 }]
549 .try_into()
550 .expect("withdrawals fit"),
551 blob_gas_used: 0,
552 excess_blob_gas: 0,
553 block_access_list: ProgressiveList::new(), slot_number: 0,
555 }
556 }
557
558 fn empty_requests() -> ExecutionRequests {
559 ExecutionRequests {
560 deposits: Vec::new().into(),
561 withdrawals: Vec::new().into(),
562 consolidations: Vec::new().into(),
563 builder_deposits: Vec::new().into(),
564 builder_exits: Vec::new().into(),
565 }
566 }
567
568 fn sample_request() -> NewPayloadRequest {
569 NewPayloadRequest {
570 execution_payload: sample_payload(),
571 versioned_hashes: Vec::new().into(),
572 parent_beacon_block_root: [8u8; 32],
573 execution_requests: empty_requests(),
574 }
575 }
576
577 #[test]
578 fn test_ssz_root_changes_with_different_data() {
579 let request1 = sample_request();
580 let mut request2 = sample_request();
581 request2.execution_payload.block_number = 99;
582
583 assert_ne!(
584 request1.hash_tree_root(&HASHER),
585 request2.hash_tree_root(&HASHER),
586 "Different payloads must produce different roots"
587 );
588 }
589
590 #[test]
591 fn test_ssz_root_is_deterministic() {
592 let request = sample_request();
593 let root1 = request.hash_tree_root(&HASHER);
594 let root2 = request.hash_tree_root(&HASHER);
595 assert_eq!(root1, root2, "Same request must produce same root");
596 }
597
598 #[test]
599 fn test_execution_requests_to_encoded_bytes() {
600 let requests = ExecutionRequests {
601 deposits: vec![DepositRequest {
602 pubkey: [0x11; 48],
603 withdrawal_credentials: [0x22; 32],
604 amount: 32_000_000_000,
605 signature: [0x33; 96],
606 index: 7,
607 }]
608 .into(),
609 withdrawals: vec![WithdrawalRequest {
610 source_address: Bytes20([0x44; 20]),
611 validator_pubkey: [0x55; 48],
612 amount: 1_000_000,
613 }]
614 .into(),
615 consolidations: vec![ConsolidationRequest {
616 source_address: Bytes20([0x66; 20]),
617 source_pubkey: [0x77; 48],
618 target_pubkey: [0x88; 48],
619 }]
620 .into(),
621 builder_deposits: ProgressiveList::new(),
622 builder_exits: ProgressiveList::new(),
623 };
624
625 let encoded = requests.to_encoded_requests();
626 assert_eq!(encoded.len(), 5, "must emit 5 EIP-7685 entries");
630
631 assert_eq!(encoded[0].0[0], DEPOSIT_REQUEST_TYPE);
633 assert_eq!(encoded[0].0.len(), 1 + 192);
634
635 assert_eq!(encoded[1].0[0], WITHDRAWAL_REQUEST_TYPE);
637 assert_eq!(encoded[1].0.len(), 1 + 76);
638
639 assert_eq!(encoded[2].0[0], CONSOLIDATION_REQUEST_TYPE);
641 assert_eq!(encoded[2].0.len(), 1 + 116);
642 }
643
644 fn list<T: SszEncode + SszDecode, const N: usize>(items: Vec<T>) -> SszList<T, N> {
647 let mut list = SszList::new();
648 for item in items {
649 list.push(item).expect("test list capacity exceeded");
650 }
651 list
652 }
653
654 fn round_trip<T: SszEncode + SszDecode + PartialEq + std::fmt::Debug>(value: &T) {
655 let mut buf = Vec::new();
656 value.ssz_append(&mut buf);
657 let decoded = T::from_ssz_bytes(&buf).expect("SSZ decode failed");
658 assert_eq!(*value, decoded, "round-trip mismatch");
659 }
660
661 #[test]
662 fn ssz_execution_witness_round_trip() {
663 let witness = SszExecutionWitness {
664 state: vec![list(vec![1u8, 2, 3]), list(vec![4u8, 5])].into(),
665 codes: vec![list(vec![0x60u8, 0x00, 0x60, 0x00, 0xf3])].into(),
666 headers: list(vec![list(vec![0xf9u8, 0x02, 0x11])]),
667 };
668 round_trip(&witness);
669 }
670
671 #[test]
672 fn ssz_execution_witness_empty_round_trip() {
673 let witness = SszExecutionWitness {
674 state: ProgressiveList::new(),
675 codes: ProgressiveList::new(),
676 headers: SszList::new(),
677 };
678 round_trip(&witness);
679 }
680
681 #[test]
682 fn ssz_execution_payload_has_block_access_list_round_trip() {
683 let payload = ExecutionPayload {
684 parent_hash: [0x11; 32],
685 fee_recipient: Bytes20([0x22; 20]),
686 state_root: [0x33; 32],
687 receipts_root: [0x44; 32],
688 logs_bloom: vec![0u8; 256].try_into().expect("logs_bloom length"),
689 prev_randao: [0x55; 32],
690 block_number: 7,
691 gas_limit: 30_000_000,
692 gas_used: 21_000,
693 timestamp: 1_700_000_000,
694 extra_data: list(vec![0xde, 0xad]),
695 base_fee_per_gas: [0u8; 32],
696 block_hash: [0x66; 32],
697 transactions: ProgressiveList::new(),
698 withdrawals: ProgressiveList::new(),
699 blob_gas_used: 0,
700 excess_blob_gas: 0,
701 block_access_list: vec![0x01u8, 0x02, 0x03].into(),
702 slot_number: 0,
703 };
704 round_trip(&payload);
705 assert_eq!(payload.block_access_list.len(), 3);
706 }
707
708 #[test]
709 fn ssz_public_keys_are_65_byte_vectors_round_trip() {
710 let key: SszVector<u8, PUBLIC_KEY_BYTES> =
711 vec![0x04u8; 65].try_into().expect("pubkey length");
712 let mut keys: SszPublicKeys = ProgressiveList::new();
713 keys.push(key);
714 round_trip(&keys);
715 assert_eq!(keys.first().unwrap().len(), 65);
716 }
717
718 #[test]
719 fn ssz_stateless_validation_result_round_trip() {
720 let result = SszStatelessValidationResult {
721 new_payload_request_root: [0xab; 32],
722 successful_validation: true,
723 chain_id: 42,
724 schema_id: 0x1501,
725 };
726 round_trip(&result);
727
728 let result_false = SszStatelessValidationResult {
729 new_payload_request_root: [0x00; 32],
730 successful_validation: false,
731 chain_id: 1,
732 schema_id: 0x1501,
733 };
734 round_trip(&result_false);
735 }
736
737 const SOL_RESULT_SUCCESS_OFFSET: usize = 32;
745 const SOL_RESULT_CHAIN_ID_OFFSET: usize = 33;
749 const SOL_RESULT_SCHEMA_ID_OFFSET: usize = 41;
750 const SOL_RESULT_FIXED_LEN: usize = 43;
751 const SOL_EP_STATE_ROOT_OFFSET: usize = 52;
752 const SOL_EP_BLOCK_NUMBER_OFFSET: usize = 404;
753 const SOL_EP_GAS_LIMIT_OFFSET: usize = 412;
754 const SOL_EP_BLOCK_HASH_OFFSET: usize = 472;
755 const SOL_EP_BAL_OFFSET_POS: usize = 528;
758 const SOL_EP_SLOT_NUMBER_OFFSET: usize = 532;
759 const SOL_EP_FIXED_PREFIX_LEN: usize = 540;
760
761 fn u32_le(bytes: &[u8], off: usize) -> usize {
762 (bytes[off] as usize)
763 | ((bytes[off + 1] as usize) << 8)
764 | ((bytes[off + 2] as usize) << 16)
765 | ((bytes[off + 3] as usize) << 24)
766 }
767
768 fn sample_execution_payload() -> ExecutionPayload {
769 ExecutionPayload {
770 parent_hash: [0x11; 32],
771 fee_recipient: Bytes20([0x22; 20]),
772 state_root: [0x33; 32],
773 receipts_root: [0x44; 32],
774 logs_bloom: vec![0u8; 256].try_into().expect("bloom"),
775 prev_randao: [0x55; 32],
776 block_number: 7,
777 gas_limit: 30_000_000,
778 gas_used: 21_000,
779 timestamp: 1_700_000_000,
780 extra_data: SszList::new(),
781 base_fee_per_gas: [0u8; 32],
782 block_hash: [0x66; 32],
783 transactions: ProgressiveList::new(),
784 withdrawals: ProgressiveList::new(),
785 blob_gas_used: 0,
786 excess_blob_gas: 0,
787 block_access_list: ProgressiveList::new(),
788 slot_number: 0x7843,
789 }
790 }
791
792 #[test]
793 fn nativerollup_sol_result_layout_matches() {
794 let result = SszStatelessValidationResult {
798 new_payload_request_root: [0xAA; 32],
799 successful_validation: true,
800 chain_id: 0x1122334455667788,
801 schema_id: 0x1501,
802 };
803 let mut buf = Vec::new();
804 result.ssz_append(&mut buf);
805
806 assert_eq!(
807 buf.len(),
808 SOL_RESULT_FIXED_LEN,
809 "result must be exactly 43 fixed bytes; a variable tail means \
810 ChainConfig is still in there"
811 );
812 assert_eq!(
813 buf[SOL_RESULT_SUCCESS_OFFSET], 1,
814 "successful_validation must be byte 32"
815 );
816 let chain_id = u64::from_le_bytes(
817 buf[SOL_RESULT_CHAIN_ID_OFFSET..SOL_RESULT_CHAIN_ID_OFFSET + 8]
818 .try_into()
819 .unwrap(),
820 );
821 assert_eq!(
822 chain_id, 0x1122334455667788,
823 "chain_id must be a direct u64 LE read at 33"
824 );
825 let schema_id = u16::from_le_bytes(
826 buf[SOL_RESULT_SCHEMA_ID_OFFSET..SOL_RESULT_SCHEMA_ID_OFFSET + 2]
827 .try_into()
828 .unwrap(),
829 );
830 assert_eq!(schema_id, 0x1501, "schema_id must be a u16 LE read at 41");
831 }
832
833 #[test]
834 fn nativerollup_sol_ep_offsets_match() {
835 let ep = sample_execution_payload();
838 let npr = NewPayloadRequest {
839 execution_payload: ep,
840 versioned_hashes: ProgressiveList::new(),
841 parent_beacon_block_root: [0x00; 32],
842 execution_requests: ExecutionRequests {
843 deposits: ProgressiveList::new(),
844 withdrawals: ProgressiveList::new(),
845 consolidations: ProgressiveList::new(),
846 builder_deposits: ProgressiveList::new(),
847 builder_exits: ProgressiveList::new(),
848 },
849 };
850 let input = SszStatelessInput {
851 new_payload_request: npr,
852 witness: SszExecutionWitness {
853 state: ProgressiveList::new(),
854 codes: ProgressiveList::new(),
855 headers: SszList::new(),
856 },
857 chain_id: 1,
858 public_keys: ProgressiveList::new(),
859 };
860 let mut buf = Vec::new();
861 input.ssz_append(&mut buf);
862
863 let npr_abs = u32_le(&buf, 0);
868 let ep_abs = npr_abs + u32_le(&buf, npr_abs);
870 let actual_block_number = u64::from_le_bytes(
872 buf[ep_abs + SOL_EP_BLOCK_NUMBER_OFFSET..ep_abs + SOL_EP_BLOCK_NUMBER_OFFSET + 8]
873 .try_into()
874 .unwrap(),
875 );
876 assert_eq!(
877 actual_block_number, 7,
878 "block_number must be at EP offset 404, actual offset produces: {}",
879 actual_block_number
880 );
881 let actual_gas_limit = u64::from_le_bytes(
882 buf[ep_abs + SOL_EP_GAS_LIMIT_OFFSET..ep_abs + SOL_EP_GAS_LIMIT_OFFSET + 8]
883 .try_into()
884 .unwrap(),
885 );
886 assert_eq!(
887 actual_gas_limit, 30_000_000,
888 "gas_limit must be at EP offset 412, actual offset produces: {}",
889 actual_gas_limit
890 );
891 assert_eq!(
892 &buf[ep_abs + SOL_EP_STATE_ROOT_OFFSET..ep_abs + SOL_EP_STATE_ROOT_OFFSET + 32],
893 &[0x33; 32],
894 "state_root @52"
895 );
896 assert_eq!(
897 &buf[ep_abs + SOL_EP_BLOCK_HASH_OFFSET..ep_abs + SOL_EP_BLOCK_HASH_OFFSET + 32],
898 &[0x66; 32],
899 "block_hash @472"
900 );
901 assert_eq!(
906 u32_le(&buf, ep_abs + SOL_EP_BAL_OFFSET_POS),
907 SOL_EP_FIXED_PREFIX_LEN,
908 "block_access_list offset slot @EP+528 must equal the EP fixed-prefix length (540)",
909 );
910 assert_eq!(
912 u64::from_le_bytes(
913 buf[ep_abs + SOL_EP_SLOT_NUMBER_OFFSET..ep_abs + SOL_EP_SLOT_NUMBER_OFFSET + 8]
914 .try_into()
915 .unwrap()
916 ),
917 0x7843,
918 "slot_number must be readable at EP+532",
919 );
920 }
921}