1#![expect(incomplete_features, reason = "explicit_tail_calls")]
31#![feature(
32 const_block_items,
33 const_cmp,
34 const_convert,
35 const_default,
36 const_index,
37 const_trait_impl,
38 const_try,
39 const_try_residual,
40 core_io,
41 core_io_borrowed_buf,
42 derive_const,
43 explicit_tail_calls,
44 fn_align,
45 maybe_uninit_fill,
46 signed_bigint_helpers,
47 trusted_len,
48 try_blocks
49)]
50#![no_std]
51
52pub mod instruction;
53
54use crate::instruction::ContractInstruction;
55use ab_contracts_common::metadata::decode::{
56 MetadataDecoder, MetadataDecodingError, MetadataItem, MethodMetadataItem,
57 MethodsMetadataDecoder,
58};
59use ab_io_type::trivial_type::TrivialType;
60use ab_io_type::unaligned::Unaligned;
61use ab_riscv_primitives::prelude::*;
62use core::io::BorrowedCursor;
63use core::iter;
64use core::iter::TrustedLen;
65use replace_with::replace_with_or_abort;
66use tracing::{debug, trace};
67
68pub const CONTRACT_FILE_MAGIC: [u8; 4] = *b"ABC0";
70
71const {
73 assert!(size_of::<ContractInstruction>() == 8);
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, TrivialType)]
78#[repr(C)]
79pub struct ContractFileHeader {
80 pub magic: [u8; 4],
82 pub read_only_section_file_size: u32,
84 pub read_only_section_memory_size: u32,
88 pub metadata_offset: u32,
90 pub metadata_size: u16,
92 pub num_methods: u16,
94 pub host_call_fn_offset: u32,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, TrivialType)]
102#[repr(C)]
103pub struct ContractFileMethodMetadata {
104 pub offset: u32,
106 pub size: u32,
108}
109
110#[derive(Debug, Copy, Clone)]
111pub struct ContractFileMethod<'a> {
112 pub address: u32,
114 pub method_metadata_item: MethodMetadataItem<'a>,
116 pub method_metadata_bytes: &'a [u8],
122}
123
124#[derive(Debug, thiserror::Error)]
126pub enum ContractFileParseError {
127 #[error("The file is too large, must fit into `u32`: {file_size} bytes")]
129 FileTooLarge {
130 file_size: usize,
132 },
133 #[error("The file does not have a header (not enough bytes)")]
135 NoHeader,
136 #[error("The magic bytes in the header are incorrect")]
138 WrongMagicBytes,
139 #[error(
141 "The metadata section is out of bounds of the file: offset {offset}, size {size}, file \
142 size {file_size}"
143 )]
144 MetadataOutOfRange {
145 offset: u32,
147 size: u16,
149 file_size: u32,
151 },
152 #[error("Failed to decode metadata item")]
154 MetadataDecoding,
155 #[error(
157 "The file is too small: num_methods {num_methods}, read_only_section_size \
158 {read_only_section_size}, file_size {file_size}"
159 )]
160 FileTooSmall {
161 num_methods: u16,
163 read_only_section_size: u32,
165 file_size: u32,
167 },
168 #[error("Method is unaligned: file offset {file_offset}, memory address {memory_address}")]
170 MethodUnaligned {
171 file_offset: u32,
173 memory_address: u32,
175 },
176 #[error(
178 "Method offset is out of bounds of the file: offset {offset}, code section \
179 offset {code_section_offset} file_size {file_size}"
180 )]
181 MethodOutOfRange {
182 offset: u32,
184 code_section_offset: u32,
186 file_size: u32,
188 },
189 #[error(
191 "Host call function is unaligned: file offset {file_offset}, memory address \
192 {memory_address}"
193 )]
194 HostCallFnUnaligned {
195 file_offset: u32,
197 memory_address: u32,
199 },
200 #[error(
202 "The host call function offset is out of bounds of the file: offset {offset}, code section \
203 offset {code_section_offset} file_size {file_size}"
204 )]
205 HostCallFnOutOfRange {
206 offset: u32,
208 code_section_offset: u32,
210 file_size: u32,
212 },
213 #[error("The host call function doesn't have jal tailcall instruction: {instruction}")]
215 InvalidHostCallFnPattern {
216 instruction: ContractInstruction,
218 },
219 #[error(
221 "The read-only section file size is larger than the memory size: file_size {file_size}, \
222 memory_size {memory_size}"
223 )]
224 InvalidReadOnlySizes {
225 file_size: u32,
227 memory_size: u32,
229 },
230 #[error(
233 "There are not enough methods in the header to match the number of methods in the actual \
234 metadata: header_num_methods {header_num_methods}, metadata_method_index \
235 {metadata_method_index}"
236 )]
237 InsufficientHeaderMethods {
238 header_num_methods: u16,
240 metadata_method_index: u16,
242 },
243 #[error(
246 "The number of methods in the header {header_num_methods} does not match the number of \
247 methods in the actual metadata {metadata_num_methods}"
248 )]
249 MetadataNumMethodsMismatch {
250 header_num_methods: u16,
252 metadata_num_methods: u16,
254 },
255 #[error("Invalid instruction encountered while parsing the code section: {instruction:#x}")]
257 InvalidInstruction {
258 instruction: u32,
260 },
261 #[error("The code section is empty")]
263 CodeEmpty,
264 #[error(
266 "Unexpected trailing code bytes encountered while parsing the code section: {num_bytes} \
267 trailing bytes"
268 )]
269 UnexpectedTrailingCodeBytes {
270 num_bytes: usize,
272 },
273 #[error("The last instruction in the code section must be a jump instruction: {instruction}")]
275 LastInstructionMustBeJump {
276 instruction: ContractInstruction,
278 },
279}
280
281impl From<MetadataDecodingError<'_>> for ContractFileParseError {
282 fn from(error: MetadataDecodingError<'_>) -> Self {
283 debug!(?error, "Failed to decode metadata item");
284 Self::MetadataDecoding
285 }
286}
287
288#[derive(Debug)]
290pub struct ContractFile<'a> {
291 read_only_section_file_size: u32,
292 read_only_section_memory_size: u32,
293 num_methods: u16,
294 bytes: &'a [u8],
295}
296
297impl<'a> ContractFile<'a> {
298 pub fn parse<CM>(
307 file_bytes: &'a [u8],
308 mut contract_method: CM,
309 ) -> Result<Self, ContractFileParseError>
310 where
311 CM: FnMut(ContractFileMethod<'a>) -> Result<(), ContractFileParseError>,
312 {
313 let file_size = u32::try_from(file_bytes.len()).map_err(|_error| {
314 ContractFileParseError::FileTooLarge {
315 file_size: file_bytes.len(),
316 }
317 })?;
318 let (header_bytes, after_header_bytes) = file_bytes
319 .split_at_checked(size_of::<ContractFileHeader>())
320 .ok_or(ContractFileParseError::NoHeader)?;
321 let header = unsafe { ContractFileHeader::read_unaligned_unchecked(header_bytes) };
323
324 if header.magic != CONTRACT_FILE_MAGIC {
325 return Err(ContractFileParseError::WrongMagicBytes);
326 }
327
328 if header.read_only_section_file_size > header.read_only_section_memory_size {
329 return Err(ContractFileParseError::InvalidReadOnlySizes {
330 file_size: header.read_only_section_file_size,
331 memory_size: header.read_only_section_memory_size,
332 });
333 }
334
335 let metadata_bytes = file_bytes
336 .get(header.metadata_offset as usize..)
337 .ok_or(ContractFileParseError::MetadataOutOfRange {
338 offset: header.metadata_offset,
339 size: header.metadata_size,
340 file_size,
341 })?
342 .get(..header.metadata_size as usize)
343 .ok_or(ContractFileParseError::MetadataOutOfRange {
344 offset: header.metadata_offset,
345 size: header.metadata_size,
346 file_size,
347 })?;
348
349 let read_only_padding_size =
350 header.read_only_section_memory_size - header.read_only_section_file_size;
351 let read_only_section_offset = ContractFileHeader::SIZE
352 + u32::from(header.num_methods) * ContractFileMethodMetadata::SIZE;
353 let code_section_offset =
354 read_only_section_offset.saturating_add(header.read_only_section_file_size);
355
356 {
357 let mut contract_file_methods_metadata_iter = {
358 let mut file_contract_metadata_bytes = after_header_bytes;
359
360 iter::repeat_with(move || {
361 let contract_file_method_metadata_bytes = file_contract_metadata_bytes
362 .split_off(..size_of::<ContractFileMethodMetadata>())
363 .ok_or(ContractFileParseError::FileTooSmall {
364 num_methods: header.num_methods,
365 read_only_section_size: header.read_only_section_file_size,
366 file_size,
367 })?;
368 let contract_file_method_metadata = unsafe {
370 ContractFileMethodMetadata::read_unaligned_unchecked(
371 contract_file_method_metadata_bytes,
372 )
373 };
374
375 if (contract_file_method_metadata.offset + contract_file_method_metadata.size)
376 > file_size
377 {
378 return Err(ContractFileParseError::FileTooSmall {
379 num_methods: header.num_methods,
380 read_only_section_size: header.read_only_section_file_size,
381 file_size,
382 });
383 }
384
385 if contract_file_method_metadata.offset < code_section_offset {
386 return Err(ContractFileParseError::MethodOutOfRange {
387 offset: contract_file_method_metadata.offset,
388 code_section_offset,
389 file_size,
390 });
391 }
392
393 Ok(contract_file_method_metadata)
394 })
395 .take(usize::from(header.num_methods))
396 };
397
398 let mut metadata_num_methods = 0;
399 let mut remaining_metadata_bytes = metadata_bytes;
400 let mut metadata_decoder = MetadataDecoder::new(metadata_bytes);
401
402 while let Some(maybe_metadata_item) = metadata_decoder.decode_next() {
403 let metadata_item = maybe_metadata_item?;
404 trace!(?metadata_item, "Decoded metadata item");
405
406 let mut methods_metadata_decoder = metadata_item.into_decoder();
407 loop {
408 let Some(method_metadata_decoder) = methods_metadata_decoder.decode_next()
412 else {
413 break;
414 };
415
416 let before_remaining_bytes = method_metadata_decoder.remaining_metadata_bytes();
417 let (_, method_metadata_item) = method_metadata_decoder.decode_next()?;
418
419 trace!(?method_metadata_item, "Decoded method metadata item");
420 metadata_num_methods += 1;
421
422 let method_metadata_bytes = remaining_metadata_bytes
423 .split_off(
424 ..before_remaining_bytes
425 - methods_metadata_decoder.remaining_metadata_bytes(),
426 )
427 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
428
429 let contract_file_method_metadata = contract_file_methods_metadata_iter
430 .next()
431 .ok_or(ContractFileParseError::InsufficientHeaderMethods {
432 header_num_methods: header.num_methods,
433 metadata_method_index: metadata_num_methods - 1,
434 })??;
435 let address = contract_file_method_metadata.offset - read_only_section_offset
436 + read_only_padding_size;
437
438 if !address.is_multiple_of(size_of::<u16>() as u32) {
439 return Err(ContractFileParseError::MethodUnaligned {
440 file_offset: contract_file_method_metadata.offset,
441 memory_address: address,
442 });
443 }
444
445 contract_method(ContractFileMethod {
446 address,
447 method_metadata_item,
448 method_metadata_bytes,
449 })?;
450 }
451 }
452
453 if metadata_num_methods != header.num_methods {
454 return Err(ContractFileParseError::MetadataNumMethodsMismatch {
455 header_num_methods: header.num_methods,
456 metadata_num_methods,
457 });
458 }
459 }
460
461 if code_section_offset >= file_size {
462 return Err(ContractFileParseError::FileTooSmall {
463 num_methods: header.num_methods,
464 read_only_section_size: header.read_only_section_file_size,
465 file_size,
466 });
467 }
468
469 if header.host_call_fn_offset != 0 {
470 if header.host_call_fn_offset >= file_size
471 || header.host_call_fn_offset < code_section_offset
472 {
473 return Err(ContractFileParseError::HostCallFnOutOfRange {
474 offset: header.host_call_fn_offset,
475 code_section_offset,
476 file_size,
477 });
478 }
479
480 let instruction_bytes = file_bytes
481 .get(header.host_call_fn_offset as usize..)
482 .ok_or(ContractFileParseError::HostCallFnOutOfRange {
483 offset: header.host_call_fn_offset,
484 code_section_offset,
485 file_size,
486 })?;
487 let instruction = if let Some(instruction_bytes) =
489 unsafe { <Unaligned<u32>>::from_bytes(instruction_bytes) }
490 {
491 instruction_bytes.as_inner()
492 } else {
493 let Some(instruction_bytes) =
495 (unsafe { <Unaligned<u16>>::from_bytes(instruction_bytes) })
496 else {
497 return Err(ContractFileParseError::HostCallFnOutOfRange {
498 offset: header.host_call_fn_offset,
499 code_section_offset,
500 file_size,
501 });
502 };
503
504 u32::from(instruction_bytes.as_inner())
505 };
506
507 let instruction = ContractInstruction::try_decode(instruction)
508 .ok_or(ContractFileParseError::InvalidInstruction { instruction })?;
509
510 #[expect(
513 clippy::rest_pattern_accessible_field,
514 reason = "Do not need other fields"
515 )]
516 let matches_expected_pattern = match instruction {
517 ContractInstruction::Jal { rd, .. } => rd == Register::ZERO,
518 ContractInstruction::CJ { .. } => true,
519 _ => false,
520 };
521
522 if !matches_expected_pattern {
523 return Err(ContractFileParseError::InvalidHostCallFnPattern { instruction });
524 }
525
526 let address =
527 header.host_call_fn_offset - read_only_section_offset + read_only_padding_size;
528
529 if !address.is_multiple_of(size_of::<u16>() as u32) {
530 return Err(ContractFileParseError::HostCallFnUnaligned {
531 file_offset: header.host_call_fn_offset,
532 memory_address: address,
533 });
534 }
535 }
536
537 {
539 let mut offset = code_section_offset as usize;
540
541 let mut instruction = ContractInstruction::Unimp {
542 rs1: Register::ZERO,
543 rs2: Register::ZERO,
544 };
545 while offset < file_bytes.len() {
546 let remaining = &file_bytes[offset..];
547
548 let instruction_word = if remaining.len() >= size_of::<u32>() {
549 u32::from_le_bytes([remaining[0], remaining[1], remaining[2], remaining[3]])
550 } else if remaining.len() >= size_of::<u16>() {
551 u32::from_le_bytes([remaining[0], remaining[1], 0, 0])
552 } else {
553 return Err(ContractFileParseError::UnexpectedTrailingCodeBytes {
555 num_bytes: remaining.len(),
556 });
557 };
558
559 instruction = ContractInstruction::try_decode(instruction_word).ok_or(
560 ContractFileParseError::InvalidInstruction {
561 instruction: instruction_word,
562 },
563 )?;
564
565 offset += usize::from(instruction.size());
566 }
567
568 if !instruction.is_jump() {
569 return Err(ContractFileParseError::LastInstructionMustBeJump { instruction });
570 }
571 }
572
573 Ok(Self {
574 read_only_section_file_size: header.read_only_section_file_size,
575 read_only_section_memory_size: header.read_only_section_memory_size,
576 num_methods: header.num_methods,
577 bytes: file_bytes,
578 })
579 }
580
581 pub unsafe fn parse_unchecked(file_bytes: &'a [u8]) -> Self {
589 let header = unsafe { ContractFileHeader::read_unaligned_unchecked(file_bytes) };
591
592 Self {
593 read_only_section_file_size: header.read_only_section_file_size,
594 read_only_section_memory_size: header.read_only_section_memory_size,
595 num_methods: header.num_methods,
596 bytes: file_bytes,
597 }
598 }
599
600 #[inline(always)]
602 pub fn header(&self) -> ContractFileHeader {
603 unsafe { ContractFileHeader::read_unaligned_unchecked(self.bytes) }
605 }
606
607 #[inline]
609 pub fn metadata_bytes(&self) -> &[u8] {
610 let header = self.header();
611 unsafe {
613 self.bytes
614 .get_unchecked(header.metadata_offset as usize..)
615 .get_unchecked(..header.metadata_size as usize)
616 }
617 }
618
619 #[inline]
621 pub fn contract_memory_size(&self) -> u32 {
622 let read_only_section_offset = ContractFileHeader::SIZE
623 + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
624 let read_only_padding_size =
625 self.read_only_section_memory_size - self.read_only_section_file_size;
626 self.bytes.len() as u32 - read_only_section_offset + read_only_padding_size
627 }
628
629 #[must_use = "Must check that contract memory was large enough"]
634 pub fn initialize_contract_memory(&self, mut contract_memory: BorrowedCursor<'_, u8>) -> bool {
635 let contract_memory_input_size = contract_memory.capacity();
636 let read_only_section_offset = ContractFileHeader::SIZE
637 + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
638 let read_only_padding_size =
639 self.read_only_section_memory_size - self.read_only_section_file_size;
640
641 let source_bytes = unsafe {
643 self.bytes
644 .get_unchecked(read_only_section_offset as usize..)
645 };
646
647 let mut contract_memory_bytes = unsafe { contract_memory.as_mut() };
649
650 if contract_memory_bytes.len() == source_bytes.len() {
652 contract_memory_bytes.write_copy_of_slice(source_bytes);
653 unsafe {
655 contract_memory.advance(contract_memory_input_size);
656 }
657 return true;
658 }
659
660 let Some(read_only_file_target_bytes) =
661 contract_memory_bytes.split_off_mut(..self.read_only_section_file_size as usize)
662 else {
663 trace!(
664 %contract_memory_input_size,
665 contract_memory_size = %self.contract_memory_size(),
666 read_only_section_file_size = self.read_only_section_file_size,
667 "Not enough bytes to write read-only section from the file"
668 );
669
670 return false;
671 };
672
673 let (read_only_file_source_bytes, code_source_bytes) =
675 unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
676 read_only_file_target_bytes.write_copy_of_slice(read_only_file_source_bytes);
678
679 let Some(read_only_padding_bytes) =
680 contract_memory_bytes.split_off_mut(..read_only_padding_size as usize)
681 else {
682 trace!(
683 %contract_memory_input_size,
684 contract_memory_size = %self.contract_memory_size(),
685 read_only_section_file_size = self.read_only_section_file_size,
686 read_only_section_memory_size = self.read_only_section_memory_size,
687 %read_only_padding_size,
688 "Not enough bytes to write read-only padding section"
689 );
690
691 return false;
692 };
693
694 read_only_padding_bytes.write_filled(0);
696
697 if code_source_bytes.len() != contract_memory_bytes.len() {
698 trace!(
699 %contract_memory_input_size,
700 contract_memory_size = %self.contract_memory_size(),
701 read_only_section_file_size = self.read_only_section_file_size,
702 read_only_section_memory_size = self.read_only_section_memory_size,
703 %read_only_padding_size,
704 code_size = %code_source_bytes.len(),
705 "Not enough bytes to write code section from the file"
706 );
707
708 return false;
709 }
710
711 contract_memory_bytes.write_copy_of_slice(code_source_bytes);
712 unsafe {
714 contract_memory.advance(contract_memory_input_size);
715 }
716
717 true
718 }
719
720 pub fn get_code(&self) -> &[u8] {
722 let read_only_section_offset = ContractFileHeader::SIZE
723 + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
724
725 let source_bytes = unsafe {
727 self.bytes
728 .get_unchecked(read_only_section_offset as usize..)
729 };
730
731 let (_read_only_file_source_bytes, code_source_bytes) =
733 unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
734
735 code_source_bytes
736 }
737
738 pub fn iterate_methods(
740 &self,
741 ) -> impl ExactSizeIterator<Item = ContractFileMethod<'_>> + TrustedLen {
742 let metadata_bytes = self.metadata_bytes();
743
744 #[ouroboros::self_referencing]
745 struct MethodsMetadataIterState<'metadata> {
746 metadata_decoder: MetadataDecoder<'metadata>,
747 #[borrows(mut metadata_decoder)]
748 #[covariant]
749 methods_metadata_decoder: Option<MethodsMetadataDecoder<'this, 'metadata>>,
750 }
751
752 let metadata_decoder = MetadataDecoder::new(metadata_bytes);
753
754 let mut methods_metadata_state =
755 MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
756 metadata_decoder
757 .decode_next()
758 .and_then(Result::ok)
759 .map(MetadataItem::into_decoder)
760 });
761
762 let mut metadata_methods_iter = iter::from_fn(move || {
763 loop {
764 let maybe_next_item = methods_metadata_state.with_methods_metadata_decoder_mut(
765 |maybe_methods_metadata_decoder| {
766 let methods_metadata_decoder = maybe_methods_metadata_decoder.as_mut()?;
767 let method_metadata_decoder = methods_metadata_decoder.decode_next()?;
768
769 let before_remaining_bytes =
770 method_metadata_decoder.remaining_metadata_bytes();
771
772 let (_, method_metadata_item) = method_metadata_decoder
773 .decode_next()
774 .expect("Input is valid according to function contract; qed");
775
776 let method_metadata_bytes = unsafe {
778 metadata_bytes
779 .get_unchecked(metadata_bytes.len() - before_remaining_bytes..)
780 .get_unchecked(
781 ..before_remaining_bytes
782 - methods_metadata_decoder.remaining_metadata_bytes(),
783 )
784 };
785
786 Some((method_metadata_item, method_metadata_bytes))
787 },
788 );
789
790 if let Some(next_item) = maybe_next_item {
791 return Some(next_item);
792 }
793
794 replace_with_or_abort(&mut methods_metadata_state, |methods_metadata_state| {
796 let metadata_decoder = methods_metadata_state.into_heads().metadata_decoder;
797 MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
798 metadata_decoder
799 .decode_next()
800 .and_then(Result::ok)
801 .map(MetadataItem::into_decoder)
802 })
803 });
804
805 if methods_metadata_state
806 .borrow_methods_metadata_decoder()
807 .is_none()
808 {
809 return None;
810 }
811 }
812 });
813
814 let read_only_padding_size =
815 self.read_only_section_memory_size - self.read_only_section_file_size;
816 let contract_file_methods_metadata_bytes =
818 unsafe { self.bytes.get_unchecked(size_of::<ContractFileHeader>()..) };
819
820 (0..self.num_methods).map(move |method_index| {
821 let contract_file_method_metadata_bytes = unsafe {
823 contract_file_methods_metadata_bytes
824 .get_unchecked(
825 method_index as usize * size_of::<ContractFileMethodMetadata>()..,
826 )
827 .get_unchecked(..size_of::<ContractFileMethodMetadata>())
828 };
829 let contract_file_method_metadata = unsafe {
831 ContractFileMethodMetadata::read_unaligned_unchecked(
832 contract_file_method_metadata_bytes,
833 )
834 };
835
836 let (method_metadata_item, method_metadata_bytes) = metadata_methods_iter
837 .next()
838 .expect("Protected internal invariant checked in constructor; qed");
839
840 ContractFileMethod {
841 address: contract_file_method_metadata.offset + read_only_padding_size,
842 method_metadata_item,
843 method_metadata_bytes,
844 }
845 })
846 }
847}