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}