1use std::fmt::Display;
2
3use alloy_network::TransactionBuilder;
4use alloy_primitives::{hex, Address, U256};
5use alloy_rpc_types::TransactionRequest;
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct AssembleRequest {
12 pub user_addr: Address,
13 pub path_id: String,
14 pub simulate: bool,
15 pub receiver: Option<Address>,
16}
17
18impl Display for AssembleRequest {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 write!(
21 f,
22 "AssembleRequest {{ user_addr: {}, path_id: {}, simulate: {}, receiver: {} }}",
23 self.user_addr,
24 self.path_id,
25 self.simulate,
26 self.receiver
27 .as_ref()
28 .map_or("None".to_string(), |s| s.to_string())
29 )
30 }
31}
32
33#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
35#[serde(rename_all = "camelCase")]
36pub struct AssemblyResponse {
37 pub transaction: TransactionData,
38 pub simulation: Option<Simulation>,
39}
40
41impl Display for AssemblyResponse {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 write!(
44 f,
45 "AssemblyResponse {{ transaction: {}, simulation: {} }}",
46 self.transaction,
47 self.simulation
48 .as_ref()
49 .map_or("None".to_string(), |s| s.to_string())
50 )
51 }
52}
53
54#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct TransactionData {
58 pub to: Address,
59 pub from: Address,
60 pub data: String,
61 pub value: String,
62 pub gas: i128,
63 pub gas_price: u128,
64 pub chain_id: u64,
65 pub nonce: u64,
66}
67
68impl TryFrom<TransactionData> for TransactionRequest {
70 type Error = crate::OdosError;
71
72 fn try_from(data: TransactionData) -> Result<Self, Self::Error> {
73 let input = hex::decode(&data.data)?;
74 let value = parse_value(&data.value)?;
75
76 Ok(TransactionRequest::default()
77 .with_input(input)
78 .with_value(value))
79 }
80}
81
82impl Display for TransactionData {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(
85 f,
86 "TransactionData {{ to: {}, from: {}, data: {}, value: {}, gas: {}, gas_price: {}, chain_id: {}, nonce: {} }}",
87 self.to,
88 self.from,
89 self.data,
90 self.value,
91 self.gas,
92 self.gas_price,
93 self.chain_id,
94 self.nonce
95 )
96 }
97}
98
99#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub struct Simulation {
103 is_success: bool,
104 amounts_out: Vec<String>,
105 gas_estimate: i64,
106 simulation_error: SimulationError,
107}
108
109impl Simulation {
110 pub fn is_success(&self) -> bool {
111 self.is_success
112 }
113
114 pub fn error_message(&self) -> &str {
115 &self.simulation_error.error_message
116 }
117}
118
119impl Display for Simulation {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(
122 f,
123 "Simulation {{ is_success: {}, amounts_out: {:?}, gas_estimate: {}, simulation_error: {} }}",
124 self.is_success,
125 self.amounts_out,
126 self.gas_estimate,
127 self.simulation_error.error_message
128 )
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
134#[serde(rename_all = "camelCase")]
135pub struct SimulationError {
136 r#type: String,
137 error_message: String,
138}
139
140impl SimulationError {
141 pub fn error_message(&self) -> &str {
142 &self.error_message
143 }
144}
145
146impl Display for SimulationError {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "Simulation error: {}", self.error_message)
149 }
150}
151
152pub fn parse_value(value: &str) -> crate::Result<U256> {
187 use crate::OdosError;
188
189 if value == "0" {
190 return Ok(U256::ZERO);
191 }
192
193 U256::from_str_radix(value, 10).or_else(|decimal_err| {
195 let hex_value = value.strip_prefix("0x").unwrap_or(value);
197 U256::from_str_radix(hex_value, 16).map_err(|hex_err| {
198 OdosError::invalid_input(format!(
199 "Failed to parse value '{}' as decimal ({}) or hexadecimal ({})",
200 value, decimal_err, hex_err
201 ))
202 })
203 })
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_parse_value_zero() {
212 let result = parse_value("0").unwrap();
213 assert_eq!(result, U256::ZERO);
214 }
215
216 #[test]
217 fn test_parse_value_decimal() {
218 assert_eq!(parse_value("1").unwrap(), U256::from(1));
220 assert_eq!(parse_value("123").unwrap(), U256::from(123));
221 assert_eq!(parse_value("1000").unwrap(), U256::from(1000));
222
223 assert_eq!(
225 parse_value("1000000000000000000").unwrap(),
226 U256::from(1000000000000000000u64)
227 );
228
229 let large_decimal = "123456789012345678901234567890";
231 let result = parse_value(large_decimal);
232 assert!(result.is_ok(), "Should parse large decimal values");
233 }
234
235 #[test]
236 fn test_parse_value_hex_with_prefix() {
237 assert_eq!(parse_value("0x0").unwrap(), U256::ZERO);
239 assert_eq!(parse_value("0xff").unwrap(), U256::from(255));
240 assert_eq!(parse_value("0xFF").unwrap(), U256::from(255));
241 assert_eq!(parse_value("0x1234").unwrap(), U256::from(0x1234));
242 assert_eq!(parse_value("0xabcdef").unwrap(), U256::from(0xabcdef));
243 }
244
245 #[test]
246 fn test_parse_value_hex_without_prefix() {
247 assert_eq!(parse_value("ff").unwrap(), U256::from(255));
249 assert_eq!(parse_value("FF").unwrap(), U256::from(255));
250 assert_eq!(parse_value("abcdef").unwrap(), U256::from(0xabcdef));
251 assert_eq!(parse_value("ABCDEF").unwrap(), U256::from(0xabcdef));
252
253 assert_eq!(parse_value("1234").unwrap(), U256::from(1234));
256 assert_ne!(parse_value("1234").unwrap(), U256::from(0x1234));
257 }
258
259 #[test]
260 fn test_parse_value_invalid() {
261 let result = parse_value("xyz");
263 assert!(result.is_err(), "Invalid characters should fail");
264
265 let result = parse_value("0xGHI");
267 assert!(result.is_err(), "Invalid hex characters should fail");
268
269 let result = parse_value("12@34");
271 assert!(result.is_err(), "Special characters should fail");
272
273 let result = parse_value("");
276 assert_eq!(
277 result.unwrap(),
278 U256::ZERO,
279 "Empty string parses to zero (standard Rust behavior)"
280 );
281 }
282
283 #[test]
284 fn test_parse_value_edge_cases() {
285 assert_eq!(parse_value("00123").unwrap(), U256::from(123));
287 assert_eq!(parse_value("0x00ff").unwrap(), U256::from(255));
288
289 let max_u64_str = u64::MAX.to_string();
291 let result = parse_value(&max_u64_str).unwrap();
292 assert_eq!(result, U256::from(u64::MAX));
293
294 let max_u128_str = u128::MAX.to_string();
296 let result = parse_value(&max_u128_str);
297 assert!(result.is_ok(), "Should handle u128::MAX");
298 }
299
300 #[test]
301 fn test_parse_value_realistic_transaction_values() {
302 let one_eth = "1000000000000000000";
304 assert_eq!(
305 parse_value(one_eth).unwrap(),
306 U256::from(1000000000000000000u64)
307 );
308
309 let hundred_eth = "100000000000000000000";
311 let result = parse_value(hundred_eth);
312 assert!(result.is_ok(), "Should parse 100 ETH");
313
314 let gas_price_hex = "0x2540be400"; let result = parse_value(gas_price_hex);
317 assert!(result.is_ok(), "Should parse hex gas price");
318 }
319
320 #[test]
321 fn test_parse_value_error_messages() {
322 let result = parse_value("invalid");
324 match result {
325 Err(e) => {
326 let error_msg = e.to_string();
327 assert!(
328 error_msg.contains("invalid"),
329 "Error should mention the invalid value"
330 );
331 assert!(
332 error_msg.contains("decimal") || error_msg.contains("hexadecimal"),
333 "Error should mention attempted parsing formats"
334 );
335 }
336 Ok(_) => panic!("Should have failed to parse 'invalid'"),
337 }
338 }
339}