1use alloy_primitives::{Address, Bytes, U256, hex};
12use eyre::{Result, WrapErr, eyre};
13use std::str::FromStr;
14
15#[derive(Debug, Clone)]
17pub struct CallSpec {
18 pub to: Address,
20 pub value: U256,
22 pub sig: Option<String>,
24 pub args: Vec<String>,
26 pub data: Option<Bytes>,
28}
29
30impl CallSpec {
31 pub fn parse(s: &str) -> Result<Self> {
40 let s = s.trim();
41 if s.is_empty() {
42 return Err(eyre!("Empty call specification"));
43 }
44
45 let parts: Vec<&str> = s.split(':').collect();
47
48 if parts.is_empty() {
49 return Err(eyre!("Invalid call specification: {}", s));
50 }
51
52 let to = Address::from_str(parts[0])
54 .map_err(|e| eyre!("Invalid address '{}': {}", parts[0], e))?;
55
56 let mut value = U256::ZERO;
57 let mut sig = None;
58 let mut args = Vec::new();
59 let mut data = None;
60
61 let mut idx = 1;
64
65 if idx < parts.len() {
67 let part = parts[idx];
68 if !part.is_empty() && !part.starts_with("0x") && !part.contains('(') {
69 value = parse_ether_or_wei(part)?;
71 idx += 1;
72 } else if part.is_empty() {
73 idx += 1;
75 }
76 }
77
78 if idx < parts.len() {
80 let part = parts[idx];
81 if part.starts_with("0x") {
82 data = Some(Bytes::from(
84 hex::decode(part).map_err(|e| eyre!("Invalid hex data '{}': {}", part, e))?,
85 ));
86 } else if !part.is_empty() {
87 sig = Some(part.to_string());
89 idx += 1;
90
91 if idx < parts.len() {
93 let args_str = parts[idx..].join(":");
94 args = args_str.split(',').map(|s| s.trim().to_string()).collect();
95 }
96 }
97 }
98
99 Ok(Self { to, value, sig, args, data })
100 }
101}
102
103impl FromStr for CallSpec {
104 type Err = eyre::Error;
105
106 fn from_str(s: &str) -> Result<Self> {
107 Self::parse(s)
108 }
109}
110
111fn parse_ether_or_wei(s: &str) -> Result<U256> {
113 if s.starts_with("0x") {
115 U256::from_str_radix(s, 16).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e))
116 } else {
117 alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), s)
118 .wrap_err_with(|| format!("Invalid value '{s}'"))?
119 .as_uint()
120 .map(|(v, _)| v)
121 .ok_or_else(|| eyre!("Could not parse value '{}'", s))
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_parse_address_only() {
131 let spec = CallSpec::parse("0x1234567890123456789012345678901234567890").unwrap();
132 assert_eq!(
133 spec.to,
134 "0x1234567890123456789012345678901234567890".parse::<Address>().unwrap()
135 );
136 assert_eq!(spec.value, U256::ZERO);
137 assert!(spec.sig.is_none());
138 assert!(spec.args.is_empty());
139 assert!(spec.data.is_none());
140 }
141
142 #[test]
143 fn test_parse_with_value() {
144 let spec = CallSpec::parse("0x1234567890123456789012345678901234567890:1ether").unwrap();
145 assert_eq!(spec.value, parse_ether_or_wei("1ether").unwrap());
146 assert!(spec.sig.is_none());
147 }
148
149 #[test]
150 fn test_parse_with_sig() {
151 let spec = CallSpec::parse(
152 "0x1234567890123456789012345678901234567890::transfer(address,uint256):0xabc,1000",
153 )
154 .unwrap();
155 assert_eq!(spec.value, U256::ZERO);
156 assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string()));
157 assert_eq!(spec.args, vec!["0xabc", "1000"]);
158 }
159
160 #[test]
161 fn test_parse_with_value_and_sig() {
162 let spec = CallSpec::parse(
163 "0x1234567890123456789012345678901234567890:0.5ether:transfer(address,uint256):0xabc,1000",
164 )
165 .unwrap();
166 assert_eq!(spec.value, parse_ether_or_wei("0.5ether").unwrap());
167 assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string()));
168 }
169
170 #[test]
171 fn test_parse_with_raw_data() {
172 let spec = CallSpec::parse("0x1234567890123456789012345678901234567890::0xabcdef").unwrap();
173 assert_eq!(spec.value, U256::ZERO);
174 assert!(spec.sig.is_none());
175 assert_eq!(spec.data, Some(Bytes::from(hex::decode("abcdef").unwrap())));
176 }
177}