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
12pub fn validate_raw_eof(raw: Bytes) -> Result<Eof, EofError> {
14 validate_raw_eof_inner(raw, Some(CodeType::ReturnContract))
15}
16
17#[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
31pub 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 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 let tracker_containers = validate_eof_codes(&eof, code_type)?;
59 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#[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 return Err(EofValidationError::NoCodeSections);
88 }
89
90 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 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 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 if !tracker.codes.into_iter().all(identity) {
119 return Err(EofValidationError::CodeSectionNotAccessed);
120 }
121 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#[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#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
171pub enum EofValidationError {
172 FalsePositive,
173 UnknownOpcode,
175 OpcodeDisabled,
177 InstructionNotForwardAccessed,
183 MissingImmediateBytes,
185 MissingRJUMPVImmediateBytes,
189 JumpToImmediateBytes,
191 BackwardJumpToImmediateBytes,
193 RJUMPVZeroMaxIndex,
195 JumpZeroOffset,
197 EOFCREATEInvalidIndex,
199 CodeSectionOutOfBounds,
201 CALLFNonReturningFunction,
203 StackOverflow,
205 JUMPFEnoughOutputs,
207 JUMPFStackHigherThanOutputs,
209 DataLoadOutOfBounds,
211 RETFBiggestStackNumMoreThenOutputs,
213 StackUnderflow,
215 TypesStackUnderflow,
217 JumpUnderflow,
219 JumpOverflow,
221 BackwardJumpBiggestNumMismatch,
223 BackwardJumpSmallestNumMismatch,
225 LastInstructionNotTerminating,
227 CodeSectionNotAccessed,
229 InvalidTypesSection,
231 InvalidFirstTypesSection,
234 MaxStackMismatch,
236 NoCodeSections,
238 SubContainerCalledInTwoModes,
242 SubContainerNotAccessed,
244 DataNotFilled,
246 NonReturningSectionIsReturning,
249}
250
251#[derive(Clone, Debug, PartialEq, Eq)]
252pub struct AccessTracker {
253 pub this_container_code_type: Option<CodeType>,
255 pub codes: Vec<bool>,
257 pub processing_stack: Vec<usize>,
259 pub subcontainers: Vec<Option<CodeType>>,
266}
267
268impl AccessTracker {
269 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
333pub enum CodeType {
334 ReturnContract,
336 ReturnOrStop,
338}
339
340impl CodeType {
341 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
399pub 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: bool,
419 is_jumpdest: bool,
422 smallest: i32,
424 biggest: i32,
426 }
427
428 impl InstructionInfo {
429 #[inline]
430 fn mark_as_immediate(&mut self) -> Result<(), EofValidationError> {
431 if self.is_jumpdest {
432 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 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 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 return Err(EofValidationError::UnknownOpcode);
469 };
470
471 if opcode.is_disabled_in_eof() {
472 return Err(EofValidationError::OpcodeDisabled);
474 }
475
476 let this_instruction = &mut jumps[i];
477
478 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 if is_after_termination && !this_instruction.is_jumpdest {
488 return Err(EofValidationError::InstructionNotForwardAccessed);
490 }
491 is_after_termination = opcode.is_terminating();
492
493 if opcode.immediate_size() != 0 {
495 if i + opcode.immediate_size() as usize >= code.len() {
497 return Err(EofValidationError::MissingImmediateBytes);
499 }
500
501 for imm in 1..opcode.immediate_size() as usize + 1 {
503 jumps[i + imm].mark_as_immediate()?;
505 }
506 }
507 let mut stack_io_diff = opcode.io_diff() as i32;
509 let mut stack_requirement = opcode.inputs() as i32;
511 let mut rjumpv_additional_immediates = 0;
513 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 }
521 opcode::RJUMPV => {
522 let max_index = code[i + 1] as usize;
524 let len = max_index + 1;
525 rjumpv_additional_immediates = len * 2;
527
528 if i + 1 + rjumpv_additional_immediates >= code.len() {
530 return Err(EofValidationError::MissingRJUMPVImmediateBytes);
532 }
533
534 for imm in 0..rjumpv_additional_immediates {
536 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 return Err(EofValidationError::CodeSectionOutOfBounds);
553 };
554
555 if target_types.is_non_returning() {
557 return Err(EofValidationError::CALLFNonReturningFunction);
558 }
559 stack_requirement = target_types.inputs as i32;
561 stack_io_diff = target_types.io_diff();
563 tracker.access_code(section_i);
565
566 if this_instruction.biggest - stack_requirement + target_types.max_stack_size as i32
569 > STACK_LIMIT as i32
570 {
571 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 let Some(target_types) = types.get(target_index) else {
579 return Err(EofValidationError::CodeSectionOutOfBounds);
581 };
582
583 if this_instruction.biggest - target_types.inputs as i32
586 + target_types.max_stack_size as i32
587 > STACK_LIMIT as i32
588 {
589 return Err(EofValidationError::StackOverflow);
591 }
592 tracker.access_code(target_index);
593
594 if target_types.is_non_returning() {
595 stack_requirement = target_types.inputs as i32;
597 } else {
598 is_returning = true;
599 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 if this_instruction.biggest > stack_requirement {
609 return Err(EofValidationError::JUMPFStackHigherThanOutputs);
610 }
611
612 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 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 return Err(EofValidationError::EOFCREATEInvalidIndex);
632 }
633 if *tracker
634 .this_container_code_type
635 .get_or_insert(CodeType::ReturnContract)
636 != CodeType::ReturnContract
637 {
638 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 return Err(EofValidationError::DataLoadOutOfBounds);
657 }
658 }
659 opcode::RETF => {
660 stack_requirement = this_types.outputs as i32;
661 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 if stack_requirement > this_instruction.smallest {
684 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 for absolute_jump in absolute_jumpdest {
693 if absolute_jump < 0 {
694 return Err(EofValidationError::JumpUnderflow);
696 }
697 if absolute_jump >= code.len() as isize {
698 return Err(EofValidationError::JumpOverflow);
700 }
701 let absolute_jump = absolute_jump as usize;
703
704 let target_jump = &mut jumps[absolute_jump];
705 if target_jump.is_immediate {
706 return Err(EofValidationError::BackwardJumpToImmediateBytes);
708 }
709
710 target_jump.is_jumpdest = true;
712
713 if absolute_jump <= i {
714 if target_jump.biggest != next_biggest {
716 return Err(EofValidationError::BackwardJumpBiggestNumMismatch);
718 }
719 if target_jump.smallest != next_smallest {
720 return Err(EofValidationError::BackwardJumpSmallestNumMismatch);
722 }
723 } else {
724 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 i += 1 + opcode.immediate_size() as usize + rjumpv_additional_immediates;
733 }
734
735 if is_returning == this_types.is_non_returning() {
737 return Err(EofValidationError::NonReturningSectionIsReturning);
739 }
740
741 if !is_after_termination {
743 return Err(EofValidationError::LastInstructionNotTerminating);
745 }
746 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 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 let err =
769 validate_raw_eof(hex!("ef0001010004020001000704000000008000016000e200fffc00").into());
770 assert!(err.is_err(), "{err:#?}");
771 }
772
773 #[test]
774 fn test2() {
775 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 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 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}