forge/cmd/
eip712.rs
1use clap::{Parser, ValueHint};
2use eyre::Result;
3use foundry_cli::opts::{solar_pcx_from_build_opts, BuildOpts};
4use solar_parse::interface::Session;
5use solar_sema::{
6 hir::StructId,
7 thread_local::ThreadLocal,
8 ty::{Ty, TyKind},
9 GcxWrapper, Hir,
10};
11use std::{collections::BTreeMap, fmt::Write, path::PathBuf};
12
13foundry_config::impl_figment_convert!(Eip712Args, build);
14
15#[derive(Clone, Debug, Parser)]
17pub struct Eip712Args {
18 #[arg(value_hint = ValueHint::FilePath, value_name = "PATH")]
20 pub target_path: PathBuf,
21
22 #[command(flatten)]
23 build: BuildOpts,
24}
25
26impl Eip712Args {
27 pub fn run(self) -> Result<()> {
28 let mut sess = Session::builder().with_stderr_emitter().build();
29 sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false);
30
31 let result = sess.enter(|| -> Result<()> {
32 let parsing_context =
34 solar_pcx_from_build_opts(&sess, self.build, Some(vec![self.target_path]))?;
35
36 let hir_arena = ThreadLocal::new();
38 if let Ok(Some(gcx)) = parsing_context.parse_and_lower(&hir_arena) {
39 let resolver = Resolver::new(gcx);
40 for id in &resolver.struct_ids() {
41 if let Some(resolved) = resolver.resolve_struct_eip712(*id) {
42 _ = sh_println!("{resolved}\n");
43 }
44 }
45 }
46
47 Ok(())
48 });
49
50 eyre::ensure!(result.is_ok() && sess.dcx.has_errors().is_ok(), "failed parsing");
51
52 Ok(())
53 }
54}
55
56pub struct Resolver<'hir> {
60 hir: &'hir Hir<'hir>,
61 gcx: GcxWrapper<'hir>,
62}
63
64impl<'hir> Resolver<'hir> {
65 pub fn new(gcx: GcxWrapper<'hir>) -> Self {
67 Self { hir: &gcx.get().hir, gcx }
68 }
69
70 pub fn struct_ids(&self) -> Vec<StructId> {
72 self.hir.strukt_ids().collect()
73 }
74
75 pub fn resolve_struct_eip712(&self, id: StructId) -> Option<String> {
80 let mut subtypes = BTreeMap::new();
81 subtypes.insert(self.hir.strukt(id).name.as_str().into(), id);
82 self.resolve_eip712_inner(id, &mut subtypes, true, None)
83 }
84
85 fn resolve_eip712_inner(
86 &self,
87 id: StructId,
88 subtypes: &mut BTreeMap<String, StructId>,
89 append_subtypes: bool,
90 rename: Option<&str>,
91 ) -> Option<String> {
92 let def = self.hir.strukt(id);
93 let mut result = format!("{}(", rename.unwrap_or(def.name.as_str()));
94
95 for (idx, field_id) in def.fields.iter().enumerate() {
96 let field = self.hir.variable(*field_id);
97 let ty = self.resolve_type(self.gcx.get().type_of_hir_ty(&field.ty), subtypes)?;
98
99 write!(result, "{ty} {name}", name = field.name?.as_str()).ok()?;
100
101 if idx < def.fields.len() - 1 {
102 result.push(',');
103 }
104 }
105
106 result.push(')');
107
108 if append_subtypes {
109 for (subtype_name, subtype_id) in
110 subtypes.iter().map(|(name, id)| (name.clone(), *id)).collect::<Vec<_>>()
111 {
112 if subtype_id == id {
113 continue
114 }
115 let encoded_subtype =
116 self.resolve_eip712_inner(subtype_id, subtypes, false, Some(&subtype_name))?;
117
118 result.push_str(&encoded_subtype);
119 }
120 }
121
122 Some(result)
123 }
124
125 fn resolve_type(
126 &self,
127 ty: Ty<'hir>,
128 subtypes: &mut BTreeMap<String, StructId>,
129 ) -> Option<String> {
130 let ty = ty.peel_refs();
131 match ty.kind {
132 TyKind::Elementary(elem_ty) => Some(elem_ty.to_abi_str().to_string()),
133 TyKind::Array(element_ty, size) => {
134 let inner_type = self.resolve_type(element_ty, subtypes)?;
135 let size = size.to_string();
136 Some(format!("{inner_type}[{size}]"))
137 }
138 TyKind::DynArray(element_ty) => {
139 let inner_type = self.resolve_type(element_ty, subtypes)?;
140 Some(format!("{inner_type}[]"))
141 }
142 TyKind::Udvt(ty, _) => self.resolve_type(ty, subtypes),
143 TyKind::Struct(id) => {
144 let def = self.hir.strukt(id);
145 let name = match subtypes.iter().find(|(_, cached_id)| id == **cached_id) {
146 Some((name, _)) => name.to_string(),
147 None => {
148 let mut i = 0;
150 let mut name = def.name.as_str().into();
151 while subtypes.contains_key(&name) {
152 i += 1;
153 name = format!("{}_{i}", def.name.as_str());
154 }
155
156 subtypes.insert(name.clone(), id);
157
158 for field_id in def.fields {
160 let field_ty =
161 self.gcx.get().type_of_hir_ty(&self.hir.variable(*field_id).ty);
162 self.resolve_type(field_ty, subtypes)?;
163 }
164 name
165 }
166 };
167
168 Some(name)
169 }
170 TyKind::Enum(_) => Some("uint8".to_string()),
172 TyKind::Contract(_) => Some("address".to_string()),
174 _ => None,
176 }
177 }
178}