xenith-read 0.1.0

Multi-chain parallel storage reads and divergence detection for xenith
Documentation
use std::collections::HashMap;

use async_trait::async_trait;
use bytes::Bytes;
use xenith_core::Result;

/// Thin abstraction over an EVM chain's RPC layer.
///
/// Decouples [`crate::MultiChainReader`] from any specific alloy provider
/// version; real providers wrap `alloy_provider::Provider`, test providers use
/// [`MockProvider`].
///
/// All implementations must be `Send + Sync` for use behind `Arc`.
#[async_trait]
pub trait ChainProvider: Send + Sync {
    /// Read a 32-byte storage slot from `address` on this chain.
    async fn read_storage(&self, address: [u8; 20], slot: [u8; 32]) -> Result<[u8; 32]>;

    /// Execute a read-only call to `address` with the given ABI-encoded `calldata`.
    async fn call(&self, address: [u8; 20], calldata: Bytes) -> Result<Bytes>;
}

/// In-memory [`ChainProvider`] for unit tests.
///
/// Stores a fixed mapping of storage slot → value. Calls always return empty bytes.
pub struct MockProvider {
    slots: HashMap<[u8; 32], [u8; 32]>,
}

impl MockProvider {
    /// Create a provider pre-loaded with `slots`.
    pub fn new(slots: HashMap<[u8; 32], [u8; 32]>) -> Self {
        Self { slots }
    }
}

#[async_trait]
impl ChainProvider for MockProvider {
    async fn read_storage(&self, _address: [u8; 20], slot: [u8; 32]) -> Result<[u8; 32]> {
        self.slots.get(&slot).copied().ok_or_else(|| {
            xenith_core::XenithError::StoreError(format!("MockProvider: slot {slot:?} not found"))
        })
    }

    async fn call(&self, _address: [u8; 20], _calldata: Bytes) -> Result<Bytes> {
        Ok(Bytes::new())
    }
}