Skip to main content

xenith_read/
provider.rs

1use std::collections::HashMap;
2
3use async_trait::async_trait;
4use bytes::Bytes;
5use xenith_core::Result;
6
7/// Thin abstraction over an EVM chain's RPC layer.
8///
9/// Decouples [`crate::MultiChainReader`] from any specific alloy provider
10/// version; real providers wrap `alloy_provider::Provider`, test providers use
11/// [`MockProvider`].
12///
13/// All implementations must be `Send + Sync` for use behind `Arc`.
14#[async_trait]
15pub trait ChainProvider: Send + Sync {
16    /// Read a 32-byte storage slot from `address` on this chain.
17    async fn read_storage(&self, address: [u8; 20], slot: [u8; 32]) -> Result<[u8; 32]>;
18
19    /// Execute a read-only call to `address` with the given ABI-encoded `calldata`.
20    async fn call(&self, address: [u8; 20], calldata: Bytes) -> Result<Bytes>;
21}
22
23/// In-memory [`ChainProvider`] for unit tests.
24///
25/// Stores a fixed mapping of storage slot → value. Calls always return empty bytes.
26pub struct MockProvider {
27    slots: HashMap<[u8; 32], [u8; 32]>,
28}
29
30impl MockProvider {
31    /// Create a provider pre-loaded with `slots`.
32    pub fn new(slots: HashMap<[u8; 32], [u8; 32]>) -> Self {
33        Self { slots }
34    }
35}
36
37#[async_trait]
38impl ChainProvider for MockProvider {
39    async fn read_storage(&self, _address: [u8; 20], slot: [u8; 32]) -> Result<[u8; 32]> {
40        self.slots.get(&slot).copied().ok_or_else(|| {
41            xenith_core::XenithError::StoreError(format!("MockProvider: slot {slot:?} not found"))
42        })
43    }
44
45    async fn call(&self, _address: [u8; 20], _calldata: Bytes) -> Result<Bytes> {
46        Ok(Bytes::new())
47    }
48}