cubipods 0.1.1

A minimal EVM implemented in Rust.
Documentation
use std::error::Error;

use crate::{instruction::InstructionType, vm::Vm};

use super::{bytes32::Bytes32, errors::HistoryError};

#[derive(Debug, Default)]
pub struct History {
    registry: Vec<Registry>,
    memory_locations: Vec<Bytes32>,
    storage_slots: Vec<Bytes32>,
}

#[derive(Debug)]
pub struct Registry {
    pub description: String,
    pub component: Component,
}

#[derive(Debug)]
pub struct StackInfo {
    pub instruction: InstructionType,
    pub item_1: Option<Bytes32>,
    pub item_1_index: Option<u16>,
    pub item_2: Option<Bytes32>,
    pub item_2_index: Option<u16>,
}

#[derive(Debug)]
pub struct MemoryInfo {
    pub location: Bytes32,
    pub value: Bytes32,
}

#[derive(Debug)]
pub struct StorageInfo {
    pub slot: Bytes32,
    pub value: Bytes32,
}

#[derive(Debug)]
pub enum Component {
    Stack(StackInfo),
    Memory(MemoryInfo),
    Storage(StorageInfo),
}

impl Registry {
    pub fn new(description: String, component: Component) -> Result<Self, HistoryError> {
        if description.is_empty() {
            return Err(HistoryError::EmptyDescription);
        }

        Ok(Self {
            description,
            component,
        })
    }
}

impl History {
    pub fn new() -> Self {
        Self {
            ..Default::default()
        }
    }

    pub fn save_on_event(&mut self, component: Component) -> Result<(), Box<dyn Error>> {
        match &component {
            Component::Stack(info) => {
                let format_item_info = |item: Bytes32, index: u16| -> String {
                    let item = item.parse_and_trim().unwrap();

                    format!("the item {item} with the index of {index}")
                };

                let mut description = format!(
                    "[STACK]: The opcode {:?} was called by using {}",
                    info.instruction,
                    format_item_info(info.item_1.unwrap(), info.item_1_index.unwrap())
                );

                if info.item_2.is_some() {
                    description = format!(
                        "{} and {}",
                        description,
                        format_item_info(info.item_2.unwrap(), info.item_2_index.unwrap())
                    );
                }

                self.registry
                    .push(Registry::new(format!("{}.", description), component)?);
            }
            Component::Memory(info) => {
                let description = format!(
                    "[MEMORY]: The value {} was pushed to the location of {}.",
                    info.value, info.location,
                );

                self.registry.push(Registry::new(description, component)?);
            }
            Component::Storage(info) => {
                let description = format!(
                    "[STORAGE]: The value {} was pushed to the storage slot of {}.",
                    info.value, info.slot,
                );

                self.registry.push(Registry::new(description, component)?);
            }
        }

        Ok(())
    }

    pub fn size(&self) -> usize {
        self.registry.len()
    }

    pub fn summarize(&self) {
        println!("History:");
        println!(
            "{}",
            &self
                .registry
                .iter()
                .map(|r| r.description.clone() + "\n")
                .collect::<String>()
        );
    }

    pub fn analyze(&self, vm: &Vm) {
        println!("Stack:");
        println!("{:?}", vm.stack);
        println!("\nMemory:");
        self.memory_locations.iter().for_each(|ml| unsafe {
            let data = vm.memory.load_only(*ml);
            println!("Location: 0x{}, Data: 0x{}", ml, data);
        });
        println!("\nStorage:");
        self.storage_slots.iter().for_each(|ss| {
            let data = vm.storage.sload(*ss).unwrap();
            println!("Location: 0x{}, Data: 0x{}", ss, data);
        });
    }

    pub fn save_memory_location(&mut self, location: Bytes32) {
        self.memory_locations.push(location);
    }

    pub fn save_storage_slot(&mut self, slot: Bytes32) {
        self.storage_slots.push(slot);
    }
}

impl Component {
    pub fn build_stack(
        instruction: InstructionType,
        item_1: Bytes32,
        item_1_index: u16,
        item_2: Bytes32,
        item_2_index: u16,
    ) -> Self {
        Component::Stack(StackInfo {
            instruction: instruction.clone(),
            item_1: Some(item_1),
            item_1_index: Some(item_1_index),
            item_2: Some(item_2),
            item_2_index: Some(item_2_index),
        })
    }

    pub fn build_stack_with_one_item(
        instruction: InstructionType,
        item_1: Bytes32,
        item_1_index: u16,
    ) -> Self {
        Component::Stack(StackInfo {
            instruction,
            item_1: Some(item_1),
            item_1_index: Some(item_1_index),
            item_2: None,
            item_2_index: None,
        })
    }

    pub fn build_memory(location: Bytes32, value: Bytes32) -> Self {
        Component::Memory(MemoryInfo { location, value })
    }

    pub fn build_storage(slot: Bytes32, value: Bytes32) -> Self {
        Component::Storage(StorageInfo { slot, value })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_save_on_event() -> Result<(), Box<dyn Error>> {
        let mut history = History::new();

        history.save_on_event(Component::Stack(StackInfo {
            instruction: InstructionType::PUSH(1),
            item_1: Some("01".parse::<Bytes32>()?),
            item_1_index: Some(2),
            item_2: None,
            item_2_index: None,
        }))?;
        history.save_on_event(Component::Stack(StackInfo {
            instruction: InstructionType::PUSH(3),
            item_1: Some("010203".parse::<Bytes32>()?),
            item_1_index: Some(1),
            item_2: None,
            item_2_index: None,
        }))?;
        history.save_on_event(Component::Stack(StackInfo {
            instruction: InstructionType::MSTORE,
            item_1: Some("01".parse::<Bytes32>()?),
            item_1_index: Some(2),
            item_2: Some("010203".parse::<Bytes32>()?),
            item_2_index: Some(1),
        }))?;
        history.save_on_event(Component::Memory(MemoryInfo {
            location: Bytes32::from(1),
            value: "010203".parse::<Bytes32>()?,
        }))?;
        history.save_on_event(Component::Storage(StorageInfo {
            slot: "01".parse::<Bytes32>()?,
            value: "010203".parse::<Bytes32>()?,
        }))?;

        assert_eq!(history.registry.len(), 5);

        Ok(())
    }

    #[test]
    fn it_creates_registry_with_empty_description_returns_history_error(
    ) -> Result<(), Box<dyn Error>> {
        let result = Registry::new(
            "".to_string(),
            Component::Stack(StackInfo {
                instruction: InstructionType::STOP,
                item_1: None,
                item_1_index: None,
                item_2: None,
                item_2_index: None,
            }),
        );
        assert!(matches!(result, Err(HistoryError::EmptyDescription)));

        Ok(())
    }

    #[test]
    fn test_build_stack() {
        let stack_component = Component::build_stack(
            InstructionType::ADD,
            Bytes32::from(1),
            1,
            Bytes32::from(1),
            2,
        );

        if let Component::Stack(stack_info) = stack_component {
            assert_eq!(stack_info.item_1.is_some(), true);
            assert_eq!(stack_info.item_1_index.is_some(), true);
            assert_eq!(stack_info.item_2.is_some(), true);
            assert_eq!(stack_info.item_2_index.is_some(), true);
        }
    }

    #[test]
    fn test_build_stack_with_one_item() {
        let stack_component =
            Component::build_stack_with_one_item(InstructionType::ADD, Bytes32::from(1), 1);

        if let Component::Stack(stack_info) = stack_component {
            assert_eq!(stack_info.item_1.is_some(), true);
            assert_eq!(stack_info.item_1_index.is_some(), true);
            assert_eq!(stack_info.item_2.is_some(), false);
            assert_eq!(stack_info.item_2_index.is_some(), false);
        }
    }

    #[test]
    fn test_build_memory() {
        let memory_component = Component::build_memory(Bytes32::from(1), Bytes32::from(1));

        if let Component::Memory(memory_info) = memory_component {
            assert_eq!(memory_info.location, Bytes32::from(1));
            assert_eq!(memory_info.value, Bytes32::from(1));
        }
    }

    #[test]
    fn test_build_storage() {
        let storage_component = Component::build_storage(Bytes32::from(1), Bytes32::from(1));

        if let Component::Storage(storage_info) = storage_component {
            assert_eq!(storage_info.slot, Bytes32::from(1));
            assert_eq!(storage_info.value, Bytes32::from(1));
        }
    }
}