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}