namada_core/
ethereum_events.rs

1//! Types representing data intended for Namada via Ethereum events
2
3use std::cmp::Ordering;
4use std::fmt::{Display, Formatter};
5use std::str::FromStr;
6
7use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
8use ethabi::Token;
9use ethabi::ethereum_types::{H160, U256 as ethUint};
10use eyre::{Context, eyre};
11use namada_macros::BorshDeserializer;
12#[cfg(feature = "migrations")]
13use namada_migrations::*;
14use serde::{Deserialize, Serialize};
15
16use crate::address::Address;
17use crate::borsh::BorshSerializeExt;
18use crate::eth_abi::Encode;
19use crate::ethereum_structs::Erc20Transfer;
20use crate::hash::Hash;
21use crate::keccak::KeccakHash;
22use crate::storage::{DbKeySeg, KeySeg};
23use crate::token::Amount;
24
25/// Namada native type to replace the ethabi::Uint type
26#[derive(
27    Copy,
28    Clone,
29    Debug,
30    Default,
31    Hash,
32    PartialEq,
33    Eq,
34    Serialize,
35    Deserialize,
36    BorshSerialize,
37    BorshDeserialize,
38    BorshDeserializer,
39    BorshSchema,
40)]
41#[repr(align(32))]
42pub struct Uint(pub [u64; 4]);
43
44impl PartialOrd for Uint {
45    #[inline]
46    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
47        Some(self.cmp(other))
48    }
49}
50
51impl Ord for Uint {
52    #[inline]
53    fn cmp(&self, other: &Self) -> Ordering {
54        ethUint(self.0).cmp(&ethUint(other.0))
55    }
56}
57
58impl Uint {
59    /// Convert to an Ethereum-compatible byte representation.
60    ///
61    /// The Ethereum virtual machine employs big-endian integers
62    /// (Wood, 2014), therefore the returned byte array has the
63    /// same endianness.
64    pub fn to_bytes(self) -> [u8; 32] {
65        let mut bytes = [0; 32];
66        ethUint(self.0).to_big_endian(&mut bytes);
67        bytes
68    }
69
70    /// Try to increment this [`Uint`], whilst checking
71    /// for overflows.
72    pub fn checked_increment(self) -> Option<Self> {
73        ethUint::from(self).checked_add(1.into()).map(Self::from)
74    }
75}
76
77impl Display for Uint {
78    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
79        ethUint(self.0).fmt(f)
80    }
81}
82
83impl Encode<1> for Uint {
84    fn tokenize(&self) -> [Token; 1] {
85        [Token::Uint(self.into())]
86    }
87}
88
89impl From<ethUint> for Uint {
90    fn from(value: ethUint) -> Self {
91        Self(value.0)
92    }
93}
94
95impl From<Uint> for ethUint {
96    fn from(value: Uint) -> Self {
97        Self(value.0)
98    }
99}
100
101impl From<&Uint> for ethUint {
102    fn from(value: &Uint) -> Self {
103        Self(value.0)
104    }
105}
106
107impl From<u64> for Uint {
108    fn from(value: u64) -> Self {
109        ethUint::from(value).into()
110    }
111}
112
113/// Representation of address on Ethereum. The inner value is the last 20 bytes
114/// of the public key that controls the account.
115#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
116#[derive(
117    Copy,
118    Clone,
119    Debug,
120    PartialEq,
121    Eq,
122    PartialOrd,
123    Ord,
124    Hash,
125    Serialize,
126    Deserialize,
127    BorshSerialize,
128    BorshDeserialize,
129    BorshDeserializer,
130    BorshSchema,
131)]
132#[serde(try_from = "String")]
133#[serde(into = "String")]
134pub struct EthAddress(pub [u8; 20]);
135
136impl EthAddress {
137    /// The canonical way we represent an [`EthAddress`] in storage keys. A
138    /// 40-character lower case hexadecimal address prefixed by '0x'.
139    /// e.g. "0x6b175474e89094c44da98b954eedeac495271d0f"
140    pub fn to_canonical(&self) -> String {
141        format!("{:?}", ethabi::ethereum_types::Address::from(&self.0))
142    }
143}
144
145impl From<H160> for EthAddress {
146    fn from(H160(addr): H160) -> Self {
147        Self(addr)
148    }
149}
150
151impl From<EthAddress> for H160 {
152    fn from(EthAddress(addr): EthAddress) -> Self {
153        Self(addr)
154    }
155}
156
157impl Display for EthAddress {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        write!(f, "{}", self.to_canonical())
160    }
161}
162
163impl FromStr for EthAddress {
164    type Err = eyre::Error;
165
166    /// Parses an [`EthAddress`] from a standard hex-encoded Ethereum address
167    /// string. e.g. "0x6B175474E89094C44Da98b954EedeAC495271d0F"
168    fn from_str(s: &str) -> Result<Self, Self::Err> {
169        let h160 = ethabi::ethereum_types::Address::from_str(s)
170            .wrap_err_with(|| eyre!("couldn't parse Ethereum address {}", s))?;
171        Ok(Self(h160.into()))
172    }
173}
174
175impl TryFrom<String> for EthAddress {
176    type Error = eyre::Error;
177
178    fn try_from(string: String) -> Result<Self, eyre::Error> {
179        Self::from_str(string.as_ref())
180    }
181}
182
183impl From<EthAddress> for String {
184    fn from(addr: EthAddress) -> Self {
185        addr.to_string()
186    }
187}
188
189impl KeySeg for EthAddress {
190    fn parse(string: String) -> crate::storage::Result<Self> {
191        Self::from_str(string.as_str())
192            .map_err(|_| crate::storage::Error::ParseKeySeg(string))
193    }
194
195    fn raw(&self) -> String {
196        self.to_canonical()
197    }
198
199    fn to_db_key(&self) -> DbKeySeg {
200        DbKeySeg::StringSeg(self.raw())
201    }
202}
203
204/// Nonces of Ethereum events.
205pub trait GetEventNonce {
206    /// Returns the nonce of an Ethereum event.
207    fn get_event_nonce(&self) -> Uint;
208}
209
210/// Event transferring batches of ether or Ethereum based ERC20 tokens
211/// from Ethereum to wrapped assets on Namada
212#[derive(
213    PartialEq,
214    Eq,
215    PartialOrd,
216    Hash,
217    Ord,
218    Clone,
219    Debug,
220    BorshSerialize,
221    BorshDeserialize,
222    BorshDeserializer,
223    BorshSchema,
224)]
225pub struct TransfersToNamada {
226    /// Monotonically increasing nonce
227    pub nonce: Uint,
228    /// The batch of transfers
229    pub transfers: Vec<TransferToNamada>,
230}
231
232impl GetEventNonce for TransfersToNamada {
233    #[inline]
234    fn get_event_nonce(&self) -> Uint {
235        self.nonce
236    }
237}
238
239impl From<TransfersToNamada> for EthereumEvent {
240    #[inline]
241    fn from(event: TransfersToNamada) -> Self {
242        let TransfersToNamada { nonce, transfers } = event;
243        Self::TransfersToNamada { nonce, transfers }
244    }
245}
246
247/// An Ethereum event to be processed by the Namada ledger
248#[derive(
249    PartialEq,
250    Eq,
251    PartialOrd,
252    Hash,
253    Ord,
254    Clone,
255    Debug,
256    BorshSerialize,
257    BorshDeserialize,
258    BorshDeserializer,
259    BorshSchema,
260)]
261// NOTE: Avoid changing the order of the elements in this struct,
262// to maintain compatibility between Namada versions.
263pub enum EthereumEvent {
264    /// Event transferring batches of ether or Ethereum based ERC20 tokens
265    /// from Ethereum to wrapped assets on Namada
266    TransfersToNamada {
267        /// Monotonically increasing nonce
268        nonce: Uint,
269        /// The batch of transfers
270        transfers: Vec<TransferToNamada>,
271    },
272    /// A confirmation event that a batch of transfers have been made
273    /// from Namada to Ethereum
274    TransfersToEthereum {
275        /// Monotonically increasing nonce
276        nonce: Uint,
277        /// The batch of transfers
278        transfers: Vec<TransferToEthereum>,
279        /// The Namada address that receives the gas fees
280        /// for relaying a batch of transfers
281        relayer: Address,
282    },
283    /// Event indication that the validator set has been updated
284    /// in the governance contract
285    ValidatorSetUpdate {
286        /// Monotonically increasing nonce
287        nonce: Uint,
288        /// Hash of the validators in the bridge contract
289        bridge_validator_hash: KeccakHash,
290        /// Hash of the validators in the governance contract
291        governance_validator_hash: KeccakHash,
292    },
293}
294
295impl EthereumEvent {
296    /// SHA256 of the Borsh serialization of the [`EthereumEvent`].
297    pub fn hash(&self) -> Result<Hash, std::io::Error> {
298        let bytes = self.serialize_to_vec();
299        Ok(Hash::sha256(bytes))
300    }
301}
302
303/// An event transferring some kind of value from Ethereum to Namada
304#[derive(
305    Clone,
306    Debug,
307    PartialEq,
308    Eq,
309    PartialOrd,
310    Hash,
311    Ord,
312    BorshSerialize,
313    BorshDeserialize,
314    BorshDeserializer,
315    BorshSchema,
316)]
317pub struct TransferToNamada {
318    /// Quantity of the ERC20 token in the transfer
319    pub amount: Amount,
320    /// Address of the smart contract issuing the token
321    pub asset: EthAddress,
322    /// The address receiving wrapped assets on Namada
323    pub receiver: Address,
324}
325
326/// An event transferring some kind of value from Namada to Ethereum
327#[derive(
328    Clone,
329    Debug,
330    PartialEq,
331    Eq,
332    Hash,
333    PartialOrd,
334    Ord,
335    BorshSerialize,
336    BorshDeserialize,
337    BorshDeserializer,
338    BorshSchema,
339    Serialize,
340    Deserialize,
341)]
342pub struct TransferToEthereum {
343    /// Quantity of wrapped Asset in the transfer
344    pub amount: Amount,
345    /// Address of the smart contract issuing the token
346    pub asset: EthAddress,
347    /// The address receiving assets on Ethereum
348    pub receiver: EthAddress,
349    /// Checksum of all Namada specific fields, including,
350    /// but not limited to, whether it is a NUT transfer,
351    /// the address of the sender, etc
352    ///
353    /// It serves to uniquely identify an event stored under
354    /// the Bridge pool, in Namada
355    pub checksum: Hash,
356}
357
358impl From<Erc20Transfer> for TransferToEthereum {
359    #[inline]
360    fn from(transfer: Erc20Transfer) -> Self {
361        Self {
362            amount: {
363                let uint = {
364                    use crate::uint::Uint as NamadaUint;
365                    let mut num_buf = [0; 32];
366                    transfer.amount.to_little_endian(&mut num_buf);
367                    NamadaUint::from_little_endian(&num_buf)
368                };
369                // this is infallible for a denom of 0
370                Amount::from_uint(uint, 0).unwrap()
371            },
372            asset: EthAddress(transfer.from.0),
373            receiver: EthAddress(transfer.to.0),
374            checksum: Hash(transfer.data_digest),
375        }
376    }
377}
378
379#[cfg(test)]
380mod tests {
381
382    use super::*;
383
384    #[test]
385    fn test_eth_address_to_canonical() {
386        let canonical = testing::DAI_ERC20_ETH_ADDRESS.to_canonical();
387
388        assert_eq!(
389            testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(),
390            canonical,
391        );
392    }
393
394    #[test]
395    fn test_eth_address_from_str() {
396        let addr =
397            EthAddress::from_str(testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED)
398                .unwrap();
399
400        assert_eq!(testing::DAI_ERC20_ETH_ADDRESS, addr);
401    }
402
403    #[test]
404    fn test_eth_address_from_str_error() {
405        let result = EthAddress::from_str(
406            "arbitrary string which isn't an Ethereum address",
407        );
408
409        assert!(result.is_err());
410    }
411
412    /// Test that serde correct serializes EthAddress types to/from lowercase
413    /// hex encodings
414    #[test]
415    fn test_eth_address_serde_roundtrip() {
416        let addr =
417            EthAddress::from_str(testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED)
418                .unwrap();
419        let serialized = serde_json::to_string(&addr).expect("Test failed");
420        assert_eq!(
421            serialized,
422            format!(
423                r#""{}""#,
424                testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_lowercase()
425            )
426        );
427        let deserialized: EthAddress =
428            serde_json::from_str(&serialized).expect("Test failed");
429        assert_eq!(addr, deserialized);
430    }
431}
432
433#[allow(missing_docs)]
434#[allow(clippy::arithmetic_side_effects)]
435/// Test helpers
436#[cfg(any(test, feature = "testing", feature = "benches"))]
437pub mod testing {
438    use proptest::prop_compose;
439
440    use super::*;
441    use crate::token;
442
443    pub const DAI_ERC20_ETH_ADDRESS_CHECKSUMMED: &str =
444        "0x6B175474E89094C44Da98b954EedeAC495271d0F";
445    pub const DAI_ERC20_ETH_ADDRESS: EthAddress = EthAddress([
446        107, 23, 84, 116, 232, 144, 148, 196, 77, 169, 139, 149, 78, 237, 234,
447        196, 149, 39, 29, 15,
448    ]);
449    pub const USDC_ERC20_ETH_ADDRESS_CHECKSUMMED: &str =
450        "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
451    pub const USDC_ERC20_ETH_ADDRESS: EthAddress = EthAddress([
452        160, 184, 105, 145, 198, 33, 139, 54, 193, 209, 157, 74, 46, 158, 176,
453        206, 54, 6, 235, 72,
454    ]);
455
456    impl std::ops::Add<u64> for Uint {
457        type Output = Self;
458
459        fn add(self, rhs: u64) -> Self::Output {
460            (ethUint(self.0) + rhs).into()
461        }
462    }
463
464    impl std::ops::Sub<u64> for Uint {
465        type Output = Self;
466
467        fn sub(self, rhs: u64) -> Self::Output {
468            (ethUint(self.0) - rhs).into()
469        }
470    }
471
472    pub fn arbitrary_eth_address() -> EthAddress {
473        DAI_ERC20_ETH_ADDRESS
474    }
475
476    pub fn arbitrary_nonce() -> Uint {
477        0.into()
478    }
479
480    pub fn arbitrary_keccak_hash() -> KeccakHash {
481        KeccakHash([0; 32])
482    }
483
484    pub fn arbitrary_amount() -> Amount {
485        Amount::from(1_000)
486    }
487
488    pub fn arbitrary_bonded_stake() -> token::Amount {
489        token::Amount::from(1_000)
490    }
491
492    /// A [`EthereumEvent::TransfersToNamada`] containing a single transfer of
493    /// some arbitrary ERC20
494    pub fn arbitrary_single_transfer(
495        nonce: Uint,
496        receiver: Address,
497    ) -> EthereumEvent {
498        EthereumEvent::TransfersToNamada {
499            nonce,
500            transfers: vec![TransferToNamada {
501                amount: arbitrary_amount(),
502                asset: arbitrary_eth_address(),
503                receiver,
504            }],
505        }
506    }
507
508    prop_compose! {
509        // Generate an arbitrary Ethereum address
510        pub fn arb_eth_address()(bytes: [u8; 20]) -> EthAddress {
511            EthAddress(bytes)
512        }
513    }
514}