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_bytes, after_header_bytes) = file_bytes
327            .split_at_checked(size_of::<ContractFileHeader>())
328            .ok_or(ContractFileParseError::NoHeader)?;
329        // SAFETY: Size is correct, content is checked below
330        let header = unsafe { ContractFileHeader::read_unaligned_unchecked(header_bytes) };
331
332        if header.magic != CONTRACT_FILE_MAGIC {
333            return Err(ContractFileParseError::WrongMagicBytes);
334        }
335
336        if header.read_only_section_file_size > header.read_only_section_memory_size {
337            return Err(ContractFileParseError::InvalidReadOnlySizes {
338                file_size: header.read_only_section_file_size,
339                memory_size: header.read_only_section_memory_size,
340            });
341        }
342
343        let metadata_bytes = file_bytes
344            .get(header.metadata_offset as usize..)
345            .ok_or(ContractFileParseError::MetadataOutOfRange {
346                offset: header.metadata_offset,
347                size: header.metadata_size,
348                file_size,
349            })?
350            .get(..header.metadata_size as usize)
351            .ok_or(ContractFileParseError::MetadataOutOfRange {
352                offset: header.metadata_offset,
353                size: header.metadata_size,
354                file_size,
355            })?;
356
357        let read_only_padding_size =
358            header.read_only_section_memory_size - header.read_only_section_file_size;
359        let read_only_section_offset = ContractFileHeader::SIZE
360            + u32::from(header.num_methods) * ContractFileMethodMetadata::SIZE;
361        let code_section_offset =
362            read_only_section_offset.saturating_add(header.read_only_section_file_size);
363
364        let Some(code_size) = file_size
365            .checked_sub(code_section_offset)
366            .filter(|&code_size| code_size > 0)
367        else {
368            return Err(ContractFileParseError::FileTooSmall {
369                num_methods: header.num_methods,
370                read_only_section_size: header.read_only_section_file_size,
371                file_size,
372            });
373        };
374
375        // Addresses within contract memory are calculated as `u32` below
376        if header
377            .read_only_section_memory_size
378            .checked_add(code_size)
379            .is_none()
380        {
381            return Err(ContractFileParseError::ContractMemoryTooLarge {
382                read_only_section_memory_size: header.read_only_section_memory_size,
383                code_size,
384            });
385        }
386
387        {
388            let mut contract_file_methods_metadata_iter = {
389                let mut file_contract_metadata_bytes = after_header_bytes;
390
391                iter::repeat_with(move || {
392                    let contract_file_method_metadata_bytes = file_contract_metadata_bytes
393                        .split_off(..size_of::<ContractFileMethodMetadata>())
394                        .ok_or(ContractFileParseError::FileTooSmall {
395                            num_methods: header.num_methods,
396                            read_only_section_size: header.read_only_section_file_size,
397                            file_size,
398                        })?;
399                    // SAFETY: The number of bytes is correct, content is checked below
400                    let contract_file_method_metadata = unsafe {
401                        ContractFileMethodMetadata::read_unaligned_unchecked(
402                            contract_file_method_metadata_bytes,
403                        )
404                    };
405
406                    if contract_file_method_metadata
407                        .offset
408                        .checked_add(contract_file_method_metadata.size)
409                        .is_none_or(|method_end| method_end > file_size)
410                    {
411                        return Err(ContractFileParseError::FileTooSmall {
412                            num_methods: header.num_methods,
413                            read_only_section_size: header.read_only_section_file_size,
414                            file_size,
415                        });
416                    }
417
418                    // Method must start within the code section, which also rules out an empty
419                    // method right at the end of it
420                    if contract_file_method_metadata.offset < code_section_offset
421                        || contract_file_method_metadata.offset >= file_size
422                    {
423                        return Err(ContractFileParseError::MethodOutOfRange {
424                            offset: contract_file_method_metadata.offset,
425                            code_section_offset,
426                            file_size,
427                        });
428                    }
429
430                    Ok(contract_file_method_metadata)
431                })
432                .take(usize::from(header.num_methods))
433            };
434
435            let mut metadata_num_methods = 0;
436            let mut remaining_metadata_bytes = metadata_bytes;
437            let mut metadata_decoder = MetadataDecoder::new(metadata_bytes);
438
439            while let Some(maybe_metadata_item) = metadata_decoder.decode_next() {
440                let metadata_item = maybe_metadata_item?;
441                trace!(?metadata_item, "Decoded metadata item");
442
443                let mut methods_metadata_decoder = metadata_item.into_decoder();
444                loop {
445                    // This is used instead of `while let Some(method_metadata_decoder)` because the
446                    // compiler is not smart enough to understand where `method_metadata_decoder` is
447                    // dropped
448                    let Some(method_metadata_decoder) = methods_metadata_decoder.decode_next()
449                    else {
450                        break;
451                    };
452
453                    let before_remaining_bytes = method_metadata_decoder.remaining_metadata_bytes();
454                    let (_, method_metadata_item) = method_metadata_decoder.decode_next()?;
455
456                    trace!(?method_metadata_item, "Decoded method metadata item");
457                    metadata_num_methods += 1;
458
459                    let method_metadata_bytes = remaining_metadata_bytes
460                        .split_off(
461                            ..before_remaining_bytes
462                                - methods_metadata_decoder.remaining_metadata_bytes(),
463                        )
464                        .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
465
466                    let contract_file_method_metadata = contract_file_methods_metadata_iter
467                        .next()
468                        .ok_or(ContractFileParseError::InsufficientHeaderMethods {
469                            header_num_methods: header.num_methods,
470                            metadata_method_index: metadata_num_methods - 1,
471                        })??;
472                    let address = contract_file_method_metadata.offset - read_only_section_offset
473                        + read_only_padding_size;
474
475                    if !address.is_multiple_of(u32::from(<ContractInstruction>::ALIGNMENT)) {
476                        return Err(ContractFileParseError::MethodUnaligned {
477                            file_offset: contract_file_method_metadata.offset,
478                            memory_address: address,
479                        });
480                    }
481
482                    contract_method(ContractFileMethod {
483                        address,
484                        size: contract_file_method_metadata.size,
485                        method_metadata_item,
486                        method_metadata_bytes,
487                    })?;
488                }
489            }
490
491            if metadata_num_methods != header.num_methods {
492                return Err(ContractFileParseError::MetadataNumMethodsMismatch {
493                    header_num_methods: header.num_methods,
494                    metadata_num_methods,
495                });
496            }
497        }
498
499        if header.host_call_fn_offset != 0 {
500            if header.host_call_fn_offset >= file_size
501                || header.host_call_fn_offset < code_section_offset
502            {
503                return Err(ContractFileParseError::HostCallFnOutOfRange {
504                    offset: header.host_call_fn_offset,
505                    code_section_offset,
506                    file_size,
507                });
508            }
509
510            let instruction_bytes = file_bytes
511                .get(header.host_call_fn_offset as usize..)
512                .ok_or(ContractFileParseError::HostCallFnOutOfRange {
513                    offset: header.host_call_fn_offset,
514                    code_section_offset,
515                    file_size,
516                })?;
517            // SAFETY: All bit patterns are valid for u32
518            let instruction = if let Some(instruction_bytes) =
519                unsafe { <Unaligned<u32>>::from_bytes(instruction_bytes) }
520            {
521                instruction_bytes.as_inner()
522            } else {
523                // SAFETY: All bit patterns are valid for u16
524                let Some(instruction_bytes) =
525                    (unsafe { <Unaligned<u16>>::from_bytes(instruction_bytes) })
526                else {
527                    return Err(ContractFileParseError::HostCallFnOutOfRange {
528                        offset: header.host_call_fn_offset,
529                        code_section_offset,
530                        file_size,
531                    });
532                };
533
534                u32::from(instruction_bytes.as_inner())
535            };
536
537            let instruction = ContractInstruction::try_decode(instruction)
538                .ok_or(ContractFileParseError::InvalidInstruction { instruction })?;
539
540            // A 32-bit instruction in the last two bytes of the file was decoded with zero-padding
541            // that isn't present in the file
542            if usize::from(instruction.size()) > instruction_bytes.len() {
543                return Err(ContractFileParseError::HostCallFnOutOfRange {
544                    offset: header.host_call_fn_offset,
545                    code_section_offset,
546                    file_size,
547                });
548            }
549
550            // The instruction is an unconditional relative jump:
551            //   jal x0, offset
552            #[expect(
553                clippy::rest_pattern_accessible_field,
554                reason = "Do not need other fields"
555            )]
556            let matches_expected_pattern = match instruction {
557                ContractInstruction::Jal { rd, .. } => rd == Register::ZERO,
558                ContractInstruction::CJ { .. } => true,
559                _ => false,
560            };
561
562            if !matches_expected_pattern {
563                return Err(ContractFileParseError::InvalidHostCallFnPattern { instruction });
564            }
565
566            let address =
567                header.host_call_fn_offset - read_only_section_offset + read_only_padding_size;
568
569            if !address.is_multiple_of(u32::from(<ContractInstruction>::ALIGNMENT)) {
570                return Err(ContractFileParseError::HostCallFnUnaligned {
571                    file_offset: header.host_call_fn_offset,
572                    memory_address: address,
573                });
574            }
575        }
576
577        // Ensure code only consists of expected instructions
578        {
579            let mut offset = code_section_offset as usize;
580
581            while offset < file_bytes.len() {
582                let remaining = &file_bytes[offset..];
583
584                let instruction_word = if remaining.len() >= size_of::<u32>() {
585                    u32::from_le_bytes([remaining[0], remaining[1], remaining[2], remaining[3]])
586                } else if remaining.len() >= size_of::<u16>() {
587                    u32::from_le_bytes([remaining[0], remaining[1], 0, 0])
588                } else {
589                    // Need at least 2 bytes to read a compressed instruction
590                    return Err(ContractFileParseError::UnexpectedTrailingCodeBytes {
591                        num_bytes: remaining.len(),
592                    });
593                };
594
595                let instruction = <ContractInstruction>::try_decode(instruction_word).ok_or(
596                    ContractFileParseError::InvalidInstruction {
597                        instruction: instruction_word,
598                    },
599                )?;
600
601                // A 32-bit instruction in the last two bytes of the file was decoded with
602                // zero-padding that isn't present in the file
603                if usize::from(instruction.size()) > remaining.len() {
604                    return Err(ContractFileParseError::UnexpectedTrailingCodeBytes {
605                        num_bytes: remaining.len(),
606                    });
607                }
608
609                offset += usize::from(instruction.size());
610            }
611        }
612
613        Ok(Self {
614            read_only_section_file_size: header.read_only_section_file_size,
615            read_only_section_memory_size: header.read_only_section_memory_size,
616            num_methods: header.num_methods,
617            bytes: file_bytes,
618        })
619    }
620
621    /// Similar to [`ContractFile::parse()`] but does not verify internal invariants and assumes the
622    /// input is valid.
623    ///
624    /// This method is more efficient and does no checks that [`ContractFile::parse()`] does.
625    ///
626    /// # Safety
627    /// Must be a valid input, for example, previously verified using [`ContractFile::parse()`].
628    pub unsafe fn parse_unchecked(file_bytes: &'a [u8]) -> Self {
629        // SAFETY: Unchecked method assumed input is correct
630        let header = unsafe { ContractFileHeader::read_unaligned_unchecked(file_bytes) };
631
632        Self {
633            read_only_section_file_size: header.read_only_section_file_size,
634            read_only_section_memory_size: header.read_only_section_memory_size,
635            num_methods: header.num_methods,
636            bytes: file_bytes,
637        }
638    }
639
640    /// Get file header
641    #[inline(always)]
642    pub fn header(&self) -> ContractFileHeader {
643        // SAFETY: Protected internal invariant checked in constructor
644        unsafe { ContractFileHeader::read_unaligned_unchecked(self.bytes) }
645    }
646
647    /// Metadata stored in the file
648    #[inline]
649    pub fn metadata_bytes(&self) -> &[u8] {
650        let header = self.header();
651        // SAFETY: Protected internal invariant checked in constructor
652        unsafe {
653            self.bytes
654                .get_unchecked(header.metadata_offset as usize..)
655                .get_unchecked(..header.metadata_size as usize)
656        }
657    }
658
659    /// Memory allocation required for the contract
660    #[inline]
661    pub fn contract_memory_size(&self) -> u32 {
662        let read_only_section_offset = ContractFileHeader::SIZE
663            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
664        let read_only_padding_size =
665            self.read_only_section_memory_size - self.read_only_section_file_size;
666        self.bytes.len() as u32 - read_only_section_offset + read_only_padding_size
667    }
668
669    /// Initialize contract memory with file contents.
670    ///
671    /// Use [`Self::contract_memory_size()`] to identify the exact necessary amount of memory, which
672    /// must match the capacity of the cursor. The whole cursor is filled on success.
673    #[must_use = "Must check that contract memory was large enough"]
674    pub fn initialize_contract_memory(&self, mut contract_memory: BorrowedCursor<'_, u8>) -> bool {
675        let contract_memory_input_size = contract_memory.capacity();
676        let read_only_section_offset = ContractFileHeader::SIZE
677            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
678        let read_only_padding_size =
679            self.read_only_section_memory_size - self.read_only_section_file_size;
680
681        // SAFETY: Protected internal invariant checked in constructor
682        let source_bytes = unsafe {
683            self.bytes
684                .get_unchecked(read_only_section_offset as usize..)
685        };
686
687        // SAFETY: Only initialized bytes are written into contract memory
688        let mut contract_memory_bytes = unsafe { contract_memory.as_mut() };
689
690        // Simple case: memory exactly matches the file-backed sections
691        if contract_memory_bytes.len() == source_bytes.len() {
692            contract_memory_bytes.write_copy_of_slice(source_bytes);
693            // SAFETY: The whole contract memory was just initialized
694            unsafe {
695                contract_memory.advance(contract_memory_input_size);
696            }
697            return true;
698        }
699
700        let Some(read_only_file_target_bytes) =
701            contract_memory_bytes.split_off_mut(..self.read_only_section_file_size as usize)
702        else {
703            trace!(
704                %contract_memory_input_size,
705                contract_memory_size = %self.contract_memory_size(),
706                read_only_section_file_size = self.read_only_section_file_size,
707                "Not enough bytes to write read-only section from the file"
708            );
709
710            return false;
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        // Write read-only data
717        read_only_file_target_bytes.write_copy_of_slice(read_only_file_source_bytes);
718
719        let Some(read_only_padding_bytes) =
720            contract_memory_bytes.split_off_mut(..read_only_padding_size as usize)
721        else {
722            trace!(
723                %contract_memory_input_size,
724                contract_memory_size = %self.contract_memory_size(),
725                read_only_section_file_size = self.read_only_section_file_size,
726                read_only_section_memory_size = self.read_only_section_memory_size,
727                %read_only_padding_size,
728                "Not enough bytes to write read-only padding section"
729            );
730
731            return false;
732        };
733
734        // Write read-only padding
735        read_only_padding_bytes.write_filled(0);
736
737        if code_source_bytes.len() != contract_memory_bytes.len() {
738            trace!(
739                %contract_memory_input_size,
740                contract_memory_size = %self.contract_memory_size(),
741                read_only_section_file_size = self.read_only_section_file_size,
742                read_only_section_memory_size = self.read_only_section_memory_size,
743                %read_only_padding_size,
744                code_size = %code_source_bytes.len(),
745                "Not enough bytes to write code section from the file"
746            );
747
748            return false;
749        }
750
751        contract_memory_bytes.write_copy_of_slice(code_source_bytes);
752        // SAFETY: The whole contract memory was just initialized
753        unsafe {
754            contract_memory.advance(contract_memory_input_size);
755        }
756
757        true
758    }
759
760    /// Get the complete code section with instructions
761    pub fn get_code(&self) -> &[u8] {
762        let read_only_section_offset = ContractFileHeader::SIZE
763            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
764
765        // SAFETY: Protected internal invariant checked in constructor
766        let source_bytes = unsafe {
767            self.bytes
768                .get_unchecked(read_only_section_offset as usize..)
769        };
770
771        // SAFETY: Protected internal invariant checked in constructor
772        let (_read_only_file_source_bytes, code_source_bytes) =
773            unsafe { source_bytes.split_at_unchecked(self.read_only_section_file_size as usize) };
774
775        code_source_bytes
776    }
777
778    /// Iterate over all methods in the contract
779    pub fn iterate_methods(
780        &self,
781    ) -> impl ExactSizeIterator<Item = ContractFileMethod<'_>> + TrustedLen {
782        let metadata_bytes = self.metadata_bytes();
783
784        #[ouroboros::self_referencing]
785        struct MethodsMetadataIterState<'metadata> {
786            metadata_decoder: MetadataDecoder<'metadata>,
787            #[borrows(mut metadata_decoder)]
788            #[covariant]
789            methods_metadata_decoder: Option<MethodsMetadataDecoder<'this, 'metadata>>,
790        }
791
792        let metadata_decoder = MetadataDecoder::new(metadata_bytes);
793
794        let mut methods_metadata_state =
795            MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
796                metadata_decoder
797                    .decode_next()
798                    .and_then(Result::ok)
799                    .map(MetadataItem::into_decoder)
800            });
801
802        let mut metadata_methods_iter = iter::from_fn(move || {
803            loop {
804                let maybe_next_item = methods_metadata_state.with_methods_metadata_decoder_mut(
805                    |maybe_methods_metadata_decoder| {
806                        let methods_metadata_decoder = maybe_methods_metadata_decoder.as_mut()?;
807                        let method_metadata_decoder = methods_metadata_decoder.decode_next()?;
808
809                        let before_remaining_bytes =
810                            method_metadata_decoder.remaining_metadata_bytes();
811
812                        let (_, method_metadata_item) = method_metadata_decoder
813                            .decode_next()
814                            .expect("Input is valid according to function contract; qed");
815
816                        // SAFETY: Protected internal invariant checked in constructor
817                        let method_metadata_bytes = unsafe {
818                            metadata_bytes
819                                .get_unchecked(metadata_bytes.len() - before_remaining_bytes..)
820                                .get_unchecked(
821                                    ..before_remaining_bytes
822                                        - methods_metadata_decoder.remaining_metadata_bytes(),
823                                )
824                        };
825
826                        Some((method_metadata_item, method_metadata_bytes))
827                    },
828                );
829
830                if let Some(next_item) = maybe_next_item {
831                    return Some(next_item);
832                }
833
834                // Process methods of the next contract/trait
835                replace_with_or_abort(&mut methods_metadata_state, |methods_metadata_state| {
836                    let metadata_decoder = methods_metadata_state.into_heads().metadata_decoder;
837                    MethodsMetadataIterState::new(metadata_decoder, |metadata_decoder| {
838                        metadata_decoder
839                            .decode_next()
840                            .and_then(Result::ok)
841                            .map(MetadataItem::into_decoder)
842                    })
843                });
844
845                if methods_metadata_state
846                    .borrow_methods_metadata_decoder()
847                    .is_none()
848                {
849                    return None;
850                }
851            }
852        });
853
854        let read_only_section_offset = ContractFileHeader::SIZE
855            + u32::from(self.num_methods) * ContractFileMethodMetadata::SIZE;
856        let read_only_padding_size =
857            self.read_only_section_memory_size - self.read_only_section_file_size;
858        // SAFETY: Protected internal invariant checked in constructor
859        let contract_file_methods_metadata_bytes =
860            unsafe { self.bytes.get_unchecked(size_of::<ContractFileHeader>()..) };
861
862        (0..self.num_methods).map(move |method_index| {
863            // SAFETY: Protected internal invariant checked in constructor
864            let contract_file_method_metadata_bytes = unsafe {
865                contract_file_methods_metadata_bytes
866                    .get_unchecked(
867                        method_index as usize * size_of::<ContractFileMethodMetadata>()..,
868                    )
869                    .get_unchecked(..size_of::<ContractFileMethodMetadata>())
870            };
871            // SAFETY: Protected internal invariant checked in constructor
872            let contract_file_method_metadata = unsafe {
873                ContractFileMethodMetadata::read_unaligned_unchecked(
874                    contract_file_method_metadata_bytes,
875                )
876            };
877
878            let (method_metadata_item, method_metadata_bytes) = metadata_methods_iter
879                .next()
880                .expect("Protected internal invariant checked in constructor; qed");
881
882            ContractFileMethod {
883                address: contract_file_method_metadata.offset - read_only_section_offset
884                    + read_only_padding_size,
885                size: contract_file_method_metadata.size,
886                method_metadata_item,
887                method_metadata_bytes,
888            }
889        })
890    }
891}