yevm-core 0.1.0

Async, WASM-native Ethereum VM engine: opcode dispatch, gas accounting, precompiles, state traits, structured trace events.
Documentation
use serde::{Deserialize, Serialize};
use yevm_base::{Acc, Int};
use yevm_misc::buf::Buf;

use crate::{
    Result,
    call::{Block, Head},
    evm::Fetch,
    state::{Account, State},
};

#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
pub trait Chain {
    async fn get(&self, acc: &Acc, key: &Int) -> eyre::Result<Int>;
    async fn acc(&self, acc: &Acc) -> eyre::Result<Account>;
    async fn code(&self, acc: &Acc) -> eyre::Result<(Buf, Int)>;
    async fn nonce(&self, acc: &Acc) -> eyre::Result<u64>;
    async fn balance(&self, acc: &Acc) -> eyre::Result<Int>;
    async fn head(&self, number: u64) -> eyre::Result<Head>;
    async fn block(&self, number: u64) -> eyre::Result<Block>;
    async fn chain_id(&self) -> eyre::Result<u64>;
}

#[allow(clippy::large_enum_variant)] // TODO: wrap Block with Box?
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum Fetched {
    Account(Acc, Account),
    State(Acc, Int, Int),
    Hash(u64, Int),
    Block(Block),
    ChainId(u64),
}

pub async fn fetch(f: Fetch, state: &mut impl State, chain: &impl Chain) -> Result<()> {
    match f {
        Fetch::Account(acc) | Fetch::Balance(acc) | Fetch::Nonce(acc) | Fetch::Code(acc) => {
            if state.is_offline() {
                let next = state.next_fetched();
                let Some(Fetched::Account(_, account)) = next else {
                    return Err(
                        eyre::eyre!("Offline fetch: expected account but got {next:?}").into(),
                    );
                };
                state.merge(&acc, account.clone());
            } else {
                let account = chain.acc(&acc).await?;
                state.merge(&acc, account.clone());
                state.save_fetched(Fetched::Account(acc, account));
            }
            Ok(())
        }
        Fetch::BlockHash(number) => {
            if state.is_offline() {
                let next = state.next_fetched();
                let Some(Fetched::Hash(number, hash)) = next else {
                    return Err(
                        eyre::eyre!("Offline fetch: expected block hash but got {next:?}").into(),
                    );
                };
                state.hash(number, hash);
            } else {
                let hash = chain
                    .head(number)
                    .await
                    .map(|head| head.hash)
                    .unwrap_or(Int::ZERO);
                state.hash(number, hash);
                state.save_fetched(Fetched::Hash(number, hash));
            }
            Ok(())
        }
        Fetch::StateCell(acc, key) => {
            if state.is_offline() {
                let next = state.next_fetched();
                let Some(Fetched::State(_, _, val)) = next else {
                    return Err(
                        eyre::eyre!("Offline fetch: expected state cell but got {next:?}").into(),
                    );
                };
                state.init(&acc, &key, val);
            } else {
                let val = chain.get(&acc, &key).await?;
                state.init(&acc, &key, val);
                state.save_fetched(Fetched::State(acc, key, val));
            }
            Ok(())
        }
    }
}