1use crate::{Encoding, Layout, LayoutError, Location, Result, Value};
17use alloy_primitives::{keccak256, Address, B256, I256, U256};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Getter {
22 pub name: String,
24 pub selector: [u8; 4],
26 pub inputs: Vec<String>,
28 pub outputs: Vec<String>,
30}
31
32#[derive(Debug, Clone, Default)]
34pub struct Getters(Vec<Getter>);
35
36impl Getters {
37 pub fn from_abi(abi: &serde_json::Value) -> Self {
41 let mut out = Vec::new();
42 let Some(items) = abi.as_array() else {
43 return Self(out);
44 };
45 for f in items {
46 if f["type"].as_str() != Some("function") {
47 continue;
48 }
49 if !matches!(f["stateMutability"].as_str(), Some("view") | Some("pure")) {
50 continue;
51 }
52 let Some(name) = f["name"].as_str() else {
53 continue;
54 };
55 let types = |k: &str| -> Option<Vec<String>> {
56 f[k].as_array()?
57 .iter()
58 .map(|p| p["type"].as_str().map(String::from))
59 .collect()
60 };
61 let (Some(inputs), Some(outputs)) = (types("inputs"), types("outputs")) else {
62 continue;
63 };
64 if !inputs.iter().all(|t| is_static_word(t)) {
65 continue;
66 }
67 let sig = format!("{name}({})", inputs.join(","));
68 let h = keccak256(sig.as_bytes());
69 out.push(Getter {
70 name: name.to_string(),
71 selector: [h[0], h[1], h[2], h[3]],
72 inputs,
73 outputs,
74 });
75 }
76 Self(out)
77 }
78
79 pub fn from_artifact_json(v: &serde_json::Value) -> Self {
81 v.get("abi").map(Self::from_abi).unwrap_or_default()
82 }
83
84 pub fn find(&self, selector: &[u8; 4]) -> Option<&Getter> {
86 self.0.iter().find(|g| &g.selector == selector)
87 }
88
89 pub fn is_empty(&self) -> bool {
91 self.0.is_empty()
92 }
93
94 pub fn len(&self) -> usize {
96 self.0.len()
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct ResolvedCall {
103 pub name: String,
105 pub path: String,
107 pub reads: Vec<Location>,
109 pub outputs: Vec<String>,
111}
112
113fn is_static_word(t: &str) -> bool {
114 t == "address"
115 || t == "bool"
116 || (t.starts_with("uint") || t.starts_with("int")) && !t.contains('[')
117 || (t.starts_with("bytes") && t.len() > 5 && t[5..].parse::<u8>().is_ok())
118}
119
120fn abi_type_of(label: &str) -> String {
122 if label == "address payable" || label.starts_with("contract ") {
123 return "address".into();
124 }
125 if label.starts_with("enum ") {
126 return "uint8".into();
127 }
128 if label == "string storage ref" {
129 return "string".into();
130 }
131 if label == "bytes storage ref" {
132 return "bytes".into();
133 }
134 label.to_string()
135}
136
137fn word_text(w: &[u8]) -> String {
141 format!("0x{}", alloy_primitives::hex::encode(w))
142}
143
144impl Layout {
145 pub fn resolve_call(&self, getters: &Getters, calldata: &[u8]) -> Result<Option<ResolvedCall>> {
150 if calldata.len() < 4 {
151 return Err(LayoutError::Syntax(
152 "calldata shorter than a selector".into(),
153 ));
154 }
155 let selector: [u8; 4] = [calldata[0], calldata[1], calldata[2], calldata[3]];
156 let Some(g) = getters.find(&selector) else {
157 return Ok(None);
158 };
159 let args = &calldata[4..];
160 if args.len() != 32 * g.inputs.len() {
161 return Err(LayoutError::Syntax(format!(
162 "{}: expected {} argument word(s), got {} bytes",
163 g.name,
164 g.inputs.len(),
165 args.len()
166 )));
167 }
168 let Some(top) = self.storage.iter().find(|e| e.label == g.name) else {
170 return Ok(None);
171 };
172 let mut path = g.name.clone();
174 let mut type_id = top.type_id.clone();
175 for (i, in_ty) in g.inputs.iter().enumerate() {
176 let Ok(t) = self.ty(&type_id) else {
177 return Ok(None);
178 };
179 let word = &args[32 * i..32 * i + 32];
180 match t.encoding {
181 Encoding::Mapping => {
182 let (Some(kt), Some(vt)) = (t.key.as_deref(), t.value.as_deref()) else {
183 return Ok(None);
184 };
185 let key_abi = self
186 .ty(kt)
187 .map(|k| abi_type_of(&k.label))
188 .unwrap_or_default();
189 if &key_abi != in_ty {
190 return Ok(None);
191 }
192 path = format!("{path}[{}]", word_text(word));
193 type_id = vt.to_string();
194 }
195 Encoding::DynamicArray | Encoding::Inplace if t.base.is_some() => {
196 if in_ty != "uint256" {
197 return Ok(None);
198 }
199 let idx = U256::from_be_slice(word);
200 path = format!("{path}[{idx}]");
201 type_id = t.base.clone().unwrap_or_default();
202 }
203 _ => return Ok(None),
204 }
205 }
206 let Ok(t) = self.ty(&type_id) else {
208 return Ok(None);
209 };
210 let (reads, outputs): (Vec<Location>, Vec<String>) =
211 match (t.encoding, t.members.as_deref(), t.base.is_some()) {
212 (Encoding::Inplace, Some(members), _) => {
213 let mut reads = Vec::new();
215 let mut outs = Vec::new();
216 for m in members {
217 let Ok(mt) = self.ty(&m.type_id) else {
218 return Ok(None);
219 };
220 if mt.encoding == Encoding::Mapping || mt.encoding == Encoding::DynamicArray
221 {
222 continue;
223 }
224 if mt.members.is_some() || mt.base.is_some() {
225 return Ok(None); }
227 let Ok(loc) = self.locate(&format!("{path}.{}", m.label)) else {
228 return Ok(None);
229 };
230 reads.push(loc);
231 outs.push(abi_type_of(&mt.label));
232 }
233 (reads, outs)
234 }
235 (Encoding::Inplace, None, false) | (Encoding::Bytes, _, _) => {
236 let Ok(loc) = self.locate(&path) else {
237 return Ok(None);
238 };
239 (vec![loc], vec![abi_type_of(&t.label)])
240 }
241 _ => return Ok(None), };
243 if outputs != g.outputs {
244 return Ok(None);
245 }
246 Ok(Some(ResolvedCall {
247 name: g.name.clone(),
248 path,
249 reads,
250 outputs,
251 }))
252 }
253}
254
255pub fn encode_return(values: &[Value], types: &[String]) -> Result<Vec<u8>> {
258 if values.len() != types.len() {
259 return Err(LayoutError::Syntax("values/types length mismatch".into()));
260 }
261 let n = values.len();
262 let mut head: Vec<[u8; 32]> = Vec::with_capacity(n);
263 let mut tail: Vec<u8> = Vec::new();
264 for (v, t) in values.iter().zip(types) {
265 let dynamic = t == "string" || t == "bytes";
266 if dynamic {
267 let bytes: Vec<u8> = match v {
268 Value::Str(s) => s.as_bytes().to_vec(),
269 Value::Bytes(b) | Value::FixedBytes(b) => b.clone(),
270 _ => return Err(LayoutError::Syntax(format!("{t}: not a byte value"))),
271 };
272 let offset = U256::from(32 * n + tail.len());
273 head.push(offset.to_be_bytes::<32>());
274 tail.extend_from_slice(&U256::from(bytes.len()).to_be_bytes::<32>());
275 tail.extend_from_slice(&bytes);
276 let pad = (32 - bytes.len() % 32) % 32;
277 tail.extend(std::iter::repeat_n(0u8, pad));
278 } else {
279 head.push(static_word(v, t)?);
280 }
281 }
282 let mut out = Vec::with_capacity(32 * n + tail.len());
283 for w in head {
284 out.extend_from_slice(&w);
285 }
286 out.extend_from_slice(&tail);
287 Ok(out)
288}
289
290fn static_word(v: &Value, t: &str) -> Result<[u8; 32]> {
291 Ok(match v {
292 Value::Uint(u) => u.to_be_bytes::<32>(),
293 Value::Int(i) => i.to_be_bytes::<32>(),
294 Value::Bool(b) => U256::from(*b as u8).to_be_bytes::<32>(),
295 Value::Address(a) => B256::left_padding_from(a.as_slice()).0,
296 Value::FixedBytes(b) => {
297 let mut w = [0u8; 32];
298 let n = b.len().min(32);
299 w[..n].copy_from_slice(&b[..n]);
300 w
301 }
302 Value::Raw(w) => {
303 if t.starts_with("int") {
304 I256::from_raw(U256::from_be_bytes(w.0)).to_be_bytes::<32>()
305 } else {
306 w.0
307 }
308 }
309 Value::Str(_) | Value::Bytes(_) => {
310 return Err(LayoutError::Syntax(format!(
311 "{t}: dynamic value in a static slot"
312 )))
313 }
314 })
315}
316
317pub fn word_address(w: &[u8; 32]) -> Address {
319 Address::from_slice(&w[12..])
320}
321
322#[cfg(test)]
323mod tests {
324 #![allow(clippy::unwrap_used)]
325 use super::*;
326
327 const ARTIFACT: &str = include_str!("../tests/fixtures/Playground.artifact.json");
328
329 fn setup() -> (Layout, Getters) {
330 let v: serde_json::Value = serde_json::from_str(ARTIFACT).unwrap();
331 (
332 Layout::from_json(ARTIFACT).unwrap(),
333 Getters::from_artifact_json(&v),
334 )
335 }
336
337 fn call(sig: &str, args: &[[u8; 32]]) -> Vec<u8> {
338 let h = keccak256(sig.as_bytes());
339 let mut d = h[..4].to_vec();
340 for a in args {
341 d.extend_from_slice(a);
342 }
343 d
344 }
345
346 #[test]
347 fn value_mapping_nested_array_and_struct_getters_resolve() {
348 let (l, g) = setup();
349 assert_eq!(g.len(), 9);
350 let user: Address = "0x35825972e2ca90851b14576C531F13dA0B5d53ce"
351 .parse()
352 .unwrap();
353 let ukey = B256::left_padding_from(user.as_slice()).0;
354 let seven = U256::from(7).to_be_bytes::<32>();
355
356 let r = l
357 .resolve_call(&g, &call("counter()", &[]))
358 .unwrap()
359 .unwrap();
360 assert_eq!(
361 (r.path.as_str(), r.outputs.as_slice()),
362 ("counter", &["uint256".to_string()][..])
363 );
364 assert_eq!(r.reads[0], l.locate("counter").unwrap());
365
366 let r = l
367 .resolve_call(&g, &call("balances(address)", &[ukey]))
368 .unwrap()
369 .unwrap();
370 assert_eq!(r.reads[0], l.locate(&format!("balances[{user}]")).unwrap());
371
372 let r = l
373 .resolve_call(&g, &call("nested(address,uint256)", &[ukey, seven]))
374 .unwrap()
375 .unwrap();
376 assert_eq!(r.reads[0], l.locate(&format!("nested[{user}][7]")).unwrap());
377
378 let r = l
379 .resolve_call(&g, &call("items(uint256)", &[seven]))
380 .unwrap()
381 .unwrap();
382 assert_eq!(r.reads[0], l.locate("items[7]").unwrap());
383
384 let r = l.resolve_call(&g, &call("totals()", &[])).unwrap().unwrap();
386 assert_eq!(r.outputs, vec!["uint64".to_string(), "uint192".to_string()]);
387 assert_eq!(r.reads.len(), 2);
388 assert_eq!(r.reads[0], l.locate("totals.lastTime").unwrap());
389 assert_eq!(r.reads[1], l.locate("totals.index").unwrap());
390 }
391
392 #[test]
393 fn non_getters_are_not_resolved() {
394 let (l, g) = setup();
395 assert_eq!(
397 l.resolve_call(&g, &call("getReserves()", &[])).unwrap(),
398 None
399 );
400 assert!(l.resolve_call(&g, &call("balances(address)", &[])).is_err());
402 let mut abi: serde_json::Value = serde_json::from_str(ARTIFACT).unwrap();
404 abi["abi"].as_array_mut().unwrap().push(serde_json::json!({
405 "type": "function", "name": "counterPlusOne", "stateMutability": "view",
406 "inputs": [], "outputs": [{ "type": "uint256" }]
407 }));
408 let g2 = Getters::from_artifact_json(&abi);
409 assert_eq!(
410 l.resolve_call(&g2, &call("counterPlusOne()", &[])).unwrap(),
411 None
412 );
413 abi["abi"].as_array_mut().unwrap().push(serde_json::json!({
415 "type": "function", "name": "lastPoker", "stateMutability": "view",
416 "inputs": [{ "type": "uint256" }], "outputs": [{ "type": "address" }]
417 }));
418 let g3 = Getters::from_artifact_json(&abi);
419 assert_eq!(
420 l.resolve_call(
421 &g3,
422 &call("lastPoker(uint256)", &[U256::ZERO.to_be_bytes::<32>()])
423 )
424 .unwrap(),
425 None
426 );
427 }
428
429 #[test]
430 fn return_encoding_static_and_dynamic() {
431 let user: Address = "0x35825972e2ca90851b14576C531F13dA0B5d53ce"
432 .parse()
433 .unwrap();
434 let out = encode_return(
435 &[
436 Value::Uint(U256::from(5)),
437 Value::Bool(true),
438 Value::Address(user),
439 ],
440 &["uint64".into(), "bool".into(), "address".into()],
441 )
442 .unwrap();
443 assert_eq!(out.len(), 96);
444 assert_eq!(out[31], 5);
445 assert_eq!(out[63], 1);
446 assert_eq!(&out[76..96], user.as_slice());
447 let out = encode_return(&[Value::Str("hi".into())], &["string".into()]).unwrap();
449 assert_eq!(out.len(), 96);
450 assert_eq!(out[31], 32);
451 assert_eq!(out[63], 2);
452 assert_eq!(&out[64..66], b"hi");
453 let out = encode_return(
455 &[Value::Int(I256::try_from(-1i64).unwrap())],
456 &["int128".into()],
457 )
458 .unwrap();
459 assert!(out.iter().all(|b| *b == 0xff));
460 }
461}