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_ether_value, 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> {
41 let s = s.trim();
42 if s.is_empty() {
43 return Err(eyre!("Empty call specification"));
44 }
45
46 let parts: Vec<&str> = s.split(':').collect();
47 let to = Address::from_str(parts[0])
48 .map_err(|e| eyre!("Invalid address '{}': {}", parts[0], e))?;
49 let mut spec = Self { to, value: U256::ZERO, sig: None, args: Vec::new(), data: None };
50
51 let mut rest = &parts[1..];
54 if let Some((part, tail)) = rest.split_first() {
55 if part.is_empty() {
56 rest = tail;
57 } else if (!part.starts_with("0x") || !tail.is_empty()) && !part.contains('(') {
58 spec.value =
59 parse_ether_value(part).wrap_err_with(|| format!("Invalid value '{part}'"))?;
60 rest = tail;
61 }
62 }
63
64 match rest.split_first() {
65 Some((part, tail)) if part.starts_with("0x") => {
66 let decoded =
67 hex::decode(part).map_err(|e| eyre!("Invalid hex data '{}': {}", part, e))?;
68 eyre::ensure!(tail.is_empty(), "Unexpected trailing field(s) after raw calldata");
69 spec.data = Some(Bytes::from(decoded));
70 }
71 Some((part, tail)) if !part.is_empty() => {
72 spec.sig = Some(part.to_string());
73 if !tail.is_empty() {
74 spec.args = tail.join(":").split(',').map(|s| s.trim().to_string()).collect();
76 }
77 }
78 _ => {}
79 }
80
81 Ok(spec)
82 }
83
84 pub async fn resolve<N: Network, P: Provider<N>>(
87 &self,
88 i: usize,
89 chain: Chain,
90 provider: &P,
91 etherscan_api_key: Option<&str>,
92 etherscan_api_url: Option<&str>,
93 ) -> Result<Call> {
94 let input = if let Some(data) = &self.data {
95 data.clone()
96 } else if let Some(sig) = &self.sig {
97 let (encoded, _) = parse_function_args(
98 sig,
99 self.args.clone(),
100 Some(self.to),
101 chain,
102 provider,
103 etherscan_api_key,
104 etherscan_api_url,
105 )
106 .await
107 .map_err(|e| eyre!("Failed to encode call {}: {e}", i + 1))?;
108 Bytes::from(encoded)
109 } else {
110 Bytes::new()
111 };
112 Ok(Call { to: self.to.into(), value: self.value, input })
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_parse_address_and_value() {
122 let address = "0x1234567890123456789012345678901234567890";
123
124 let spec = CallSpec::parse(address).unwrap();
125 assert_eq!(spec.to, address.parse::<Address>().unwrap());
126 assert_eq!(spec.value, U256::ZERO);
127 assert!(spec.sig.is_none() && spec.args.is_empty() && spec.data.is_none());
128
129 let spec = CallSpec::parse(&format!("{address}:1ether")).unwrap();
130 assert_eq!(spec.value, parse_ether_value("1ether").unwrap());
131 assert!(spec.sig.is_none());
132 }
133
134 #[test]
135 fn test_parse_lowercase_hex_value() {
136 let address = "0x1234567890123456789012345678901234567890";
137
138 let spec = CallSpec::parse(&format!("{address}:0x10:deposit()")).unwrap();
139 assert_eq!(spec.value, U256::from(16));
140 assert_eq!(spec.sig.as_deref(), Some("deposit()"));
141
142 let spec = CallSpec::parse(&format!("{address}:0x10")).unwrap();
143 assert_eq!(spec.value, U256::ZERO);
144 assert_eq!(spec.data, Some(Bytes::from([0x10])));
145 }
146
147 #[test]
148 fn test_parse_with_sig() {
149 let spec = CallSpec::parse(
150 "0x1234567890123456789012345678901234567890::transfer(address,uint256):0xabc,1000",
151 )
152 .unwrap();
153 assert_eq!(spec.value, U256::ZERO);
154 assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string()));
155 assert_eq!(spec.args, vec!["0xabc", "1000"]);
156 }
157
158 #[test]
159 fn test_parse_with_value_and_sig() {
160 let spec = CallSpec::parse(
161 "0x1234567890123456789012345678901234567890:0.5ether:transfer(address,uint256):0xabc,1000",
162 )
163 .unwrap();
164 assert_eq!(spec.value, parse_ether_value("0.5ether").unwrap());
165 assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string()));
166 }
167
168 #[test]
169 fn test_parse_with_raw_data() {
170 let spec = CallSpec::parse("0x1234567890123456789012345678901234567890::0xabcdef").unwrap();
171 assert_eq!(spec.value, U256::ZERO);
172 assert!(spec.sig.is_none());
173 assert_eq!(spec.data, Some(Bytes::from(hex::decode("abcdef").unwrap())));
174 }
175
176 #[test]
177 fn test_parse_raw_data_rejects_trailing_fields() {
178 for spec in [
179 "0x1234567890123456789012345678901234567890::0xabcdef:typo",
180 "0x1234567890123456789012345678901234567890:1wei:0xabcdef:unexpected",
181 ] {
182 assert_eq!(
183 CallSpec::parse(spec).unwrap_err().to_string(),
184 "Unexpected trailing field(s) after raw calldata"
185 );
186 }
187 }
188}