Skip to main content

ab_contract_file/
lib.rs

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