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 [`encodeType`](Self::eip712_encode_type)
78 /// 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 /// Calculates the EIP-712 [`hashStruct`] for this value.
88 ///
89 /// [`hashStruct`]: https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct
90 #[inline]
91 fn eip712_hash_struct(&self) -> B256 {
92 let mut hasher = alloy_primitives::Keccak256::new();
93 hasher.update(self.eip712_type_hash());
94 hasher.update(self.eip712_encode_data());
95 hasher.finalize()
96 }
97
98 /// Calculate the [EIP-712 signing hash](https://eips.ethereum.org/EIPS/eip-712#specification-of-the-eth_signtypeddata-json-rpc)
99 /// for this struct.
100 /// Note that this does not **sign** the hash, only calculates it.
101 ///
102 /// This is the hash of the magic bytes 0x1901 concatenated with the domain
103 /// separator and the `hashStruct` result:
104 /// `keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message))`
105 #[doc(alias = "sign_typed_data")]
106 #[doc(alias = "hash_typed_data")]
107 fn eip712_signing_hash(&self, domain: &Eip712Domain) -> B256 {
108 let mut digest_input = [0u8; 2 + 32 + 32];
109 digest_input[0] = 0x19;
110 digest_input[1] = 0x01;
111 digest_input[2..34].copy_from_slice(&domain.hash_struct()[..]);
112 digest_input[34..66].copy_from_slice(&self.eip712_hash_struct()[..]);
113 keccak256(digest_input)
114 }
115}