neo_devpack_solidity/neo/
build.rs1use super::constants::{
2 MAX_CALL_FLAGS, MAX_METHOD_TOKENS, MAX_TOKEN_METHOD_LENGTH, NEF_MAGIC, NEF_SOURCE_MAX_BYTES,
3};
4use super::encoding::{
5 calculate_checksum, write_fixed_string, write_varbytes, write_varint, write_varstring,
6};
7use super::method_token::MethodToken;
8use super::source::clamp_nef_source;
9
10pub fn build_nef(script: &[u8], compiler: &str) -> Result<Vec<u8>, String> {
30 build_nef_with_tokens(script, compiler, "", &[])
31}
32
33pub fn build_nef_with_tokens(
44 script: &[u8],
45 compiler: &str,
46 source: &str,
47 tokens: &[MethodToken],
48) -> Result<Vec<u8>, String> {
49 if script.is_empty() {
50 return Err("NEF script payload cannot be empty".to_string());
51 }
52 let source = clamp_nef_source(source);
53 if tokens.len() > MAX_METHOD_TOKENS {
54 return Err(format!(
55 "NEF method token table exceeds {MAX_METHOD_TOKENS} entries"
56 ));
57 }
58
59 for token in tokens {
60 let method_len = token.method.len();
61 if method_len > MAX_TOKEN_METHOD_LENGTH {
62 return Err(format!(
63 "method token '{}' exceeds {MAX_TOKEN_METHOD_LENGTH} bytes",
64 token.method
65 ));
66 }
67 if token.method.starts_with('_') {
68 return Err(format!(
69 "method token '{}' must not start with '_'",
70 token.method
71 ));
72 }
73 if token.call_flags & !MAX_CALL_FLAGS != 0 {
74 return Err(format!(
75 "method token '{}' has invalid call flags {:#x}",
76 token.method, token.call_flags
77 ));
78 }
79 }
80
81 let token_size: usize = tokens.iter().map(|t| 20 + t.method.len() + 10).sum();
83 let mut buffer = Vec::with_capacity(80 + token_size + script.len());
84
85 buffer.extend_from_slice(b"NEF3");
87
88 write_fixed_string(&mut buffer, compiler, 64);
90
91 write_varstring(&mut buffer, &source);
93
94 buffer.push(0u8);
96
97 write_varint(&mut buffer, tokens.len() as u64);
99 for token in tokens {
100 token.serialize(&mut buffer)?;
101 }
102
103 buffer.extend_from_slice(&[0u8; 2]);
105
106 write_varbytes(&mut buffer, script);
108
109 let checksum = calculate_checksum(&buffer);
111 buffer.extend_from_slice(&checksum.to_le_bytes());
112
113 Ok(buffer)
114}
115
116#[derive(Debug, Clone)]
121pub struct ParsedNef {
122 pub compiler: String,
124 pub source: String,
126 pub tokens: Vec<MethodToken>,
128 pub script: Vec<u8>,
130}
131
132pub fn parse_nef(bytes: &[u8]) -> Result<ParsedNef, String> {
147 const MIN_NEF_SIZE: usize = 4 + 64 + 1 + 1 + 1 + 2 + 1 + 4;
150 if bytes.len() < MIN_NEF_SIZE {
151 return Err(format!(
152 "NEF buffer too small: got {} bytes, need at least {}",
153 bytes.len(),
154 MIN_NEF_SIZE
155 ));
156 }
157
158 let (prefix, trailer) = bytes.split_at(bytes.len() - 4);
160 let expected = calculate_checksum(prefix).to_le_bytes();
161 if trailer != expected {
162 return Err(format!(
163 "NEF checksum mismatch: stored={trailer:02x?} expected={expected:02x?}"
164 ));
165 }
166
167 let mut cursor = Cursor::new(prefix);
168
169 let magic = cursor.take(4)?;
171 if magic != NEF_MAGIC {
172 return Err(format!(
173 "NEF magic mismatch: got {magic:02x?}, expected {NEF_MAGIC:02x?}"
174 ));
175 }
176
177 let compiler_bytes = cursor.take(64)?;
179 let trimmed_end = compiler_bytes
181 .iter()
182 .rposition(|&b| b != 0)
183 .map(|i| i + 1)
184 .unwrap_or(0);
185 let compiler = std::str::from_utf8(&compiler_bytes[..trimmed_end])
186 .map_err(|e| format!("NEF compiler field is not valid UTF-8: {e}"))?
187 .to_string();
188
189 let source_len = cursor.read_varint()? as usize;
191 if source_len > NEF_SOURCE_MAX_BYTES {
192 return Err(format!(
193 "NEF source length {source_len} exceeds maximum {NEF_SOURCE_MAX_BYTES}"
194 ));
195 }
196 let source_bytes = cursor.take(source_len)?;
197 let source = std::str::from_utf8(source_bytes)
198 .map_err(|e| format!("NEF source is not valid UTF-8: {e}"))?
199 .to_string();
200
201 let reserved1 = cursor.take(1)?[0];
203 if reserved1 != 0 {
204 return Err(format!(
205 "NEF reserved byte after source must be 0, got {reserved1:#x}"
206 ));
207 }
208
209 let token_count = cursor.read_varint()? as usize;
211 if token_count > MAX_METHOD_TOKENS {
212 return Err(format!(
213 "NEF method token count {token_count} exceeds maximum {MAX_METHOD_TOKENS}"
214 ));
215 }
216 let mut tokens = Vec::with_capacity(token_count);
217 for i in 0..token_count {
218 let hash_bytes = cursor.take(20)?;
220 let mut hash = [0u8; 20];
221 hash.copy_from_slice(hash_bytes);
222
223 let method_len = cursor.read_varint()? as usize;
225 if method_len > MAX_TOKEN_METHOD_LENGTH {
226 return Err(format!(
227 "NEF token[{i}] method length {method_len} exceeds maximum {MAX_TOKEN_METHOD_LENGTH}"
228 ));
229 }
230 let method_bytes = cursor.take(method_len)?;
231 let method = std::str::from_utf8(method_bytes)
232 .map_err(|e| format!("NEF token[{i}] method name is not valid UTF-8: {e}"))?
233 .to_string();
234 if method.starts_with('_') {
235 return Err(format!(
236 "NEF token[{i}] method name '{method}' must not start with '_'"
237 ));
238 }
239
240 let params_bytes = cursor.take(2)?;
242 let parameters_count = u16::from_le_bytes([params_bytes[0], params_bytes[1]]);
243
244 let has_return_value = cursor.take(1)?[0] != 0;
246
247 let call_flags = cursor.take(1)?[0];
249 if call_flags & !MAX_CALL_FLAGS != 0 {
250 return Err(format!(
251 "NEF token[{i}] has invalid call flags {call_flags:#x}"
252 ));
253 }
254
255 tokens.push(MethodToken {
256 hash,
257 method,
258 parameters_count,
259 has_return_value,
260 call_flags,
261 });
262 }
263
264 let reserved2 = cursor.take(2)?;
266 if reserved2 != [0u8, 0u8] {
267 return Err(format!(
268 "NEF reserved bytes before script must be [0,0], got {reserved2:02x?}"
269 ));
270 }
271
272 let script_len = cursor.read_varint()? as usize;
274 let script = cursor.take(script_len)?.to_vec();
275 if script.is_empty() {
276 return Err("NEF script payload cannot be empty".to_string());
277 }
278
279 if !cursor.is_empty() {
281 return Err(format!(
282 "NEF has {} unexpected trailing bytes before checksum",
283 cursor.remaining()
284 ));
285 }
286
287 Ok(ParsedNef {
288 compiler,
289 source,
290 tokens,
291 script,
292 })
293}
294
295struct Cursor<'a> {
300 buf: &'a [u8],
301 pos: usize,
302}
303
304impl<'a> Cursor<'a> {
305 fn new(buf: &'a [u8]) -> Self {
306 Self { buf, pos: 0 }
307 }
308
309 fn remaining(&self) -> usize {
310 self.buf.len() - self.pos
311 }
312
313 fn is_empty(&self) -> bool {
314 self.pos >= self.buf.len()
315 }
316
317 fn take(&mut self, n: usize) -> Result<&'a [u8], String> {
318 if self.pos + n > self.buf.len() {
319 return Err(format!(
320 "NEF truncated: tried to read {} bytes at offset {}, only {} available",
321 n,
322 self.pos,
323 self.buf.len().saturating_sub(self.pos)
324 ));
325 }
326 let slice = &self.buf[self.pos..self.pos + n];
327 self.pos += n;
328 Ok(slice)
329 }
330
331 fn read_varint(&mut self) -> Result<u64, String> {
333 let prefix = self.take(1)?[0];
334 match prefix {
335 0xFD => {
336 let b = self.take(2)?;
337 Ok(u16::from_le_bytes([b[0], b[1]]) as u64)
338 }
339 0xFE => {
340 let b = self.take(4)?;
341 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as u64)
342 }
343 0xFF => {
344 let b = self.take(8)?;
345 Ok(u64::from_le_bytes([
346 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
347 ]))
348 }
349 n => Ok(n as u64),
350 }
351 }
352}