Skip to main content

df_hyperliquid_core/transfer/
link.rs

1//! HyperCore and HyperEVM, linked: a linked spot token crosses between Core and EVM spot through
2//! its system address. Core to EVM is a `spotSend` or `sendAsset` to [`Linked::system`]; EVM to
3//! Core is a HyperEVM transaction to the same address ([`Linked::to_core`]).
4
5use crate::client::evm::chain::NATIVE_DECIMALS;
6use crate::client::evm::tx::Call;
7use crate::client::evm::u256::{BadAmount, U256};
8use crate::transfer::erc20;
9use crate::utils::{Address, Dec};
10
11/// Where a token crosses.
12pub use crate::transfer::system::{HYPE_SYSTEM, is_system_address, system_address};
13pub use crate::transfer::system_token;
14
15/// A spot token as it is linked to HyperEVM. Its contract can hold arbitrary code; nothing here
16/// checks its supply or what its `transfer` does.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct Linked {
19    pub index: u64,
20    /// The ERC-20; `None` for HYPE, which is the chain's native coin.
21    pub contract: Option<Address>,
22    /// The decimals Core counts the token in (`spotMeta`'s `weiDecimals`).
23    pub wei_decimals: u32,
24    /// How many more the ERC-20 counts in, -2 to 18.
25    pub evm_extra_wei_decimals: i32,
26}
27
28impl Linked {
29    /// HYPE: native on HyperEVM, eighteen decimals there.
30    #[must_use]
31    pub const fn hype(wei_decimals: u32) -> Linked {
32        Linked { index: 0, contract: None, wei_decimals, evm_extra_wei_decimals: 0 }
33    }
34
35    /// A token linked to an ERC-20 (`spotMeta`'s `evmContract`).
36    #[must_use]
37    pub const fn erc20(index: u64, contract: Address, wei_decimals: u32, evm_extra_wei_decimals: i32) -> Linked {
38        Linked { index, contract: Some(contract), wei_decimals, evm_extra_wei_decimals }
39    }
40
41    /// Where the token crosses, both ways. Sent there on Core, it arrives at the sender's address
42    /// on HyperEVM.
43    #[must_use]
44    pub fn system(&self) -> Address {
45        match self.contract {
46            None => HYPE_SYSTEM,
47            Some(_) => system_address(self.index),
48        }
49    }
50
51    /// The decimals the token counts in on HyperEVM.
52    #[must_use]
53    pub fn evm_decimals(&self) -> Option<u32> {
54        match self.contract {
55            None => Some(NATIVE_DECIMALS),
56            Some(_) => u32::try_from(i64::from(self.wei_decimals) + i64::from(self.evm_extra_wei_decimals)).ok(),
57        }
58    }
59
60    /// The most places an amount crossing either way may have: the fewer of
61    /// Core's and the EVM's.
62    #[must_use]
63    pub fn core_places(&self) -> Option<u32> {
64        self.evm_decimals().map(|evm| evm.min(self.wei_decimals))
65    }
66
67    /// The HyperEVM call that sends `amount` back to Core: HYPE as a plain value transfer, any
68    /// other token as an ERC-20 `transfer` to the system address. Exact, positive, and no finer
69    /// than [`Linked::core_places`].
70    pub fn to_core(&self, amount: Dec) -> Result<Call, BadAmount> {
71        let places = self.core_places().ok_or(BadAmount::TooLarge)?;
72        let decimals = self.evm_decimals().ok_or(BadAmount::TooLarge)?;
73        if !amount.is_positive() {
74            return Err(BadAmount::NotPositive(amount));
75        }
76        if amount.places() > places {
77            return Err(BadAmount::TooFine { value: amount, decimals: places });
78        }
79        let units = U256::from_dec(amount, decimals)?;
80        Ok(match self.contract {
81            None => Call { to: Some(HYPE_SYSTEM), value: units, data: Vec::new() },
82            Some(contract) => {
83                Call { to: Some(contract), value: U256::ZERO, data: erc20::transfer(self.system(), units) }
84            }
85        })
86    }
87}
88
89#[cfg(test)]
90#[path = "tests/link.rs"]
91mod tests;