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    core_io,
41    core_io_borrowed_buf,
42    derive_const,
43    explicit_tail_calls,
44    fn_align,
45    maybe_uninit_fill,
46    signed_bigint_helpers,
47    trusted_len,
48    try_blocks
49)]
50#![no_std]
51
52pub mod instruction;
53
54use crate::instruction::ContractInstruction;
55use ab_contracts_common::metadata::decode::{
56    MetadataDecoder, MetadataDecodingError, MetadataItem, MethodMetadataItem,
57    MethodsMetadataDecoder,
58};
59use ab_io_type::trivial_type::TrivialType;
60use ab_io_type::unaligned::Unaligned;
61use ab_riscv_primitives::prelude::*;
62use core::io::BorrowedCursor;
63use core::iter;
64use core::iter::TrustedLen;
65use replace_with::replace_with_or_abort;
66use tracing::{debug, trace};
67
68/// Magic bytes at the beginning of the file
69pub const CONTRACT_FILE_MAGIC: [u8; 4] = *b"ABC0";
70
71// Ensure expected size of the instruction enum
72const {
73    assert!(size_of::<ContractInstruction>() == 8);
74}
75
76/// Header of the contract file
77#[derive(Debug, Clone, Copy, PartialEq, Eq, TrivialType)]
78#[repr(C)]
79pub struct ContractFileHeader {
80    /// Always [`CONTRACT_FILE_MAGIC`]
81    pub magic: [u8; 4],
82    /// Size of the read-only section in bytes as stored in the file
83    pub read_only_section_file_size: u32,
84    /// Size of the read-only section in bytes as will be written to memory during execution.
85    ///
86    /// If larger than `read_only_section_file_size`, then zeroed padding needs to be added.
87    pub read_only_section_memory_size: u32,
88    /// Offset of the metadata section in bytes relative to the start of the file
89    pub metadata_offset: u32,
90    /// Size of the metadata section in bytes
91    pub metadata_size: u16,
92    /// Number of methods in the contract
93    pub num_methods: u16,
94    /// Host call function offset in bytes relative to the start of the file.
95    ///
96    /// `0` means no host call.
97    pub host_call_fn_offset: u32,
98}
99
100/// Metadata about each method of the contract that can be called from the outside
101#[derive(Debug, Clone, Copy, PartialEq, Eq, TrivialType)]
102#[repr(C)]
103pub struct ContractFileMethodMetadata {
104    /// Offset of the method code in bytes relative to the start of the file
105    pub offset: u32,
106    /// Size of the method code in bytes
107    pub size: u32,
108}
109
110#[derive(Debug, Copy, Clone)]
111pub struct ContractFileMethod<'a> {
112    /// Address of the method in the contract memory
113    pub address: u32,
114    /// Method metadata item
115    pub method_metadata_item: MethodMetadataItem<'a>,
116    /// Method metadata bytes.
117    ///
118    /// Can be used to compute [`MethodFingerprint`].
119    ///
120    /// [`MethodFingerprint`]: ab_contracts_common::method::MethodFingerprint
121    pub method_metadata_bytes: &'a [u8],
122}
123
124/// Error for [`ContractFile::parse()`]
125#[derive(Debug, thiserror::Error)]
126pub enum ContractFileParseError {
127    /// The file is too large, must fit into `u32`
128    #[error("The file is too large, must fit into `u32`: {file_size} bytes")]
129    FileTooLarge {
130        /// Actual file size
131        file_size: usize,
132    },
133    /// The file does not have a header (not enough bytes)
134    #[error("The file does not have a header (not enough bytes)")]
135    NoHeader,
136    /// The magic bytes in the header are incorrect
137    #[error("The magic bytes in the header are incorrect")]
138    WrongMagicBytes,
139    /// The metadata section is out of bounds of the file
140    #[error(
141        "The metadata section is out of bounds of the file: offset {offset}, size {size}, file \
142        size {file_size}"
143    )]
144    MetadataOutOfRange {
145        /// Offset of the metadata section in bytes relative to the start of the file
146        offset: u32,
147        /// Size of the metadata section in bytes
148        size: u16,
149        /// Size of the file in bytes
150        file_size: u32,
151    },
152    /// Failed to decode metadata item
153    #[error("Failed to decode metadata item")]
154    MetadataDecoding,
155    /// The file is too small
156    #[error(
157        "The file is too small: num_methods {num_methods}, read_only_section_size \
158        {read_only_section_size}, file_size {file_size}"
159    )]
160    FileTooSmall {
161        /// Number of methods in the contract
162        num_methods: u16,
163        /// Size of the read-only section in bytes as stored in the file
164        read_only_section_size: u32,
165        /// Size of the file in bytes
166        file_size: u32,
167    },
168    /// Method is unaligned
169    #[error("Method is unaligned: file offset {file_offset}, memory address {memory_address}")]
170    MethodUnaligned {
171        /// Offset of the method in bytes relative to the start of the file
172        file_offset: u32,
173        /// Address of the method in bytes relative to the beginning of the initialized memory
174        memory_address: u32,
175    },
176    /// Method offset is out of bounds of the file
177    #[error(
178        "Method offset is out of bounds of the file: offset {offset}, code section \
179        offset {code_section_offset} file_size {file_size}"
180    )]
181    MethodOutOfRange {
182        /// Offset of the method in bytes relative to the start of the file
183        offset: u32,
184        /// Offset of the code section in bytes relative to the start of the file
185        code_section_offset: u32,
186        /// Size of the file in bytes
187        file_size: u32,
188    },
189    /// Host call function is unaligned
190    #[error(
191        "Host call function is unaligned: file offset {file_offset}, memory address \
192        {memory_address}"
193    )]
194    HostCallFnUnaligned {
195        /// Offset of the method in bytes relative to the start of the file
196        file_offset: u32,
197        /// Address of the method in bytes relative to the beginning of the initialized memory
198        memory_address: u32,
199    },
200    /// The host call function offset is out of bounds of the file
201    #[error(
202        "The host call function offset is out of bounds of the file: offset {offset}, code section \
203        offset {code_section_offset} file_size {file_size}"
204    )]
205    HostCallFnOutOfRange {
206        /// Offset of the host call function in bytes relative to the start of the file
207        offset: u32,
208        /// Offset of the code section in bytes relative to the start of the file
209        code_section_offset: u32,
210        /// Size of the file in bytes
211        file_size: u32,
212    },
213    /// Host call function doesn't have `jal` tailcall instruction
214    #[error("The host call function doesn't have jal tailcall instruction: {instruction}")]
215    InvalidHostCallFnPattern {
216        /// Instruction of the host call function
217        instruction: ContractInstruction,
218    },
219    /// The read-only section file size is larger than the memory size
220    #[error(
221        "The read-only section file size is larger than the memory size: file_size {file_size}, \
222        memory_size {memory_size}"
223    )]
224    InvalidReadOnlySizes {
225        /// Size of the read-only section in bytes as stored in the file
226        file_size: u32,
227        /// Size of the read-only section in bytes as will be written to memory during execution
228        memory_size: u32,
229    },
230    /// There are not enough methods in the header to match the number of methods in the actual
231    /// metadata
232    #[error(
233        "There are not enough methods in the header to match the number of methods in the actual \
234        metadata: header_num_methods {header_num_methods}, metadata_method_index \
235        {metadata_method_index}"
236    )]
237    InsufficientHeaderMethods {
238        /// Number of methods in the header
239        header_num_methods: u16,
240        /// Index of the method in the actual metadata that is missing from the header
241        metadata_method_index: u16,
242    },
243    /// The number of methods in the header does not match the number of methods in the actual
244    /// metadata
245    #[error(
246        "The number of methods in the header {header_num_methods} does not match the number of \
247        methods in the actual metadata {metadata_num_methods}"
248    )]
249    MetadataNumMethodsMismatch {
250        /// Number of methods in the header
251        header_num_methods: u16,
252        /// Number of methods in the actual metadata
253        metadata_num_methods: u16,
254    },
255    /// Invalid instruction encountered while parsing the code section
256    #[error("Invalid instruction encountered while parsing the code section: {instruction:#x}")]
257    InvalidInstruction {
258        /// Instruction
259        instruction: u32,
260    },
261    /// The code section is empty
262    #[error("The code section is empty")]
263    CodeEmpty,
264    /// Unexpected trailing code bytes encountered while parsing the code section
265    #[error(
266        "Unexpected trailing code bytes encountered while parsing the code section: {num_bytes} \
267        trailing bytes"
268    )]
269    UnexpectedTrailingCodeBytes {
270        /// Number of trailing bytes encountered
271        num_bytes: usize,
272    },
273    /// The last instruction in the code section must be a jump instruction
274    #[error("The last instruction in the code section must be a jump instruction: {instruction}")]
275    LastInstructionMustBeJump {
276        /// Instruction that is expected to be a jump instruction
277        instruction: ContractInstruction,
278    },
279}
280
281impl From<MetadataDecodingError<'_>> for ContractFileParseError {
282    fn from(error: MetadataDecodingError<'_>) -> Self {
283        debug!(?error, "Failed to decode metadata item");
284        Self::MetadataDecoding
285    }
286}
287
288/// A container for a parsed contract file
289#[derive(Debug)]
290pub struct ContractFile<'a> {
291    read_only_section_file_size: u32,
292    read_only_section_memory_size: u32,
293    num_methods: u16,
294    bytes: &'a [u8],
295}
296
297impl<'a> ContractFile<'a> {
298    /// Parse file bytes and verify that internal invariants are valid.
299    ///
300    /// `contract_method` argument is an optional callback called for each method in the contract
301    /// file with its method address in the contract memory, metadata item, and corresponding
302    /// metadata bytes. This can be used to collect available methods during parsing and avoid extra
303    /// iteration later using [`Self::iterate_methods()`] to compute [`MethodFingerprint`], etc.
304    ///
305    /// [`MethodFingerprint`]: ab_contracts_common::method::MethodFingerprint
306    pub fn parse<CM>(
307        file_bytes: &'a [u8],
308        mut contract_method: CM,
309    ) -> Result<Self, ContractFileParseError>
310    where
311        CM: FnMut(ContractFileMethod<'a>) -> Result<(), ContractFileParseError>,
312    {
313        let file_size = u32::try_from(file_bytes.len()).map_err(|_error| {
314            ContractFileParseError::FileTooLarge {
315                file_size: file_bytes.len(),
316            }
317        })?;
318        let (header_bytes, after_header_bytes) = file_bytes
319            .split_at_checked(size_of::<ContractFileHeader>())
320            .ok_or(ContractFileParseError::NoHeader)?;
321        // SAFETY: Size is correct, content is checked below
322        let header = unsafe { ContractFileHeader::read_unaligned_unchecked(header_bytes) };
323
324        if header.magic != CONTRACT_FILE_MAGIC {
325            return Err(ContractFileParseError::WrongMagicBytes);
326        }
327
328        if header.read_only_section_file_size > header.read_only_section_memory_size {
329            return Err(ContractFileParseError::InvalidReadOnlySizes {
330                file_size: header.read_only_section_file_size,
331                memory_size: header.read_only_section_memory_size,
332            });
333        }
334
335        let metadata_bytes = file_bytes
336            .get(header.metadata_offset as usize..)
337            .ok_or(ContractFileParseError::MetadataOutOfRange {
338                offset: header.metadata_offset,
339                size: header.metadata_size,
340                file_size,
341            })?
342            .get(..header.metadata_size as usize)
343            .ok_or(ContractFileParseError::MetadataOutOfRange {
344                offset: header.metadata_offset,
345                size: header.metadata_size,
346                file_size,
347            })?;
348
349        let read_only_padding_size =
350            header.read_only_section_memory_size - header.read_only_section_file_size;
351        let read_only_section_offset = ContractFileHeader::SIZE
352            + u32::from(header.num_methods) * ContractFileMethodMetadata::SIZE;
353        let code_section_offset =
354            read_only_section_offset.saturating_add(header.read_only_section_file_size);
355
356        {
357            let mut contract_file_methods_metadata_iter = {
358                let mut file_contract_metadata_bytes = after_header_bytes;
359
360                iter::repeat_with(move || {
361                    let contract_file_method_metadata_bytes = file_contract_metadata_bytes
362                        .split_off(..size_of::<ContractFileMethodMetadata>())
363                        .ok_or(ContractFileParseError::FileTooSmall {
364                            num_methods: header.num_methods,
365                            read_only_section_size: header.read_only_section_file_size,
366                            file_size,
367                        })?;
368                    // SAFETY: The number of bytes is correct, content is checked below
369                    let contract_file_method_metadata = unsafe {
370                        ContractFileMethodMetadata::read_unaligned_unchecked(
371                            contract_file_method_metadata_bytes,
372                        )
373                    };
374
375                    if (contract_file_method_metadata.offset + contract_file_method_metadata.size)
376                        > file_size
377                    {
378                        return Err(ContractFileParseError::FileTooSmall {
379                            num_methods: header.num_methods,
380                            read_only_section_size: header.read_only_section_file_size,
381                            file_size,
382                        });
383                    }
384
385                    if contract_file_method_metadata.offset < code_section_offset {
386                        return Err(ContractFileParseError::MethodOutOfRange {
387                            offset: contract_file_method_metadata.offset,
388                            code_section_offset,
389                            file_size,
390                        });
391                    }
392
393                    Ok(contract_file_method_metadata)
394                })
395                .take(usize::from(header.num_methods))
396            };
397
398            let mut metadata_num_methods = 0;
399            let mut remaining_metadata_bytes = metadata_bytes;
400            let mut metadata_decoder = MetadataDecoder::new(metadata_bytes);
401
402            while let Some(maybe_metadata_item) = metadata_decoder.decode_next() {
403                let metadata_item = maybe_metadata_item?;
404                trace!(?metadata_item, "Decoded metadata item");
405
406                let mut methods_metadata_decoder = metadata_item.into_decoder();
407                loop {
408                    // This is used instead of `while let Some(method_metadata_decoder)` because the
409                    // compiler is not smart enough to understand where `method_metadata_decoder` is
410                    // dropped
411                    let Some(method_metadata_decoder) = methods_metadata_decoder.decode_next()
412                    else {
413                        break;
414                    };
415
416                    let before_remaining_bytes = method_metadata_decoder.remaining_metadata_bytes();
417                    let (_, method_metadata_item) = method_metadata_decoder.decode_next()?;
418
419                    trace!(?method_metadata_item, "Decoded method metadata item");
420                    metadata_num_methods += 1;
421
422                    let method_metadata_bytes = remaining_metadata_bytes
423                        .split_off(
424                            ..before_remaining_bytes
425                                - methods_metadata_decoder.remaining_metadata_bytes(),
426                        )
427                        .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
428
429                    let contract_file_method_metadata = contract_file_methods_metadata_iter
430                        .next()
431                        .ok_or(ContractFileParseError::InsufficientHeaderMethods {
432                            header_num_methods: header.num_methods,
433                            metadata_method_index: metadata_num_methods - 1,
434                        })??;
435                    let address = contract_file_method_metadata.offset - read_only_section_offset
436                        + read_only_padding_size;
437
438                    if !address.is_multiple_of(size_of::<u16>() as u32) {
439                        return Err(ContractFileParseError::MethodUnaligned {
440                            file_offset: contract_file_method_metadata.offset,
441                            memory_address: address,
442                        });
443                    }
444
445                    contract_method(ContractFileMethod {
446                        address,
447                        method_metadata_item,
448                        method_metadata_bytes,
449                    })?;
450                }
451            }
452
453            if metadata_num_methods != header.num_methods {
454                return Err(ContractFileParseError::MetadataNumMethodsMismatch {
455                    header_num_methods: header.num_methods,
456                    metadata_num_methods,
457                });
458            }
459        }
460
461        if code_section_offset >= file_size {
462            return Err(ContractFileParseError::FileTooSmall {
463                num_methods: header.num_methods,
464                read_only_section_size: header.read_only_section_file_size,
465                file_size,
466            });
467        }
468
469        if header.host_call_fn_offset != 0 {
470            if header.host_call_fn_offset >= file_size
471                || header.host_call_fn_offset < code_section_offset
472            {
473                return Err(ContractFileParseError::HostCallFnOutOfRange {
474                    offset: header.host_call_fn_offset,
475                    code_section_offset,
476                    file_size,
477                });
478            }
479
480            let instruction_bytes = file_bytes
481                .get(header.host_call_fn_offset as usize..)
482                .ok_or(ContractFileParseError::HostCallFnOutOfRange {
483                    offset: header.host_call_fn_offset,
484                    code_section_offset,
485                    file_size,
486                })?;
487            // SAFETY: All bit patterns are valid for u32
488            let instruction = if let Some(instruction_bytes) =
489                unsafe { <Unaligned<u32>>::from_bytes(instruction_bytes) }
490            {
491                instruction_bytes.as_inner()
492            } else {
493                // SAFETY: All bit patterns are valid for u16
494                let Some(instruction_bytes) =
495                    (unsafe { <Unaligned<u16>>::from_bytes(instruction_bytes) })
496                else {
497                    return Err(ContractFileParseError::HostCallFnOutOfRange {
498                        offset: header.host_call_fn_offset,
499                        code_section_offset,
500                        file_size,
501                    });
502                };
503
504                u32::from(instruction_bytes.as_inner())
505            };
506
507            let instruction = ContractInstruction::try_decode(instruction)
508                .ok_or(ContractFileParseError::InvalidInstruction { instruction })?;
509
510            // The instruction is an unconditional relative jump:
511            //   jal x0, offset
512            #[expect(
513                clippy::rest_pattern_accessible_field,
514                reason = "Do not need other fields"
515            )]
516            let matches_expected_pattern = match instruction {
517                ContractInstruction::Jal { rd, .. } => rd == Register::ZERO,
518                ContractInstruction::CJ { .. } => true,
519                _ => false,
520            };
521
522            if !matches_expected_pattern {
523                return Err(ContractFileParseError::InvalidHostCallFnPattern { instruction });
524            }
525
526            let address =
527                header.host_call_fn_offset - read_only_section_offset + read_only_padding_size;
528
529            if !address.is_multiple_of(size_of::<u16>() as u32) {
530                return Err(ContractFileParseError::HostCallFnUnaligned {
531                    file_offset: header.host_call_fn_offset,
532                    memory_address: address,
533                });
534            }
535        }
536
537        // Ensure code only consists of expected instructions
538        {
539            let mut offset = code_section_offset as usize;
540
541            let mut instruction = ContractInstruction::Unimp {
542                rs1: Register::ZERO,
543                rs2: Register::ZERO,
544            };
545            while offset < file_bytes.len() {
546                let remaining = &file_bytes[offset..];
547
548                let instruction_word = if remaining.len() >= size_of::<u32>() {
549                    u32::from_le_bytes([remaining[0], remaining[1], remaining[2], remaining[3]])
550                } else if remaining.len() >= size_of::<u16>() {
551                    u32::from_le_bytes([remaining[0], remaining[1], 0, 0])
552                } else {
553                    // Need at least 2 bytes to read a compressed instruction
554                    return Err(ContractFileParseError::UnexpectedTrailingCodeBytes {
555                        num_bytes: remaining.len(),
556                    });
557                };
558
559                instruction = ContractInstruction::try_decode(instruction_word).ok_or(
560                    ContractFileParseError::InvalidInstruction {
561                        instruction: instruction_word,
562                    },
563                )?;
564
565                offset += usize::from(instruction.size());
566            }
567
568            if !instruction.is_jump() {
569                return Err(ContractFileParseError::LastInstructionMustBeJump { instruction });
570            }
571        }
572
573        Ok(Self {
574            read_only_section_file_size: header.read_only_section_file_size,
575            read_only_section_memory_size: header.read_only_section_memory_size,
576            num_methods: header.num_methods,
577            bytes: file_bytes,
578        })
579    }
580
581    /// Similar to [`ContractFile::parse()`] but does not verify internal invariants and assumes the
582    /// input is valid.
583    ///
584    /// This method is more efficient and does no checks that [`ContractFile::parse()`] does.
585    ///
586    /// # Safety
587    /// Must be a valid input, for example, previously verified using [`ContractFile::parse()`].
588    pub unsafe fn parse_unchecked(file_bytes: &'a [u8]) -> Self {
589        // SAFETY: Unchecked method assumed input is correct
590        let header = unsafe { ContractFileHeader::read_unaligned_unchecked(file_bytes) };
591
592        Self {
593            read_only_section_file_size: header.read_only_section_file_size,
594            read_only_section_memory_size: header.read_only_section_memory_size,
595            num_methods: header.num_methods,
596            bytes: file_bytes,
597        }
598    }
599
600    /// Get file header
601    #[inline(always)]
602    pub fn header(&self) -> ContractFileHeader {
603        // SAFETY: Protected internal invariant checked in constructor
604        unsafe { ContractFileHeader::read_unaligned_unchecked(self.bytes) }
605    }
606
607    /// Metadata stored in the file
608    #[inline]
609    pub fn metadata_bytes(&self) -> &[u8] {
610        let header = self.header();
611        // SAFETY: Protected internal invariant checked in constructor
612        unsafe {
613            self.bytes
614                .get_unchecked(header.metadata_offset as usize..)
615                .get_unchecked(..header.metadata_size as usize)
616        }
617    }
618
619    /// Memory allocation required for the contract
620    #[inline]
621    pub fn contract_memory_size(&self) -> u32 {
622        let read_only_section_offset = ContractFileHeader::SIZE
623            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
624        let read_only_padding_size =
625            self.read_only_section_memory_size - self.read_only_section_file_size;
626        self.bytes.len() as u32 - read_only_section_offset + read_only_padding_size
627    }
628
629    /// Initialize contract memory with file contents.
630    ///
631    /// Use [`Self::contract_memory_size()`] to identify the exact necessary amount of memory, which
632    /// must match the capacity of the cursor. The whole cursor is filled on success.
633    #[must_use = "Must check that contract memory was large enough"]
634    pub fn initialize_contract_memory(&self, mut contract_memory: BorrowedCursor<'_, u8>) -> bool {
635        let contract_memory_input_size = contract_memory.capacity();
636        let read_only_section_offset = ContractFileHeader::SIZE
637            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
638        let read_only_padding_size =
639            self.read_only_section_memory_size - self.read_only_section_file_size;
640
641        // SAFETY: Protected internal invariant checked in constructor
642        let source_bytes = unsafe {
643            self.bytes
644                .get_unchecked(read_only_section_offset as usize..)
645        };
646
647        // SAFETY: Only initialized bytes are written into contract memory
648        let mut contract_memory_bytes = unsafe { contract_memory.as_mut() };
649
650        // Simple case: memory exactly matches the file-backed sections
651        if contract_memory_bytes.len() == source_bytes.len() {
652            contract_memory_bytes.write_copy_of_slice(source_bytes);
653            // SAFETY: The whole contract memory was just initialized
654            unsafe {
655                contract_memory.advance(contract_memory_input_size);
656            }
657            return true;
658        }
659
660        let Some(read_only_file_target_bytes) =
661            contract_memory_bytes.split_off_mut(..self.read_only_section_file_size as usize)
662        else {
663            trace!(
664                %contract_memory_input_size,
665                contract_memory_size = %self.contract_memory_size(),
666                read_only_section_file_size = self.read_only_section_file_size,
667                "Not enough bytes to write read-only section from the file"
668            );
669
670            return false;
671        };
672
673        // SAFETY: Protected internal invariant checked in constructor
674        let (read_only_file_source_bytes, code_source_bytes) =
675            unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
676        // Write read-only data
677        read_only_file_target_bytes.write_copy_of_slice(read_only_file_source_bytes);
678
679        let Some(read_only_padding_bytes) =
680            contract_memory_bytes.split_off_mut(..read_only_padding_size as usize)
681        else {
682            trace!(
683                %contract_memory_input_size,
684                contract_memory_size = %self.contract_memory_size(),
685                read_only_section_file_size = self.read_only_section_file_size,
686                read_only_section_memory_size = self.read_only_section_memory_size,
687                %read_only_padding_size,
688                "Not enough bytes to write read-only padding section"
689            );
690
691            return false;
692        };
693
694        // Write read-only padding
695        read_only_padding_bytes.write_filled(0);
696
697        if code_source_bytes.len() != contract_memory_bytes.len() {
698            trace!(
699                %contract_memory_input_size,
700                contract_memory_size = %self.contract_memory_size(),
701                read_only_section_file_size = self.read_only_section_file_size,
702                read_only_section_memory_size = self.read_only_section_memory_size,
703                %read_only_padding_size,
704                code_size = %code_source_bytes.len(),
705                "Not enough bytes to write code section from the file"
706            );
707
708            return false;
709        }
710
711        contract_memory_bytes.write_copy_of_slice(code_source_bytes);
712        // SAFETY: The whole contract memory was just initialized
713        unsafe {
714            contract_memory.advance(contract_memory_input_size);
715        }
716
717        true
718    }
719
720    /// Get the complete code section with instructions
721    pub fn get_code(&self) -> &[u8] {
722        let read_only_section_offset = ContractFileHeader::SIZE
723            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
724
725        // SAFETY: Protected internal invariant checked in constructor
726        let source_bytes = unsafe {
727            self.bytes
728                .get_unchecked(read_only_section_offset as usize..)
729        };
730
731        // SAFETY: Protected internal invariant checked in constructor
732        let (_read_only_file_source_bytes, code_source_bytes) =
733            unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
734
735        code_source_bytes
736    }
737
738    /// Iterate over all methods in the contract
739    pub fn iterate_methods(
740        &self,
741    ) -> impl ExactSizeIterator<Item = ContractFileMethod<'_>> + TrustedLen {
742        let metadata_bytes = self.metadata_bytes();
743
744        #[ouroboros::self_referencing]
745        struct MethodsMetadataIterState<'metadata> {
746            metadata_decoder: MetadataDecoder<'metadata>,
747            #[borrows(mut metadata_decoder)]
748            #[covariant]
749            methods_metadata_decoder: Option<MethodsMetadataDecoder<'this, 'metadata>>,
750        }
751
752        let metadata_decoder = MetadataDecoder::new(metadata_bytes);
753
754        let mut methods_metadata_state =
755            MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
756                metadata_decoder
757                    .decode_next()
758                    .and_then(Result::ok)
759                    .map(MetadataItem::into_decoder)
760            });
761
762        let mut metadata_methods_iter = iter::from_fn(move || {
763            loop {
764                let maybe_next_item = methods_metadata_state.with_methods_metadata_decoder_mut(
765                    |maybe_methods_metadata_decoder| {
766                        let methods_metadata_decoder = maybe_methods_metadata_decoder.as_mut()?;
767                        let method_metadata_decoder = methods_metadata_decoder.decode_next()?;
768
769                        let before_remaining_bytes =
770                            method_metadata_decoder.remaining_metadata_bytes();
771
772                        let (_, method_metadata_item) = method_metadata_decoder
773                            .decode_next()
774                            .expect("Input is valid according to function contract; qed");
775
776                        // SAFETY: Protected internal invariant checked in constructor
777                        let method_metadata_bytes = unsafe {
778                            metadata_bytes
779                                .get_unchecked(metadata_bytes.len() - before_remaining_bytes..)
780                                .get_unchecked(
781                                    ..before_remaining_bytes
782                                        - methods_metadata_decoder.remaining_metadata_bytes(),
783                                )
784                        };
785
786                        Some((method_metadata_item, method_metadata_bytes))
787                    },
788                );
789
790                if let Some(next_item) = maybe_next_item {
791                    return Some(next_item);
792                }
793
794                // Process methods of the next contract/trait
795                replace_with_or_abort(&mut methods_metadata_state, |methods_metadata_state| {
796                    let metadata_decoder = methods_metadata_state.into_heads().metadata_decoder;
797                    MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
798                        metadata_decoder
799                            .decode_next()
800                            .and_then(Result::ok)
801                            .map(MetadataItem::into_decoder)
802                    })
803                });
804
805                if methods_metadata_state
806                    .borrow_methods_metadata_decoder()
807                    .is_none()
808                {
809                    return None;
810                }
811            }
812        });
813
814        let read_only_padding_size =
815            self.read_only_section_memory_size - self.read_only_section_file_size;
816        // SAFETY: Protected internal invariant checked in constructor
817        let contract_file_methods_metadata_bytes =
818            unsafe { self.bytes.get_unchecked(size_of::<ContractFileHeader>()..) };
819
820        (0..self.num_methods).map(move |method_index| {
821            // SAFETY: Protected internal invariant checked in constructor
822            let contract_file_method_metadata_bytes = unsafe {
823                contract_file_methods_metadata_bytes
824                    .get_unchecked(
825                        method_index as usize * size_of::<ContractFileMethodMetadata>()..,
826                    )
827                    .get_unchecked(..size_of::<ContractFileMethodMetadata>())
828            };
829            // SAFETY: Protected internal invariant checked in constructor
830            let contract_file_method_metadata = unsafe {
831                ContractFileMethodMetadata::read_unaligned_unchecked(
832                    contract_file_method_metadata_bytes,
833                )
834            };
835
836            let (method_metadata_item, method_metadata_bytes) = metadata_methods_iter
837                .next()
838                .expect("Protected internal invariant checked in constructor; qed");
839
840            ContractFileMethod {
841                address: contract_file_method_metadata.offset + read_only_padding_size,
842                method_metadata_item,
843                method_metadata_bytes,
844            }
845        })
846    }
847}