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