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_compute_selector_canonical_ethereum_values() {
90 assert_eq!(
93 EvmBackend::compute_selector("transfer(address,uint256)"),
94 [0xa9, 0x05, 0x9c, 0xbb],
95 "transfer(address,uint256) selector mismatch"
96 );
97 assert_eq!(
98 EvmBackend::compute_selector("balanceOf(address)"),
99 [0x70, 0xa0, 0x82, 0x31],
100 "balanceOf(address) selector mismatch"
101 );
102 assert_eq!(
103 EvmBackend::compute_selector("approve(address,uint256)"),
104 [0x09, 0x5e, 0xa7, 0xb3],
105 "approve(address,uint256) selector mismatch"
106 );
107 assert_eq!(
108 EvmBackend::compute_selector("allowance(address,address)"),
109 [0xdd, 0x62, 0xed, 0x3e],
110 "allowance(address,address) selector mismatch"
111 );
112 assert_eq!(
113 EvmBackend::compute_selector("totalSupply()"),
114 [0x18, 0x16, 0x0d, 0xdd],
115 "totalSupply() selector mismatch"
116 );
117 assert_eq!(
119 EvmBackend::compute_selector("ownerOf(uint256)"),
120 [0x63, 0x52, 0x21, 0x1e],
121 "ownerOf(uint256) selector mismatch"
122 );
123 }
124 #[test]
125 pub(super) fn test_evm_keccak256_known_value() {
126 let hash = super::evm_keccak256(b"");
128 assert_eq!(hash[0], 0xc5);
129 assert_eq!(hash[1], 0xd2);
130 assert_eq!(hash[2], 0x46);
131 assert_eq!(hash[3], 0x01);
132 }
133 #[test]
134 pub(super) fn test_build_arithmetic_function() {
135 let sel = EvmBackend::compute_selector("add(uint256,uint256)");
136 let func = EvmBackend::build_arithmetic_function(
137 "add",
138 "add(uint256,uint256)",
139 sel,
140 EvmOpcode::Add,
141 );
142 assert_eq!(func.name, "add");
143 assert_eq!(func.selector, sel);
144 assert_eq!(func.blocks.len(), 1);
145 assert!(func.blocks[0].is_jump_target);
146 let bytes = func.encode();
147 assert!(!bytes.is_empty());
148 assert_eq!(
149 *bytes.last().expect("collection should not be empty"),
150 EvmOpcode::Return.byte()
151 );
152 }
153 #[test]
154 pub(super) fn test_emit_hex_and_assembly() {
155 let backend = EvmBackend::new();
156 let mut contract = EvmContract::new("SimpleToken");
157 contract.set_metadata("version", "0.8.0");
158 contract.allocate_storage("_balance");
159 contract.allocate_storage("_totalSupply");
160 let sel = EvmBackend::compute_selector("add(uint256,uint256)");
161 let func = EvmBackend::build_arithmetic_function(
162 "add",
163 "add(uint256,uint256)",
164 sel,
165 EvmOpcode::Add,
166 );
167 contract.add_function(func);
168 let hex = backend.emit_hex(&contract);
169 assert!(!hex.is_empty());
170 assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
171 let hex_prefixed = backend.emit_hex_prefixed(&contract);
172 assert!(hex_prefixed.starts_with("0x"));
173 let asm = backend.emit_assembly(&contract);
174 assert!(asm.contains("SimpleToken"));
175 assert!(asm.contains("CALLDATALOAD"));
176 assert!(asm.contains("_balance"));
177 assert!(asm.contains("_totalSupply"));
178 assert!(asm.contains("add"));
179 }
180}
181#[allow(dead_code)]
183pub fn evm_opcode_table() -> Vec<EvmOpcodeDesc> {
184 vec![
185 EvmOpcodeDesc {
186 name: "STOP".into(),
187 opcode: 0x00,
188 stack_in: 0,
189 stack_out: 0,
190 gas: 0,
191 category: EvmOpcodeCategory::Stop,
192 description: "Halts execution".into(),
193 },
194 EvmOpcodeDesc {
195 name: "ADD".into(),
196 opcode: 0x01,
197 stack_in: 2,
198 stack_out: 1,
199 gas: 3,
200 category: EvmOpcodeCategory::Arithmetic,
201 description: "Addition".into(),
202 },
203 EvmOpcodeDesc {
204 name: "MUL".into(),
205 opcode: 0x02,
206 stack_in: 2,
207 stack_out: 1,
208 gas: 5,
209 category: EvmOpcodeCategory::Arithmetic,
210 description: "Multiplication".into(),
211 },
212 EvmOpcodeDesc {
213 name: "SUB".into(),
214 opcode: 0x03,
215 stack_in: 2,
216 stack_out: 1,
217 gas: 3,
218 category: EvmOpcodeCategory::Arithmetic,
219 description: "Subtraction".into(),
220 },
221 EvmOpcodeDesc {
222 name: "DIV".into(),
223 opcode: 0x04,
224 stack_in: 2,
225 stack_out: 1,
226 gas: 5,
227 category: EvmOpcodeCategory::Arithmetic,
228 description: "Integer division".into(),
229 },
230 EvmOpcodeDesc {
231 name: "MOD".into(),
232 opcode: 0x06,
233 stack_in: 2,
234 stack_out: 1,
235 gas: 5,
236 category: EvmOpcodeCategory::Arithmetic,
237 description: "Modulo".into(),
238 },
239 EvmOpcodeDesc {
240 name: "EXP".into(),
241 opcode: 0x0a,
242 stack_in: 2,
243 stack_out: 1,
244 gas: 10,
245 category: EvmOpcodeCategory::Arithmetic,
246 description: "Exponentiation".into(),
247 },
248 EvmOpcodeDesc {
249 name: "LT".into(),
250 opcode: 0x10,
251 stack_in: 2,
252 stack_out: 1,
253 gas: 3,
254 category: EvmOpcodeCategory::Comparison,
255 description: "Less than".into(),
256 },
257 EvmOpcodeDesc {
258 name: "GT".into(),
259 opcode: 0x11,
260 stack_in: 2,
261 stack_out: 1,
262 gas: 3,
263 category: EvmOpcodeCategory::Comparison,
264 description: "Greater than".into(),
265 },
266 EvmOpcodeDesc {
267 name: "EQ".into(),
268 opcode: 0x14,
269 stack_in: 2,
270 stack_out: 1,
271 gas: 3,
272 category: EvmOpcodeCategory::Comparison,
273 description: "Equality".into(),
274 },
275 EvmOpcodeDesc {
276 name: "ISZERO".into(),
277 opcode: 0x15,
278 stack_in: 1,
279 stack_out: 1,
280 gas: 3,
281 category: EvmOpcodeCategory::Comparison,
282 description: "Is zero".into(),
283 },
284 EvmOpcodeDesc {
285 name: "AND".into(),
286 opcode: 0x16,
287 stack_in: 2,
288 stack_out: 1,
289 gas: 3,
290 category: EvmOpcodeCategory::Bitwise,
291 description: "Bitwise AND".into(),
292 },
293 EvmOpcodeDesc {
294 name: "OR".into(),
295 opcode: 0x17,
296 stack_in: 2,
297 stack_out: 1,
298 gas: 3,
299 category: EvmOpcodeCategory::Bitwise,
300 description: "Bitwise OR".into(),
301 },
302 EvmOpcodeDesc {
303 name: "XOR".into(),
304 opcode: 0x18,
305 stack_in: 2,
306 stack_out: 1,
307 gas: 3,
308 category: EvmOpcodeCategory::Bitwise,
309 description: "Bitwise XOR".into(),
310 },
311 EvmOpcodeDesc {
312 name: "NOT".into(),
313 opcode: 0x19,
314 stack_in: 1,
315 stack_out: 1,
316 gas: 3,
317 category: EvmOpcodeCategory::Bitwise,
318 description: "Bitwise NOT".into(),
319 },
320 EvmOpcodeDesc {
321 name: "SHA3".into(),
322 opcode: 0x20,
323 stack_in: 2,
324 stack_out: 1,
325 gas: 30,
326 category: EvmOpcodeCategory::Sha3,
327 description: "Keccak-256".into(),
328 },
329 EvmOpcodeDesc {
330 name: "SLOAD".into(),
331 opcode: 0x54,
332 stack_in: 1,
333 stack_out: 1,
334 gas: 2100,
335 category: EvmOpcodeCategory::Storage,
336 description: "Load storage".into(),
337 },
338 EvmOpcodeDesc {
339 name: "SSTORE".into(),
340 opcode: 0x55,
341 stack_in: 2,
342 stack_out: 0,
343 gas: 20000,
344 category: EvmOpcodeCategory::Storage,
345 description: "Store to storage".into(),
346 },
347 EvmOpcodeDesc {
348 name: "JUMP".into(),
349 opcode: 0x56,
350 stack_in: 1,
351 stack_out: 0,
352 gas: 8,
353 category: EvmOpcodeCategory::Control,
354 description: "Unconditional jump".into(),
355 },
356 EvmOpcodeDesc {
357 name: "JUMPI".into(),
358 opcode: 0x57,
359 stack_in: 2,
360 stack_out: 0,
361 gas: 10,
362 category: EvmOpcodeCategory::Control,
363 description: "Conditional jump".into(),
364 },
365 EvmOpcodeDesc {
366 name: "RETURN".into(),
367 opcode: 0xf3,
368 stack_in: 2,
369 stack_out: 0,
370 gas: 0,
371 category: EvmOpcodeCategory::System,
372 description: "Return from call".into(),
373 },
374 EvmOpcodeDesc {
375 name: "REVERT".into(),
376 opcode: 0xfd,
377 stack_in: 2,
378 stack_out: 0,
379 gas: 0,
380 category: EvmOpcodeCategory::System,
381 description: "Revert".into(),
382 },
383 EvmOpcodeDesc {
384 name: "CALL".into(),
385 opcode: 0xf1,
386 stack_in: 7,
387 stack_out: 1,
388 gas: 100,
389 category: EvmOpcodeCategory::System,
390 description: "Message call".into(),
391 },
392 EvmOpcodeDesc {
393 name: "DELEGATECALL".into(),
394 opcode: 0xf4,
395 stack_in: 6,
396 stack_out: 1,
397 gas: 100,
398 category: EvmOpcodeCategory::System,
399 description: "Delegatecall".into(),
400 },
401 EvmOpcodeDesc {
402 name: "STATICCALL".into(),
403 opcode: 0xfa,
404 stack_in: 6,
405 stack_out: 1,
406 gas: 100,
407 category: EvmOpcodeCategory::System,
408 description: "Staticcall".into(),
409 },
410 EvmOpcodeDesc {
411 name: "CREATE".into(),
412 opcode: 0xf0,
413 stack_in: 3,
414 stack_out: 1,
415 gas: 32000,
416 category: EvmOpcodeCategory::System,
417 description: "Create contract".into(),
418 },
419 EvmOpcodeDesc {
420 name: "CREATE2".into(),
421 opcode: 0xf5,
422 stack_in: 4,
423 stack_out: 1,
424 gas: 32000,
425 category: EvmOpcodeCategory::System,
426 description: "Create2 contract".into(),
427 },
428 ]
429}
430#[allow(dead_code)]
432pub const EVM_PASS_VERSION: &str = "1.0.0";
433#[allow(dead_code)]
435pub const EVM_ADDRESS_SIZE_BYTES: usize = 20;
436#[allow(dead_code)]
438pub const EVM_WORD_SIZE_BYTES: usize = 32;
439#[allow(dead_code)]
441pub const EVM_STACK_MAX_DEPTH: usize = 1024;
442#[allow(dead_code)]
444pub const EVM_MAX_CODE_SIZE: usize = 24576;
445#[allow(dead_code)]
447pub const EVM_MAX_INIT_CODE_SIZE: usize = 49152;
448#[allow(dead_code)]
450pub fn evm_estimate_gas(bytecode_len: usize, storage_ops: usize, call_ops: usize) -> u64 {
451 let base = 21_000u64;
452 let code_gas = (bytecode_len / 32) as u64 * 200;
453 let storage_gas = storage_ops as u64 * 20_000;
454 let call_gas = call_ops as u64 * 100;
455 base + code_gas + storage_gas + call_gas
456}
457#[allow(dead_code)]
459pub const EVM_BACKEND_PASS_VERSION: &str = "1.0.0";
460#[allow(dead_code)]
462pub const EVM_DEPLOY_GAS_OVERHEAD: u64 = 32_000;
463#[allow(dead_code)]
465pub const EVM_ABI_SELECTOR_SIZE: usize = 4;
466#[cfg(test)]
467mod EVM_infra_tests {
468 use super::*;
469 #[test]
470 pub(super) fn test_pass_config() {
471 let config = EVMPassConfig::new("test_pass", EVMPassPhase::Transformation);
472 assert!(config.enabled);
473 assert!(config.phase.is_modifying());
474 assert_eq!(config.phase.name(), "transformation");
475 }
476 #[test]
477 pub(super) fn test_pass_stats() {
478 let mut stats = EVMPassStats::new();
479 stats.record_run(10, 100, 3);
480 stats.record_run(20, 200, 5);
481 assert_eq!(stats.total_runs, 2);
482 assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
483 assert!((stats.success_rate() - 1.0).abs() < 0.01);
484 let s = stats.format_summary();
485 assert!(s.contains("Runs: 2/2"));
486 }
487 #[test]
488 pub(super) fn test_pass_registry() {
489 let mut reg = EVMPassRegistry::new();
490 reg.register(EVMPassConfig::new("pass_a", EVMPassPhase::Analysis));
491 reg.register(EVMPassConfig::new("pass_b", EVMPassPhase::Transformation).disabled());
492 assert_eq!(reg.total_passes(), 2);
493 assert_eq!(reg.enabled_count(), 1);
494 reg.update_stats("pass_a", 5, 50, 2);
495 let stats = reg.get_stats("pass_a").expect("stats should exist");
496 assert_eq!(stats.total_changes, 5);
497 }
498 #[test]
499 pub(super) fn test_analysis_cache() {
500 let mut cache = EVMAnalysisCache::new(10);
501 cache.insert("key1".to_string(), vec![1, 2, 3]);
502 assert!(cache.get("key1").is_some());
503 assert!(cache.get("key2").is_none());
504 assert!((cache.hit_rate() - 0.5).abs() < 0.01);
505 cache.invalidate("key1");
506 assert!(!cache.entries["key1"].valid);
507 assert_eq!(cache.size(), 1);
508 }
509 #[test]
510 pub(super) fn test_worklist() {
511 let mut wl = EVMWorklist::new();
512 assert!(wl.push(1));
513 assert!(wl.push(2));
514 assert!(!wl.push(1));
515 assert_eq!(wl.len(), 2);
516 assert_eq!(wl.pop(), Some(1));
517 assert!(!wl.contains(1));
518 assert!(wl.contains(2));
519 }
520 #[test]
521 pub(super) fn test_dominator_tree() {
522 let mut dt = EVMDominatorTree::new(5);
523 dt.set_idom(1, 0);
524 dt.set_idom(2, 0);
525 dt.set_idom(3, 1);
526 assert!(dt.dominates(0, 3));
527 assert!(dt.dominates(1, 3));
528 assert!(!dt.dominates(2, 3));
529 assert!(dt.dominates(3, 3));
530 }
531 #[test]
532 pub(super) fn test_liveness() {
533 let mut liveness = EVMLivenessInfo::new(3);
534 liveness.add_def(0, 1);
535 liveness.add_use(1, 1);
536 assert!(liveness.defs[0].contains(&1));
537 assert!(liveness.uses[1].contains(&1));
538 }
539 #[test]
540 pub(super) fn test_constant_folding() {
541 assert_eq!(EVMConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
542 assert_eq!(EVMConstantFoldingHelper::fold_div_i64(10, 0), None);
543 assert_eq!(EVMConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
544 assert_eq!(
545 EVMConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
546 0b1000
547 );
548 assert_eq!(EVMConstantFoldingHelper::fold_bitnot_i64(0), -1);
549 }
550 #[test]
551 pub(super) fn test_dep_graph() {
552 let mut g = EVMDepGraph::new();
553 g.add_dep(1, 2);
554 g.add_dep(2, 3);
555 g.add_dep(1, 3);
556 assert_eq!(g.dependencies_of(2), vec![1]);
557 let topo = g.topological_sort();
558 assert_eq!(topo.len(), 3);
559 assert!(!g.has_cycle());
560 let pos: std::collections::HashMap<u32, usize> =
561 topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
562 assert!(pos[&1] < pos[&2]);
563 assert!(pos[&1] < pos[&3]);
564 assert!(pos[&2] < pos[&3]);
565 }
566}
567#[allow(dead_code)]
569pub fn evm_mangle_identifier(name: &str) -> String {
570 name.chars()
571 .map(|c| {
572 if c.is_alphanumeric() || c == '_' {
573 c
574 } else {
575 '_'
576 }
577 })
578 .collect()
579}
580#[allow(dead_code)]
583pub fn evm_keccak256(input: &[u8]) -> [u8; 32] {
584 use tiny_keccak::{Hasher, Keccak};
585 let mut keccak = Keccak::v256();
586 let mut out = [0u8; 32];
587 keccak.update(input);
588 keccak.finalize(&mut out);
589 out
590}
591#[allow(dead_code)]
593pub fn evm_is_valid_selector(selector: &[u8]) -> bool {
594 selector.len() == 4
595}
596#[allow(dead_code)]
598pub const EVM_CODE_PASS_VERSION: &str = "1.0.0";
599#[allow(dead_code)]
601pub const EVM_SOLIDITY_MIN_VERSION: &str = "0.8.0";