Skip to main content

ab_contract_file/
lib.rs

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