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