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}