namada_core/
eth_abi.rs

1//! This module defines encoding methods compatible with Ethereum
2//! smart contracts.
3
4use std::marker::PhantomData;
5
6use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
7#[doc(inline)]
8pub use ethabi::token::Token;
9
10use crate::keccak::{KeccakHash, keccak_hash};
11use crate::key::{Signable, SignableEthMessage};
12
13/// A container for data types that are able to be Ethereum ABI-encoded.
14#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)]
15#[repr(transparent)]
16pub struct EncodeCell<T: ?Sized> {
17    /// ABI-encoded value of type `T`.
18    encoded_data: Vec<u8>,
19    /// Indicate we do not own values of type `T`.
20    ///
21    /// Passing `PhantomData<T>` here would trigger the drop checker,
22    /// which is not the desired behavior, since we own an encoded value
23    /// of `T`, not a value of `T` itself.
24    _marker: PhantomData<*const T>,
25}
26
27impl<T> AsRef<[u8]> for EncodeCell<T> {
28    fn as_ref(&self) -> &[u8] {
29        &self.encoded_data
30    }
31}
32
33impl<T> ::std::cmp::Eq for EncodeCell<T> {}
34
35impl<T> ::std::cmp::PartialEq for EncodeCell<T> {
36    fn eq(&self, other: &Self) -> bool {
37        self.encoded_data == other.encoded_data
38    }
39}
40
41impl<T> ::std::cmp::PartialOrd for EncodeCell<T> {
42    fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
43        Some(self.cmp(other))
44    }
45}
46
47impl<T> ::std::cmp::Ord for EncodeCell<T> {
48    fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
49        self.encoded_data.cmp(&other.encoded_data)
50    }
51}
52
53impl<T> EncodeCell<T> {
54    /// Return a new ABI encoded value of type `T`.
55    pub fn new<const N: usize>(value: &T) -> Self
56    where
57        T: Encode<N>,
58    {
59        let encoded_data = {
60            let tokens = value.tokenize();
61            ethabi::encode(tokens.as_slice())
62        };
63        Self {
64            encoded_data,
65            _marker: PhantomData,
66        }
67    }
68
69    /// Here the type information is not compiler deduced,
70    /// proceed with caution!
71    pub fn new_from<const N: usize>(tokens: [Token; N]) -> Self {
72        Self {
73            encoded_data: ethabi::encode(&tokens),
74            _marker: PhantomData,
75        }
76    }
77
78    /// Return the underlying ABI encoded value.
79    pub fn into_inner(self) -> Vec<u8> {
80        self.encoded_data
81    }
82}
83
84/// Contains a method to encode data to a format compatible with Ethereum.
85pub trait Encode<const N: usize>: Sized {
86    /// Encodes a struct into a sequence of ABI
87    /// [`Token`] instances.
88    fn tokenize(&self) -> [Token; N];
89
90    /// Returns the encoded [`Token`] instances, in a type-safe enclosure.
91    fn encode(&self) -> EncodeCell<Self> {
92        EncodeCell::new(self)
93    }
94
95    /// Encodes a slice of [`Token`] instances, and returns the
96    /// keccak hash of the encoded string.
97    fn keccak256(&self) -> KeccakHash {
98        keccak_hash(self.encode().into_inner().as_slice())
99    }
100
101    /// Encodes a slice of [`Token`] instances, and returns the
102    /// keccak hash of the encoded string appended to an Ethereum
103    /// signature header. This can then be signed.
104    fn signable_keccak256(&self) -> KeccakHash {
105        let message = self.keccak256();
106        SignableEthMessage::as_signable(&message)
107    }
108}
109
110/// Represents an Ethereum encoding method equivalent
111/// to `abi.encode`.
112pub type AbiEncode<const N: usize> = [Token; N];
113
114impl<const N: usize> Encode<N> for AbiEncode<N> {
115    #[inline]
116    fn tokenize(&self) -> [Token; N] {
117        self.clone()
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use std::str::FromStr;
124
125    use data_encoding::HEXLOWER;
126    use ethabi::ethereum_types::U256;
127    use tiny_keccak::{Hasher, Keccak};
128
129    use super::*;
130    use crate::ethereum_events::EthAddress;
131
132    /// Checks if we get the same result as `abi.encode`, for some given
133    /// input data.
134    #[test]
135    fn test_abi_encode() {
136        let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000";
137        let expected = HEXLOWER
138            .decode(&expected.as_bytes()[2..])
139            .expect("Test failed");
140        let got = AbiEncode::encode(&[
141            Token::Uint(U256::from(42u64)),
142            Token::String("test".into()),
143        ]);
144        assert_eq!(expected, got.into_inner());
145    }
146
147    /// Sanity check our keccak hash implementation.
148    #[test]
149    fn test_keccak_hash_impl() {
150        let expected =
151            "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8";
152        assert_eq!(
153            expected,
154            &HEXLOWER.encode(
155                &{
156                    let mut st = Keccak::v256();
157                    let mut output = [0; 32];
158                    st.update(b"hello");
159                    st.finalize(&mut output);
160                    output
161                }[..]
162            )
163        );
164    }
165
166    /// Test that the methods for converting a keccak hash to/from
167    /// a string type are inverses.
168    #[test]
169    fn test_hex_roundtrip() {
170        let original =
171            "1C8AFF950685C2ED4BC3174F3472287B56D9517B9C948127319A09A7A36DEAC8";
172        let keccak_hash: KeccakHash = original.try_into().expect("Test failed");
173        assert_eq!(keccak_hash.to_string().as_str(), original);
174    }
175
176    #[test]
177    fn test_abi_encode_address() {
178        let address =
179            EthAddress::from_str("0xF0457e703bf0B9dEb1a6003FFD71C77E44575f95")
180                .expect("Test failed");
181        let expected = "0x000000000000000000000000f0457e703bf0b9deb1a6003ffd71c77e44575f95";
182        let expected = HEXLOWER
183            .decode(&expected.as_bytes()[2..])
184            .expect("Test failed");
185        let encoded = ethabi::encode(&[Token::Address(address.0.into())]);
186        assert_eq!(expected, encoded);
187    }
188}