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