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