revm_bytecode/eof/
verification.rs

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