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