neo_devpack_solidity/
utils.rs1use std::collections::HashMap;
6
7pub fn canonical_param_type(ty: &str) -> String {
20 let mut parts = ty.split_whitespace();
21 match parts.next() {
22 Some("struct" | "enum") => parts.next().unwrap_or_default().to_string(),
23 Some(first) => first.to_string(),
24 None => String::new(),
25 }
26}
27
28pub fn canonical_param_type_simple(ty: &str) -> String {
32 ty.split_whitespace().next().unwrap_or_default().to_string()
33}
34
35pub fn canonical_param_type_with_structs(
50 ty: &str,
51 struct_fields: &HashMap<String, Vec<(String, String)>>,
52) -> String {
53 let trimmed = ty.trim();
56 let without_location = trimmed
57 .replace(" memory", "")
58 .replace(" calldata", "")
59 .replace(" storage", "");
60 let t = without_location.trim();
61
62 if t.ends_with(']') {
68 if let Some(open) = t.rfind('[') {
69 let (element, suffix) = t.split_at(open);
70 let inner = canonical_param_type_with_structs(element.trim(), struct_fields);
71 return format!("{inner}{suffix}");
72 }
73 }
74
75 let mut parts = t.split_whitespace();
80 match parts.next() {
81 Some("struct") => {
82 let name = parts.next().unwrap_or_default();
83 if name.is_empty() {
84 return String::new();
85 }
86 let mut visited = std::collections::HashSet::new();
89 expand_struct_canonical(name, struct_fields, &mut visited)
90 }
91 Some("enum") => "uint8".to_string(),
92 Some(first) => {
93 if struct_fields.contains_key(first) {
97 let mut visited = std::collections::HashSet::new();
98 expand_struct_canonical(first, struct_fields, &mut visited)
99 } else {
100 first.to_string()
101 }
102 }
103 None => String::new(),
104 }
105}
106
107fn expand_struct_canonical(
108 name: &str,
109 struct_fields: &HashMap<String, Vec<(String, String)>>,
110 visited: &mut std::collections::HashSet<String>,
111) -> String {
112 if !visited.insert(name.to_string()) {
113 return name.to_string();
117 }
118 let Some(fields) = struct_fields.get(name) else {
119 visited.remove(name);
121 return name.to_string();
122 };
123 let field_sigs: Vec<String> = fields
124 .iter()
125 .map(|(_, field_ty)| {
126 let tt = field_ty
128 .replace(" memory", "")
129 .replace(" calldata", "")
130 .replace(" storage", "");
131 let tt = tt.trim();
132 let mut parts = tt.split_whitespace();
133 match parts.next() {
134 Some("struct") => {
135 let sub_name = parts.next().unwrap_or_default();
136 if sub_name.is_empty() {
137 String::new()
138 } else {
139 expand_struct_canonical(sub_name, struct_fields, visited)
140 }
141 }
142 Some("enum") => "uint8".to_string(),
143 Some(first) => {
144 if struct_fields.contains_key(first) {
145 expand_struct_canonical(first, struct_fields, visited)
146 } else {
147 first.to_string()
148 }
149 }
150 None => String::new(),
151 }
152 })
153 .collect();
154 visited.remove(name);
155 format!("({})", field_sigs.join(","))
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_canonical_param_type() {
164 assert_eq!(canonical_param_type("uint256"), "uint256");
165 assert_eq!(canonical_param_type("struct MyStruct"), "MyStruct");
166 assert_eq!(canonical_param_type("enum MyEnum"), "MyEnum");
167 assert_eq!(canonical_param_type("address payable"), "address");
168 assert_eq!(canonical_param_type(""), "");
169 }
170
171 #[test]
172 fn test_canonical_param_type_simple() {
173 assert_eq!(canonical_param_type_simple("uint256"), "uint256");
174 assert_eq!(canonical_param_type_simple("struct MyStruct"), "struct");
175 assert_eq!(canonical_param_type_simple("address payable"), "address");
176 }
177
178 #[test]
179 fn test_canonical_param_type_with_structs_flat() {
180 let mut map: HashMap<String, Vec<(String, String)>> = HashMap::new();
181 map.insert(
182 "P".to_string(),
183 vec![
184 ("a".to_string(), "uint256".to_string()),
185 ("b".to_string(), "bool".to_string()),
186 ],
187 );
188 assert_eq!(
189 canonical_param_type_with_structs("struct P", &map),
190 "(uint256,bool)"
191 );
192 assert_eq!(
193 canonical_param_type_with_structs("struct P memory", &map),
194 "(uint256,bool)"
195 );
196 assert_eq!(
197 canonical_param_type_with_structs("uint256", &map),
198 "uint256"
199 );
200 }
201
202 #[test]
203 fn test_canonical_param_type_with_structs_nested() {
204 let mut map: HashMap<String, Vec<(String, String)>> = HashMap::new();
205 map.insert(
206 "Inner".to_string(),
207 vec![("x".to_string(), "uint256".to_string())],
208 );
209 map.insert(
210 "Outer".to_string(),
211 vec![
212 ("inner".to_string(), "struct Inner".to_string()),
213 ("who".to_string(), "address".to_string()),
214 ],
215 );
216 assert_eq!(
217 canonical_param_type_with_structs("struct Outer", &map),
218 "((uint256),address)"
219 );
220 }
221
222 #[test]
223 fn test_canonical_param_type_with_structs_unknown_fallback() {
224 let map: HashMap<String, Vec<(String, String)>> = HashMap::new();
225 assert_eq!(
227 canonical_param_type_with_structs("struct Unknown", &map),
228 "Unknown"
229 );
230 }
231
232 #[test]
233 fn test_canonical_param_type_with_structs_enum() {
234 let map: HashMap<String, Vec<(String, String)>> = HashMap::new();
235 assert_eq!(
236 canonical_param_type_with_structs("enum MyEnum", &map),
237 "uint8"
238 );
239 }
240}