Skip to main content

df_papertrade_core/client/
chain.rs

1//! Contract reads on HyperEVM: the deposit address, the win fee and staking rewards. Each is a
2//! pure `eth_call` ([`Read`]) run at `latest`, the only block mainnet's RPC serves. The book's
3//! BBO is HyperCore's own precompile, read through
4//! [`df_hyperliquid_core::client::evm::precompiles::bbo`].
5
6use df_hyperliquid_core::client::Http;
7use df_hyperliquid_core::client::evm::{AbiError, BlockTag, CallRequest, Function, Rpc, RpcError, Token};
8use df_hyperliquid_core::utils::Address;
9
10use crate::client::deployment::{DEPOSIT_PROXY_FACTORY, EXCHANGE, PAPER_STAKING};
11
12/// One contract read: the call and the decoder for its answer.
13pub struct Read<T> {
14    pub to: Address,
15    pub data: Vec<u8>,
16    decode: fn(&[Token]) -> Option<T>,
17    function: Function,
18}
19
20impl<T> std::fmt::Debug for Read<T> {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        f.debug_struct("Read").field("to", &self.to).field("function", &self.function.signature()).finish()
23    }
24}
25
26/// Why a read failed.
27#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
28pub enum ReadError {
29    #[error(transparent)]
30    Rpc(#[from] RpcError),
31    #[error("the answer does not fit the function's outputs: {0}")]
32    Abi(#[from] AbiError),
33    #[error("the answer is not the value the function returns")]
34    Shape,
35}
36
37impl<T> Read<T> {
38    fn new(to: Address, signature: &str, args: &[Token], decode: fn(&[Token]) -> Option<T>) -> Result<Self, AbiError> {
39        let function = Function::parse(signature)?;
40        Ok(Read { to, data: function.encode(args)?, decode, function })
41    }
42
43    /// The `eth_call` that makes the read.
44    #[must_use]
45    pub fn request(&self) -> CallRequest {
46        CallRequest::to(self.to, self.data.clone())
47    }
48
49    /// Decodes the answer against the function's outputs.
50    pub fn decode(&self, answer: &[u8]) -> Result<T, ReadError> {
51        let tokens = self.function.decode_output(answer)?;
52        (self.decode)(&tokens).ok_or(ReadError::Shape)
53    }
54
55    /// Runs the read at the latest block.
56    pub async fn run<H: Http>(&self, rpc: &Rpc<H>) -> Result<T, ReadError> {
57        let answer = rpc.call(&self.request(), BlockTag::Latest).await?;
58        self.decode(&answer)
59    }
60}
61
62/// The wallet's deposit address, the CREATE2 proxy the factory derives for it (deployed
63/// or not). USDC sent there on HyperCore is credited to the wallet.
64pub fn deposit_address(wallet: Address) -> Result<Read<Address>, AbiError> {
65    Read::new(
66        DEPOSIT_PROXY_FACTORY,
67        "computeProxyAddress(address user) view returns (address)",
68        &[Token::address(wallet)],
69        |tokens| tokens.first()?.as_address(),
70    )
71}
72
73/// The Exchange's win fee, 18-decimal fraction.
74pub fn win_fee_rate() -> Result<Read<u128>, AbiError> {
75    Read::new(EXCHANGE, "winFeeRate() view returns (uint256)", &[], |tokens| tokens.first()?.as_u128())
76}
77
78/// USDC rewards PaperStaking has distributed to `wallet` but not paid, in collateral units.
79pub fn pending_reward(wallet: Address) -> Result<Read<u128>, AbiError> {
80    Read::new(
81        PAPER_STAKING,
82        "pendingReward(address user) view returns (uint256)",
83        &[Token::address(wallet)],
84        |tokens| tokens.first()?.as_u128(),
85    )
86}
87
88#[cfg(test)]
89#[path = "tests/chain.rs"]
90mod tests;