1use alloy_network::Network;
12use alloy_primitives::{Address, Bytes, U256, hex};
13use alloy_provider::Provider;
14use eyre::{Result, WrapErr, eyre};
15use foundry_cli::utils::parse_function_args;
16use foundry_config::Chain;
17use std::str::FromStr;
18use tempo_primitives::transaction::Call;
19
20#[derive(Debug, Clone)]
22pub struct CallSpec {
23 pub to: Address,
25 pub value: U256,
27 pub sig: Option<String>,
29 pub args: Vec<String>,
31 pub data: Option<Bytes>,
33}
34
35impl CallSpec {
36 pub fn parse(s: &str) -> Result<Self> {
45 let s = s.trim();
46 if s.is_empty() {
47 return Err(eyre!("Empty call specification"));
48 }
49
50 let parts: Vec<&str> = s.split(':').collect();
52
53 if parts.is_empty() {
54 return Err(eyre!("Invalid call specification: {}", s));
55 }
56
57 let to = Address::from_str(parts[0])
59 .map_err(|e| eyre!("Invalid address '{}': {}", parts[0], e))?;
60
61 let mut value = U256::ZERO;
62 let mut sig = None;
63 let mut args = Vec::new();
64 let mut data = None;
65
66 let mut idx = 1;
69
70 if idx < parts.len() {
72 let part = parts[idx];
73 if !part.is_empty() && !part.starts_with("0x") && !part.contains('(') {
74 value = parse_ether_or_wei(part)?;
76 idx += 1;
77 } else if part.is_empty() {
78 idx += 1;
80 }
81 }
82
83 if idx < parts.len() {
85 let part = parts[idx];
86 if part.starts_with("0x") {
87 data = Some(Bytes::from(
89 hex::decode(part).map_err(|e| eyre!("Invalid hex data '{}': {}", part, e))?,
90 ));
91 } else if !part.is_empty() {
92 sig = Some(part.to_string());
94 idx += 1;
95
96 if idx < parts.len() {
98 let args_str = parts[idx..].join(":");
99 args = args_str.split(',').map(|s| s.trim().to_string()).collect();
100 }
101 }
102 }
103
104 Ok(Self { to, value, sig, args, data })
105 }
106
107 pub async fn resolve<N: Network, P: Provider<N>>(
110 &self,
111 i: usize,
112 chain: Chain,
113 provider: &P,
114 etherscan_api_key: Option<&str>,
115 etherscan_api_url: Option<&str>,
116 ) -> Result<Call> {
117 let input = if let Some(data) = &self.data {
118 data.clone()
119 } else if let Some(sig) = &self.sig {
120 let (encoded, _) = parse_function_args(
121 sig,
122 self.args.clone(),
123 Some(self.to),
124 chain,
125 provider,
126 etherscan_api_key,
127 etherscan_api_url,
128 )
129 .await
130 .map_err(|e| eyre!("Failed to encode call {}: {e}", i + 1))?;
131 Bytes::from(encoded)
132 } else {
133 Bytes::new()
134 };
135 Ok(Call { to: self.to.into(), value: self.value, input })
136 }
137}
138
139impl FromStr for CallSpec {
140 type Err = eyre::Error;
141
142 fn from_str(s: &str) -> Result<Self> {
143 Self::parse(s)
144 }
145}
146
147fn parse_ether_or_wei(s: &str) -> Result<U256> {
149 if s.starts_with("0x") || s.starts_with("0X") {
151 U256::from_str(s).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e))
152 } else {
153 alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), s)
154 .wrap_err_with(|| format!("Invalid value '{s}'"))?
155 .as_uint()
156 .map(|(v, _)| v)
157 .ok_or_else(|| eyre!("Could not parse value '{}'", s))
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_parse_address_only() {
167 let spec = CallSpec::parse("0x1234567890123456789012345678901234567890").unwrap();
168 assert_eq!(
169 spec.to,
170 "0x1234567890123456789012345678901234567890".parse::<Address>().unwrap()
171 );
172 assert_eq!(spec.value, U256::ZERO);
173 assert!(spec.sig.is_none());
174 assert!(spec.args.is_empty());
175 assert!(spec.data.is_none());
176 }
177
178 #[test]
179 fn test_parse_with_value() {
180 let spec = CallSpec::parse("0x1234567890123456789012345678901234567890:1ether").unwrap();
181 assert_eq!(spec.value, parse_ether_or_wei("1ether").unwrap());
182 assert!(spec.sig.is_none());
183 }
184
185 #[test]
186 fn test_parse_hex_value() {
187 assert_eq!(parse_ether_or_wei("0x10").unwrap(), U256::from(16));
188 assert_eq!(parse_ether_or_wei("0X10").unwrap(), U256::from(16));
189 }
190
191 #[test]
192 fn test_parse_with_sig() {
193 let spec = CallSpec::parse(
194 "0x1234567890123456789012345678901234567890::transfer(address,uint256):0xabc,1000",
195 )
196 .unwrap();
197 assert_eq!(spec.value, U256::ZERO);
198 assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string()));
199 assert_eq!(spec.args, vec!["0xabc", "1000"]);
200 }
201
202 #[test]
203 fn test_parse_with_value_and_sig() {
204 let spec = CallSpec::parse(
205 "0x1234567890123456789012345678901234567890:0.5ether:transfer(address,uint256):0xabc,1000",
206 )
207 .unwrap();
208 assert_eq!(spec.value, parse_ether_or_wei("0.5ether").unwrap());
209 assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string()));
210 }
211
212 #[test]
213 fn test_parse_with_raw_data() {
214 let spec = CallSpec::parse("0x1234567890123456789012345678901234567890::0xabcdef").unwrap();
215 assert_eq!(spec.value, U256::ZERO);
216 assert!(spec.sig.is_none());
217 assert_eq!(spec.data, Some(Bytes::from(hex::decode("abcdef").unwrap())));
218 }
219}