1use std::collections::HashMap;
6
7use super::types::{
8 EVMAnalysisCache, EVMConstantFoldingHelper, EVMDepGraph, EVMDominatorTree, EVMLivenessInfo,
9 EVMPassConfig, EVMPassPhase, EVMPassRegistry, EVMPassStats, EVMWorklist, EvmBackend,
10 EvmBasicBlock, EvmContract, EvmInstruction, EvmOpcode, EvmOpcodeCategory, EvmOpcodeDesc,
11 StorageLayout,
12};
13
14#[cfg(test)]
15mod tests {
16 use super::*;
17 #[test]
18 pub(super) fn test_opcode_bytes() {
19 assert_eq!(EvmOpcode::Stop.byte(), 0x00);
20 assert_eq!(EvmOpcode::Add.byte(), 0x01);
21 assert_eq!(EvmOpcode::Push1.byte(), 0x60);
22 assert_eq!(EvmOpcode::Push32.byte(), 0x7f);
23 assert_eq!(EvmOpcode::Dup1.byte(), 0x80);
24 assert_eq!(EvmOpcode::Swap1.byte(), 0x90);
25 assert_eq!(EvmOpcode::Return.byte(), 0xf3);
26 assert_eq!(EvmOpcode::Revert.byte(), 0xfd);
27 assert_eq!(EvmOpcode::Invalid.byte(), 0xfe);
28 }
29 #[test]
30 pub(super) fn test_opcode_mnemonic() {
31 assert_eq!(EvmOpcode::Add.mnemonic(), "ADD");
32 assert_eq!(EvmOpcode::Mstore.mnemonic(), "MSTORE");
33 assert_eq!(EvmOpcode::Calldataload.mnemonic(), "CALLDATALOAD");
34 assert_eq!(EvmOpcode::Jumpdest.mnemonic(), "JUMPDEST");
35 }
36 #[test]
37 pub(super) fn test_instruction_encode() {
38 let stop = EvmInstruction::new(EvmOpcode::Stop);
39 assert_eq!(stop.encode(), vec![0x00]);
40 assert_eq!(stop.byte_len(), 1);
41 let push1 = EvmInstruction::push1(0x42);
42 assert_eq!(push1.encode(), vec![0x60, 0x42]);
43 assert_eq!(push1.byte_len(), 2);
44 let push4 = EvmInstruction::push4(0xdeadbeef);
45 assert_eq!(push4.encode(), vec![0x63, 0xde, 0xad, 0xbe, 0xef]);
46 assert_eq!(push4.byte_len(), 5);
47 }
48 #[test]
49 pub(super) fn test_push_auto_size() {
50 let p =
51 EvmInstruction::push(vec![0x01, 0x02, 0x03]).expect("p instruction should be valid");
52 assert_eq!(p.opcode, EvmOpcode::Push3);
53 assert_eq!(p.encode(), vec![0x62, 0x01, 0x02, 0x03]);
54 assert!(EvmInstruction::push(vec![]).is_none());
55 assert!(EvmInstruction::push(vec![0u8; 33]).is_none());
56 }
57 #[test]
58 pub(super) fn test_basic_block_encode() {
59 let mut block = EvmBasicBlock::new_jump_target("entry");
60 block.push_op(EvmOpcode::Caller);
61 block.push_op(EvmOpcode::Stop);
62 let bytes = block.encode();
63 assert_eq!(bytes, vec![0x5b, 0x33, 0x00]);
64 assert_eq!(block.byte_len(), 3);
65 }
66 #[test]
67 pub(super) fn test_storage_layout() {
68 let mut layout = StorageLayout::new();
69 assert!(layout.is_empty());
70 let slot_a = layout.allocate("balance");
71 let slot_b = layout.allocate("totalSupply");
72 assert_eq!(slot_a, 0);
73 assert_eq!(slot_b, 1);
74 assert_eq!(layout.slot_of("balance"), Some(0));
75 assert_eq!(layout.slot_of("totalSupply"), Some(1));
76 assert_eq!(layout.slot_of("unknown"), None);
77 assert_eq!(layout.len(), 2);
78 assert!(!layout.is_empty());
79 }
80 #[test]
81 pub(super) fn test_compute_selector() {
82 let sel = EvmBackend::compute_selector("transfer(address,uint256)");
83 let sel2 = EvmBackend::compute_selector("transfer(address,uint256)");
84 assert_eq!(sel, sel2);
85 let sel3 = EvmBackend::compute_selector("balanceOf(address)");
86 assert_ne!(sel, sel3);
87 }
88 #[test]
89 pub(super) fn test_build_arithmetic_function() {
90 let sel = EvmBackend::compute_selector("add(uint256,uint256)");
91 let func = EvmBackend::build_arithmetic_function(
92 "add",
93 "add(uint256,uint256)",
94 sel,
95 EvmOpcode::Add,
96 );
97 assert_eq!(func.name, "add");
98 assert_eq!(func.selector, sel);
99 assert_eq!(func.blocks.len(), 1);
100 assert!(func.blocks[0].is_jump_target);
101 let bytes = func.encode();
102 assert!(!bytes.is_empty());
103 assert_eq!(
104 *bytes.last().expect("collection should not be empty"),
105 EvmOpcode::Return.byte()
106 );
107 }
108 #[test]
109 pub(super) fn test_emit_hex_and_assembly() {
110 let backend = EvmBackend::new();
111 let mut contract = EvmContract::new("SimpleToken");
112 contract.set_metadata("version", "0.8.0");
113 contract.allocate_storage("_balance");
114 contract.allocate_storage("_totalSupply");
115 let sel = EvmBackend::compute_selector("add(uint256,uint256)");
116 let func = EvmBackend::build_arithmetic_function(
117 "add",
118 "add(uint256,uint256)",
119 sel,
120 EvmOpcode::Add,
121 );
122 contract.add_function(func);
123 let hex = backend.emit_hex(&contract);
124 assert!(!hex.is_empty());
125 assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
126 let hex_prefixed = backend.emit_hex_prefixed(&contract);
127 assert!(hex_prefixed.starts_with("0x"));
128 let asm = backend.emit_assembly(&contract);
129 assert!(asm.contains("SimpleToken"));
130 assert!(asm.contains("CALLDATALOAD"));
131 assert!(asm.contains("_balance"));
132 assert!(asm.contains("_totalSupply"));
133 assert!(asm.contains("add"));
134 }
135}
136#[allow(dead_code)]
138pub fn evm_opcode_table() -> Vec<EvmOpcodeDesc> {
139 vec![
140 EvmOpcodeDesc {
141 name: "STOP".into(),
142 opcode: 0x00,
143 stack_in: 0,
144 stack_out: 0,
145 gas: 0,
146 category: EvmOpcodeCategory::Stop,
147 description: "Halts execution".into(),
148 },
149 EvmOpcodeDesc {
150 name: "ADD".into(),
151 opcode: 0x01,
152 stack_in: 2,
153 stack_out: 1,
154 gas: 3,
155 category: EvmOpcodeCategory::Arithmetic,
156 description: "Addition".into(),
157 },
158 EvmOpcodeDesc {
159 name: "MUL".into(),
160 opcode: 0x02,
161 stack_in: 2,
162 stack_out: 1,
163 gas: 5,
164 category: EvmOpcodeCategory::Arithmetic,
165 description: "Multiplication".into(),
166 },
167 EvmOpcodeDesc {
168 name: "SUB".into(),
169 opcode: 0x03,
170 stack_in: 2,
171 stack_out: 1,
172 gas: 3,
173 category: EvmOpcodeCategory::Arithmetic,
174 description: "Subtraction".into(),
175 },
176 EvmOpcodeDesc {
177 name: "DIV".into(),
178 opcode: 0x04,
179 stack_in: 2,
180 stack_out: 1,
181 gas: 5,
182 category: EvmOpcodeCategory::Arithmetic,
183 description: "Integer division".into(),
184 },
185 EvmOpcodeDesc {
186 name: "MOD".into(),
187 opcode: 0x06,
188 stack_in: 2,
189 stack_out: 1,
190 gas: 5,
191 category: EvmOpcodeCategory::Arithmetic,
192 description: "Modulo".into(),
193 },
194 EvmOpcodeDesc {
195 name: "EXP".into(),
196 opcode: 0x0a,
197 stack_in: 2,
198 stack_out: 1,
199 gas: 10,
200 category: EvmOpcodeCategory::Arithmetic,
201 description: "Exponentiation".into(),
202 },
203 EvmOpcodeDesc {
204 name: "LT".into(),
205 opcode: 0x10,
206 stack_in: 2,
207 stack_out: 1,
208 gas: 3,
209 category: EvmOpcodeCategory::Comparison,
210 description: "Less than".into(),
211 },
212 EvmOpcodeDesc {
213 name: "GT".into(),
214 opcode: 0x11,
215 stack_in: 2,
216 stack_out: 1,
217 gas: 3,
218 category: EvmOpcodeCategory::Comparison,
219 description: "Greater than".into(),
220 },
221 EvmOpcodeDesc {
222 name: "EQ".into(),
223 opcode: 0x14,
224 stack_in: 2,
225 stack_out: 1,
226 gas: 3,
227 category: EvmOpcodeCategory::Comparison,
228 description: "Equality".into(),
229 },
230 EvmOpcodeDesc {
231 name: "ISZERO".into(),
232 opcode: 0x15,
233 stack_in: 1,
234 stack_out: 1,
235 gas: 3,
236 category: EvmOpcodeCategory::Comparison,
237 description: "Is zero".into(),
238 },
239 EvmOpcodeDesc {
240 name: "AND".into(),
241 opcode: 0x16,
242 stack_in: 2,
243 stack_out: 1,
244 gas: 3,
245 category: EvmOpcodeCategory::Bitwise,
246 description: "Bitwise AND".into(),
247 },
248 EvmOpcodeDesc {
249 name: "OR".into(),
250 opcode: 0x17,
251 stack_in: 2,
252 stack_out: 1,
253 gas: 3,
254 category: EvmOpcodeCategory::Bitwise,
255 description: "Bitwise OR".into(),
256 },
257 EvmOpcodeDesc {
258 name: "XOR".into(),
259 opcode: 0x18,
260 stack_in: 2,
261 stack_out: 1,
262 gas: 3,
263 category: EvmOpcodeCategory::Bitwise,
264 description: "Bitwise XOR".into(),
265 },
266 EvmOpcodeDesc {
267 name: "NOT".into(),
268 opcode: 0x19,
269 stack_in: 1,
270 stack_out: 1,
271 gas: 3,
272 category: EvmOpcodeCategory::Bitwise,
273 description: "Bitwise NOT".into(),
274 },
275 EvmOpcodeDesc {
276 name: "SHA3".into(),
277 opcode: 0x20,
278 stack_in: 2,
279 stack_out: 1,
280 gas: 30,
281 category: EvmOpcodeCategory::Sha3,
282 description: "Keccak-256".into(),
283 },
284 EvmOpcodeDesc {
285 name: "SLOAD".into(),
286 opcode: 0x54,
287 stack_in: 1,
288 stack_out: 1,
289 gas: 2100,
290 category: EvmOpcodeCategory::Storage,
291 description: "Load storage".into(),
292 },
293 EvmOpcodeDesc {
294 name: "SSTORE".into(),
295 opcode: 0x55,
296 stack_in: 2,
297 stack_out: 0,
298 gas: 20000,
299 category: EvmOpcodeCategory::Storage,
300 description: "Store to storage".into(),
301 },
302 EvmOpcodeDesc {
303 name: "JUMP".into(),
304 opcode: 0x56,
305 stack_in: 1,
306 stack_out: 0,
307 gas: 8,
308 category: EvmOpcodeCategory::Control,
309 description: "Unconditional jump".into(),
310 },
311 EvmOpcodeDesc {
312 name: "JUMPI".into(),
313 opcode: 0x57,
314 stack_in: 2,
315 stack_out: 0,
316 gas: 10,
317 category: EvmOpcodeCategory::Control,
318 description: "Conditional jump".into(),
319 },
320 EvmOpcodeDesc {
321 name: "RETURN".into(),
322 opcode: 0xf3,
323 stack_in: 2,
324 stack_out: 0,
325 gas: 0,
326 category: EvmOpcodeCategory::System,
327 description: "Return from call".into(),
328 },
329 EvmOpcodeDesc {
330 name: "REVERT".into(),
331 opcode: 0xfd,
332 stack_in: 2,
333 stack_out: 0,
334 gas: 0,
335 category: EvmOpcodeCategory::System,
336 description: "Revert".into(),
337 },
338 EvmOpcodeDesc {
339 name: "CALL".into(),
340 opcode: 0xf1,
341 stack_in: 7,
342 stack_out: 1,
343 gas: 100,
344 category: EvmOpcodeCategory::System,
345 description: "Message call".into(),
346 },
347 EvmOpcodeDesc {
348 name: "DELEGATECALL".into(),
349 opcode: 0xf4,
350 stack_in: 6,
351 stack_out: 1,
352 gas: 100,
353 category: EvmOpcodeCategory::System,
354 description: "Delegatecall".into(),
355 },
356 EvmOpcodeDesc {
357 name: "STATICCALL".into(),
358 opcode: 0xfa,
359 stack_in: 6,
360 stack_out: 1,
361 gas: 100,
362 category: EvmOpcodeCategory::System,
363 description: "Staticcall".into(),
364 },
365 EvmOpcodeDesc {
366 name: "CREATE".into(),
367 opcode: 0xf0,
368 stack_in: 3,
369 stack_out: 1,
370 gas: 32000,
371 category: EvmOpcodeCategory::System,
372 description: "Create contract".into(),
373 },
374 EvmOpcodeDesc {
375 name: "CREATE2".into(),
376 opcode: 0xf5,
377 stack_in: 4,
378 stack_out: 1,
379 gas: 32000,
380 category: EvmOpcodeCategory::System,
381 description: "Create2 contract".into(),
382 },
383 ]
384}
385#[allow(dead_code)]
387pub const EVM_PASS_VERSION: &str = "1.0.0";
388#[allow(dead_code)]
390pub const EVM_ADDRESS_SIZE_BYTES: usize = 20;
391#[allow(dead_code)]
393pub const EVM_WORD_SIZE_BYTES: usize = 32;
394#[allow(dead_code)]
396pub const EVM_STACK_MAX_DEPTH: usize = 1024;
397#[allow(dead_code)]
399pub const EVM_MAX_CODE_SIZE: usize = 24576;
400#[allow(dead_code)]
402pub const EVM_MAX_INIT_CODE_SIZE: usize = 49152;
403#[allow(dead_code)]
405pub fn evm_estimate_gas(bytecode_len: usize, storage_ops: usize, call_ops: usize) -> u64 {
406 let base = 21_000u64;
407 let code_gas = (bytecode_len / 32) as u64 * 200;
408 let storage_gas = storage_ops as u64 * 20_000;
409 let call_gas = call_ops as u64 * 100;
410 base + code_gas + storage_gas + call_gas
411}
412#[allow(dead_code)]
414pub const EVM_BACKEND_PASS_VERSION: &str = "1.0.0";
415#[allow(dead_code)]
417pub const EVM_DEPLOY_GAS_OVERHEAD: u64 = 32_000;
418#[allow(dead_code)]
420pub const EVM_ABI_SELECTOR_SIZE: usize = 4;
421#[cfg(test)]
422mod EVM_infra_tests {
423 use super::*;
424 #[test]
425 pub(super) fn test_pass_config() {
426 let config = EVMPassConfig::new("test_pass", EVMPassPhase::Transformation);
427 assert!(config.enabled);
428 assert!(config.phase.is_modifying());
429 assert_eq!(config.phase.name(), "transformation");
430 }
431 #[test]
432 pub(super) fn test_pass_stats() {
433 let mut stats = EVMPassStats::new();
434 stats.record_run(10, 100, 3);
435 stats.record_run(20, 200, 5);
436 assert_eq!(stats.total_runs, 2);
437 assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
438 assert!((stats.success_rate() - 1.0).abs() < 0.01);
439 let s = stats.format_summary();
440 assert!(s.contains("Runs: 2/2"));
441 }
442 #[test]
443 pub(super) fn test_pass_registry() {
444 let mut reg = EVMPassRegistry::new();
445 reg.register(EVMPassConfig::new("pass_a", EVMPassPhase::Analysis));
446 reg.register(EVMPassConfig::new("pass_b", EVMPassPhase::Transformation).disabled());
447 assert_eq!(reg.total_passes(), 2);
448 assert_eq!(reg.enabled_count(), 1);
449 reg.update_stats("pass_a", 5, 50, 2);
450 let stats = reg.get_stats("pass_a").expect("stats should exist");
451 assert_eq!(stats.total_changes, 5);
452 }
453 #[test]
454 pub(super) fn test_analysis_cache() {
455 let mut cache = EVMAnalysisCache::new(10);
456 cache.insert("key1".to_string(), vec![1, 2, 3]);
457 assert!(cache.get("key1").is_some());
458 assert!(cache.get("key2").is_none());
459 assert!((cache.hit_rate() - 0.5).abs() < 0.01);
460 cache.invalidate("key1");
461 assert!(!cache.entries["key1"].valid);
462 assert_eq!(cache.size(), 1);
463 }
464 #[test]
465 pub(super) fn test_worklist() {
466 let mut wl = EVMWorklist::new();
467 assert!(wl.push(1));
468 assert!(wl.push(2));
469 assert!(!wl.push(1));
470 assert_eq!(wl.len(), 2);
471 assert_eq!(wl.pop(), Some(1));
472 assert!(!wl.contains(1));
473 assert!(wl.contains(2));
474 }
475 #[test]
476 pub(super) fn test_dominator_tree() {
477 let mut dt = EVMDominatorTree::new(5);
478 dt.set_idom(1, 0);
479 dt.set_idom(2, 0);
480 dt.set_idom(3, 1);
481 assert!(dt.dominates(0, 3));
482 assert!(dt.dominates(1, 3));
483 assert!(!dt.dominates(2, 3));
484 assert!(dt.dominates(3, 3));
485 }
486 #[test]
487 pub(super) fn test_liveness() {
488 let mut liveness = EVMLivenessInfo::new(3);
489 liveness.add_def(0, 1);
490 liveness.add_use(1, 1);
491 assert!(liveness.defs[0].contains(&1));
492 assert!(liveness.uses[1].contains(&1));
493 }
494 #[test]
495 pub(super) fn test_constant_folding() {
496 assert_eq!(EVMConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
497 assert_eq!(EVMConstantFoldingHelper::fold_div_i64(10, 0), None);
498 assert_eq!(EVMConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
499 assert_eq!(
500 EVMConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
501 0b1000
502 );
503 assert_eq!(EVMConstantFoldingHelper::fold_bitnot_i64(0), -1);
504 }
505 #[test]
506 pub(super) fn test_dep_graph() {
507 let mut g = EVMDepGraph::new();
508 g.add_dep(1, 2);
509 g.add_dep(2, 3);
510 g.add_dep(1, 3);
511 assert_eq!(g.dependencies_of(2), vec![1]);
512 let topo = g.topological_sort();
513 assert_eq!(topo.len(), 3);
514 assert!(!g.has_cycle());
515 let pos: std::collections::HashMap<u32, usize> =
516 topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
517 assert!(pos[&1] < pos[&2]);
518 assert!(pos[&1] < pos[&3]);
519 assert!(pos[&2] < pos[&3]);
520 }
521}
522#[allow(dead_code)]
524pub fn evm_mangle_identifier(name: &str) -> String {
525 name.chars()
526 .map(|c| {
527 if c.is_alphanumeric() || c == '_' {
528 c
529 } else {
530 '_'
531 }
532 })
533 .collect()
534}
535#[allow(dead_code)]
537pub fn evm_keccak_placeholder(input: &[u8]) -> [u8; 32] {
538 let mut out = [0u8; 32];
539 for (i, &b) in input.iter().take(32).enumerate() {
540 out[i] = b;
541 }
542 out
543}
544#[allow(dead_code)]
546pub fn evm_is_valid_selector(selector: &[u8]) -> bool {
547 selector.len() == 4
548}
549#[allow(dead_code)]
551pub const EVM_CODE_PASS_VERSION: &str = "1.0.0";
552#[allow(dead_code)]
554pub const EVM_SOLIDITY_MIN_VERSION: &str = "0.8.0";