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;
423
424pub const STATELESS_INPUT_SCHEMA_ID_SIZE: usize = 2;
426
427pub type SszPublicKeys = ProgressiveList<SszVector<u8, PUBLIC_KEY_BYTES>>;
432
433#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
444pub struct SszExecutionWitness {
445 pub state: ProgressiveList<SszList<u8, MAX_BYTES_PER_WITNESS_NODE>>,
446 pub codes: ProgressiveList<SszList<u8, MAX_BYTES_PER_CODE>>,
447 pub headers: SszList<SszList<u8, MAX_BYTES_PER_HEADER>, MAX_WITNESS_HEADERS>,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
455pub struct SszStatelessInput {
456 pub new_payload_request: NewPayloadRequest,
457 pub witness: SszExecutionWitness,
458 pub chain_id: u64,
462 pub public_keys: SszPublicKeys,
463}
464
465#[derive(Debug, Clone, Default, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
467pub struct SszStatelessValidationResult {
468 pub new_payload_request_root: [u8; 32],
469 pub successful_validation: bool,
470 pub chain_id: u64,
473 pub schema_id: u16,
477}
478
479impl SszExecutionWitness {
482 pub fn codes_as_vecs(&self) -> Vec<Vec<u8>> {
484 self.codes
485 .iter()
486 .map(|c| c.iter().copied().collect())
487 .collect()
488 }
489
490 pub fn headers_as_vecs(&self) -> Vec<Vec<u8>> {
492 self.headers
493 .iter()
494 .map(|h| h.iter().copied().collect())
495 .collect()
496 }
497
498 pub fn state_as_vecs(&self) -> Vec<Vec<u8>> {
500 self.state
501 .iter()
502 .map(|n| n.iter().copied().collect())
503 .collect()
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 use libssz_merkle::Sha2Hasher;
511
512 const HASHER: Sha2Hasher = Sha2Hasher;
513
514 fn sample_payload() -> ExecutionPayload {
517 ExecutionPayload {
518 parent_hash: [1u8; 32],
519 fee_recipient: Bytes20([2u8; 20]),
520 state_root: [3u8; 32],
521 receipts_root: [4u8; 32],
522 logs_bloom: vec![0u8; 256].try_into().expect("logs_bloom length"),
523 prev_randao: [5u8; 32],
524 block_number: 42,
525 gas_limit: 30_000_000,
526 gas_used: 21_000,
527 timestamp: 1_700_000_000,
528 extra_data: vec![0xAB, 0xCD].try_into().expect("extra_data fits"),
529 base_fee_per_gas: {
530 let mut b = [0u8; 32];
531 b[0] = 7; b
533 },
534 block_hash: [6u8; 32],
535 transactions: vec![
536 vec![0xDE, 0xAD, 0xBE, 0xEF]
537 .try_into()
538 .expect("tx bytes fit"),
539 ]
540 .try_into()
541 .expect("txs fit"),
542 withdrawals: vec![Withdrawal {
543 index: 0,
544 validator_index: 1,
545 address: Bytes20([7u8; 20]),
546 amount: 1_000_000,
547 }]
548 .try_into()
549 .expect("withdrawals fit"),
550 blob_gas_used: 0,
551 excess_blob_gas: 0,
552 block_access_list: ProgressiveList::new(), slot_number: 0,
554 }
555 }
556
557 fn empty_requests() -> ExecutionRequests {
558 ExecutionRequests {
559 deposits: Vec::new().into(),
560 withdrawals: Vec::new().into(),
561 consolidations: Vec::new().into(),
562 builder_deposits: Vec::new().into(),
563 builder_exits: Vec::new().into(),
564 }
565 }
566
567 fn sample_request() -> NewPayloadRequest {
568 NewPayloadRequest {
569 execution_payload: sample_payload(),
570 versioned_hashes: Vec::new().into(),
571 parent_beacon_block_root: [8u8; 32],
572 execution_requests: empty_requests(),
573 }
574 }
575
576 #[test]
577 fn test_ssz_root_changes_with_different_data() {
578 let request1 = sample_request();
579 let mut request2 = sample_request();
580 request2.execution_payload.block_number = 99;
581
582 assert_ne!(
583 request1.hash_tree_root(&HASHER),
584 request2.hash_tree_root(&HASHER),
585 "Different payloads must produce different roots"
586 );
587 }
588
589 #[test]
590 fn test_ssz_root_is_deterministic() {
591 let request = sample_request();
592 let root1 = request.hash_tree_root(&HASHER);
593 let root2 = request.hash_tree_root(&HASHER);
594 assert_eq!(root1, root2, "Same request must produce same root");
595 }
596
597 #[test]
598 fn test_execution_requests_to_encoded_bytes() {
599 let requests = ExecutionRequests {
600 deposits: vec![DepositRequest {
601 pubkey: [0x11; 48],
602 withdrawal_credentials: [0x22; 32],
603 amount: 32_000_000_000,
604 signature: [0x33; 96],
605 index: 7,
606 }]
607 .into(),
608 withdrawals: vec![WithdrawalRequest {
609 source_address: Bytes20([0x44; 20]),
610 validator_pubkey: [0x55; 48],
611 amount: 1_000_000,
612 }]
613 .into(),
614 consolidations: vec![ConsolidationRequest {
615 source_address: Bytes20([0x66; 20]),
616 source_pubkey: [0x77; 48],
617 target_pubkey: [0x88; 48],
618 }]
619 .into(),
620 builder_deposits: ProgressiveList::new(),
621 builder_exits: ProgressiveList::new(),
622 };
623
624 let encoded = requests.to_encoded_requests();
625 assert_eq!(encoded.len(), 5, "must emit 5 EIP-7685 entries");
629
630 assert_eq!(encoded[0].0[0], DEPOSIT_REQUEST_TYPE);
632 assert_eq!(encoded[0].0.len(), 1 + 192);
633
634 assert_eq!(encoded[1].0[0], WITHDRAWAL_REQUEST_TYPE);
636 assert_eq!(encoded[1].0.len(), 1 + 76);
637
638 assert_eq!(encoded[2].0[0], CONSOLIDATION_REQUEST_TYPE);
640 assert_eq!(encoded[2].0.len(), 1 + 116);
641 }
642
643 fn list<T: SszEncode + SszDecode, const N: usize>(items: Vec<T>) -> SszList<T, N> {
646 let mut list = SszList::new();
647 for item in items {
648 list.push(item).expect("test list capacity exceeded");
649 }
650 list
651 }
652
653 fn round_trip<T: SszEncode + SszDecode + PartialEq + std::fmt::Debug>(value: &T) {
654 let mut buf = Vec::new();
655 value.ssz_append(&mut buf);
656 let decoded = T::from_ssz_bytes(&buf).expect("SSZ decode failed");
657 assert_eq!(*value, decoded, "round-trip mismatch");
658 }
659
660 #[test]
661 fn ssz_execution_witness_round_trip() {
662 let witness = SszExecutionWitness {
663 state: vec![list(vec![1u8, 2, 3]), list(vec![4u8, 5])].into(),
664 codes: vec![list(vec![0x60u8, 0x00, 0x60, 0x00, 0xf3])].into(),
665 headers: list(vec![list(vec![0xf9u8, 0x02, 0x11])]),
666 };
667 round_trip(&witness);
668 }
669
670 #[test]
671 fn ssz_execution_witness_empty_round_trip() {
672 let witness = SszExecutionWitness {
673 state: ProgressiveList::new(),
674 codes: ProgressiveList::new(),
675 headers: SszList::new(),
676 };
677 round_trip(&witness);
678 }
679
680 #[test]
681 fn ssz_execution_payload_has_block_access_list_round_trip() {
682 let payload = ExecutionPayload {
683 parent_hash: [0x11; 32],
684 fee_recipient: Bytes20([0x22; 20]),
685 state_root: [0x33; 32],
686 receipts_root: [0x44; 32],
687 logs_bloom: vec![0u8; 256].try_into().expect("logs_bloom length"),
688 prev_randao: [0x55; 32],
689 block_number: 7,
690 gas_limit: 30_000_000,
691 gas_used: 21_000,
692 timestamp: 1_700_000_000,
693 extra_data: list(vec![0xde, 0xad]),
694 base_fee_per_gas: [0u8; 32],
695 block_hash: [0x66; 32],
696 transactions: ProgressiveList::new(),
697 withdrawals: ProgressiveList::new(),
698 blob_gas_used: 0,
699 excess_blob_gas: 0,
700 block_access_list: vec![0x01u8, 0x02, 0x03].into(),
701 slot_number: 0,
702 };
703 round_trip(&payload);
704 assert_eq!(payload.block_access_list.len(), 3);
705 }
706
707 #[test]
708 fn ssz_public_keys_are_65_byte_vectors_round_trip() {
709 let key: SszVector<u8, PUBLIC_KEY_BYTES> =
710 vec![0x04u8; 65].try_into().expect("pubkey length");
711 let mut keys: SszPublicKeys = ProgressiveList::new();
712 keys.push(key);
713 round_trip(&keys);
714 assert_eq!(keys.first().unwrap().len(), 65);
715 }
716
717 #[test]
718 fn ssz_stateless_validation_result_round_trip() {
719 let result = SszStatelessValidationResult {
720 new_payload_request_root: [0xab; 32],
721 successful_validation: true,
722 chain_id: 42,
723 schema_id: 0x1501,
724 };
725 round_trip(&result);
726
727 let result_false = SszStatelessValidationResult {
728 new_payload_request_root: [0x00; 32],
729 successful_validation: false,
730 chain_id: 1,
731 schema_id: 0x1501,
732 };
733 round_trip(&result_false);
734 }
735
736 const SOL_RESULT_SUCCESS_OFFSET: usize = 32;
744 const SOL_RESULT_CHAIN_ID_OFFSET: usize = 33;
748 const SOL_RESULT_SCHEMA_ID_OFFSET: usize = 41;
749 const SOL_RESULT_FIXED_LEN: usize = 43;
750 const SOL_EP_STATE_ROOT_OFFSET: usize = 52;
751 const SOL_EP_BLOCK_NUMBER_OFFSET: usize = 404;
752 const SOL_EP_GAS_LIMIT_OFFSET: usize = 412;
753 const SOL_EP_BLOCK_HASH_OFFSET: usize = 472;
754 const SOL_EP_BAL_OFFSET_POS: usize = 528;
757 const SOL_EP_SLOT_NUMBER_OFFSET: usize = 532;
758 const SOL_EP_FIXED_PREFIX_LEN: usize = 540;
759
760 fn u32_le(bytes: &[u8], off: usize) -> usize {
761 (bytes[off] as usize)
762 | ((bytes[off + 1] as usize) << 8)
763 | ((bytes[off + 2] as usize) << 16)
764 | ((bytes[off + 3] as usize) << 24)
765 }
766
767 fn sample_execution_payload() -> ExecutionPayload {
768 ExecutionPayload {
769 parent_hash: [0x11; 32],
770 fee_recipient: Bytes20([0x22; 20]),
771 state_root: [0x33; 32],
772 receipts_root: [0x44; 32],
773 logs_bloom: vec![0u8; 256].try_into().expect("bloom"),
774 prev_randao: [0x55; 32],
775 block_number: 7,
776 gas_limit: 30_000_000,
777 gas_used: 21_000,
778 timestamp: 1_700_000_000,
779 extra_data: SszList::new(),
780 base_fee_per_gas: [0u8; 32],
781 block_hash: [0x66; 32],
782 transactions: ProgressiveList::new(),
783 withdrawals: ProgressiveList::new(),
784 blob_gas_used: 0,
785 excess_blob_gas: 0,
786 block_access_list: ProgressiveList::new(),
787 slot_number: 0x7843,
788 }
789 }
790
791 #[test]
792 fn nativerollup_sol_result_layout_matches() {
793 let result = SszStatelessValidationResult {
797 new_payload_request_root: [0xAA; 32],
798 successful_validation: true,
799 chain_id: 0x1122334455667788,
800 schema_id: 0x1501,
801 };
802 let mut buf = Vec::new();
803 result.ssz_append(&mut buf);
804
805 assert_eq!(
806 buf.len(),
807 SOL_RESULT_FIXED_LEN,
808 "result must be exactly 43 fixed bytes; a variable tail means \
809 ChainConfig is still in there"
810 );
811 assert_eq!(
812 buf[SOL_RESULT_SUCCESS_OFFSET], 1,
813 "successful_validation must be byte 32"
814 );
815 let chain_id = u64::from_le_bytes(
816 buf[SOL_RESULT_CHAIN_ID_OFFSET..SOL_RESULT_CHAIN_ID_OFFSET + 8]
817 .try_into()
818 .unwrap(),
819 );
820 assert_eq!(
821 chain_id, 0x1122334455667788,
822 "chain_id must be a direct u64 LE read at 33"
823 );
824 let schema_id = u16::from_le_bytes(
825 buf[SOL_RESULT_SCHEMA_ID_OFFSET..SOL_RESULT_SCHEMA_ID_OFFSET + 2]
826 .try_into()
827 .unwrap(),
828 );
829 assert_eq!(schema_id, 0x1501, "schema_id must be a u16 LE read at 41");
830 }
831
832 #[test]
833 fn nativerollup_sol_ep_offsets_match() {
834 let ep = sample_execution_payload();
837 let npr = NewPayloadRequest {
838 execution_payload: ep,
839 versioned_hashes: ProgressiveList::new(),
840 parent_beacon_block_root: [0x00; 32],
841 execution_requests: ExecutionRequests {
842 deposits: ProgressiveList::new(),
843 withdrawals: ProgressiveList::new(),
844 consolidations: ProgressiveList::new(),
845 builder_deposits: ProgressiveList::new(),
846 builder_exits: ProgressiveList::new(),
847 },
848 };
849 let input = SszStatelessInput {
850 new_payload_request: npr,
851 witness: SszExecutionWitness {
852 state: ProgressiveList::new(),
853 codes: ProgressiveList::new(),
854 headers: SszList::new(),
855 },
856 chain_id: 1,
857 public_keys: ProgressiveList::new(),
858 };
859 let mut buf = Vec::new();
860 input.ssz_append(&mut buf);
861
862 let npr_abs = u32_le(&buf, 0);
867 let ep_abs = npr_abs + u32_le(&buf, npr_abs);
869 let actual_block_number = u64::from_le_bytes(
871 buf[ep_abs + SOL_EP_BLOCK_NUMBER_OFFSET..ep_abs + SOL_EP_BLOCK_NUMBER_OFFSET + 8]
872 .try_into()
873 .unwrap(),
874 );
875 assert_eq!(
876 actual_block_number, 7,
877 "block_number must be at EP offset 404, actual offset produces: {}",
878 actual_block_number
879 );
880 let actual_gas_limit = u64::from_le_bytes(
881 buf[ep_abs + SOL_EP_GAS_LIMIT_OFFSET..ep_abs + SOL_EP_GAS_LIMIT_OFFSET + 8]
882 .try_into()
883 .unwrap(),
884 );
885 assert_eq!(
886 actual_gas_limit, 30_000_000,
887 "gas_limit must be at EP offset 412, actual offset produces: {}",
888 actual_gas_limit
889 );
890 assert_eq!(
891 &buf[ep_abs + SOL_EP_STATE_ROOT_OFFSET..ep_abs + SOL_EP_STATE_ROOT_OFFSET + 32],
892 &[0x33; 32],
893 "state_root @52"
894 );
895 assert_eq!(
896 &buf[ep_abs + SOL_EP_BLOCK_HASH_OFFSET..ep_abs + SOL_EP_BLOCK_HASH_OFFSET + 32],
897 &[0x66; 32],
898 "block_hash @472"
899 );
900 assert_eq!(
905 u32_le(&buf, ep_abs + SOL_EP_BAL_OFFSET_POS),
906 SOL_EP_FIXED_PREFIX_LEN,
907 "block_access_list offset slot @EP+528 must equal the EP fixed-prefix length (540)",
908 );
909 assert_eq!(
911 u64::from_le_bytes(
912 buf[ep_abs + SOL_EP_SLOT_NUMBER_OFFSET..ep_abs + SOL_EP_SLOT_NUMBER_OFFSET + 8]
913 .try_into()
914 .unwrap()
915 ),
916 0x7843,
917 "slot_number must be readable at EP+532",
918 );
919 }
920}