nash_protocol/types/blockchain/
eth.rs

1//! Ethereum specific types shared across protocol requests
2
3use super::super::{Amount, Asset, AssetOrCrosschain, AssetofPrecision, Nonce, OrderRate, Rate};
4use super::{bigdecimal_to_nash_u64, nash_u64_to_bigdecimal};
5use crate::errors::{ProtocolError, Result};
6use byteorder::{BigEndian, ReadBytesExt};
7#[cfg(feature = "secp256k1")]
8use nash_mpc::curves::secp256_k1::Secp256k1Point;
9#[cfg(feature = "k256")]
10use nash_mpc::curves::secp256_k1_rust::Secp256k1Point;
11use nash_mpc::curves::traits::ECPoint;
12use sha3::{Digest, Keccak256};
13
14impl Rate {
15    /// Convert any Rate into bytes for encoding in a Ethereum payload
16    pub fn to_be_bytes(&self, order_precision: u32, fee_precision: u32) -> Result<Vec<u8>> {
17        let zero_bytes = (0 as f64).to_be_bytes();
18        let bytes = match self {
19            Self::OrderRate(rate) => rate.to_be_bytes(order_precision)?.to_vec(),
20            Self::FeeRate(rate) => rate.to_be_bytes(fee_precision)?.to_vec(),
21            Self::MinOrderRate | Self::MinFeeRate => zero_bytes.to_vec(),
22            Self::MaxOrderRate => [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].to_vec(),
23            // 0.0025 * 10^8 = 250,000
24            Self::MaxFeeRate => [0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xD0, 0x90].to_vec(),
25        };
26        Ok(bytes)
27    }
28
29    /// Create Rate from big endian bytes in Ethereum FillOrder payload
30    pub fn from_be_bytes(bytes: [u8; 8]) -> Result<Self> {
31        Ok(Self::OrderRate(OrderRate::from_be_bytes(bytes)?))
32    }
33}
34
35impl OrderRate {
36    /// Serialize the OrderRate to bytes for payload creation. We always use a
37    /// precision of 8 and multiplication factor of 10^8
38    pub fn to_be_bytes(&self, precision: u32) -> Result<Vec<u8>> {
39        let precision = precision.max(8);
40        let bytes = bigdecimal_to_nash_u64(&self.to_bigdecimal(), precision)?.to_be_bytes();
41        let bytes = bytes.iter().cloned().take(precision as usize).collect();
42        Ok(bytes)
43    }
44
45    /// Create OrderRate from big endian bytes in Ethereum FillOrder payload
46    pub fn from_be_bytes(bytes: [u8; 8]) -> Result<Self> {
47        let num = u64::from_be_bytes(bytes);
48        let big_num = nash_u64_to_bigdecimal(num, 8);
49        Ok(OrderRate::from_bigdecimal(big_num))
50    }
51}
52
53impl Amount {
54    /// Serialize Amount to Big Endian bytes for ETH payload creation.
55    pub fn to_be_bytes(&self) -> Result<[u8; 8]> {
56        let bytes = bigdecimal_to_nash_u64(&self.to_bigdecimal(), 8)?.to_be_bytes();
57        Ok(bytes)
58    }
59    /// Create an amount of given precision from ETH payload bytes
60    pub fn from_bytes(bytes: [u8; 8], precision: u32) -> Result<Self> {
61        let value = nash_u64_to_bigdecimal(
62            (&bytes[..])
63                .read_u64::<BigEndian>()
64                .map_err(|_| ProtocolError("Could not convert bytes to u64"))?,
65            precision,
66        );
67        Ok(Self { value, precision })
68    }
69}
70
71impl Nonce {
72    /// Serialize Nonce for ETH payload as BigEndian bytes
73    pub fn to_be_bytes(&self) -> [u8; 4] {
74        match self {
75            Self::Value(value) => value.to_be_bytes(),
76            Self::Crosschain => Nonce::crosschain().to_be_bytes(),
77        }
78    }
79    /// Create a nonce from ETH payload bytes
80    pub fn from_be_bytes(bytes: [u8; 4]) -> Result<Self> {
81        let value = (&bytes[..])
82            .read_u32::<BigEndian>()
83            .map_err(|_| ProtocolError("Could not read bytes into u32 for Nonce"))?;
84        if value == Nonce::crosschain() {
85            Ok(Nonce::Crosschain)
86        } else {
87            Ok(Nonce::Value(value))
88        }
89    }
90}
91
92impl Asset {
93    /// This maps assets onto their representation in the ETH SC protocol.
94    /// Each asset is represented by two bytes which serve as an identifier
95    pub fn to_eth_bytes(&self) -> [u8; 2] {
96        match self {
97            Self::ETH => [0x00, 0x00],
98            Self::BAT => [0x00, 0x01],
99            Self::OMG => [0x00, 0x02],
100            Self::USDC => [0x00, 0x03],
101            Self::USDT => [0x00, 0x11],
102            Self::ZRX => [0x00, 0x04],
103            Self::LINK => [0x00, 0x05],
104            Self::QNT => [0x00, 0x06],
105            Self::RLC => [0x00, 0x0a],
106            Self::ANT => [0x00, 0x0e],
107            Self::TRAC => [0x00, 0x14],
108            Self::GUNTHY => [0x00, 0x15],
109            Self::NOIA => [0x00, 0x19],
110            Self::BTC => [0xff, 0xff],
111            Self::NEO => [0xff, 0xff],
112            Self::GAS => [0xff, 0xff],
113            Self::NNN => [0xff, 0xff],
114        }
115    }
116
117    /// Given two bytes asset id in ETH payload, return asset
118    pub fn from_eth_bytes(bytes: [u8; 2]) -> Result<Self> {
119        match bytes {
120            [0x00, 0x00] => Ok(Self::ETH),
121            [0x00, 0x01] => Ok(Self::BAT),
122            [0x00, 0x02] => Ok(Self::OMG),
123            [0x00, 0x03] => Ok(Self::USDC),
124            [0x00, 0x11] => Ok(Self::USDT),
125            [0x00, 0x04] => Ok(Self::ZRX),
126            [0x00, 0x05] => Ok(Self::LINK),
127            [0x00, 0x06] => Ok(Self::QNT),
128            [0x00, 0x0a] => Ok(Self::RLC),
129            [0x00, 0x0e] => Ok(Self::ANT),
130            [0x00, 0x14] => Ok(Self::TRAC),
131            [0x00, 0x15] => Ok(Self::GUNTHY),
132            [0x00, 0x19] => Ok(Self::NOIA),
133            _ => Err(ProtocolError("Invalid Asset ID in bytes")),
134        }
135    }
136}
137
138impl AssetOrCrosschain {
139    /// Convert asset to id in bytes interpretable by the Ethereum
140    /// smart contract, or `0xffff` if it is a cross-chain asset
141    pub fn to_eth_bytes(&self) -> [u8; 2] {
142        match self {
143            Self::Crosschain => [0xff, 0xff],
144            Self::Asset(asset) => asset.to_eth_bytes(),
145        }
146    }
147    /// Read asset bytes from a protocol payload and convert into
148    /// an Asset or mark as cross-chain
149    pub fn from_eth_bytes(bytes: [u8; 2]) -> Result<Self> {
150        Ok(match bytes {
151            [0xff, 0xff] => Self::Crosschain,
152            _ => Self::Asset(Asset::from_eth_bytes(bytes)?),
153        })
154    }
155}
156
157impl From<Asset> for AssetOrCrosschain {
158    fn from(asset: Asset) -> Self {
159        Self::Asset(asset)
160    }
161}
162
163impl From<AssetofPrecision> for AssetOrCrosschain {
164    fn from(asset_prec: AssetofPrecision) -> Self {
165        Self::Asset(asset_prec.asset)
166    }
167}
168
169/// Ethereum addresses are 20 bytes of data that follow a specific
170/// encoding, usually represented by a hex string
171#[derive(Clone, Debug, PartialEq)]
172pub struct Address {
173    pub(crate) inner: [u8; 20],
174}
175
176impl Address {
177    /// Construct a new address from a string. FIXME: probably not a major
178    /// issue, but we could consider other validation here as well
179    /// FIXME: we could take a public key / Secp256k1Point as input (or Secp256r1Point
180    /// in case of NEO) instead of an address, and convert that point to the blockchain-specific address then.
181    /// In this way, we would at least ensure that the input represents a valid point on the curve.
182    pub fn new(s: &str) -> Result<Self> {
183        let hex_bytes = hex::decode(s)
184            .map_err(|_| ProtocolError("Could not decode string to hex in ETH address"))?;
185        match hex_bytes.len() {
186            20 => {
187                let mut inner: [u8; 20] = [0; 20];
188                inner[..20].copy_from_slice(&hex_bytes[..20]);
189                Ok(Self { inner })
190            }
191            _ => Err(ProtocolError("Invalid address, string hex length != 20")),
192        }
193    }
194
195    /// Serialize the address into bytes for payload creation
196    pub fn to_bytes(&self) -> [u8; 20] {
197        self.inner
198    }
199
200    /// Create an address from ETH payload bytes
201    pub fn from_bytes(bytes: [u8; 20]) -> Result<Self> {
202        let _to_validate = hex::encode(bytes);
203        // FIXME: do some validation here
204        Ok(Self { inner: bytes })
205    }
206}
207
208/// Public key representation for Ethereum
209#[derive(Clone, Debug, PartialEq)]
210pub struct PublicKey {
211    inner: Secp256k1Point,
212}
213
214impl PublicKey {
215    /// Create a new Ethereum public key from a hex string
216    pub fn new(hex_str: &str) -> Result<Self> {
217        let inner = Secp256k1Point::from_hex(hex_str).map_err(|_| {
218            ProtocolError("Could not create public key (Secp256r1Point) from hex string")
219        })?;
220        Ok(Self { inner })
221    }
222
223    /// generate Ethereum address from public key
224    pub fn to_address(&self) -> Address {
225        // remove leading byte (0x04) that indicates an uncompressed public key
226        let pk_bytes = &self.inner.to_vec()[1..];
227        // hash public key
228        let hash: [u8; 32] = Keccak256::digest(&pk_bytes).into();
229        // last 20 hex-encoded bytes of hash are the address
230        Address::new(&hex::encode(&hash[12..])).unwrap()
231    }
232    /// Convert Ethereum public key into a hex string
233    pub fn to_hex(&self) -> String {
234        self.inner.to_hex()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::{Address, PublicKey};
241    use crate::types::{Amount, Asset, Nonce};
242
243    #[test]
244    fn test_serialize_and_deserialize() {
245        let nonce = Nonce::Value(35);
246        assert_eq!(nonce, Nonce::from_be_bytes(nonce.to_be_bytes()).unwrap());
247        let address = Address::new("D58547F100B67BB99BBE8E94523B6BB4FDA76954").unwrap();
248        assert_eq!(address, Address::from_bytes(address.to_bytes()).unwrap());
249        let asset = Asset::ETH;
250        assert_eq!(asset, Asset::from_eth_bytes(asset.to_eth_bytes()).unwrap());
251        let amount = Amount::new("45", 8).unwrap();
252        assert_eq!(
253            amount,
254            Amount::from_bytes(amount.to_be_bytes().unwrap(), 8).unwrap()
255        );
256    }
257
258    #[test]
259    fn test_pk_to_addr() {
260        assert_eq!(
261            PublicKey::new("04be641c583207c310739a23973fb7cb7336d2b835517ede791e9fa53fa5b0fc46390ebb4dab62e8b01352f37308dbff1512615856bffd3c752db95737d3bc93a4").unwrap().to_address(),
262            Address::new("55D16CA38DFB219141AB6617B2872B978AF84702").unwrap(),
263        );
264        assert_eq!(
265            PublicKey::new("0475f8a0f4eda35b7194d265df33720cf80164f196765980fae29795f713b340d93a0fa0632f56b265450c8d9d3a631c9a79089a57f40316bb3b66a09c51ba9fcb").unwrap().to_address(),
266            Address::new("CA7E5135E04371D048A78C4592BA3B61A984B563").unwrap(),
267        );
268    }
269}