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