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#![feature(
31    const_block_items,
32    const_cmp,
33    const_convert,
34    const_default,
35    const_index,
36    const_trait_impl,
37    const_try,
38    const_try_residual,
39    maybe_uninit_fill,
40    signed_bigint_helpers,
41    trusted_len,
42    try_blocks
43)]
44#![expect(incomplete_features, reason = "generic_const_exprs")]
45// TODO: This feature is not actually used in this crate, but is added as a workaround for
46//  https://github.com/rust-lang/rust/issues/141492
47#![feature(generic_const_exprs)]
48#![no_std]
49
50pub mod instruction;
51
52use crate::instruction::ContractInstruction;
53use ab_contracts_common::metadata::decode::{
54    MetadataDecoder, MetadataDecodingError, MetadataItem, MethodMetadataItem,
55    MethodsMetadataDecoder,
56};
57use ab_io_type::trivial_type::TrivialType;
58use ab_io_type::unaligned::Unaligned;
59use ab_riscv_primitives::prelude::*;
60use core::iter;
61use core::iter::TrustedLen;
62use core::mem::MaybeUninit;
63use replace_with::replace_with_or_abort;
64use tracing::{debug, trace};
65
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            let matches_expected_pattern = match instruction {
511                ContractInstruction::Jal { rd, .. } => rd == Register::ZERO,
512                ContractInstruction::CJ { .. } => true,
513                _ => false,
514            };
515
516            if !matches_expected_pattern {
517                return Err(ContractFileParseError::InvalidHostCallFnPattern { instruction });
518            }
519
520            let address =
521                header.host_call_fn_offset - read_only_section_offset + read_only_padding_size;
522
523            if !address.is_multiple_of(size_of::<u16>() as u32) {
524                return Err(ContractFileParseError::HostCallFnUnaligned {
525                    file_offset: header.host_call_fn_offset,
526                    memory_address: address,
527                });
528            }
529        }
530
531        // Ensure code only consists of expected instructions
532        {
533            let mut offset = code_section_offset as usize;
534
535            let mut instruction = ContractInstruction::Unimp {
536                rs1: Register::ZERO,
537                rs2: Register::ZERO,
538            };
539            while offset < file_bytes.len() {
540                let remaining = &file_bytes[offset..];
541
542                let instruction_word = if remaining.len() >= size_of::<u32>() {
543                    u32::from_le_bytes([remaining[0], remaining[1], remaining[2], remaining[3]])
544                } else if remaining.len() >= size_of::<u16>() {
545                    u32::from_le_bytes([remaining[0], remaining[1], 0, 0])
546                } else {
547                    // Need at least 2 bytes to read a compressed instruction
548                    return Err(ContractFileParseError::UnexpectedTrailingCodeBytes {
549                        num_bytes: remaining.len(),
550                    });
551                };
552
553                instruction = ContractInstruction::try_decode(instruction_word).ok_or(
554                    ContractFileParseError::InvalidInstruction {
555                        instruction: instruction_word,
556                    },
557                )?;
558
559                offset += usize::from(instruction.size());
560            }
561
562            if !instruction.is_jump() {
563                return Err(ContractFileParseError::LastInstructionMustBeJump { instruction });
564            }
565        }
566
567        Ok(Self {
568            read_only_section_file_size: header.read_only_section_file_size,
569            read_only_section_memory_size: header.read_only_section_memory_size,
570            num_methods: header.num_methods,
571            bytes: file_bytes,
572        })
573    }
574
575    /// Similar to [`ContractFile::parse()`] but does not verify internal invariants and assumes the
576    /// input is valid.
577    ///
578    /// This method is more efficient and does no checks that [`ContractFile::parse()`] does.
579    ///
580    /// # Safety
581    /// Must be a valid input, for example, previously verified using [`ContractFile::parse()`].
582    pub unsafe fn parse_unchecked(file_bytes: &'a [u8]) -> Self {
583        // SAFETY: Unchecked method assumed input is correct
584        let header = unsafe { ContractFileHeader::read_unaligned_unchecked(file_bytes) };
585
586        Self {
587            read_only_section_file_size: header.read_only_section_file_size,
588            read_only_section_memory_size: header.read_only_section_memory_size,
589            num_methods: header.num_methods,
590            bytes: file_bytes,
591        }
592    }
593
594    /// Get file header
595    #[inline(always)]
596    pub fn header(&self) -> ContractFileHeader {
597        // SAFETY: Protected internal invariant checked in constructor
598        unsafe { ContractFileHeader::read_unaligned_unchecked(self.bytes) }
599    }
600
601    /// Metadata stored in the file
602    #[inline]
603    pub fn metadata_bytes(&self) -> &[u8] {
604        let header = self.header();
605        // SAFETY: Protected internal invariant checked in constructor
606        unsafe {
607            self.bytes
608                .get_unchecked(header.metadata_offset as usize..)
609                .get_unchecked(..header.metadata_size as usize)
610        }
611    }
612
613    /// Memory allocation required for the contract
614    #[inline]
615    pub fn contract_memory_size(&self) -> u32 {
616        let read_only_section_offset = ContractFileHeader::SIZE
617            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
618        let read_only_padding_size =
619            self.read_only_section_memory_size - self.read_only_section_file_size;
620        self.bytes.len() as u32 - read_only_section_offset + read_only_padding_size
621    }
622
623    /// Initialize contract memory with file contents.
624    ///
625    /// Use [`Self::contract_memory_size()`] to identify the exact necessary amount of memory.
626    #[must_use = "Must check that contract memory was large enough"]
627    pub fn initialize_contract_memory(&self, mut contract_memory: &mut [MaybeUninit<u8>]) -> bool {
628        let contract_memory_input_size = contract_memory.len();
629        let read_only_section_offset = ContractFileHeader::SIZE
630            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
631        let read_only_padding_size =
632            self.read_only_section_memory_size - self.read_only_section_file_size;
633
634        // SAFETY: Protected internal invariant checked in constructor
635        let source_bytes = unsafe {
636            self.bytes
637                .get_unchecked(read_only_section_offset as usize..)
638        };
639
640        // Simple case: memory exactly matches the file-backed sections
641        if contract_memory.len() == source_bytes.len() {
642            contract_memory.write_copy_of_slice(source_bytes);
643            return true;
644        }
645
646        let Some(read_only_file_target_bytes) =
647            contract_memory.split_off_mut(..self.read_only_section_file_size as usize)
648        else {
649            trace!(
650                %contract_memory_input_size,
651                contract_memory_size = %self.contract_memory_size(),
652                read_only_section_file_size = self.read_only_section_file_size,
653                "Not enough bytes to write read-only section from the file"
654            );
655
656            return false;
657        };
658
659        // SAFETY: Protected internal invariant checked in constructor
660        let (read_only_file_source_bytes, code_source_bytes) =
661            unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
662        // Write read-only data
663        read_only_file_target_bytes.write_copy_of_slice(read_only_file_source_bytes);
664
665        let Some(read_only_padding_bytes) =
666            contract_memory.split_off_mut(..read_only_padding_size as usize)
667        else {
668            trace!(
669                %contract_memory_input_size,
670                contract_memory_size = %self.contract_memory_size(),
671                read_only_section_file_size = self.read_only_section_file_size,
672                read_only_section_memory_size = self.read_only_section_memory_size,
673                %read_only_padding_size,
674                "Not enough bytes to write read-only padding section"
675            );
676
677            return false;
678        };
679
680        // Write read-only padding
681        read_only_padding_bytes.write_filled(0);
682
683        if code_source_bytes.len() != contract_memory.len() {
684            trace!(
685                %contract_memory_input_size,
686                contract_memory_size = %self.contract_memory_size(),
687                read_only_section_file_size = self.read_only_section_file_size,
688                read_only_section_memory_size = self.read_only_section_memory_size,
689                %read_only_padding_size,
690                code_size = %code_source_bytes.len(),
691                "Not enough bytes to write code section from the file"
692            );
693
694            return false;
695        }
696
697        contract_memory.write_copy_of_slice(code_source_bytes);
698
699        true
700    }
701
702    /// Get the complete code section with instructions
703    pub fn get_code(&self) -> &[u8] {
704        let read_only_section_offset = ContractFileHeader::SIZE
705            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
706
707        // SAFETY: Protected internal invariant checked in constructor
708        let source_bytes = unsafe {
709            self.bytes
710                .get_unchecked(read_only_section_offset as usize..)
711        };
712
713        // SAFETY: Protected internal invariant checked in constructor
714        let (_read_only_file_source_bytes, code_source_bytes) =
715            unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
716
717        code_source_bytes
718    }
719
720    /// Iterate over all methods in the contract
721    pub fn iterate_methods(
722        &self,
723    ) -> impl ExactSizeIterator<Item = ContractFileMethod<'_>> + TrustedLen {
724        let metadata_bytes = self.metadata_bytes();
725
726        #[ouroboros::self_referencing]
727        struct MethodsMetadataIterState<'metadata> {
728            metadata_decoder: MetadataDecoder<'metadata>,
729            #[borrows(mut metadata_decoder)]
730            #[covariant]
731            methods_metadata_decoder: Option<MethodsMetadataDecoder<'this, 'metadata>>,
732        }
733
734        let metadata_decoder = MetadataDecoder::new(metadata_bytes);
735
736        let mut methods_metadata_state =
737            MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
738                metadata_decoder
739                    .decode_next()
740                    .and_then(Result::ok)
741                    .map(MetadataItem::into_decoder)
742            });
743
744        let mut metadata_methods_iter = iter::from_fn(move || {
745            loop {
746                let maybe_next_item = methods_metadata_state.with_methods_metadata_decoder_mut(
747                    |maybe_methods_metadata_decoder| {
748                        let methods_metadata_decoder = maybe_methods_metadata_decoder.as_mut()?;
749                        let method_metadata_decoder = methods_metadata_decoder.decode_next()?;
750
751                        let before_remaining_bytes =
752                            method_metadata_decoder.remaining_metadata_bytes();
753
754                        let (_, method_metadata_item) = method_metadata_decoder
755                            .decode_next()
756                            .expect("Input is valid according to function contract; qed");
757
758                        // SAFETY: Protected internal invariant checked in constructor
759                        let method_metadata_bytes = unsafe {
760                            metadata_bytes
761                                .get_unchecked(metadata_bytes.len() - before_remaining_bytes..)
762                                .get_unchecked(
763                                    ..before_remaining_bytes
764                                        - methods_metadata_decoder.remaining_metadata_bytes(),
765                                )
766                        };
767
768                        Some((method_metadata_item, method_metadata_bytes))
769                    },
770                );
771
772                if let Some(next_item) = maybe_next_item {
773                    return Some(next_item);
774                }
775
776                // Process methods of the next contract/trait
777                replace_with_or_abort(&mut methods_metadata_state, |methods_metadata_state| {
778                    let metadata_decoder = methods_metadata_state.into_heads().metadata_decoder;
779                    MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
780                        metadata_decoder
781                            .decode_next()
782                            .and_then(Result::ok)
783                            .map(MetadataItem::into_decoder)
784                    })
785                });
786
787                if methods_metadata_state
788                    .borrow_methods_metadata_decoder()
789                    .is_none()
790                {
791                    return None;
792                }
793            }
794        });
795
796        let read_only_padding_size =
797            self.read_only_section_memory_size - self.read_only_section_file_size;
798        // SAFETY: Protected internal invariant checked in constructor
799        let contract_file_methods_metadata_bytes =
800            unsafe { self.bytes.get_unchecked(size_of::<ContractFileHeader>()..) };
801
802        (0..self.num_methods).map(move |method_index| {
803            // SAFETY: Protected internal invariant checked in constructor
804            let contract_file_method_metadata_bytes = unsafe {
805                contract_file_methods_metadata_bytes
806                    .get_unchecked(
807                        method_index as usize * size_of::<ContractFileMethodMetadata>()..,
808                    )
809                    .get_unchecked(..size_of::<ContractFileMethodMetadata>())
810            };
811            // SAFETY: Protected internal invariant checked in constructor
812            let contract_file_method_metadata = unsafe {
813                ContractFileMethodMetadata::read_unaligned_unchecked(
814                    contract_file_method_metadata_bytes,
815                )
816            };
817
818            let (method_metadata_item, method_metadata_bytes) = metadata_methods_iter
819                .next()
820                .expect("Protected internal invariant checked in constructor; qed");
821
822            ContractFileMethod {
823                address: contract_file_method_metadata.offset + read_only_padding_size,
824                method_metadata_item,
825                method_metadata_bytes,
826            }
827        })
828    }
829}