neo3/neo_builder/script/script_reader.rs
1use crate::{
2 builder::{BuilderError, InteropService},
3 codec::Decoder,
4 neo_crypto::utils::ToHexString,
5 Bytes, OpCode, OperandSize,
6};
7use tokio::io::AsyncReadExt;
8
9/// A utility struct for reading and interpreting Neo smart contract scripts.
10pub struct ScriptReader;
11
12impl ScriptReader {
13 /// Retrieves the InteropService code from a given hash.
14 ///
15 /// # Arguments
16 ///
17 /// * `_hash` - A string representation of the hash.
18 ///
19 /// # Returns
20 ///
21 /// An Option containing the InteropService if found, or None if not found.
22 ///
23 /// # Example
24 ///
25 /// ```rust
26 /// use neo3::neo_builder::ScriptReader;
27 ///
28 /// let hash = "9bf667ce".to_string();
29 /// if let Some(_service) = ScriptReader::get_interop_service_code(hash) {
30 /// println!("InteropService found");
31 /// } else {
32 /// println!("InteropService not found");
33 /// }
34 /// ```
35 pub fn get_interop_service_code(_hash: String) -> Option<InteropService> {
36 InteropService::from_hash(_hash)
37 }
38
39 /// Converts a byte script to a human-readable string of OpCodes.
40 ///
41 /// # Arguments
42 ///
43 /// * `script` - The byte script to convert.
44 ///
45 /// # Returns
46 ///
47 /// A string representation of the OpCodes in the script.
48 ///
49 /// # Example
50 ///
51 /// ```rust
52 /// use neo3::neo_builder::ScriptReader;
53 ///
54 /// let script = hex::decode("0c0548656c6c6f").unwrap();
55 /// let op_code_string = ScriptReader::convert_to_op_code_string(&script);
56 /// println!("OpCodes: {}", op_code_string);
57 /// // Output: OpCodes: PUSHDATA1 5 48656c6c6f
58 /// ```
59 pub fn convert_to_op_code_string(script: &Bytes) -> String {
60 let mut reader = Decoder::new(script);
61 let mut result = String::new();
62
63 while reader.pointer().clone() < script.len() {
64 if let Ok(op_code) = OpCode::try_from(reader.read_u8()) {
65 // Add the OpCode to the result string
66 result.push_str(&format!("{:?}", op_code).to_uppercase());
67
68 // Handle operands if present
69 if let Some(size) = op_code.operand_size() {
70 if size.size().clone() > 0 {
71 // Fixed size operand
72 result.push_str(&format!(
73 " {}",
74 reader
75 .read_bytes(size.size().clone() as usize)
76 .unwrap()
77 .to_hex_string()
78 ));
79 } else if size.prefix_size().clone() > 0 {
80 // Variable size operand with prefix
81 let prefix_size = Self::get_prefix_size(&mut reader, size).unwrap();
82 result.push_str(&format!(
83 " {} {}",
84 prefix_size,
85 reader.read_bytes(prefix_size).unwrap().to_hex_string()
86 ));
87 }
88 }
89 result.push('\n');
90 }
91 }
92 result
93 }
94
95 /// Helper function to get the size of a variable-length operand.
96 ///
97 /// # Arguments
98 ///
99 /// * `reader` - The Decoder to read from.
100 /// * `size` - The OperandSize specifying the prefix size.
101 ///
102 /// # Returns
103 ///
104 /// A Result containing the size of the operand or a BuilderError.
105 ///
106 /// # Example
107 ///
108 /// ```rust,no_run
109 /// use neo3::neo_builder::ScriptReader;
110 /// use neo3::neo_codec::Decoder;
111 /// use neo3::neo_types::OperandSize;
112 ///
113 /// let mut decoder = Decoder::new(&[0x05]); // Example: prefix size of 5
114 /// let operand_size = OperandSize::with_prefix_size(1); // 1-byte prefix
115 /// // Note: get_prefix_size is a private function used internally
116 /// ```
117 fn get_prefix_size(reader: &mut Decoder, size: OperandSize) -> Result<usize, BuilderError> {
118 match size.prefix_size() {
119 1 => Ok(reader.read_u8() as usize),
120 2 => Ok(reader.read_i16().map(|v| v as usize)?),
121 4 => Ok(reader.read_i32().map(|v| v as usize)?),
122 _ => Err(BuilderError::UnsupportedOperation(
123 "Only operand prefix sizes 1, 2, and 4 are supported".to_string(),
124 )),
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_convert_to_op_code_string() {
135 // Test script in hexadecimal format
136 let script =
137 hex::decode("0c0548656c6c6f0c05576f726c642150419bf667ce41e63f18841140").unwrap();
138
139 // Expected output after conversion
140 let expected_op_code_string = "PUSHDATA1 5 48656c6c6f\nPUSHDATA1 5 576f726c64\nNOP\nSWAP\nSYSCALL 9bf667ce\nSYSCALL e63f1884\nPUSH1\nRET\n";
141
142 // Convert the script to OpCode string
143 let op_code_string = ScriptReader::convert_to_op_code_string(&script);
144
145 // Assert that the conversion matches the expected output
146 assert_eq!(op_code_string.as_str(), expected_op_code_string);
147 }
148}