alloy_sol_types/types/
struct.rs

1//! This module contains the [`SolStruct`] trait, which is used to implement
2//! Solidity structs logic, particularly for EIP-712 encoding/decoding.
3
4use super::SolType;
5use crate::Eip712Domain;
6use alloc::{borrow::Cow, string::String, vec::Vec};
7use alloy_primitives::{keccak256, B256};
8
9/// A Solidity struct.
10///
11/// This trait is used to implement ABI and EIP-712 encoding and decoding.
12///
13/// # Implementer's Guide
14///
15/// It should not be necessary to implement this trait manually. Instead, use
16/// the [`sol!`](crate::sol!) procedural macro to parse Solidity syntax into
17/// types that implement this trait.
18///
19/// # Note
20///
21/// Special attention should be paid to [`eip712_encode_type`] for complex
22/// Solidity types. Nested Solidity structs **must** properly encode their type.
23///
24/// To be clear, a struct with a nested struct must encode the nested struct's
25/// type as well.
26///
27/// See [EIP-712#definition-of-encodetype][ref] for more details.
28///
29/// [`eip712_encode_type`]: SolStruct::eip712_encode_type
30/// [ref]: https://eips.ethereum.org/EIPS/eip-712#definition-of-encodetype
31pub trait SolStruct: SolType<RustType = Self> {
32    /// The struct name.
33    ///
34    /// Used in [`eip712_encode_type`][SolStruct::eip712_encode_type].
35    const NAME: &'static str;
36
37    /// Returns component EIP-712 types. These types are used to construct
38    /// the `encodeType` string. These are the types of the struct's fields,
39    /// and should not include the root type.
40    fn eip712_components() -> Vec<Cow<'static, str>>;
41
42    /// Return the root EIP-712 type. This type is used to construct the
43    /// `encodeType` string.
44    fn eip712_root_type() -> Cow<'static, str>;
45
46    /// The EIP-712-encoded type string.
47    ///
48    /// See [EIP-712 `encodeType`](https://eips.ethereum.org/EIPS/eip-712#definition-of-encodetype).
49    fn eip712_encode_type() -> Cow<'static, str> {
50        fn eip712_encode_types(
51            root_type: Cow<'static, str>,
52            mut components: Vec<Cow<'static, str>>,
53        ) -> Cow<'static, str> {
54            if components.is_empty() {
55                return root_type;
56            }
57
58            components.sort_unstable();
59            components.dedup();
60
61            let mut s = String::with_capacity(
62                root_type.len() + components.iter().map(|s| s.len()).sum::<usize>(),
63            );
64            s.push_str(&root_type);
65            for component in components {
66                s.push_str(&component);
67            }
68            Cow::Owned(s)
69        }
70
71        eip712_encode_types(Self::eip712_root_type(), Self::eip712_components())
72    }
73
74    /// Calculates the [EIP-712 `typeHash`](https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash)
75    /// for this struct.
76    ///
77    /// This is defined as the Keccak-256 hash of the
78    /// [`encodeType`](Self::eip712_encode_type) string.
79    #[inline]
80    fn eip712_type_hash(&self) -> B256 {
81        keccak256(Self::eip712_encode_type().as_bytes())
82    }
83
84    /// Encodes this domain using [EIP-712 `encodeData`](https://eips.ethereum.org/EIPS/eip-712#definition-of-encodedata).
85    fn eip712_encode_data(&self) -> Vec<u8>;
86
87    /// Hashes this struct according to [EIP-712 `hashStruct`](https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct).
88    #[inline]
89    fn eip712_hash_struct(&self) -> B256 {
90        let mut hasher = alloy_primitives::Keccak256::new();
91        hasher.update(self.eip712_type_hash());
92        hasher.update(self.eip712_encode_data());
93        hasher.finalize()
94    }
95
96    /// Does something.
97    ///
98    /// See [EIP-712 `signTypedData`](https://eips.ethereum.org/EIPS/eip-712#specification-of-the-eth_signtypeddata-json-rpc).
99    #[inline]
100    fn eip712_signing_hash(&self, domain: &Eip712Domain) -> B256 {
101        let mut digest_input = [0u8; 2 + 32 + 32];
102        digest_input[0] = 0x19;
103        digest_input[1] = 0x01;
104        digest_input[2..34].copy_from_slice(&domain.hash_struct()[..]);
105        digest_input[34..66].copy_from_slice(&self.eip712_hash_struct()[..]);
106        keccak256(digest_input)
107    }
108}