use crate::{Fork, OpcodeRegistry};
use std::collections::{HashMap, HashSet};
pub fn validate_registry(registry: &OpcodeRegistry) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
errors.extend(validate_opcode_uniqueness(registry));
errors.extend(validate_fork_inheritance(registry));
errors.extend(validate_historical_accuracy(registry));
errors.extend(validate_gas_cost_consistency(registry));
errors.extend(validate_stack_consistency(registry));
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn validate_opcode_uniqueness(registry: &OpcodeRegistry) -> Vec<String> {
let mut errors = Vec::new();
for (fork, opcodes) in ®istry.opcodes {
let mut seen = HashSet::new();
for &opcode_byte in opcodes.keys() {
if !seen.insert(opcode_byte) {
errors.push(format!(
"Duplicate opcode 0x{opcode_byte:02x} found in fork {fork:?}"
));
}
}
}
errors
}
fn validate_fork_inheritance(registry: &OpcodeRegistry) -> Vec<String> {
let mut errors = Vec::new();
let fork_order = [
Fork::Frontier,
Fork::Homestead,
Fork::Byzantium,
Fork::Constantinople,
Fork::Istanbul,
Fork::Berlin,
Fork::London,
Fork::Shanghai,
Fork::Cancun,
];
for i in 1..fork_order.len() {
let current_fork = fork_order[i];
let previous_fork = fork_order[i - 1];
let current_opcodes = registry.get_opcodes(current_fork);
let previous_opcodes = registry.get_opcodes(previous_fork);
for (opcode_byte, metadata) in &previous_opcodes {
if metadata.introduced_in <= previous_fork && !current_opcodes.contains_key(opcode_byte)
{
errors.push(format!(
"Opcode 0x{:02x} ({}) missing from fork {:?} but exists in {:?}",
opcode_byte, metadata.name, current_fork, previous_fork
));
}
}
}
errors
}
fn validate_historical_accuracy(registry: &OpcodeRegistry) -> Vec<String> {
let mut errors = Vec::new();
let known_introductions = [
(0xf4, Fork::Homestead, "DELEGATECALL"),
(0x3d, Fork::Byzantium, "RETURNDATASIZE"),
(0x3e, Fork::Byzantium, "RETURNDATACOPY"),
(0xfa, Fork::Byzantium, "STATICCALL"),
(0xfd, Fork::Byzantium, "REVERT"),
(0x1b, Fork::Constantinople, "SHL"),
(0x1c, Fork::Constantinople, "SHR"),
(0x1d, Fork::Constantinople, "SAR"),
(0x3f, Fork::Constantinople, "EXTCODEHASH"),
(0xf5, Fork::Constantinople, "CREATE2"),
(0x5f, Fork::Shanghai, "PUSH0"),
(0x5c, Fork::Cancun, "TLOAD"),
(0x5d, Fork::Cancun, "TSTORE"),
(0x5e, Fork::Cancun, "MCOPY"),
(0x49, Fork::Cancun, "BLOBHASH"),
(0x4a, Fork::Cancun, "BLOBBASEFEE"),
];
for (opcode_byte, expected_fork, name) in &known_introductions {
let all_opcodes = registry.get_opcodes(Fork::Cancun);
if let Some(metadata) = all_opcodes.get(opcode_byte) {
if metadata.introduced_in != *expected_fork {
errors.push(format!(
"Opcode 0x{:02x} ({}) should be introduced in {:?} but found in {:?}",
opcode_byte, name, expected_fork, metadata.introduced_in
));
}
} else {
errors.push(format!(
"Missing expected opcode 0x{opcode_byte:02x} ({name}) introduced in {expected_fork:?}"
));
}
}
errors
}
fn validate_gas_cost_consistency(registry: &OpcodeRegistry) -> Vec<String> {
let mut errors = Vec::new();
for (fork, opcodes) in ®istry.opcodes {
for (opcode_byte, metadata) in opcodes {
if metadata.gas_cost > 50000 {
errors.push(format!(
"Unusually high gas cost {} for opcode 0x{:02x} ({}) in fork {:?}",
metadata.gas_cost, opcode_byte, metadata.name, fork
));
}
let mut last_fork = None;
for (gas_fork, _) in metadata.gas_history {
if let Some(last) = last_fork {
if gas_fork < &last {
errors.push(format!(
"Gas history for opcode 0x{:02x} ({}) is not in chronological order",
opcode_byte, metadata.name
));
}
}
last_fork = Some(*gas_fork);
}
}
}
errors
}
fn validate_stack_consistency(registry: &OpcodeRegistry) -> Vec<String> {
let mut errors = Vec::new();
for opcodes in registry.opcodes.values() {
for (opcode_byte, metadata) in opcodes {
if metadata.stack_inputs > 17 {
errors.push(format!(
"Opcode 0x{:02x} ({}) has more than 17 stack inputs ({}), which exceeds EVM stack limit",
opcode_byte, metadata.name, metadata.stack_inputs
));
}
if metadata.stack_outputs > 1 && !matches!(opcode_byte, 0x80..=0x8f) {
errors.push(format!(
"Non-DUP opcode 0x{:02x} ({}) produces {} stack outputs",
opcode_byte, metadata.name, metadata.stack_outputs
));
}
if (0x80..=0x8f).contains(opcode_byte) {
let dup_num = opcode_byte - 0x7f;
if metadata.stack_inputs != dup_num {
errors.push(format!(
"DUP{} opcode should have {} stack inputs, found {}",
dup_num, dup_num, metadata.stack_inputs
));
}
if metadata.stack_outputs != dup_num + 1 {
errors.push(format!(
"DUP{} opcode should have {} stack outputs, found {}",
dup_num,
dup_num + 1,
metadata.stack_outputs
));
}
}
if (0x90..=0x9f).contains(opcode_byte) {
let swap_num = opcode_byte - 0x8f;
if metadata.stack_inputs != swap_num + 1 {
errors.push(format!(
"SWAP{} opcode should have {} stack inputs, found {}",
swap_num,
swap_num + 1,
metadata.stack_inputs
));
}
if metadata.stack_outputs != swap_num + 1 {
errors.push(format!(
"SWAP{} opcode should have {} stack outputs, found {}",
swap_num,
swap_num + 1,
metadata.stack_outputs
));
}
}
}
}
errors
}
struct KnownGasChanges {
opcode: u8,
fork: Fork,
old_cost: u16,
new_cost: u16,
reason: &'static str,
}
pub fn validate_known_gas_changes(registry: &OpcodeRegistry) -> Vec<String> {
let mut errors = Vec::new();
let known_changes = [
KnownGasChanges {
opcode: 0x54, fork: Fork::Berlin,
old_cost: 800,
new_cost: 2100,
reason: "EIP-2929: Gas cost increases for state access opcodes",
},
KnownGasChanges {
opcode: 0x31, fork: Fork::Berlin,
old_cost: 400,
new_cost: 2600,
reason: "EIP-2929: Gas cost increases for state access opcodes",
},
KnownGasChanges {
opcode: 0x3b, fork: Fork::Berlin,
old_cost: 700,
new_cost: 2600,
reason: "EIP-2929: Gas cost increases for state access opcodes",
},
KnownGasChanges {
opcode: 0x3c, fork: Fork::Berlin,
old_cost: 700,
new_cost: 2600,
reason: "EIP-2929: Gas cost increases for state access opcodes",
},
KnownGasChanges {
opcode: 0x3f, fork: Fork::Berlin,
old_cost: 400,
new_cost: 2600,
reason: "EIP-2929: Gas cost increases for state access opcodes",
},
];
for change in &known_changes {
let pre_fork_opcodes = registry.get_opcodes(get_previous_fork(change.fork));
let post_fork_opcodes = registry.get_opcodes(change.fork);
if let (Some(pre_metadata), Some(post_metadata)) = (
pre_fork_opcodes.get(&change.opcode),
post_fork_opcodes.get(&change.opcode),
) {
if pre_metadata.gas_cost != change.old_cost {
errors.push(format!(
"Expected gas cost {} for opcode 0x{:02x} before fork {:?}, found {}",
change.old_cost, change.opcode, change.fork, pre_metadata.gas_cost
));
}
let actual_new_cost = post_metadata
.gas_history
.iter()
.find(|(f, _)| *f == change.fork)
.map(|(_, cost)| *cost)
.unwrap_or(post_metadata.gas_cost);
if actual_new_cost != change.new_cost {
errors.push(format!(
"Expected gas cost {} for opcode 0x{:02x} after fork {:?}, found {}. Reason: {}",
change.new_cost, change.opcode, change.fork, actual_new_cost, change.reason
));
}
}
}
errors
}
fn get_previous_fork(fork: Fork) -> Fork {
match fork {
Fork::IceAge => Fork::Frontier,
Fork::Homestead => Fork::IceAge,
Fork::DaoFork => Fork::Homestead,
Fork::TangerineWhistle => Fork::DaoFork,
Fork::SpuriousDragon => Fork::TangerineWhistle,
Fork::Byzantium => Fork::SpuriousDragon,
Fork::Constantinople => Fork::Byzantium,
Fork::Petersburg => Fork::Constantinople,
Fork::Istanbul => Fork::Petersburg,
Fork::MuirGlacier => Fork::Istanbul,
Fork::Berlin => Fork::MuirGlacier,
Fork::London => Fork::Berlin,
Fork::Altair => Fork::London,
Fork::ArrowGlacier => Fork::Altair,
Fork::GrayGlacier => Fork::ArrowGlacier,
Fork::Bellatrix => Fork::GrayGlacier,
Fork::Paris => Fork::Bellatrix,
Fork::Shanghai => Fork::Paris,
Fork::Capella => Fork::Shanghai,
Fork::Cancun => Fork::Capella,
Fork::Deneb => Fork::Cancun,
Fork::Frontier => Fork::Frontier, }
}
pub fn run_comprehensive_validation(registry: &OpcodeRegistry) -> ValidationReport {
let mut report = ValidationReport::new();
report.add_errors("Opcode Uniqueness", validate_opcode_uniqueness(registry));
report.add_errors("Fork Inheritance", validate_fork_inheritance(registry));
report.add_errors(
"Historical Accuracy",
validate_historical_accuracy(registry),
);
report.add_errors(
"Gas Cost Consistency",
validate_gas_cost_consistency(registry),
);
report.add_errors("Stack Consistency", validate_stack_consistency(registry));
report.add_errors("Known Gas Changes", validate_known_gas_changes(registry));
report.add_warnings("Missing EIPs", check_missing_eip_references(registry));
report.add_info("Coverage", generate_coverage_info(registry));
report
}
#[derive(Debug, Default)]
pub struct ValidationReport {
pub errors: HashMap<String, Vec<String>>,
pub warnings: HashMap<String, Vec<String>>,
pub info: HashMap<String, Vec<String>>,
}
impl ValidationReport {
pub fn new() -> Self {
Self::default()
}
pub fn add_errors(&mut self, category: &str, errors: Vec<String>) {
if !errors.is_empty() {
self.errors.insert(category.to_string(), errors);
}
}
pub fn add_warnings(&mut self, category: &str, warnings: Vec<String>) {
if !warnings.is_empty() {
self.warnings.insert(category.to_string(), warnings);
}
}
pub fn add_info(&mut self, category: &str, info: Vec<String>) {
if !info.is_empty() {
self.info.insert(category.to_string(), info);
}
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn print_summary(&self) {
println!("=== Validation Report ===");
if !self.errors.is_empty() {
println!("\n❌ ERRORS:");
for (category, errors) in &self.errors {
println!(" {category}:");
for error in errors {
println!(" - {error}");
}
}
}
if !self.warnings.is_empty() {
println!("\n⚠️ WARNINGS:");
for (category, warnings) in &self.warnings {
println!(" {category}:");
for warning in warnings {
println!(" - {warning}");
}
}
}
if !self.info.is_empty() {
println!("\nℹ️ INFO:");
for (category, info_items) in &self.info {
println!(" {category}:");
for info in info_items {
println!(" {info}:");
}
}
}
if self.errors.is_empty() && self.warnings.is_empty() {
println!("\n✅ All validations passed!");
}
}
}
fn check_missing_eip_references(registry: &OpcodeRegistry) -> Vec<String> {
let mut warnings = Vec::new();
for opcodes in registry.opcodes.values() {
for (opcode_byte, metadata) in opcodes {
if metadata.introduced_in > Fork::Frontier && metadata.eip.is_none() {
warnings.push(format!(
"Opcode 0x{:02x} ({}) introduced in {:?} is missing EIP reference",
opcode_byte, metadata.name, metadata.introduced_in
));
}
}
}
warnings
}
fn generate_coverage_info(registry: &OpcodeRegistry) -> Vec<String> {
let mut info = Vec::new();
let total_possible_opcodes = 256;
let latest_opcodes = registry.get_opcodes(Fork::Cancun);
let coverage_percentage = (latest_opcodes.len() * 100) / total_possible_opcodes;
info.push(format!(
"Total opcodes implemented: {} / {} ({:.1}% coverage)",
latest_opcodes.len(),
total_possible_opcodes,
coverage_percentage as f32
));
for fork in [
Fork::Frontier,
Fork::Homestead,
Fork::Byzantium,
Fork::Constantinople,
Fork::Istanbul,
Fork::Berlin,
Fork::London,
Fork::Shanghai,
Fork::Cancun,
] {
let opcodes = registry.get_opcodes(fork);
info.push(format!("{:?}: {} opcodes", fork, opcodes.len()));
}
info
}