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