1use crate::SolValue;
2use alloc::{borrow::Cow, string::String, vec::Vec};
3use alloy_primitives::{keccak256, Address, FixedBytes, B256, U256};
4
5#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
12#[cfg_attr(feature = "eip712-serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "eip712-serde", serde(rename_all = "camelCase"))]
14pub struct Eip712Domain {
15 #[cfg_attr(feature = "eip712-serde", serde(default, skip_serializing_if = "Option::is_none"))]
18 pub name: Option<Cow<'static, str>>,
19
20 #[cfg_attr(feature = "eip712-serde", serde(default, skip_serializing_if = "Option::is_none"))]
23 pub version: Option<Cow<'static, str>>,
24
25 #[cfg_attr(feature = "eip712-serde", serde(default, skip_serializing_if = "Option::is_none"))]
28 pub chain_id: Option<U256>,
29
30 #[cfg_attr(feature = "eip712-serde", serde(default, skip_serializing_if = "Option::is_none"))]
32 pub verifying_contract: Option<Address>,
33
34 #[cfg_attr(feature = "eip712-serde", serde(default, skip_serializing_if = "Option::is_none"))]
37 pub salt: Option<B256>,
38}
39
40impl Eip712Domain {
41 pub const NAME: &'static str = "EIP712Domain";
43
44 #[inline]
49 pub const fn new(
50 name: Option<Cow<'static, str>>,
51 version: Option<Cow<'static, str>>,
52 chain_id: Option<U256>,
53 verifying_contract: Option<Address>,
54 salt: Option<B256>,
55 ) -> Self {
56 Self { name, version, chain_id, verifying_contract, salt }
57 }
58
59 #[inline]
61 pub fn separator(&self) -> B256 {
62 self.hash_struct()
63 }
64
65 pub fn encode_type(&self) -> String {
69 macro_rules! encode_type {
71 ($($field:ident => $repr:literal),+ $(,)?) => {
72 let mut ty = String::with_capacity(Self::NAME.len() + 2 $(+ $repr.len() * self.$field.is_some() as usize)+);
73 ty.push_str(Self::NAME);
74 ty.push('(');
75
76 $(
77 if self.$field.is_some() {
78 ty.push_str($repr);
79 }
80 )+
81 if ty.ends_with(',') {
82 ty.pop();
83 }
84
85 ty.push(')');
86 ty
87 };
88 }
89
90 encode_type! {
91 name => "string name,",
92 version => "string version,",
93 chain_id => "uint256 chainId,",
94 verifying_contract => "address verifyingContract,",
95 salt => "bytes32 salt",
96 }
97 }
98
99 #[inline]
105 pub fn type_hash(&self) -> B256 {
106 keccak256(self.encode_type().as_bytes())
107 }
108
109 #[inline]
112 pub const fn num_words(&self) -> usize {
113 self.name.is_some() as usize
114 + self.version.is_some() as usize
115 + self.chain_id.is_some() as usize
116 + self.verifying_contract.is_some() as usize
117 + self.salt.is_some() as usize
118 }
119
120 #[inline]
122 pub const fn abi_encoded_size(&self) -> usize {
123 self.num_words() * 32
124 }
125
126 pub fn encode_data_to(&self, out: &mut Vec<u8>) {
129 macro_rules! encode_opt {
131 ($opt:expr) => {
132 if let Some(t) = $opt {
133 out.extend_from_slice(t.tokenize().as_slice());
134 }
135 };
136 }
137
138 #[inline]
139 #[allow(clippy::ptr_arg)]
140 fn cow_keccak256(s: &Cow<'_, str>) -> FixedBytes<32> {
141 keccak256(s.as_bytes())
142 }
143
144 out.reserve(self.abi_encoded_size());
145 encode_opt!(self.name.as_ref().map(cow_keccak256));
146 encode_opt!(self.version.as_ref().map(cow_keccak256));
147 encode_opt!(&self.chain_id);
148 encode_opt!(&self.verifying_contract);
149 encode_opt!(&self.salt);
150 }
151
152 pub fn encode_data(&self) -> Vec<u8> {
154 let mut out = Vec::new();
155 self.encode_data_to(&mut out);
156 out
157 }
158
159 #[inline]
161 pub fn hash_struct(&self) -> B256 {
162 let mut hasher = alloy_primitives::Keccak256::new();
163 hasher.update(self.type_hash());
164 hasher.update(self.encode_data());
165 hasher.finalize()
166 }
167}
168
169#[macro_export]
200macro_rules! eip712_domain {
201 (@opt) => { $crate::private::None };
202 (@opt $e:expr) => { $crate::private::Some($e) };
203
204 (@cow) => { $crate::private::None };
206 (@cow $l:literal) => { $crate::private::Some($crate::private::Cow::Borrowed($l)) };
207 (@cow $e:expr) => { $crate::private::Some(<$crate::private::Cow<'static, str> as $crate::private::From<_>>::from($e)) };
208
209 (
210 $(name: $name:expr,)?
211 $(version: $version:expr,)?
212 $(chain_id: $chain_id:expr,)?
213 $(verifying_contract: $verifying_contract:expr,)?
214 $(salt: $salt:expr)?
215 $(,)?
216 ) => {
217 $crate::Eip712Domain::new(
218 $crate::eip712_domain!(@cow $($name)?),
219 $crate::eip712_domain!(@cow $($version)?),
220 $crate::eip712_domain!(@opt $($crate::private::u256($chain_id))?),
221 $crate::eip712_domain!(@opt $($verifying_contract)?),
222 $crate::eip712_domain!(@opt $($salt)?),
223 )
224 };
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 const _: Eip712Domain = eip712_domain! {
232 name: "abcd",
233 };
234 const _: Eip712Domain = eip712_domain! {
235 name: "abcd",
236 version: "1",
237 };
238 const _: Eip712Domain = eip712_domain! {
239 name: "abcd",
240 version: "1",
241 chain_id: 1,
242 };
243 const _: Eip712Domain = eip712_domain! {
244 name: "abcd",
245 version: "1",
246 chain_id: 1,
247 verifying_contract: Address::ZERO,
248 };
249 const _: Eip712Domain = eip712_domain! {
250 name: "abcd",
251 version: "1",
252 chain_id: 1,
253 verifying_contract: Address::ZERO,
254 salt: B256::ZERO };
256 const _: Eip712Domain = eip712_domain! {
257 name: "abcd",
258 version: "1",
259 chain_id: 1,
260 verifying_contract: Address::ZERO,
261 salt: B256::ZERO, };
263
264 const _: Eip712Domain = eip712_domain! {
265 name: "abcd",
266 version: "1",
267 verifying_contract: Address::ZERO,
269 salt: B256::ZERO,
270 };
271 const _: Eip712Domain = eip712_domain! {
272 name: "abcd",
273 chain_id: 1,
275 verifying_contract: Address::ZERO,
276 salt: B256::ZERO,
277 };
278 const _: Eip712Domain = eip712_domain! {
279 name: "abcd",
280 verifying_contract: Address::ZERO,
283 salt: B256::ZERO,
284 };
285 const _: Eip712Domain = eip712_domain! {
286 name: "abcd",
287 salt: B256::ZERO,
291 };
292 const _: Eip712Domain = eip712_domain! {
293 version: "1",
295 salt: B256::ZERO,
298 };
299 const _: Eip712Domain = eip712_domain! {
300 version: "1",
302 verifying_contract: Address::ZERO,
304 salt: B256::ZERO,
305 };
306
307 #[test]
308 fn runtime_domains() {
309 let _: Eip712Domain = eip712_domain! {
310 name: String::new(),
311 version: String::new(),
312 };
313
314 let my_string = String::from("!@#$%^&*()_+");
315 let _: Eip712Domain = eip712_domain! {
316 name: my_string.clone(),
317 version: my_string,
318 };
319
320 let my_cow = Cow::from("my_cow");
321 let _: Eip712Domain = eip712_domain! {
322 name: my_cow.clone(),
323 version: my_cow.into_owned(),
324 };
325 }
326}