neo_solidity/cli/bytecode/
bytecode_core.rs1#[derive(Clone, Debug)]
2struct CallPatch {
3 position: usize,
4 target: String,
5}
6
7#[derive(Clone, Debug)]
8pub(crate) struct BytecodeBuildOutput {
9 pub script: Vec<u8>,
10 pub tokens: Vec<neo_solidity::neo::MethodToken>,
11}
12
13#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
14struct MethodTokenKey {
15 hash: [u8; 20],
16 method: String,
17 parameters_count: u16,
18 has_return_value: bool,
19 call_flags: u8,
20}
21
22#[derive(Clone, Debug)]
23struct MethodTokenPatch {
24 position: usize,
25 token: MethodTokenKey,
26}
27
28const CALLFLAGS_ALL: u8 = 0x0F;
30const CALLFLAGS_READ_ONLY: u8 = 0x05;
32
33const NATIVE_NEO_HASH_LE: [u8; 20] = *b"\xf5\x63\xea\x40\xbc\x28\x3d\x4d\x0e\x05\xc4\x8e\xa3\x05\xb3\xf2\xa0\x73\x40\xef";
36const NATIVE_GAS_HASH_LE: [u8; 20] = *b"\xcf\x76\xe2\x8b\xd0\x06\x2c\x4a\x47\x8e\xe3\x55\x61\x01\x13\x19\xf3\xcf\xa4\xd2";
37const NATIVE_CONTRACT_MANAGEMENT_HASH_LE: [u8; 20] =
38 *b"\xfd\xa3\xfa\x43\x46\xea\x53\x2a\x25\x8f\xc4\x97\xdd\xad\xdb\x64\x37\xc9\xfd\xff";
39const NATIVE_POLICY_HASH_LE: [u8; 20] =
40 *b"\x7b\xc6\x81\xc0\xa1\xf7\x1d\x54\x34\x57\xb6\x8b\xba\x8d\x5f\x9f\xdd\x4e\x5e\xcc";
41const NATIVE_ORACLE_HASH_LE: [u8; 20] =
42 *b"\x58\x87\x17\x11\x7e\x0a\xa8\x10\x72\xaf\xab\x71\xd2\xdd\x89\xfe\x7c\x4b\x92\xfe";
43const NATIVE_ROLE_MANAGEMENT_HASH_LE: [u8; 20] =
44 *b"\xe2\x95\xe3\x91\x54\x4c\x17\x8a\xd9\x4f\x03\xec\x4d\xcd\xff\x78\x53\x4e\xcf\x49";
45const NATIVE_NOTARY_HASH_LE: [u8; 20] =
46 *b"\x3b\xec\x35\x31\x11\x9b\xba\xd7\x6d\xd0\x44\x92\x0b\x0d\xe6\xc3\x19\x4f\xe1\xc1";
47const NATIVE_TREASURY_HASH_LE: [u8; 20] =
48 *b"\xc1\x3a\x56\xc9\x83\x53\xa7\xea\x6a\x32\x4d\x9a\x83\x5d\x1b\x5b\xf2\x26\x63\x15";
49const NATIVE_LEDGER_HASH_LE: [u8; 20] =
50 *b"\xbe\xf2\x04\x31\x40\x36\x2a\x77\xc1\x50\x99\xc7\xe6\x4c\x12\xf7\x00\xb6\x65\xda";
51const NATIVE_CRYPTOLIB_HASH_LE: [u8; 20] =
52 *b"\x1b\xf5\x75\xab\x11\x89\x68\x84\x13\x61\x0a\x35\xa1\x28\x86\xcd\xe0\xb6\x6c\x72";
53const NATIVE_STDLIB_HASH_LE: [u8; 20] =
54 *b"\xc0\xef\x39\xce\xe0\xe4\xe9\x25\xc6\xc2\xa0\x6a\x79\xe1\x44\x0d\xd8\x6f\xce\xac";
55
56pub(crate) fn native_contract_hash(contract: ir::NativeContract) -> [u8; 20] {
57 match contract {
58 ir::NativeContract::Neo => NATIVE_NEO_HASH_LE,
59 ir::NativeContract::Gas => NATIVE_GAS_HASH_LE,
60 ir::NativeContract::ContractManagement => NATIVE_CONTRACT_MANAGEMENT_HASH_LE,
61 ir::NativeContract::Policy => NATIVE_POLICY_HASH_LE,
62 ir::NativeContract::Oracle => NATIVE_ORACLE_HASH_LE,
63 ir::NativeContract::RoleManagement => NATIVE_ROLE_MANAGEMENT_HASH_LE,
64 ir::NativeContract::Notary => NATIVE_NOTARY_HASH_LE,
65 ir::NativeContract::Treasury => NATIVE_TREASURY_HASH_LE,
66 ir::NativeContract::Ledger => NATIVE_LEDGER_HASH_LE,
67 ir::NativeContract::CryptoLib => NATIVE_CRYPTOLIB_HASH_LE,
68 ir::NativeContract::StdLib => NATIVE_STDLIB_HASH_LE,
69 }
70}
71
72pub(crate) fn generate_contract_bytecode(
73 metadata: &mut ContractMetadata,
74 ir_module: &ir::Module,
75 verbose: bool,
76 optimizer_level: u8,
77 use_callt: bool,
78) -> Result<BytecodeBuildOutput, String> {
79 let function_map: HashMap<_, _> = ir_module
80 .functions
81 .iter()
82 .map(|function| (function.name.as_str(), function))
83 .collect();
84
85 if verbose {
86 println!(" • Using optimizer level {}", optimizer_level.min(3));
87 }
88
89 let mut bytecode = Vec::new();
90 let mut call_fixups: Vec<CallPatch> = Vec::new();
91 let mut token_fixups: Vec<MethodTokenPatch> = Vec::new();
92
93 for method in metadata.methods.iter_mut() {
94 if matches!(method.kind, FunctionKind::Constructor) && method.body.is_none() {
97 continue;
98 }
99
100 let method_name = method.neo_name.clone();
101 let ir_function = function_map.get(method_name.as_str()).copied().ok_or_else(|| {
102 format!("internal compiler error: missing IR for method '{method_name}'")
103 })?;
104
105 let instruction_count: usize = ir_function
106 .basic_blocks
107 .iter()
108 .map(|block| block.instructions.len())
109 .sum();
110
111 let offset = bytecode.len() as u32;
112 method.offset = offset;
113
114 if verbose {
115 println!(
116 " • Emitting method '{}' at offset {} ({} IR instruction(s))",
117 method_name, offset, instruction_count
118 );
119 }
120
121 let (function_bytes, patches, token_patches) =
122 emit_ir_function(ir_function, ir_module, method, use_callt);
123 let base_position = bytecode.len();
124 bytecode.extend_from_slice(&function_bytes);
125
126 for patch in patches {
127 call_fixups.push(CallPatch {
128 position: base_position + patch.position,
129 target: patch.target,
130 });
131 }
132
133 for patch in token_patches {
134 token_fixups.push(MethodTokenPatch {
135 position: base_position + patch.position,
136 token: patch.token,
137 });
138 }
139 }
140
141 let offset_map: HashMap<String, u32> = metadata
142 .methods
143 .iter()
144 .map(|method| (method.neo_name.clone(), method.offset))
145 .collect();
146
147 for fixup in call_fixups {
148 if let Some(target_offset) = offset_map.get(&fixup.target) {
149 let opcode_pos = fixup.position.saturating_sub(1) as i32;
151 let relative = (*target_offset as i32)
152 .checked_sub(opcode_pos)
153 .unwrap_or(0);
154 let bytes = relative.to_le_bytes();
155 if fixup.position + 4 <= bytecode.len() {
156 bytecode[fixup.position..fixup.position + 4].copy_from_slice(&bytes);
157 }
158 } else {
159 eprintln!(
160 "warning: unresolved call target '{}' (offset unavailable)",
161 fixup.target
162 );
163 }
164 }
165
166 if bytecode.is_empty() {
167 bytecode.push(0x40); }
169
170 let tokens = if use_callt {
171 apply_method_tokens(bytecode.as_mut_slice(), &token_fixups)?
172 } else {
173 Vec::new()
174 };
175
176 Ok(BytecodeBuildOutput {
177 script: bytecode,
178 tokens,
179 })
180}
181
182fn apply_method_tokens(
183 bytecode: &mut [u8],
184 patches: &[MethodTokenPatch],
185) -> Result<Vec<neo_solidity::neo::MethodToken>, String> {
186 use std::collections::{BTreeMap, BTreeSet};
187
188 if patches.is_empty() {
189 return Ok(Vec::new());
190 }
191
192 let unique: BTreeSet<MethodTokenKey> = patches.iter().map(|p| p.token.clone()).collect();
193 if unique.len() > neo_solidity::neo::MAX_METHOD_TOKENS {
194 return Err(format!(
195 "CALLT requires <= {} method token(s); got {}",
196 neo_solidity::neo::MAX_METHOD_TOKENS,
197 unique.len()
198 ));
199 }
200
201 let mut token_map: BTreeMap<MethodTokenKey, u16> = BTreeMap::new();
202 let mut tokens: Vec<neo_solidity::neo::MethodToken> = Vec::with_capacity(unique.len());
203
204 for (index, key) in unique.into_iter().enumerate() {
205 let token_index = u16::try_from(index).unwrap_or(u16::MAX);
206 token_map.insert(key.clone(), token_index);
207 tokens.push(neo_solidity::neo::MethodToken::new(
208 key.hash,
209 &key.method,
210 key.parameters_count,
211 key.has_return_value,
212 key.call_flags,
213 ));
214 }
215
216 for patch in patches {
217 let Some(index) = token_map.get(&patch.token) else {
218 continue;
219 };
220 let start = patch.position;
221 let end = start + 2;
222 if end <= bytecode.len() {
223 bytecode[start..end].copy_from_slice(&index.to_le_bytes());
224 }
225 }
226
227 Ok(tokens)
228}