Skip to main content

ab_contract_file/
lib.rs

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