1use crate::{
4 eof::{CodeInfo, Eof, EofDecodeError},
5 opcode::{self, OPCODE_INFO},
6 utils::{read_i16, read_u16},
7};
8use primitives::{
9 constants::{MAX_INITCODE_SIZE, STACK_LIMIT},
10 Bytes,
11};
12
13use core::{convert::identity, mem};
14use std::{borrow::Cow, fmt, vec, vec::Vec};
15
16pub fn validate_raw_eof(raw: Bytes) -> Result<Eof, EofError> {
18 validate_raw_eof_inner(raw, Some(CodeType::Initcode))
19}
20
21#[inline]
23pub fn validate_raw_eof_inner(
24 raw: Bytes,
25 first_code_type: Option<CodeType>,
26) -> Result<Eof, EofError> {
27 if raw.len() > MAX_INITCODE_SIZE {
28 return Err(EofError::Decode(EofDecodeError::InvalidEOFSize));
29 }
30 let eof = Eof::decode(raw)?;
31 validate_eof_inner(&eof, first_code_type)?;
32 Ok(eof)
33}
34
35pub fn validate_eof(eof: &Eof) -> Result<(), EofError> {
43 validate_eof_inner(eof, Some(CodeType::Initcode))
44}
45
46#[inline]
49pub fn validate_eof_inner(eof: &Eof, first_code_type: Option<CodeType>) -> Result<(), EofError> {
50 if !eof.body.is_data_filled {
52 return Err(EofError::Validation(EofValidationError::DataNotFilled));
53 }
54 if eof.body.container_section.is_empty() {
55 validate_eof_codes(eof, first_code_type)?;
56 return Ok(());
57 }
58
59 let mut stack = Vec::with_capacity(4);
60 stack.push((Cow::Borrowed(eof), first_code_type));
61
62 while let Some((eof, code_type)) = stack.pop() {
63 let tracker_containers = validate_eof_codes(&eof, code_type)?;
65 for (container, code_type) in eof
67 .body
68 .container_section
69 .iter()
70 .zip(tracker_containers.into_iter())
71 {
72 stack.push((Cow::Owned(Eof::decode(container.clone())?), Some(code_type)));
73 }
74 }
75
76 Ok(())
77}
78
79#[inline]
83pub fn validate_eof_codes(
84 eof: &Eof,
85 this_code_type: Option<CodeType>,
86) -> Result<Vec<CodeType>, EofValidationError> {
87 if eof.body.code_section.len() != eof.body.code_info.len() {
88 return Err(EofValidationError::InvalidCodeInfo);
89 }
90
91 if eof.body.code_section.is_empty() {
92 return Err(EofValidationError::NoCodeSections);
94 }
95
96 let first_types = &eof.body.code_info[0];
99 if first_types.inputs != 0 || !first_types.is_non_returning() {
100 return Err(EofValidationError::InvalidCodeInfo);
101 }
102
103 let mut tracker: AccessTracker = AccessTracker::new(
105 this_code_type,
106 eof.body.code_section.len(),
107 eof.body.container_section.len(),
108 );
109
110 while let Some(index) = tracker.processing_stack.pop() {
111 let code = eof.body.code(index).unwrap();
113 validate_eof_code(
114 &code,
115 eof.header.data_size as usize,
116 index,
117 eof.body.container_section.len(),
118 &eof.body.code_info,
119 &mut tracker,
120 )?;
121 }
122
123 if !tracker.codes.into_iter().all(identity) {
125 return Err(EofValidationError::CodeSectionNotAccessed);
126 }
127 if !tracker.subcontainers.iter().all(|i| i.is_some()) {
129 return Err(EofValidationError::SubContainerNotAccessed);
130 }
131
132 if tracker.this_container_code_type == Some(CodeType::Initcode) && !eof.body.is_data_filled {
133 return Err(EofValidationError::DataNotFilled);
134 }
135
136 Ok(tracker
137 .subcontainers
138 .into_iter()
139 .map(|i| i.unwrap())
140 .collect())
141}
142
143#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
145pub enum EofError {
146 Decode(EofDecodeError),
148 Validation(EofValidationError),
150}
151
152impl From<EofDecodeError> for EofError {
153 fn from(err: EofDecodeError) -> Self {
154 EofError::Decode(err)
155 }
156}
157
158impl From<EofValidationError> for EofError {
159 fn from(err: EofValidationError) -> Self {
160 EofError::Validation(err)
161 }
162}
163
164impl fmt::Display for EofError {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 EofError::Decode(e) => write!(f, "Bytecode decode error: {}", e),
168 EofError::Validation(e) => write!(f, "Bytecode validation error: {}", e),
169 }
170 }
171}
172
173impl core::error::Error for EofError {}
174
175#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
177pub enum EofValidationError {
178 FalsePositive,
180 UnknownOpcode,
182 OpcodeDisabled,
184 InstructionNotForwardAccessed,
190 MissingImmediateBytes,
192 MissingRJUMPVImmediateBytes,
196 JumpToImmediateBytes,
198 BackwardJumpToImmediateBytes,
200 RJUMPVZeroMaxIndex,
202 JumpZeroOffset,
204 EOFCREATEInvalidIndex,
206 CodeSectionOutOfBounds,
208 CALLFNonReturningFunction,
210 StackOverflow,
212 JUMPFEnoughOutputs,
214 JUMPFStackHigherThanOutputs,
216 DataLoadOutOfBounds,
218 RETFBiggestStackNumMoreThenOutputs,
220 StackUnderflow,
222 JumpUnderflow,
224 JumpOverflow,
226 BackwardJumpBiggestNumMismatch,
228 BackwardJumpSmallestNumMismatch,
230 LastInstructionNotTerminating,
232 CodeSectionNotAccessed,
234 InvalidCodeInfo,
236 InvalidFirstCodeInfo,
239 MaxStackMismatch,
241 NoCodeSections,
243 SubContainerCalledInTwoModes,
247 SubContainerNotAccessed,
249 DataNotFilled,
251 NonReturningSectionIsReturning,
254}
255
256#[derive(Clone, Debug, PartialEq, Eq)]
259pub struct AccessTracker {
260 pub this_container_code_type: Option<CodeType>,
262 pub codes: Vec<bool>,
264 pub processing_stack: Vec<usize>,
266 pub subcontainers: Vec<Option<CodeType>>,
273}
274
275impl AccessTracker {
276 pub fn new(
283 this_container_code_type: Option<CodeType>,
284 codes_size: usize,
285 subcontainers_size: usize,
286 ) -> Self {
287 if codes_size == 0 {
288 panic!("There should be at least one code section");
289 }
290 let mut this = Self {
291 this_container_code_type,
292 codes: vec![false; codes_size],
293 processing_stack: Vec::with_capacity(4),
294 subcontainers: vec![None; subcontainers_size],
295 };
296 this.codes[0] = true;
297 this.processing_stack.push(0);
298 this
299 }
300
301 pub fn access_code(&mut self, index: usize) {
307 let was_accessed = mem::replace(&mut self.codes[index], true);
308 if !was_accessed {
309 self.processing_stack.push(index);
310 }
311 }
312
313 pub fn set_subcontainer_type(
320 &mut self,
321 index: usize,
322 new_code_type: CodeType,
323 ) -> Result<(), EofValidationError> {
324 let Some(container) = self.subcontainers.get_mut(index) else {
325 panic!("It should not be possible")
326 };
327
328 let Some(code_type) = container else {
329 *container = Some(new_code_type);
330 return Ok(());
331 };
332
333 if *code_type != new_code_type {
334 return Err(EofValidationError::SubContainerCalledInTwoModes);
335 }
336 Ok(())
337 }
338}
339
340#[derive(Clone, Copy, Debug, PartialEq, Eq)]
344pub enum CodeType {
345 Initcode,
347 Runtime,
349}
350
351impl CodeType {
352 pub fn is_initcode(&self) -> bool {
354 matches!(self, CodeType::Initcode)
355 }
356}
357
358impl fmt::Display for EofValidationError {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 let s = match self {
361 Self::FalsePositive => "False positive",
362 Self::UnknownOpcode => "Opcode is not known",
363 Self::OpcodeDisabled => "Opcode is disabled",
364 Self::InstructionNotForwardAccessed => "Should have forward jump",
365 Self::MissingImmediateBytes => "Bytecode is missing bytes",
366 Self::MissingRJUMPVImmediateBytes => "Bytecode is missing bytes after RJUMPV opcode",
367 Self::JumpToImmediateBytes => "Invalid jump",
368 Self::BackwardJumpToImmediateBytes => "Invalid backward jump",
369 Self::RJUMPVZeroMaxIndex => "Used RJUMPV with zero as MaxIndex",
370 Self::JumpZeroOffset => "Used JUMP with zero as offset",
371 Self::EOFCREATEInvalidIndex => "EOFCREATE points to out of bound index",
372 Self::CodeSectionOutOfBounds => "CALLF index is out of bounds",
373 Self::CALLFNonReturningFunction => "CALLF was used on non-returning function",
374 Self::StackOverflow => "CALLF stack overflow",
375 Self::JUMPFEnoughOutputs => "JUMPF needs more outputs",
376 Self::JUMPFStackHigherThanOutputs => "JUMPF stack is too high for outputs",
377 Self::DataLoadOutOfBounds => "DATALOAD is out of bounds",
378 Self::RETFBiggestStackNumMoreThenOutputs => {
379 "RETF biggest stack num is more than outputs"
380 }
381 Self::StackUnderflow => "Stack requirement is above smallest stack items",
382 Self::JumpUnderflow => "Jump destination is too low",
383 Self::JumpOverflow => "Jump destination is too high",
384 Self::BackwardJumpBiggestNumMismatch => {
385 "Backward jump has different biggest stack item"
386 }
387 Self::BackwardJumpSmallestNumMismatch => {
388 "Backward jump has different smallest stack item"
389 }
390 Self::LastInstructionNotTerminating => {
391 "Last instruction of bytecode is not terminating"
392 }
393 Self::CodeSectionNotAccessed => "Code section was not accessed",
394 Self::InvalidCodeInfo => "Invalid types section",
395 Self::InvalidFirstCodeInfo => "Invalid first types section",
396 Self::MaxStackMismatch => "Max stack element mismatches",
397 Self::NoCodeSections => "No code sections",
398 Self::SubContainerCalledInTwoModes => "Sub container called in two modes",
399 Self::SubContainerNotAccessed => "Sub container not accessed",
400 Self::DataNotFilled => "Data not filled",
401 Self::NonReturningSectionIsReturning => "Non returning section is returning",
402 };
403 f.write_str(s)
404 }
405}
406
407impl core::error::Error for EofValidationError {}
408
409pub fn validate_eof_code(
416 code: &[u8],
417 data_size: usize,
418 this_types_index: usize,
419 num_of_containers: usize,
420 types: &[CodeInfo],
421 tracker: &mut AccessTracker,
422) -> Result<(), EofValidationError> {
423 let this_types = &types[this_types_index];
424
425 #[derive(Debug, Copy, Clone)]
426 struct InstructionInfo {
427 is_immediate: bool,
429 is_jumpdest: bool,
432 smallest: i32,
434 biggest: i32,
436 }
437
438 impl InstructionInfo {
439 #[inline]
440 fn mark_as_immediate(&mut self) -> Result<(), EofValidationError> {
441 if self.is_jumpdest {
442 return Err(EofValidationError::JumpToImmediateBytes);
444 }
445 self.is_immediate = true;
446 Ok(())
447 }
448 }
449
450 impl Default for InstructionInfo {
451 fn default() -> Self {
452 Self {
453 is_immediate: false,
454 is_jumpdest: false,
455 smallest: i32::MAX,
456 biggest: i32::MIN,
457 }
458 }
459 }
460
461 let mut jumps = vec![InstructionInfo::default(); code.len()];
463 let mut is_after_termination = false;
464
465 let mut next_smallest = this_types.inputs as i32;
466 let mut next_biggest = this_types.inputs as i32;
467
468 let mut is_returning = false;
469
470 let mut i = 0;
471 while i < code.len() {
473 let op = code[i];
474 let opcode = &OPCODE_INFO[op as usize];
475
476 let Some(opcode) = opcode else {
477 return Err(EofValidationError::UnknownOpcode);
479 };
480
481 if opcode.is_disabled_in_eof() {
482 return Err(EofValidationError::OpcodeDisabled);
484 }
485
486 let this_instruction = &mut jumps[i];
487
488 if !is_after_termination {
490 this_instruction.smallest = core::cmp::min(this_instruction.smallest, next_smallest);
491 this_instruction.biggest = core::cmp::max(this_instruction.biggest, next_biggest);
492 }
493
494 let this_instruction = *this_instruction;
495
496 if is_after_termination && !this_instruction.is_jumpdest {
498 return Err(EofValidationError::InstructionNotForwardAccessed);
500 }
501 is_after_termination = opcode.is_terminating();
502
503 if opcode.immediate_size() != 0 {
505 if i + opcode.immediate_size() as usize >= code.len() {
507 return Err(EofValidationError::MissingImmediateBytes);
509 }
510
511 for imm in 1..opcode.immediate_size() as usize + 1 {
513 jumps[i + imm].mark_as_immediate()?;
515 }
516 }
517 let mut stack_io_diff = opcode.io_diff() as i32;
519 let mut stack_requirement = opcode.inputs() as i32;
521 let mut rjumpv_additional_immediates = 0;
523 let mut absolute_jumpdest = vec![];
525 match op {
526 opcode::RJUMP | opcode::RJUMPI => {
527 let offset = unsafe { read_i16(code.as_ptr().add(i + 1)) } as isize;
528 absolute_jumpdest = vec![offset + 3 + i as isize];
529 }
531 opcode::RJUMPV => {
532 let max_index = code[i + 1] as usize;
534 let len = max_index + 1;
535 rjumpv_additional_immediates = len * 2;
537
538 if i + 1 + rjumpv_additional_immediates >= code.len() {
540 return Err(EofValidationError::MissingRJUMPVImmediateBytes);
542 }
543
544 for imm in 0..rjumpv_additional_immediates {
546 jumps[i + 2 + imm].mark_as_immediate()?;
548 }
549
550 let mut jumps = Vec::with_capacity(len);
551 for vtablei in 0..len {
552 let offset =
553 unsafe { read_i16(code.as_ptr().add(i + 2 + 2 * vtablei)) } as isize;
554 jumps.push(offset + i as isize + 2 + rjumpv_additional_immediates as isize);
555 }
556 absolute_jumpdest = jumps
557 }
558 opcode::CALLF => {
559 let section_i: usize = unsafe { read_u16(code.as_ptr().add(i + 1)) } as usize;
560 let Some(target_types) = types.get(section_i) else {
561 return Err(EofValidationError::CodeSectionOutOfBounds);
563 };
564
565 if target_types.is_non_returning() {
567 return Err(EofValidationError::CALLFNonReturningFunction);
568 }
569 stack_requirement = target_types.inputs as i32;
571 stack_io_diff = target_types.io_diff();
573 tracker.access_code(section_i);
575
576 if this_instruction.biggest + target_types.max_stack_increase as i32
577 > STACK_LIMIT as i32
578 {
579 return Err(EofValidationError::StackOverflow);
581 }
582 }
583 opcode::JUMPF => {
584 let target_index = unsafe { read_u16(code.as_ptr().add(i + 1)) } as usize;
585 let Some(target_types) = types.get(target_index) else {
587 return Err(EofValidationError::CodeSectionOutOfBounds);
589 };
590
591 if this_instruction.biggest + target_types.max_stack_increase as i32
592 > STACK_LIMIT as i32
593 {
594 return Err(EofValidationError::StackOverflow);
596 }
597 tracker.access_code(target_index);
598
599 if target_types.is_non_returning() {
600 stack_requirement = target_types.inputs as i32;
602 } else {
603 is_returning = true;
604 if this_types.outputs < target_types.outputs {
606 return Err(EofValidationError::JUMPFEnoughOutputs);
607 }
608
609 stack_requirement = this_types.outputs as i32 + target_types.inputs as i32
610 - target_types.outputs as i32;
611
612 if this_instruction.biggest > stack_requirement {
614 return Err(EofValidationError::JUMPFStackHigherThanOutputs);
615 }
616
617 if this_instruction.biggest + stack_requirement > STACK_LIMIT as i32 {
619 return Err(EofValidationError::StackOverflow);
620 }
621 }
622 }
623 opcode::EOFCREATE => {
624 let index = code[i + 1] as usize;
625 if index >= num_of_containers {
626 return Err(EofValidationError::EOFCREATEInvalidIndex);
628 }
629 tracker.set_subcontainer_type(index, CodeType::Initcode)?;
630 }
631 opcode::RETURNCONTRACT => {
632 let index = code[i + 1] as usize;
633 if index >= num_of_containers {
634 return Err(EofValidationError::EOFCREATEInvalidIndex);
637 }
638 if *tracker
639 .this_container_code_type
640 .get_or_insert(CodeType::Initcode)
641 != CodeType::Initcode
642 {
643 return Err(EofValidationError::SubContainerCalledInTwoModes);
645 }
646 tracker.set_subcontainer_type(index, CodeType::Runtime)?;
647 }
648 opcode::RETURN | opcode::STOP => {
649 if *tracker
650 .this_container_code_type
651 .get_or_insert(CodeType::Runtime)
652 != CodeType::Runtime
653 {
654 return Err(EofValidationError::SubContainerCalledInTwoModes);
655 }
656 }
657 opcode::DATALOADN => {
658 let index = unsafe { read_u16(code.as_ptr().add(i + 1)) } as isize;
659 if data_size < 32 || index > data_size as isize - 32 {
660 return Err(EofValidationError::DataLoadOutOfBounds);
662 }
663 }
664 opcode::RETF => {
665 stack_requirement = this_types.outputs as i32;
666 is_returning = true;
668
669 if this_instruction.biggest > stack_requirement {
670 return Err(EofValidationError::RETFBiggestStackNumMoreThenOutputs);
671 }
672 }
673 opcode::DUPN => {
674 stack_requirement = code[i + 1] as i32 + 1;
675 }
676 opcode::SWAPN => {
677 stack_requirement = code[i + 1] as i32 + 2;
678 }
679 opcode::EXCHANGE => {
680 let imm = code[i + 1];
681 let n = (imm >> 4) + 1;
682 let m = (imm & 0x0F) + 1;
683 stack_requirement = n as i32 + m as i32 + 1;
684 }
685 _ => {}
686 }
687 if stack_requirement > this_instruction.smallest {
689 return Err(EofValidationError::StackUnderflow);
691 }
692
693 next_smallest = this_instruction.smallest + stack_io_diff;
694 next_biggest = this_instruction.biggest + stack_io_diff;
695
696 for absolute_jump in absolute_jumpdest {
698 if absolute_jump < 0 {
699 return Err(EofValidationError::JumpUnderflow);
701 }
702 if absolute_jump >= code.len() as isize {
703 return Err(EofValidationError::JumpOverflow);
705 }
706 let absolute_jump = absolute_jump as usize;
708
709 let target_jump = &mut jumps[absolute_jump];
710 if target_jump.is_immediate {
711 return Err(EofValidationError::BackwardJumpToImmediateBytes);
713 }
714
715 target_jump.is_jumpdest = true;
717
718 if absolute_jump <= i {
719 if target_jump.biggest != next_biggest {
721 return Err(EofValidationError::BackwardJumpBiggestNumMismatch);
723 }
724 if target_jump.smallest != next_smallest {
725 return Err(EofValidationError::BackwardJumpSmallestNumMismatch);
727 }
728 } else {
729 target_jump.smallest = core::cmp::min(target_jump.smallest, next_smallest);
732 target_jump.biggest = core::cmp::max(target_jump.biggest, next_biggest);
733 }
734 }
735
736 i += 1 + opcode.immediate_size() as usize + rjumpv_additional_immediates;
738 }
739
740 if is_returning == this_types.is_non_returning() {
742 return Err(EofValidationError::NonReturningSectionIsReturning);
744 }
745
746 if !is_after_termination {
748 return Err(EofValidationError::LastInstructionNotTerminating);
750 }
751 let this_code_info = &types[this_types_index];
753 let mut max_stack_requirement = 0;
754 for opcode in jumps {
755 max_stack_requirement = core::cmp::max(
756 opcode.biggest.saturating_sub(this_code_info.inputs as i32),
757 max_stack_requirement,
758 );
759 }
760
761 if max_stack_requirement != this_code_info.max_stack_increase as i32 {
762 return Err(EofValidationError::MaxStackMismatch);
764 }
765
766 Ok(())
767}
768
769#[cfg(test)]
770mod test {
771 use super::*;
772 use primitives::hex;
773
774 #[test]
775 fn test1() {
776 let err =
778 validate_raw_eof(hex!("ef00010100040200010007ff000000008000016000e200fffc00").into());
779 assert!(err.is_err(), "{err:#?}");
780 }
781
782 #[test]
783 fn test2() {
784 let err =
786 validate_raw_eof_inner(hex!("ef000101000c020003000400040002ff000000008000020002000100010001e30001005fe500025fe4").into(),None);
787 assert!(err.is_ok(), "{err:#?}");
788 }
789
790 #[test]
791 fn test3() {
792 let err =
794 validate_raw_eof_inner(hex!("ef000101000c020003000400080003ff000000008000020002000503010003e30001005f5f5f5f5fe500025050e4").into(),None);
795 assert_eq!(
796 err,
797 Err(EofError::Validation(
798 EofValidationError::JUMPFStackHigherThanOutputs
799 ))
800 );
801 }
802
803 #[test]
804 fn test4() {
805 let err = validate_raw_eof(
807 hex!("ef0001010004020001000eff000000008000045f6000e100025f5f6000e1fffd00").into(),
808 );
809 assert_eq!(
810 err,
811 Err(EofError::Validation(
812 EofValidationError::BackwardJumpBiggestNumMismatch
813 ))
814 );
815 }
816
817 #[test]
818 fn test5() {
819 let err = validate_raw_eof(hex!("ef00010100040200010003ff00000000800000e5ffff").into());
820 assert_eq!(
821 err,
822 Err(EofError::Validation(
823 EofValidationError::CodeSectionOutOfBounds
824 ))
825 );
826 }
827
828 #[test]
829 fn size_limit() {
830 let eof = validate_raw_eof_inner(
831 hex!("ef00010100040200010003ff0001000080000130500000").into(),
832 Some(CodeType::Runtime),
833 );
834 assert!(eof.is_ok());
835 }
836
837 #[test]
838 fn test() {
839 let eof = validate_raw_eof_inner(
840 hex!("ef00010100040200010005ffff0300008000023a60cbee1800").into(),
841 None,
842 );
843 assert_eq!(
844 eof,
845 Err(EofError::Validation(EofValidationError::DataNotFilled))
846 );
847 }
848
849 #[test]
850 fn unreachable_code_section() {
851 let eof = validate_raw_eof_inner(
852 hex!("ef000101000c020003000300010003ff000000008000000080000000800000e50001fee50002")
853 .into(),
854 None,
855 );
856 assert_eq!(
857 eof,
858 Err(EofError::Validation(
859 EofValidationError::CodeSectionNotAccessed
860 ))
861 );
862 }
863
864 #[test]
865 fn non_returning_sections() {
866 let eof = validate_raw_eof_inner(
867 hex!("ef000101000c020003000400010003ff000000008000000080000000000000e300020000e50001")
868 .into(),
869 Some(CodeType::Runtime),
870 );
871 assert_eq!(
872 eof,
873 Err(EofError::Validation(
874 EofValidationError::NonReturningSectionIsReturning
875 ))
876 );
877 }
878
879 #[test]
880 fn incompatible_container_kind() {
881 let eof = validate_raw_eof_inner(
882 hex!("ef0001010004020001000603000100000014ff0000000080000260006000ee00ef00010100040200010001040000000080000000")
883 .into(),
884 Some(CodeType::Runtime),
885 );
886 assert_eq!(
887 eof,
888 Err(EofError::Validation(
889 EofValidationError::SubContainerCalledInTwoModes
890 ))
891 );
892 }
893}