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
14pub fn validate_raw_eof(raw: Bytes) -> Result<Eof, EofError> {
16 validate_raw_eof_inner(raw, Some(CodeType::Initcode))
17}
18
19#[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
33pub 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 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 let tracker_containers = validate_eof_codes(&eof, code_type)?;
61 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#[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 return Err(EofValidationError::NoCodeSections);
90 }
91
92 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 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 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 if !tracker.codes.into_iter().all(identity) {
121 return Err(EofValidationError::CodeSectionNotAccessed);
122 }
123 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#[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 JumpUnderflow,
217 JumpOverflow,
219 BackwardJumpBiggestNumMismatch,
221 BackwardJumpSmallestNumMismatch,
223 LastInstructionNotTerminating,
225 CodeSectionNotAccessed,
227 InvalidCodeInfo,
229 InvalidFirstCodeInfo,
232 MaxStackMismatch,
234 NoCodeSections,
236 SubContainerCalledInTwoModes,
240 SubContainerNotAccessed,
242 DataNotFilled,
244 NonReturningSectionIsReturning,
247}
248
249#[derive(Clone, Debug, PartialEq, Eq)]
250pub struct AccessTracker {
251 pub this_container_code_type: Option<CodeType>,
253 pub codes: Vec<bool>,
255 pub processing_stack: Vec<usize>,
257 pub subcontainers: Vec<Option<CodeType>>,
264}
265
266impl AccessTracker {
267 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
329pub enum CodeType {
330 Initcode,
332 Runtime,
334}
335
336impl CodeType {
337 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
394pub 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: bool,
414 is_jumpdest: bool,
417 smallest: i32,
419 biggest: i32,
421 }
422
423 impl InstructionInfo {
424 #[inline]
425 fn mark_as_immediate(&mut self) -> Result<(), EofValidationError> {
426 if self.is_jumpdest {
427 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 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 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 return Err(EofValidationError::UnknownOpcode);
464 };
465
466 if opcode.is_disabled_in_eof() {
467 return Err(EofValidationError::OpcodeDisabled);
469 }
470
471 let this_instruction = &mut jumps[i];
472
473 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 if is_after_termination && !this_instruction.is_jumpdest {
483 return Err(EofValidationError::InstructionNotForwardAccessed);
485 }
486 is_after_termination = opcode.is_terminating();
487
488 if opcode.immediate_size() != 0 {
490 if i + opcode.immediate_size() as usize >= code.len() {
492 return Err(EofValidationError::MissingImmediateBytes);
494 }
495
496 for imm in 1..opcode.immediate_size() as usize + 1 {
498 jumps[i + imm].mark_as_immediate()?;
500 }
501 }
502 let mut stack_io_diff = opcode.io_diff() as i32;
504 let mut stack_requirement = opcode.inputs() as i32;
506 let mut rjumpv_additional_immediates = 0;
508 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 }
516 opcode::RJUMPV => {
517 let max_index = code[i + 1] as usize;
519 let len = max_index + 1;
520 rjumpv_additional_immediates = len * 2;
522
523 if i + 1 + rjumpv_additional_immediates >= code.len() {
525 return Err(EofValidationError::MissingRJUMPVImmediateBytes);
527 }
528
529 for imm in 0..rjumpv_additional_immediates {
531 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 return Err(EofValidationError::CodeSectionOutOfBounds);
548 };
549
550 if target_types.is_non_returning() {
552 return Err(EofValidationError::CALLFNonReturningFunction);
553 }
554 stack_requirement = target_types.inputs as i32;
556 stack_io_diff = target_types.io_diff();
558 tracker.access_code(section_i);
560
561 if this_instruction.biggest - stack_requirement + target_types.max_stack_size as i32
564 > STACK_LIMIT as i32
565 {
566 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 let Some(target_types) = types.get(target_index) else {
574 return Err(EofValidationError::CodeSectionOutOfBounds);
576 };
577
578 if this_instruction.biggest - target_types.inputs as i32
581 + target_types.max_stack_size as i32
582 > STACK_LIMIT as i32
583 {
584 return Err(EofValidationError::StackOverflow);
586 }
587 tracker.access_code(target_index);
588
589 if target_types.is_non_returning() {
590 stack_requirement = target_types.inputs as i32;
592 } else {
593 is_returning = true;
594 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 if this_instruction.biggest > stack_requirement {
604 return Err(EofValidationError::JUMPFStackHigherThanOutputs);
605 }
606
607 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 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 return Err(EofValidationError::EOFCREATEInvalidIndex);
627 }
628 if *tracker
629 .this_container_code_type
630 .get_or_insert(CodeType::Initcode)
631 != CodeType::Initcode
632 {
633 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 return Err(EofValidationError::DataLoadOutOfBounds);
652 }
653 }
654 opcode::RETF => {
655 stack_requirement = this_types.outputs as i32;
656 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 if stack_requirement > this_instruction.smallest {
679 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 for absolute_jump in absolute_jumpdest {
688 if absolute_jump < 0 {
689 return Err(EofValidationError::JumpUnderflow);
691 }
692 if absolute_jump >= code.len() as isize {
693 return Err(EofValidationError::JumpOverflow);
695 }
696 let absolute_jump = absolute_jump as usize;
698
699 let target_jump = &mut jumps[absolute_jump];
700 if target_jump.is_immediate {
701 return Err(EofValidationError::BackwardJumpToImmediateBytes);
703 }
704
705 target_jump.is_jumpdest = true;
707
708 if absolute_jump <= i {
709 if target_jump.biggest != next_biggest {
711 return Err(EofValidationError::BackwardJumpBiggestNumMismatch);
713 }
714 if target_jump.smallest != next_smallest {
715 return Err(EofValidationError::BackwardJumpSmallestNumMismatch);
717 }
718 } else {
719 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 i += 1 + opcode.immediate_size() as usize + rjumpv_additional_immediates;
728 }
729
730 if is_returning == this_types.is_non_returning() {
732 return Err(EofValidationError::NonReturningSectionIsReturning);
734 }
735
736 if !is_after_termination {
738 return Err(EofValidationError::LastInstructionNotTerminating);
740 }
741 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 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 let err =
764 validate_raw_eof(hex!("ef0001010004020001000704000000008000016000e200fffc00").into());
765 assert!(err.is_err(), "{err:#?}");
766 }
767
768 #[test]
769 fn test2() {
770 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 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 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}