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