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