Skip to main content

ab_contract_file/
lib.rs

1//! Utilities for working with contract files.
2//!
3//! # File layout
4//!
5//! Internally, a contract file contains the following sections in specified order:
6//! * a header: [`ContractFileHeader`], which allows interpreting the rest of file contents
7//! * metadata about callable methods: [`ContractFileMethodMetadata`] for each method, which allows
8//!   calling methods later
9//! * read-only data section: contains contract metadata among other things, allowing to decode
10//!   names and ABI of the methods mentioned above, and the number of methods in this metadata must
11//!   match the number of methods in the header
12//! * code section: contains only valid/supported RISC-V instructions or 16-bit zero padding, always
13//!   ending with some kind of jump instruction
14//!
15//! This file is created from an ELF source file and can, technically, be converted back to it. Note
16//! that due to the intentional lack of the `.bss` section equivalent and many other features, only
17//! simple RISC-V ELF shared library files can be converted into the contract file. Supporting more
18//! complex capabilities would be much more complex and error-prone.
19//!
20//! ELF file is expected to have at most a single export for host calls, whose address is stored in
21//! the header and jumps to that address are intercepted by the runtime.
22//!
23//! The format is designed to be very compact, easy to understand and use, and be such that it can
24//! be trivially loaded into a normal RISC-V process for debugging purposes using traditional tools
25//! like gdb.
26//!
27//! `ab-contracts-tooling` crate exists that can build and convert contracts to this format both
28//! programmatically and using CLI interface.
29
30#![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    derive_const,
41    explicit_tail_calls,
42    fn_align,
43    maybe_uninit_fill,
44    signed_bigint_helpers,
45    trusted_len,
46    try_blocks
47)]
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
66/// Magic bytes at the beginning of the file
67pub const CONTRACT_FILE_MAGIC: [u8; 4] = *b"ABC0";
68
69// Ensure expected size of the instruction enum
70const {
71    assert!(size_of::<ContractInstruction>() == 8);
72}
73
74/// Header of the contract file
75#[derive(Debug, Clone, Copy, PartialEq, Eq, TrivialType)]
76#[repr(C)]
77pub struct ContractFileHeader {
78    /// Always [`CONTRACT_FILE_MAGIC`]
79    pub magic: [u8; 4],
80    /// Size of the read-only section in bytes as stored in the file
81    pub read_only_section_file_size: u32,
82    /// Size of the read-only section in bytes as will be written to memory during execution.
83    ///
84    /// If larger than `read_only_section_file_size`, then zeroed padding needs to be added.
85    pub read_only_section_memory_size: u32,
86    /// Offset of the metadata section in bytes relative to the start of the file
87    pub metadata_offset: u32,
88    /// Size of the metadata section in bytes
89    pub metadata_size: u16,
90    /// Number of methods in the contract
91    pub num_methods: u16,
92    /// Host call function offset in bytes relative to the start of the file.
93    ///
94    /// `0` means no host call.
95    pub host_call_fn_offset: u32,
96}
97
98/// Metadata about each method of the contract that can be called from the outside
99#[derive(Debug, Clone, Copy, PartialEq, Eq, TrivialType)]
100#[repr(C)]
101pub struct ContractFileMethodMetadata {
102    /// Offset of the method code in bytes relative to the start of the file
103    pub offset: u32,
104    /// Size of the method code in bytes
105    pub size: u32,
106}
107
108#[derive(Debug, Copy, Clone)]
109pub struct ContractFileMethod<'a> {
110    /// Address of the method in the contract memory
111    pub address: u32,
112    /// Method metadata item
113    pub method_metadata_item: MethodMetadataItem<'a>,
114    /// Method metadata bytes.
115    ///
116    /// Can be used to compute [`MethodFingerprint`].
117    ///
118    /// [`MethodFingerprint`]: ab_contracts_common::method::MethodFingerprint
119    pub method_metadata_bytes: &'a [u8],
120}
121
122/// Error for [`ContractFile::parse()`]
123#[derive(Debug, thiserror::Error)]
124pub enum ContractFileParseError {
125    /// The file is too large, must fit into `u32`
126    #[error("The file is too large, must fit into `u32`: {file_size} bytes")]
127    FileTooLarge {
128        /// Actual file size
129        file_size: usize,
130    },
131    /// The file does not have a header (not enough bytes)
132    #[error("The file does not have a header (not enough bytes)")]
133    NoHeader,
134    /// The magic bytes in the header are incorrect
135    #[error("The magic bytes in the header are incorrect")]
136    WrongMagicBytes,
137    /// The metadata section is out of bounds of the file
138    #[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 of the metadata section in bytes relative to the start of the file
144        offset: u32,
145        /// Size of the metadata section in bytes
146        size: u16,
147        /// Size of the file in bytes
148        file_size: u32,
149    },
150    /// Failed to decode metadata item
151    #[error("Failed to decode metadata item")]
152    MetadataDecoding,
153    /// The file is too small
154    #[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        /// Number of methods in the contract
160        num_methods: u16,
161        /// Size of the read-only section in bytes as stored in the file
162        read_only_section_size: u32,
163        /// Size of the file in bytes
164        file_size: u32,
165    },
166    /// Method is unaligned
167    #[error("Method is unaligned: file offset {file_offset}, memory address {memory_address}")]
168    MethodUnaligned {
169        /// Offset of the method in bytes relative to the start of the file
170        file_offset: u32,
171        /// Address of the method in bytes relative to the beginning of the initialized memory
172        memory_address: u32,
173    },
174    /// Method offset is out of bounds of the file
175    #[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 of the method in bytes relative to the start of the file
181        offset: u32,
182        /// Offset of the code section in bytes relative to the start of the file
183        code_section_offset: u32,
184        /// Size of the file in bytes
185        file_size: u32,
186    },
187    /// Host call function is unaligned
188    #[error(
189        "Host call function is unaligned: file offset {file_offset}, memory address \
190        {memory_address}"
191    )]
192    HostCallFnUnaligned {
193        /// Offset of the method in bytes relative to the start of the file
194        file_offset: u32,
195        /// Address of the method in bytes relative to the beginning of the initialized memory
196        memory_address: u32,
197    },
198    /// The host call function offset is out of bounds of the file
199    #[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 of the host call function in bytes relative to the start of the file
205        offset: u32,
206        /// Offset of the code section in bytes relative to the start of the file
207        code_section_offset: u32,
208        /// Size of the file in bytes
209        file_size: u32,
210    },
211    /// Host call function doesn't have `jal` tailcall instruction
212    #[error("The host call function doesn't have jal tailcall instruction: {instruction}")]
213    InvalidHostCallFnPattern {
214        /// Instruction of the host call function
215        instruction: ContractInstruction,
216    },
217    /// The read-only section file size is larger than the memory size
218    #[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        /// Size of the read-only section in bytes as stored in the file
224        file_size: u32,
225        /// Size of the read-only section in bytes as will be written to memory during execution
226        memory_size: u32,
227    },
228    /// There are not enough methods in the header to match the number of methods in the actual
229    /// metadata
230    #[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        /// Number of methods in the header
237        header_num_methods: u16,
238        /// Index of the method in the actual metadata that is missing from the header
239        metadata_method_index: u16,
240    },
241    /// The number of methods in the header does not match the number of methods in the actual
242    /// metadata
243    #[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        /// Number of methods in the header
249        header_num_methods: u16,
250        /// Number of methods in the actual metadata
251        metadata_num_methods: u16,
252    },
253    /// Invalid instruction encountered while parsing the code section
254    #[error("Invalid instruction encountered while parsing the code section: {instruction:#x}")]
255    InvalidInstruction {
256        /// Instruction
257        instruction: u32,
258    },
259    /// The code section is empty
260    #[error("The code section is empty")]
261    CodeEmpty,
262    /// Unexpected trailing code bytes encountered while parsing the code section
263    #[error(
264        "Unexpected trailing code bytes encountered while parsing the code section: {num_bytes} \
265        trailing bytes"
266    )]
267    UnexpectedTrailingCodeBytes {
268        /// Number of trailing bytes encountered
269        num_bytes: usize,
270    },
271    /// The last instruction in the code section must be a jump instruction
272    #[error("The last instruction in the code section must be a jump instruction: {instruction}")]
273    LastInstructionMustBeJump {
274        /// Instruction that is expected to be a jump instruction
275        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/// A container for a parsed contract file
287#[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    /// Parse file bytes and verify that internal invariants are valid.
297    ///
298    /// `contract_method` argument is an optional callback called for each method in the contract
299    /// file with its method address in the contract memory, metadata item, and corresponding
300    /// metadata bytes. This can be used to collect available methods during parsing and avoid extra
301    /// iteration later using [`Self::iterate_methods()`] to compute [`MethodFingerprint`], etc.
302    ///
303    /// [`MethodFingerprint`]: ab_contracts_common::method::MethodFingerprint
304    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        // SAFETY: Size is correct, content is checked below
320        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                    // SAFETY: The number of bytes is correct, content is checked below
367                    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                    // This is used instead of `while let Some(method_metadata_decoder)` because the
407                    // compiler is not smart enough to understand where `method_metadata_decoder` is
408                    // dropped
409                    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            // SAFETY: All bit patterns are valid for u32
486            let instruction = if let Some(instruction_bytes) =
487                unsafe { <Unaligned<u32>>::from_bytes(instruction_bytes) }
488            {
489                instruction_bytes.as_inner()
490            } else {
491                // SAFETY: All bit patterns are valid for u16
492                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            // The instruction is an unconditional relative jump:
509            //   jal x0, offset
510            #[expect(
511                clippy::rest_pattern_accessible_field,
512                reason = "Do not need other fields"
513            )]
514            let matches_expected_pattern = match instruction {
515                ContractInstruction::Jal { rd, .. } => rd == Register::ZERO,
516                ContractInstruction::CJ { .. } => true,
517                _ => false,
518            };
519
520            if !matches_expected_pattern {
521                return Err(ContractFileParseError::InvalidHostCallFnPattern { instruction });
522            }
523
524            let address =
525                header.host_call_fn_offset - read_only_section_offset + read_only_padding_size;
526
527            if !address.is_multiple_of(size_of::<u16>() as u32) {
528                return Err(ContractFileParseError::HostCallFnUnaligned {
529                    file_offset: header.host_call_fn_offset,
530                    memory_address: address,
531                });
532            }
533        }
534
535        // Ensure code only consists of expected instructions
536        {
537            let mut offset = code_section_offset as usize;
538
539            let mut instruction = ContractInstruction::Unimp {
540                rs1: Register::ZERO,
541                rs2: Register::ZERO,
542            };
543            while offset < file_bytes.len() {
544                let remaining = &file_bytes[offset..];
545
546                let instruction_word = if remaining.len() >= size_of::<u32>() {
547                    u32::from_le_bytes([remaining[0], remaining[1], remaining[2], remaining[3]])
548                } else if remaining.len() >= size_of::<u16>() {
549                    u32::from_le_bytes([remaining[0], remaining[1], 0, 0])
550                } else {
551                    // Need at least 2 bytes to read a compressed instruction
552                    return Err(ContractFileParseError::UnexpectedTrailingCodeBytes {
553                        num_bytes: remaining.len(),
554                    });
555                };
556
557                instruction = ContractInstruction::try_decode(instruction_word).ok_or(
558                    ContractFileParseError::InvalidInstruction {
559                        instruction: instruction_word,
560                    },
561                )?;
562
563                offset += usize::from(instruction.size());
564            }
565
566            if !instruction.is_jump() {
567                return Err(ContractFileParseError::LastInstructionMustBeJump { instruction });
568            }
569        }
570
571        Ok(Self {
572            read_only_section_file_size: header.read_only_section_file_size,
573            read_only_section_memory_size: header.read_only_section_memory_size,
574            num_methods: header.num_methods,
575            bytes: file_bytes,
576        })
577    }
578
579    /// Similar to [`ContractFile::parse()`] but does not verify internal invariants and assumes the
580    /// input is valid.
581    ///
582    /// This method is more efficient and does no checks that [`ContractFile::parse()`] does.
583    ///
584    /// # Safety
585    /// Must be a valid input, for example, previously verified using [`ContractFile::parse()`].
586    pub unsafe fn parse_unchecked(file_bytes: &'a [u8]) -> Self {
587        // SAFETY: Unchecked method assumed input is correct
588        let header = unsafe { ContractFileHeader::read_unaligned_unchecked(file_bytes) };
589
590        Self {
591            read_only_section_file_size: header.read_only_section_file_size,
592            read_only_section_memory_size: header.read_only_section_memory_size,
593            num_methods: header.num_methods,
594            bytes: file_bytes,
595        }
596    }
597
598    /// Get file header
599    #[inline(always)]
600    pub fn header(&self) -> ContractFileHeader {
601        // SAFETY: Protected internal invariant checked in constructor
602        unsafe { ContractFileHeader::read_unaligned_unchecked(self.bytes) }
603    }
604
605    /// Metadata stored in the file
606    #[inline]
607    pub fn metadata_bytes(&self) -> &[u8] {
608        let header = self.header();
609        // SAFETY: Protected internal invariant checked in constructor
610        unsafe {
611            self.bytes
612                .get_unchecked(header.metadata_offset as usize..)
613                .get_unchecked(..header.metadata_size as usize)
614        }
615    }
616
617    /// Memory allocation required for the contract
618    #[inline]
619    pub fn contract_memory_size(&self) -> u32 {
620        let read_only_section_offset = ContractFileHeader::SIZE
621            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
622        let read_only_padding_size =
623            self.read_only_section_memory_size - self.read_only_section_file_size;
624        self.bytes.len() as u32 - read_only_section_offset + read_only_padding_size
625    }
626
627    /// Initialize contract memory with file contents.
628    ///
629    /// Use [`Self::contract_memory_size()`] to identify the exact necessary amount of memory.
630    #[must_use = "Must check that contract memory was large enough"]
631    pub fn initialize_contract_memory(&self, mut contract_memory: &mut [MaybeUninit<u8>]) -> bool {
632        let contract_memory_input_size = contract_memory.len();
633        let read_only_section_offset = ContractFileHeader::SIZE
634            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
635        let read_only_padding_size =
636            self.read_only_section_memory_size - self.read_only_section_file_size;
637
638        // SAFETY: Protected internal invariant checked in constructor
639        let source_bytes = unsafe {
640            self.bytes
641                .get_unchecked(read_only_section_offset as usize..)
642        };
643
644        // Simple case: memory exactly matches the file-backed sections
645        if contract_memory.len() == source_bytes.len() {
646            contract_memory.write_copy_of_slice(source_bytes);
647            return true;
648        }
649
650        let Some(read_only_file_target_bytes) =
651            contract_memory.split_off_mut(..self.read_only_section_file_size as usize)
652        else {
653            trace!(
654                %contract_memory_input_size,
655                contract_memory_size = %self.contract_memory_size(),
656                read_only_section_file_size = self.read_only_section_file_size,
657                "Not enough bytes to write read-only section from the file"
658            );
659
660            return false;
661        };
662
663        // SAFETY: Protected internal invariant checked in constructor
664        let (read_only_file_source_bytes, code_source_bytes) =
665            unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
666        // Write read-only data
667        read_only_file_target_bytes.write_copy_of_slice(read_only_file_source_bytes);
668
669        let Some(read_only_padding_bytes) =
670            contract_memory.split_off_mut(..read_only_padding_size as usize)
671        else {
672            trace!(
673                %contract_memory_input_size,
674                contract_memory_size = %self.contract_memory_size(),
675                read_only_section_file_size = self.read_only_section_file_size,
676                read_only_section_memory_size = self.read_only_section_memory_size,
677                %read_only_padding_size,
678                "Not enough bytes to write read-only padding section"
679            );
680
681            return false;
682        };
683
684        // Write read-only padding
685        read_only_padding_bytes.write_filled(0);
686
687        if code_source_bytes.len() != contract_memory.len() {
688            trace!(
689                %contract_memory_input_size,
690                contract_memory_size = %self.contract_memory_size(),
691                read_only_section_file_size = self.read_only_section_file_size,
692                read_only_section_memory_size = self.read_only_section_memory_size,
693                %read_only_padding_size,
694                code_size = %code_source_bytes.len(),
695                "Not enough bytes to write code section from the file"
696            );
697
698            return false;
699        }
700
701        contract_memory.write_copy_of_slice(code_source_bytes);
702
703        true
704    }
705
706    /// Get the complete code section with instructions
707    pub fn get_code(&self) -> &[u8] {
708        let read_only_section_offset = ContractFileHeader::SIZE
709            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
710
711        // SAFETY: Protected internal invariant checked in constructor
712        let source_bytes = unsafe {
713            self.bytes
714                .get_unchecked(read_only_section_offset as usize..)
715        };
716
717        // SAFETY: Protected internal invariant checked in constructor
718        let (_read_only_file_source_bytes, code_source_bytes) =
719            unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
720
721        code_source_bytes
722    }
723
724    /// Iterate over all methods in the contract
725    pub fn iterate_methods(
726        &self,
727    ) -> impl ExactSizeIterator<Item = ContractFileMethod<'_>> + TrustedLen {
728        let metadata_bytes = self.metadata_bytes();
729
730        #[ouroboros::self_referencing]
731        struct MethodsMetadataIterState<'metadata> {
732            metadata_decoder: MetadataDecoder<'metadata>,
733            #[borrows(mut metadata_decoder)]
734            #[covariant]
735            methods_metadata_decoder: Option<MethodsMetadataDecoder<'this, 'metadata>>,
736        }
737
738        let metadata_decoder = MetadataDecoder::new(metadata_bytes);
739
740        let mut methods_metadata_state =
741            MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
742                metadata_decoder
743                    .decode_next()
744                    .and_then(Result::ok)
745                    .map(MetadataItem::into_decoder)
746            });
747
748        let mut metadata_methods_iter = iter::from_fn(move || {
749            loop {
750                let maybe_next_item = methods_metadata_state.with_methods_metadata_decoder_mut(
751                    |maybe_methods_metadata_decoder| {
752                        let methods_metadata_decoder = maybe_methods_metadata_decoder.as_mut()?;
753                        let method_metadata_decoder = methods_metadata_decoder.decode_next()?;
754
755                        let before_remaining_bytes =
756                            method_metadata_decoder.remaining_metadata_bytes();
757
758                        let (_, method_metadata_item) = method_metadata_decoder
759                            .decode_next()
760                            .expect("Input is valid according to function contract; qed");
761
762                        // SAFETY: Protected internal invariant checked in constructor
763                        let method_metadata_bytes = unsafe {
764                            metadata_bytes
765                                .get_unchecked(metadata_bytes.len() - before_remaining_bytes..)
766                                .get_unchecked(
767                                    ..before_remaining_bytes
768                                        - methods_metadata_decoder.remaining_metadata_bytes(),
769                                )
770                        };
771
772                        Some((method_metadata_item, method_metadata_bytes))
773                    },
774                );
775
776                if let Some(next_item) = maybe_next_item {
777                    return Some(next_item);
778                }
779
780                // Process methods of the next contract/trait
781                replace_with_or_abort(&mut methods_metadata_state, |methods_metadata_state| {
782                    let metadata_decoder = methods_metadata_state.into_heads().metadata_decoder;
783                    MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
784                        metadata_decoder
785                            .decode_next()
786                            .and_then(Result::ok)
787                            .map(MetadataItem::into_decoder)
788                    })
789                });
790
791                if methods_metadata_state
792                    .borrow_methods_metadata_decoder()
793                    .is_none()
794                {
795                    return None;
796                }
797            }
798        });
799
800        let read_only_padding_size =
801            self.read_only_section_memory_size - self.read_only_section_file_size;
802        // SAFETY: Protected internal invariant checked in constructor
803        let contract_file_methods_metadata_bytes =
804            unsafe { self.bytes.get_unchecked(size_of::<ContractFileHeader>()..) };
805
806        (0..self.num_methods).map(move |method_index| {
807            // SAFETY: Protected internal invariant checked in constructor
808            let contract_file_method_metadata_bytes = unsafe {
809                contract_file_methods_metadata_bytes
810                    .get_unchecked(
811                        method_index as usize * size_of::<ContractFileMethodMetadata>()..,
812                    )
813                    .get_unchecked(..size_of::<ContractFileMethodMetadata>())
814            };
815            // SAFETY: Protected internal invariant checked in constructor
816            let contract_file_method_metadata = unsafe {
817                ContractFileMethodMetadata::read_unaligned_unchecked(
818                    contract_file_method_metadata_bytes,
819                )
820            };
821
822            let (method_metadata_item, method_metadata_bytes) = metadata_methods_iter
823                .next()
824                .expect("Protected internal invariant checked in constructor; qed");
825
826            ContractFileMethod {
827                address: contract_file_method_metadata.offset + read_only_padding_size,
828                method_metadata_item,
829                method_metadata_bytes,
830            }
831        })
832    }
833}