revm_bytecode/eof/
verification.rs

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