neo_solidity/neo/build.rs
1/// Build a NEF (Neo Executable Format) file from raw NeoVM bytecode.
2///
3/// The implementation follows the Neo N3 specification:
4///
5/// - Magic header `NEF3`
6/// - Compiler identifier (fixed 64 bytes, UTF-8, padded with zeros)
7/// - Source URL (varstring, max 256 bytes)
8/// - Reserved byte (must be 0)
9/// - Method token table (varint count + entries, max 128 entries)
10/// - Reserved 2 bytes (must be 0)
11/// - Script payload (varbytes)
12/// - Checksum (first four bytes of double SHA256 over all previous bytes)
13///
14/// # Arguments
15/// * `script` - The NeoVM bytecode
16/// * `compiler` - Compiler identifier string (max 64 bytes)
17///
18/// # Returns
19/// Complete NEF file as byte vector
20pub fn build_nef(script: &[u8], compiler: &str) -> Result<Vec<u8>, String> {
21 build_nef_with_tokens(script, compiler, "", &[])
22}
23
24/// Build a NEF file with method tokens for cross-contract calls.
25///
26/// # Arguments
27/// * `script` - The NeoVM bytecode
28/// * `compiler` - Compiler identifier string (max 64 bytes)
29/// * `source` - Optional source URL (max 256 bytes)
30/// * `tokens` - Array of method tokens for cross-contract calls
31///
32/// # Returns
33/// Complete NEF file as byte vector
34pub fn build_nef_with_tokens(
35 script: &[u8],
36 compiler: &str,
37 source: &str,
38 tokens: &[MethodToken],
39) -> Result<Vec<u8>, String> {
40 if script.is_empty() {
41 return Err("NEF script payload cannot be empty".to_string());
42 }
43 let source = clamp_nef_source(source);
44 if tokens.len() > MAX_METHOD_TOKENS {
45 return Err(format!(
46 "NEF method token table exceeds {MAX_METHOD_TOKENS} entries"
47 ));
48 }
49
50 for token in tokens {
51 let method_len = token.method.len();
52 if method_len > MAX_TOKEN_METHOD_LENGTH {
53 return Err(format!(
54 "method token '{}' exceeds {MAX_TOKEN_METHOD_LENGTH} bytes",
55 token.method
56 ));
57 }
58 if token.method.starts_with('_') {
59 return Err(format!(
60 "method token '{}' must not start with '_'",
61 token.method
62 ));
63 }
64 if token.call_flags & !MAX_CALL_FLAGS != 0 {
65 return Err(format!(
66 "method token '{}' has invalid call flags {:#x}",
67 token.method, token.call_flags
68 ));
69 }
70 }
71
72 // Rough capacity hint: header (magic + compiler + empty source + reserves) plus script and tokens.
73 let token_size: usize = tokens.iter().map(|t| 20 + t.method.len() + 10).sum();
74 let mut buffer = Vec::with_capacity(80 + token_size + script.len());
75
76 // Magic (4 bytes)
77 buffer.extend_from_slice(b"NEF3");
78
79 // Compiler identifier (64 bytes, zero padded)
80 write_fixed_string(&mut buffer, compiler, 64);
81
82 // Source URL (varstring, max 256 bytes)
83 write_varstring(&mut buffer, &source);
84
85 // Reserved byte must be zero
86 buffer.push(0u8);
87
88 // Method token table
89 write_varint(&mut buffer, tokens.len() as u64);
90 for token in tokens {
91 token.serialize(&mut buffer);
92 }
93
94 // Reserved bytes (2 bytes, must be 0)
95 buffer.extend_from_slice(&[0u8; 2]);
96
97 // Script payload (length-prefixed)
98 write_varbytes(&mut buffer, script);
99
100 // Checksum (first 4 bytes of double SHA-256)
101 let checksum = calculate_checksum(&buffer);
102 buffer.extend_from_slice(&checksum.to_le_bytes());
103
104 Ok(buffer)
105}