revm_bytecode/eof/
verification.rs

1//! Module that contains the verification logic for the EOF bytecode.
2
3use crate::{
4    eof::{CodeInfo, Eof, EofDecodeError},
5    opcode::{self, OPCODE_INFO},
6    utils::{read_i16, read_u16},
7};
8use primitives::{
9    constants::{MAX_INITCODE_SIZE, STACK_LIMIT},
10    Bytes,
11};
12
13use core::{convert::identity, mem};
14use std::{borrow::Cow, fmt, vec, vec::Vec};
15
16/// Decodes `raw` into an [`Eof`] container and validates it.
17pub fn validate_raw_eof(raw: Bytes) -> Result<Eof, EofError> {
18    validate_raw_eof_inner(raw, Some(CodeType::Initcode))
19}
20
21/// Decodes `raw` into an [`Eof`] container and validates it.
22#[inline]
23pub fn validate_raw_eof_inner(
24    raw: Bytes,
25    first_code_type: Option<CodeType>,
26) -> Result<Eof, EofError> {
27    if raw.len() > MAX_INITCODE_SIZE {
28        return Err(EofError::Decode(EofDecodeError::InvalidEOFSize));
29    }
30    let eof = Eof::decode(raw)?;
31    validate_eof_inner(&eof, first_code_type)?;
32    Ok(eof)
33}
34
35/// Fully validates an [`Eof`] container.
36///
37/// Only place where validation happen is in Creating Transaction.
38///
39/// Because of that we are assuming [CodeType] is [ReturnContract][CodeType::Initcode].
40///
41/// Note: If needed we can make a flag that would assume [ReturnContract][CodeType::Initcode]..
42pub fn validate_eof(eof: &Eof) -> Result<(), EofError> {
43    validate_eof_inner(eof, Some(CodeType::Initcode))
44}
45
46/// Fully validates an [`Eof`] container. If first_code_type is None it will be auto deduced
47/// in verification process.
48#[inline]
49pub fn validate_eof_inner(eof: &Eof, first_code_type: Option<CodeType>) -> Result<(), EofError> {
50    // Data needs to be filled first first container.
51    if !eof.body.is_data_filled {
52        return Err(EofError::Validation(EofValidationError::DataNotFilled));
53    }
54    if eof.body.container_section.is_empty() {
55        validate_eof_codes(eof, first_code_type)?;
56        return Ok(());
57    }
58
59    let mut stack = Vec::with_capacity(4);
60    stack.push((Cow::Borrowed(eof), first_code_type));
61
62    while let Some((eof, code_type)) = stack.pop() {
63        // Validate the current container.
64        let tracker_containers = validate_eof_codes(&eof, code_type)?;
65        // Decode subcontainers and push them to the stack.
66        for (container, code_type) in eof
67            .body
68            .container_section
69            .iter()
70            .zip(tracker_containers.into_iter())
71        {
72            stack.push((Cow::Owned(Eof::decode(container.clone())?), Some(code_type)));
73        }
74    }
75
76    Ok(())
77}
78
79/// Validates an [`Eof`] structure, without recursing into containers.
80///
81/// Returns a list of all sub containers that are accessed.
82#[inline]
83pub fn validate_eof_codes(
84    eof: &Eof,
85    this_code_type: Option<CodeType>,
86) -> Result<Vec<CodeType>, EofValidationError> {
87    if eof.body.code_section.len() != eof.body.code_info.len() {
88        return Err(EofValidationError::InvalidCodeInfo);
89    }
90
91    if eof.body.code_section.is_empty() {
92        // No code sections. This should be already checked in decode.
93        return Err(EofValidationError::NoCodeSections);
94    }
95
96    // The first code section must have a type signature
97    // (0, 0x80, max_stack_height) (0 inputs non-returning function)
98    let first_types = &eof.body.code_info[0];
99    if first_types.inputs != 0 || !first_types.is_non_returning() {
100        return Err(EofValidationError::InvalidCodeInfo);
101    }
102
103    // Tracking access of code and sub containers.
104    let mut tracker: AccessTracker = AccessTracker::new(
105        this_code_type,
106        eof.body.code_section.len(),
107        eof.body.container_section.len(),
108    );
109
110    while let Some(index) = tracker.processing_stack.pop() {
111        // Assume `index` is correct.
112        let code = eof.body.code(index).unwrap();
113        validate_eof_code(
114            &code,
115            eof.header.data_size as usize,
116            index,
117            eof.body.container_section.len(),
118            &eof.body.code_info,
119            &mut tracker,
120        )?;
121    }
122
123    // Iterate over accessed codes and check if all are accessed.
124    if !tracker.codes.into_iter().all(identity) {
125        return Err(EofValidationError::CodeSectionNotAccessed);
126    }
127    // Iterate over all accessed subcontainers and check if all are accessed.
128    if !tracker.subcontainers.iter().all(|i| i.is_some()) {
129        return Err(EofValidationError::SubContainerNotAccessed);
130    }
131
132    if tracker.this_container_code_type == Some(CodeType::Initcode) && !eof.body.is_data_filled {
133        return Err(EofValidationError::DataNotFilled);
134    }
135
136    Ok(tracker
137        .subcontainers
138        .into_iter()
139        .map(|i| i.unwrap())
140        .collect())
141}
142
143/// EOF Error
144#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
145pub enum EofError {
146    /// Decoding error.
147    Decode(EofDecodeError),
148    /// Validation Error.
149    Validation(EofValidationError),
150}
151
152impl From<EofDecodeError> for EofError {
153    fn from(err: EofDecodeError) -> Self {
154        EofError::Decode(err)
155    }
156}
157
158impl From<EofValidationError> for EofError {
159    fn from(err: EofValidationError) -> Self {
160        EofError::Validation(err)
161    }
162}
163
164impl fmt::Display for EofError {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self {
167            EofError::Decode(e) => write!(f, "Bytecode decode error: {}", e),
168            EofError::Validation(e) => write!(f, "Bytecode validation error: {}", e),
169        }
170    }
171}
172
173impl core::error::Error for EofError {}
174
175/// EOF Validation Error
176#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
177pub enum EofValidationError {
178    /// Used in testing to indicate that the bytecode validation is different from expected.
179    FalsePositive,
180    /// Opcode is not known. It is not defined in the opcode table.
181    UnknownOpcode,
182    /// Opcode is disabled in EOF. For example JUMP, JUMPI, etc
183    OpcodeDisabled,
184    /// Every instruction inside bytecode should be forward accessed
185    ///
186    /// Forward access can be a jump or sequential opcode.
187    ///
188    /// In case after terminal opcode there should be a forward jump.
189    InstructionNotForwardAccessed,
190    /// Bytecode is too small and is missing immediate bytes for instruction
191    MissingImmediateBytes,
192    /// Bytecode is too small and is missing immediate bytes for instruction
193    ///
194    /// Similar to [`MissingImmediateBytes`][EofValidationError::MissingImmediateBytes] but for special case of RJUMPV immediate bytes.
195    MissingRJUMPVImmediateBytes,
196    /// Invalid jump into immediate bytes
197    JumpToImmediateBytes,
198    /// Invalid jump into immediate bytes
199    BackwardJumpToImmediateBytes,
200    /// MaxIndex in RJUMPV can't be zero. Zero max index makes it RJUMPI
201    RJUMPVZeroMaxIndex,
202    /// Jump with zero offset would make a jump to next opcode, it does not make sense
203    JumpZeroOffset,
204    /// EOFCREATE points to container out of bounds
205    EOFCREATEInvalidIndex,
206    /// CALLF section out of bounds
207    CodeSectionOutOfBounds,
208    /// CALLF to non returning function is not allowed
209    CALLFNonReturningFunction,
210    /// CALLF stack overflow
211    StackOverflow,
212    /// JUMPF needs to have enough outputs
213    JUMPFEnoughOutputs,
214    /// JUMPF Stack
215    JUMPFStackHigherThanOutputs,
216    /// DATA load out of bounds
217    DataLoadOutOfBounds,
218    /// RETF biggest stack num more then outputs
219    RETFBiggestStackNumMoreThenOutputs,
220    /// Stack requirement is more than smallest stack items
221    StackUnderflow,
222    /// Jump out of bounds
223    JumpUnderflow,
224    /// Jump to out of bounds
225    JumpOverflow,
226    /// Backward jump should have same smallest and biggest stack items
227    BackwardJumpBiggestNumMismatch,
228    /// Backward jump should have same smallest and biggest stack items
229    BackwardJumpSmallestNumMismatch,
230    /// Last instruction should be terminating
231    LastInstructionNotTerminating,
232    /// Code section not accessed
233    CodeSectionNotAccessed,
234    /// Types section invalid
235    InvalidCodeInfo,
236    /// First types section is invalid
237    /// It should have inputs 0 and outputs `0x80`
238    InvalidFirstCodeInfo,
239    /// Max stack element mismatch
240    MaxStackMismatch,
241    /// No code sections present
242    NoCodeSections,
243    /// Sub container called in two different modes
244    ///
245    /// Check [`CodeType`] for more information.
246    SubContainerCalledInTwoModes,
247    /// Sub container not accessed
248    SubContainerNotAccessed,
249    /// Data size needs to be filled for [ReturnContract][CodeType::Initcode] type
250    DataNotFilled,
251    /// Section is marked as non-returning but has either RETF or
252    /// JUMPF to returning section opcodes
253    NonReturningSectionIsReturning,
254}
255
256/// Tracker status of verification of code sections and subcontainers.
257/// Used in validating EOF container.
258#[derive(Clone, Debug, PartialEq, Eq)]
259pub struct AccessTracker {
260    /// This code type
261    pub this_container_code_type: Option<CodeType>,
262    /// Vector of accessed codes.
263    pub codes: Vec<bool>,
264    /// Stack of codes section that needs to be processed.
265    pub processing_stack: Vec<usize>,
266    /// Code accessed by subcontainer and expected subcontainer first code type.
267    /// EOF code can be invoked in EOFCREATE mode or used in RETURNCONTRACT opcode.
268    /// if SubContainer is called from EOFCREATE it needs to be ReturnContract type.
269    /// If SubContainer is called from RETURNCONTRACT it needs to be ReturnOrStop type.
270    ///
271    /// None means it is not accessed.
272    pub subcontainers: Vec<Option<CodeType>>,
273}
274
275impl AccessTracker {
276    /// Creates a new instance with the given container type and section sizes.
277    /// The first code section is marked as accessed and added to the processing stack.
278    ///
279    /// # Panics
280    ///
281    /// Panics if `codes_size` is zero.
282    pub fn new(
283        this_container_code_type: Option<CodeType>,
284        codes_size: usize,
285        subcontainers_size: usize,
286    ) -> Self {
287        if codes_size == 0 {
288            panic!("There should be at least one code section");
289        }
290        let mut this = Self {
291            this_container_code_type,
292            codes: vec![false; codes_size],
293            processing_stack: Vec::with_capacity(4),
294            subcontainers: vec![None; subcontainers_size],
295        };
296        this.codes[0] = true;
297        this.processing_stack.push(0);
298        this
299    }
300
301    /// Marks a code section as accessed and adds it to the processing stack if not previously accessed.
302    ///
303    /// # Panics
304    ///
305    /// Panics if the index is out of bounds.
306    pub fn access_code(&mut self, index: usize) {
307        let was_accessed = mem::replace(&mut self.codes[index], true);
308        if !was_accessed {
309            self.processing_stack.push(index);
310        }
311    }
312
313    /// Sets the code type for a subcontainer. If code type is already set check if it is the same.
314    /// In case of mismatch return error.
315    ///
316    /// # Panics
317    ///
318    /// Panics if the index is out of bounds.
319    pub fn set_subcontainer_type(
320        &mut self,
321        index: usize,
322        new_code_type: CodeType,
323    ) -> Result<(), EofValidationError> {
324        let Some(container) = self.subcontainers.get_mut(index) else {
325            panic!("It should not be possible")
326        };
327
328        let Some(code_type) = container else {
329            *container = Some(new_code_type);
330            return Ok(());
331        };
332
333        if *code_type != new_code_type {
334            return Err(EofValidationError::SubContainerCalledInTwoModes);
335        }
336        Ok(())
337    }
338}
339
340/// Types of code sections in EOF container
341///
342/// Container cannot mix RETURNCONTRACT with RETURN/STOP opcodes
343#[derive(Clone, Copy, Debug, PartialEq, Eq)]
344pub enum CodeType {
345    /// Code that initializes and returns a contract.
346    Initcode,
347    /// Runtime code that ends with RETURN or STOP opcodes.
348    Runtime,
349}
350
351impl CodeType {
352    /// Returns `true` of the code is initcode.
353    pub fn is_initcode(&self) -> bool {
354        matches!(self, CodeType::Initcode)
355    }
356}
357
358impl fmt::Display for EofValidationError {
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        let s = match self {
361            Self::FalsePositive => "False positive",
362            Self::UnknownOpcode => "Opcode is not known",
363            Self::OpcodeDisabled => "Opcode is disabled",
364            Self::InstructionNotForwardAccessed => "Should have forward jump",
365            Self::MissingImmediateBytes => "Bytecode is missing bytes",
366            Self::MissingRJUMPVImmediateBytes => "Bytecode is missing bytes after RJUMPV opcode",
367            Self::JumpToImmediateBytes => "Invalid jump",
368            Self::BackwardJumpToImmediateBytes => "Invalid backward jump",
369            Self::RJUMPVZeroMaxIndex => "Used RJUMPV with zero as MaxIndex",
370            Self::JumpZeroOffset => "Used JUMP with zero as offset",
371            Self::EOFCREATEInvalidIndex => "EOFCREATE points to out of bound index",
372            Self::CodeSectionOutOfBounds => "CALLF index is out of bounds",
373            Self::CALLFNonReturningFunction => "CALLF was used on non-returning function",
374            Self::StackOverflow => "CALLF stack overflow",
375            Self::JUMPFEnoughOutputs => "JUMPF needs more outputs",
376            Self::JUMPFStackHigherThanOutputs => "JUMPF stack is too high for outputs",
377            Self::DataLoadOutOfBounds => "DATALOAD is out of bounds",
378            Self::RETFBiggestStackNumMoreThenOutputs => {
379                "RETF biggest stack num is more than outputs"
380            }
381            Self::StackUnderflow => "Stack requirement is above smallest stack items",
382            Self::JumpUnderflow => "Jump destination is too low",
383            Self::JumpOverflow => "Jump destination is too high",
384            Self::BackwardJumpBiggestNumMismatch => {
385                "Backward jump has different biggest stack item"
386            }
387            Self::BackwardJumpSmallestNumMismatch => {
388                "Backward jump has different smallest stack item"
389            }
390            Self::LastInstructionNotTerminating => {
391                "Last instruction of bytecode is not terminating"
392            }
393            Self::CodeSectionNotAccessed => "Code section was not accessed",
394            Self::InvalidCodeInfo => "Invalid types section",
395            Self::InvalidFirstCodeInfo => "Invalid first types section",
396            Self::MaxStackMismatch => "Max stack element mismatches",
397            Self::NoCodeSections => "No code sections",
398            Self::SubContainerCalledInTwoModes => "Sub container called in two modes",
399            Self::SubContainerNotAccessed => "Sub container not accessed",
400            Self::DataNotFilled => "Data not filled",
401            Self::NonReturningSectionIsReturning => "Non returning section is returning",
402        };
403        f.write_str(s)
404    }
405}
406
407impl core::error::Error for EofValidationError {}
408
409/// Validates that:
410/// * All instructions are valid.
411/// * It ends with a terminating instruction or RJUMP.
412/// * All instructions are accessed by forward jumps or .
413///
414/// Validate stack requirements and if all codes sections are used.
415pub fn validate_eof_code(
416    code: &[u8],
417    data_size: usize,
418    this_types_index: usize,
419    num_of_containers: usize,
420    types: &[CodeInfo],
421    tracker: &mut AccessTracker,
422) -> Result<(), EofValidationError> {
423    let this_types = &types[this_types_index];
424
425    #[derive(Debug, Copy, Clone)]
426    struct InstructionInfo {
427        /// Is immediate byte, jumps can't happen on this part of code.
428        is_immediate: bool,
429        /// Have forward jump to this opcode. Used to check if opcode
430        /// after termination is accessed.
431        is_jumpdest: bool,
432        /// Smallest number of stack items accessed by jumps or sequential opcodes.
433        smallest: i32,
434        /// Biggest number of stack items accessed by jumps or sequential opcodes.
435        biggest: i32,
436    }
437
438    impl InstructionInfo {
439        #[inline]
440        fn mark_as_immediate(&mut self) -> Result<(), EofValidationError> {
441            if self.is_jumpdest {
442                // Jump to immediate bytes.
443                return Err(EofValidationError::JumpToImmediateBytes);
444            }
445            self.is_immediate = true;
446            Ok(())
447        }
448    }
449
450    impl Default for InstructionInfo {
451        fn default() -> Self {
452            Self {
453                is_immediate: false,
454                is_jumpdest: false,
455                smallest: i32::MAX,
456                biggest: i32::MIN,
457            }
458        }
459    }
460
461    // All bytes that are intermediate.
462    let mut jumps = vec![InstructionInfo::default(); code.len()];
463    let mut is_after_termination = false;
464
465    let mut next_smallest = this_types.inputs as i32;
466    let mut next_biggest = this_types.inputs as i32;
467
468    let mut is_returning = false;
469
470    let mut i = 0;
471    // We can check validity and jump destinations in one pass.
472    while i < code.len() {
473        let op = code[i];
474        let opcode = &OPCODE_INFO[op as usize];
475
476        let Some(opcode) = opcode else {
477            // Err unknown opcode.
478            return Err(EofValidationError::UnknownOpcode);
479        };
480
481        if opcode.is_disabled_in_eof() {
482            // Opcode is disabled in EOF
483            return Err(EofValidationError::OpcodeDisabled);
484        }
485
486        let this_instruction = &mut jumps[i];
487
488        // Update biggest/smallest values for next instruction only if it is not after termination.
489        if !is_after_termination {
490            this_instruction.smallest = core::cmp::min(this_instruction.smallest, next_smallest);
491            this_instruction.biggest = core::cmp::max(this_instruction.biggest, next_biggest);
492        }
493
494        let this_instruction = *this_instruction;
495
496        // Opcodes after termination should be accessed by forward jumps.
497        if is_after_termination && !this_instruction.is_jumpdest {
498            // Opcode after termination was not accessed.
499            return Err(EofValidationError::InstructionNotForwardAccessed);
500        }
501        is_after_termination = opcode.is_terminating();
502
503        // Mark immediate as non-jumpable. RJUMPV is special case covered later.
504        if opcode.immediate_size() != 0 {
505            // Check if the opcode immediate are within the bounds of the code
506            if i + opcode.immediate_size() as usize >= code.len() {
507                // Malfunctional code
508                return Err(EofValidationError::MissingImmediateBytes);
509            }
510
511            // Mark immediate bytes as non-jumpable.
512            for imm in 1..opcode.immediate_size() as usize + 1 {
513                // SAFETY: Immediate size is checked above.
514                jumps[i + imm].mark_as_immediate()?;
515            }
516        }
517        // IO diff used to generate next instruction smallest/biggest value.
518        let mut stack_io_diff = opcode.io_diff() as i32;
519        // How many stack items are required for this opcode.
520        let mut stack_requirement = opcode.inputs() as i32;
521        // Additional immediate bytes for RJUMPV, it has dynamic vtable.
522        let mut rjumpv_additional_immediates = 0;
523        // If opcodes is RJUMP, RJUMPI or RJUMPV then this will have absolute jumpdest.
524        let mut absolute_jumpdest = vec![];
525        match op {
526            opcode::RJUMP | opcode::RJUMPI => {
527                let offset = unsafe { read_i16(code.as_ptr().add(i + 1)) } as isize;
528                absolute_jumpdest = vec![offset + 3 + i as isize];
529                // RJUMP is considered a terminating opcode.
530            }
531            opcode::RJUMPV => {
532                // Code length for RJUMPV is checked with immediate size.
533                let max_index = code[i + 1] as usize;
534                let len = max_index + 1;
535                // And max_index+1 is to get size of vtable as index starts from 0.
536                rjumpv_additional_immediates = len * 2;
537
538                // +1 is for max_index byte
539                if i + 1 + rjumpv_additional_immediates >= code.len() {
540                    // Malfunctional code RJUMPV vtable is not complete
541                    return Err(EofValidationError::MissingRJUMPVImmediateBytes);
542                }
543
544                // Mark vtable as immediate, max_index was already marked.
545                for imm in 0..rjumpv_additional_immediates {
546                    // SAFETY: Immediate size is checked above.
547                    jumps[i + 2 + imm].mark_as_immediate()?;
548                }
549
550                let mut jumps = Vec::with_capacity(len);
551                for vtablei in 0..len {
552                    let offset =
553                        unsafe { read_i16(code.as_ptr().add(i + 2 + 2 * vtablei)) } as isize;
554                    jumps.push(offset + i as isize + 2 + rjumpv_additional_immediates as isize);
555                }
556                absolute_jumpdest = jumps
557            }
558            opcode::CALLF => {
559                let section_i: usize = unsafe { read_u16(code.as_ptr().add(i + 1)) } as usize;
560                let Some(target_types) = types.get(section_i) else {
561                    // Code section out of bounds.
562                    return Err(EofValidationError::CodeSectionOutOfBounds);
563                };
564
565                // CALLF operand must not point to a section with 0x80 as outputs (non-returning)
566                if target_types.is_non_returning() {
567                    return Err(EofValidationError::CALLFNonReturningFunction);
568                }
569                // Stack input for this opcode is the input of the called code.
570                stack_requirement = target_types.inputs as i32;
571                // Stack diff depends on input/output of the called code.
572                stack_io_diff = target_types.io_diff();
573                // Mark called code as accessed.
574                tracker.access_code(section_i);
575
576                if this_instruction.biggest + target_types.max_stack_increase as i32
577                    > STACK_LIMIT as i32
578                {
579                    // If stack max items + called code max stack size
580                    return Err(EofValidationError::StackOverflow);
581                }
582            }
583            opcode::JUMPF => {
584                let target_index = unsafe { read_u16(code.as_ptr().add(i + 1)) } as usize;
585                // Targeted code needs to have zero outputs (be non returning).
586                let Some(target_types) = types.get(target_index) else {
587                    // Code section out of bounds.
588                    return Err(EofValidationError::CodeSectionOutOfBounds);
589                };
590
591                if this_instruction.biggest + target_types.max_stack_increase as i32
592                    > STACK_LIMIT as i32
593                {
594                    // stack overflow
595                    return Err(EofValidationError::StackOverflow);
596                }
597                tracker.access_code(target_index);
598
599                if target_types.is_non_returning() {
600                    // If it is not returning
601                    stack_requirement = target_types.inputs as i32;
602                } else {
603                    is_returning = true;
604                    // Check if target code produces enough outputs.
605                    if this_types.outputs < target_types.outputs {
606                        return Err(EofValidationError::JUMPFEnoughOutputs);
607                    }
608
609                    stack_requirement = this_types.outputs as i32 + target_types.inputs as i32
610                        - target_types.outputs as i32;
611
612                    // Stack requirement needs to more than this instruction biggest stack number.
613                    if this_instruction.biggest > stack_requirement {
614                        return Err(EofValidationError::JUMPFStackHigherThanOutputs);
615                    }
616
617                    // If this instruction max + target_types max is more then stack limit.
618                    if this_instruction.biggest + stack_requirement > STACK_LIMIT as i32 {
619                        return Err(EofValidationError::StackOverflow);
620                    }
621                }
622            }
623            opcode::EOFCREATE => {
624                let index = code[i + 1] as usize;
625                if index >= num_of_containers {
626                    // Code section out of bounds.
627                    return Err(EofValidationError::EOFCREATEInvalidIndex);
628                }
629                tracker.set_subcontainer_type(index, CodeType::Initcode)?;
630            }
631            opcode::RETURNCONTRACT => {
632                let index = code[i + 1] as usize;
633                if index >= num_of_containers {
634                    // Code section out of bounds.
635                    // TODO : Custom error
636                    return Err(EofValidationError::EOFCREATEInvalidIndex);
637                }
638                if *tracker
639                    .this_container_code_type
640                    .get_or_insert(CodeType::Initcode)
641                    != CodeType::Initcode
642                {
643                    // TODO : Make custom error
644                    return Err(EofValidationError::SubContainerCalledInTwoModes);
645                }
646                tracker.set_subcontainer_type(index, CodeType::Runtime)?;
647            }
648            opcode::RETURN | opcode::STOP => {
649                if *tracker
650                    .this_container_code_type
651                    .get_or_insert(CodeType::Runtime)
652                    != CodeType::Runtime
653                {
654                    return Err(EofValidationError::SubContainerCalledInTwoModes);
655                }
656            }
657            opcode::DATALOADN => {
658                let index = unsafe { read_u16(code.as_ptr().add(i + 1)) } as isize;
659                if data_size < 32 || index > data_size as isize - 32 {
660                    // Data load out of bounds.
661                    return Err(EofValidationError::DataLoadOutOfBounds);
662                }
663            }
664            opcode::RETF => {
665                stack_requirement = this_types.outputs as i32;
666                // Mark section as returning.
667                is_returning = true;
668
669                if this_instruction.biggest > stack_requirement {
670                    return Err(EofValidationError::RETFBiggestStackNumMoreThenOutputs);
671                }
672            }
673            opcode::DUPN => {
674                stack_requirement = code[i + 1] as i32 + 1;
675            }
676            opcode::SWAPN => {
677                stack_requirement = code[i + 1] as i32 + 2;
678            }
679            opcode::EXCHANGE => {
680                let imm = code[i + 1];
681                let n = (imm >> 4) + 1;
682                let m = (imm & 0x0F) + 1;
683                stack_requirement = n as i32 + m as i32 + 1;
684            }
685            _ => {}
686        }
687        // Check if stack requirement is more than smallest stack items.
688        if stack_requirement > this_instruction.smallest {
689            // Opcode requirement is more than smallest stack items.
690            return Err(EofValidationError::StackUnderflow);
691        }
692
693        next_smallest = this_instruction.smallest + stack_io_diff;
694        next_biggest = this_instruction.biggest + stack_io_diff;
695
696        // Check if jumpdest are correct and mark forward jumps.
697        for absolute_jump in absolute_jumpdest {
698            if absolute_jump < 0 {
699                // Jump out of bounds.
700                return Err(EofValidationError::JumpUnderflow);
701            }
702            if absolute_jump >= code.len() as isize {
703                // Jump to out of bounds
704                return Err(EofValidationError::JumpOverflow);
705            }
706            // Fine to cast as bounds are checked.
707            let absolute_jump = absolute_jump as usize;
708
709            let target_jump = &mut jumps[absolute_jump];
710            if target_jump.is_immediate {
711                // Jump target is immediate byte.
712                return Err(EofValidationError::BackwardJumpToImmediateBytes);
713            }
714
715            // Needed to mark forward jumps. It does not do anything for backward jumps.
716            target_jump.is_jumpdest = true;
717
718            if absolute_jump <= i {
719                // Backward jumps should have same smallest and biggest stack items.
720                if target_jump.biggest != next_biggest {
721                    // Wrong jumpdest.
722                    return Err(EofValidationError::BackwardJumpBiggestNumMismatch);
723                }
724                if target_jump.smallest != next_smallest {
725                    // Wrong jumpdest.
726                    return Err(EofValidationError::BackwardJumpSmallestNumMismatch);
727                }
728            } else {
729                // Forward jumps can make min even smallest size
730                // While biggest num is needed to check stack overflow
731                target_jump.smallest = core::cmp::min(target_jump.smallest, next_smallest);
732                target_jump.biggest = core::cmp::max(target_jump.biggest, next_biggest);
733            }
734        }
735
736        // Additional immediate are from RJUMPV vtable.
737        i += 1 + opcode.immediate_size() as usize + rjumpv_additional_immediates;
738    }
739
740    // Error if section is returning but marked as non-returning.
741    if is_returning == this_types.is_non_returning() {
742        // Wrong termination.
743        return Err(EofValidationError::NonReturningSectionIsReturning);
744    }
745
746    // Last opcode should be terminating
747    if !is_after_termination {
748        // Wrong termination.
749        return Err(EofValidationError::LastInstructionNotTerminating);
750    }
751    // TODO : Integrate max so we dont need to iterate again
752    let this_code_info = &types[this_types_index];
753    let mut max_stack_requirement = 0;
754    for opcode in jumps {
755        max_stack_requirement = core::cmp::max(
756            opcode.biggest.saturating_sub(this_code_info.inputs as i32),
757            max_stack_requirement,
758        );
759    }
760
761    if max_stack_requirement != this_code_info.max_stack_increase as i32 {
762        // Stack overflow
763        return Err(EofValidationError::MaxStackMismatch);
764    }
765
766    Ok(())
767}
768
769#[cfg(test)]
770mod test {
771    use super::*;
772    use primitives::hex;
773
774    #[test]
775    fn test1() {
776        // result:Result { result: false, exception: Some("EOF_ConflictingStackHeight") }
777        let err =
778            validate_raw_eof(hex!("ef00010100040200010007ff000000008000016000e200fffc00").into());
779        assert!(err.is_err(), "{err:#?}");
780    }
781
782    #[test]
783    fn test2() {
784        // result:Result { result: false, exception: Some("EOF_InvalidNumberOfOutputs") }
785        let err =
786            validate_raw_eof_inner(hex!("ef000101000c020003000400040002ff000000008000020002000100010001e30001005fe500025fe4").into(),None);
787        assert!(err.is_ok(), "{err:#?}");
788    }
789
790    #[test]
791    fn test3() {
792        // result:Result { result: false, exception: Some("EOF_InvalidNumberOfOutputs") }
793        let err =
794            validate_raw_eof_inner(hex!("ef000101000c020003000400080003ff000000008000020002000503010003e30001005f5f5f5f5fe500025050e4").into(),None);
795        assert_eq!(
796            err,
797            Err(EofError::Validation(
798                EofValidationError::JUMPFStackHigherThanOutputs
799            ))
800        );
801    }
802
803    #[test]
804    fn test4() {
805        // result:Result { result: false, exception: Some("EOF_InvalidNumberOfOutputs") }
806        let err = validate_raw_eof(
807            hex!("ef0001010004020001000eff000000008000045f6000e100025f5f6000e1fffd00").into(),
808        );
809        assert_eq!(
810            err,
811            Err(EofError::Validation(
812                EofValidationError::BackwardJumpBiggestNumMismatch
813            ))
814        );
815    }
816
817    #[test]
818    fn test5() {
819        let err = validate_raw_eof(hex!("ef00010100040200010003ff00000000800000e5ffff").into());
820        assert_eq!(
821            err,
822            Err(EofError::Validation(
823                EofValidationError::CodeSectionOutOfBounds
824            ))
825        );
826    }
827
828    #[test]
829    fn size_limit() {
830        let eof = validate_raw_eof_inner(
831            hex!("ef00010100040200010003ff0001000080000130500000").into(),
832            Some(CodeType::Runtime),
833        );
834        assert!(eof.is_ok());
835    }
836
837    #[test]
838    fn test() {
839        let eof = validate_raw_eof_inner(
840            hex!("ef00010100040200010005ffff0300008000023a60cbee1800").into(),
841            None,
842        );
843        assert_eq!(
844            eof,
845            Err(EofError::Validation(EofValidationError::DataNotFilled))
846        );
847    }
848
849    #[test]
850    fn unreachable_code_section() {
851        let eof = validate_raw_eof_inner(
852            hex!("ef000101000c020003000300010003ff000000008000000080000000800000e50001fee50002")
853                .into(),
854            None,
855        );
856        assert_eq!(
857            eof,
858            Err(EofError::Validation(
859                EofValidationError::CodeSectionNotAccessed
860            ))
861        );
862    }
863
864    #[test]
865    fn non_returning_sections() {
866        let eof = validate_raw_eof_inner(
867            hex!("ef000101000c020003000400010003ff000000008000000080000000000000e300020000e50001")
868                .into(),
869            Some(CodeType::Runtime),
870        );
871        assert_eq!(
872            eof,
873            Err(EofError::Validation(
874                EofValidationError::NonReturningSectionIsReturning
875            ))
876        );
877    }
878
879    #[test]
880    fn incompatible_container_kind() {
881        let eof = validate_raw_eof_inner(
882            hex!("ef0001010004020001000603000100000014ff0000000080000260006000ee00ef00010100040200010001040000000080000000")
883                .into(),
884            Some(CodeType::Runtime),
885        );
886        assert_eq!(
887            eof,
888            Err(EofError::Validation(
889                EofValidationError::SubContainerCalledInTwoModes
890            ))
891        );
892    }
893}