Skip to main content

df_elysium_core/client/
read.rs

1//! A typed [`Read`]: the `eth_call` that makes it and how its answer reads, sent through any
2//! [`Rpc`] (Elysium's, or HyperEVM's for the bridge's contracts), and a struct answer's fields read
3//! in declared order.
4
5use df_hyperliquid_core::client::Http;
6use df_hyperliquid_core::client::evm::abi::{AbiError, Token};
7
8use crate::client::method::{Arg, Method};
9use crate::client::rpc::{BlockTag, CallRequest, Rpc, RpcError};
10use crate::utils::Address;
11use crate::{B256, U256};
12
13/// A typed read: the call that makes it, and how its answer reads.
14#[derive(Debug, Clone)]
15pub struct Read<T> {
16    pub call: CallRequest,
17    decode: fn(&[u8]) -> Result<T, AbiError>,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
21pub enum ReadError {
22    #[error(transparent)]
23    Rpc(#[from] RpcError),
24    #[error("the answer does not fit what the contract declares: {0}")]
25    Abi(#[from] AbiError),
26}
27
28impl ReadError {
29    /// Whether the node refused it for a lagging backend ([`crate::client::rpc::lagging`]).
30    #[must_use]
31    pub fn is_lagging(&self) -> bool {
32        matches!(self, ReadError::Rpc(error) if crate::client::rpc::lagging(error))
33    }
34}
35
36impl<T> Read<T> {
37    /// `method` called on `at` with `args`, its answer read by `decode`.
38    #[must_use]
39    pub fn new(at: Address, method: &Method, args: &[Arg<'_>], decode: fn(&[u8]) -> Result<T, AbiError>) -> Read<T> {
40        Read { call: CallRequest::to(at, method.calldata(args)), decode }
41    }
42
43    /// The same read as sent by `from`: what `msg.sender` is to the contract.
44    #[must_use]
45    pub fn from(mut self, from: Address) -> Read<T> {
46        self.call.from = Some(from);
47        self
48    }
49
50    /// The same read carrying `value` wei.
51    #[must_use]
52    pub fn with_value(mut self, value: U256) -> Read<T> {
53        self.call.value = Some(value).filter(|value| !value.is_zero());
54        self
55    }
56
57    /// The answer's bytes, read against what the contract declares.
58    pub fn decode(&self, answer: &[u8]) -> Result<T, AbiError> {
59        (self.decode)(answer)
60    }
61
62    /// Sends it to `rpc` at `block` and reads the answer.
63    pub async fn on<H: Http>(&self, rpc: &Rpc<H>, block: BlockTag) -> Result<T, ReadError> {
64        let answer = rpc.call(&self.call, block).await?;
65        Ok(self.decode(&answer)?)
66    }
67}
68
69/// The fields of a struct answer, read in order.
70pub(crate) struct Fields<'a> {
71    name: &'static str,
72    items: std::slice::Iter<'a, Token>,
73}
74
75impl<'a> Fields<'a> {
76    /// The fields of an answer that is a list of return values.
77    pub(crate) fn of(name: &'static str, tokens: &'a [Token]) -> Fields<'a> {
78        Fields { name, items: tokens.iter() }
79    }
80
81    /// The fields of the one tuple an answer is.
82    pub(crate) fn tuple(name: &'static str, tokens: &'a [Token]) -> Result<Fields<'a>, AbiError> {
83        match tokens {
84            [Token::Tuple(items)] => Ok(Fields { name, items: items.iter() }),
85            _ => Err(AbiError::Type(format!("{name}: not one struct"))),
86        }
87    }
88
89    fn take<T>(&mut self, read: impl FnOnce(&'a Token) -> Option<T>) -> Result<T, AbiError> {
90        self.items
91            .next()
92            .and_then(read)
93            .ok_or_else(|| AbiError::Type(format!("{}: a field of another type", self.name)))
94    }
95
96    pub(crate) fn uint(&mut self) -> Result<U256, AbiError> {
97        self.take(Token::as_u256)
98    }
99
100    pub(crate) fn u64(&mut self) -> Result<u64, AbiError> {
101        self.take(Token::as_u64)
102    }
103
104    pub(crate) fn u16(&mut self) -> Result<u16, AbiError> {
105        self.take(Token::as_u16)
106    }
107
108    pub(crate) fn u8(&mut self) -> Result<u8, AbiError> {
109        self.take(Token::as_u8)
110    }
111
112    pub(crate) fn bool(&mut self) -> Result<bool, AbiError> {
113        self.take(Token::as_bool)
114    }
115
116    pub(crate) fn address(&mut self) -> Result<Address, AbiError> {
117        self.take(Token::as_address)
118    }
119
120    pub(crate) fn word(&mut self) -> Result<B256, AbiError> {
121        self.take(|token| <[u8; 32]>::try_from(token.as_bytes()?).ok().map(B256))
122    }
123
124    pub(crate) fn bytes(&mut self) -> Result<Vec<u8>, AbiError> {
125        self.take(|token| token.as_bytes().map(<[u8]>::to_vec))
126    }
127
128    pub(crate) fn items(&mut self) -> Result<&'a [Token], AbiError> {
129        self.take(Token::as_items)
130    }
131
132    pub(crate) fn addresses(&mut self) -> Result<Vec<Address>, AbiError> {
133        let name = self.name;
134        self.items()?
135            .iter()
136            .map(|item| item.as_address().ok_or_else(|| AbiError::Type(format!("{name}: not a list of addresses"))))
137            .collect()
138    }
139
140    pub(crate) fn words(&mut self) -> Result<Vec<B256>, AbiError> {
141        let name = self.name;
142        self.items()?
143            .iter()
144            .map(|item| {
145                item.as_bytes()
146                    .and_then(|bytes| <[u8; 32]>::try_from(bytes).ok())
147                    .map(B256)
148                    .ok_or_else(|| AbiError::Type(format!("{name}: not a list of words")))
149            })
150            .collect()
151    }
152}
153
154/// An answer that is one value, read by `read`.
155pub(crate) fn one<T>(method: &Method, answer: &[u8], read: impl FnOnce(&Token) -> Option<T>) -> Result<T, AbiError> {
156    let tokens = method.answer(answer)?;
157    match tokens.as_slice() {
158        [token] => read(token).ok_or_else(|| AbiError::Type(format!("{}: another type", method.signature))),
159        _ => Err(AbiError::Type(format!("{}: not one value", method.signature))),
160    }
161}
162
163/// An answer that is one word.
164pub(crate) fn one_word(method: &Method, answer: &[u8]) -> Result<B256, AbiError> {
165    one(method, answer, |token| token.as_bytes().and_then(|bytes| <[u8; 32]>::try_from(bytes).ok()).map(B256))
166}
167
168#[cfg(test)]
169#[path = "tests/read.rs"]
170mod tests;