1#![warn(unused_crate_dependencies)]
4
5use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt};
6use alloy_json_abi::Function;
7use alloy_primitives::{
8 Address, B256, Bytes, I256, Keccak256, U256, hex, keccak256,
9 map::{HashMap, HashSet, IndexSet},
10};
11use alloy_signer::SignerSync;
12use alloy_signer_local::{
13 PrivateKeySigner,
14 coins_bip39::{English, Wordlist},
15};
16use alloy_sol_types::SolCall;
17use base64::prelude::*;
18use foundry_cheatcodes_spec::{SymbolicVm, Vm};
19use foundry_config::{SymbolicConfig, SymbolicExplorationOrder, SymbolicStorageLayout};
20use foundry_evm::{
21 constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS},
22 core::{backend::DatabaseExt, evm::FoundryEvmNetwork},
23 executors::Executor,
24 revm::{
25 bytecode::{Bytecode, JumpTable, opcode},
26 context::{Block, Cfg, Transaction},
27 database::DatabaseRef,
28 precompile::{blake2, bn254, hash, identity, kzg_point_evaluation, modexp, secp256k1},
29 primitives::hardfork::SpecId,
30 },
31};
32use serde::{Deserialize, Serialize};
33use std::{
34 collections::VecDeque,
35 fmt::{self, Write as _},
36 io::Write,
37 ops::{ControlFlow, Deref, DerefMut},
38 path::{Path, PathBuf},
39 process::{Command, Stdio},
40 sync::{
41 Arc,
42 atomic::{AtomicBool, Ordering},
43 mpsc,
44 },
45 thread,
46 time::{Duration, Instant, SystemTime, UNIX_EPOCH},
47};
48use thiserror::Error;
49use tracing::{debug, trace, trace_span, warn};
50
51mod abi;
52mod consts;
53mod executor;
54mod runtime;
55
56pub(crate) use consts::*;
57pub use runtime::{SymbolicBranchTarget, SymbolicError, SymbolicRunInput};
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60enum SymbolicVmCheatcode {
61 CreateAddress,
62 CreateBool,
63 CreateBytes,
64 CreateBytesSized,
65 CreateBytesFixed(usize),
66 CreateCalldata,
67 CreateInt,
68 CreateIntBits(usize),
69 CreateString,
70 CreateStringSized,
71 CreateUint,
72 CreateUintBits(usize),
73 EnableSymbolicStorage,
74 SnapshotStorage,
75 SnapshotState,
76}
77
78impl SymbolicVmCheatcode {
79 fn from_selector(selector: [u8; 4]) -> Option<Self> {
80 match selector {
81 SymbolicVm::createAddressCall::SELECTOR => Some(Self::CreateAddress),
82 SymbolicVm::createBoolCall::SELECTOR => Some(Self::CreateBool),
83 SymbolicVm::createBytes_0Call::SELECTOR => Some(Self::CreateBytes),
84 SymbolicVm::createBytes_1Call::SELECTOR => Some(Self::CreateBytesSized),
85 SymbolicVm::createCalldataCall::SELECTOR => Some(Self::CreateCalldata),
86 SymbolicVm::createIntCall::SELECTOR => Some(Self::CreateInt),
87 SymbolicVm::createString_0Call::SELECTOR => Some(Self::CreateString),
88 SymbolicVm::createString_1Call::SELECTOR => Some(Self::CreateStringSized),
89 SymbolicVm::createUintCall::SELECTOR => Some(Self::CreateUint),
90 SymbolicVm::enableSymbolicStorageCall::SELECTOR
91 | Vm::setArbitraryStorage_0Call::SELECTOR => Some(Self::EnableSymbolicStorage),
92 SymbolicVm::snapshotStorageCall::SELECTOR => Some(Self::SnapshotStorage),
93 Vm::snapshotStateCall::SELECTOR => Some(Self::SnapshotState),
94 _ => {
95 let name = SymbolicVm::SymbolicVmCalls::name_by_selector(selector)?;
96 if let Some(bits) = name.strip_prefix("createUint") {
97 bits.parse().ok().map(Self::CreateUintBits)
98 } else if let Some(bits) = name.strip_prefix("createInt") {
99 bits.parse().ok().map(Self::CreateIntBits)
100 } else if let Some(bytes) = name.strip_prefix("createBytes") {
101 bytes.parse().ok().map(Self::CreateBytesFixed)
102 } else {
103 None
104 }
105 }
106 }
107 }
108
109 const fn min_input_words(self) -> usize {
110 match self {
111 Self::CreateUint
112 | Self::CreateInt
113 | Self::CreateBytesSized
114 | Self::CreateStringSized
115 | Self::EnableSymbolicStorage
116 | Self::SnapshotStorage => 1,
117 Self::CreateAddress
118 | Self::CreateBool
119 | Self::CreateBytes
120 | Self::CreateBytesFixed(_)
121 | Self::CreateCalldata
122 | Self::CreateIntBits(_)
123 | Self::CreateString
124 | Self::CreateUintBits(_)
125 | Self::SnapshotState => 0,
126 }
127 }
128}
129
130#[derive(Clone, Debug)]
137pub enum SymbolicRunResult {
138 Safe {
140 stats: SymbolicStats,
142 success_input: Option<SymbolicConcreteInput>,
144 },
145 Counterexample {
147 args: Vec<DynSolValue>,
149 calldata: Bytes,
151 stats: SymbolicStats,
153 },
154 Incomplete {
156 kind: SymbolicStopReason,
158 reason: String,
160 stats: SymbolicStats,
162 },
163}
164
165#[derive(Clone, Debug)]
167pub struct SymbolicConcreteInput {
168 pub args: Vec<DynSolValue>,
170 pub calldata: Bytes,
172}
173
174#[derive(Debug)]
176pub struct SymbolicBranchTargetSearchResult {
177 pub candidates: Vec<SymbolicConcreteInput>,
179 pub execution: SymbolicRunResult,
181}
182
183#[derive(Clone, Debug)]
185pub struct SymbolicInvariantTarget {
186 pub address: Address,
188 pub contract_name: Option<String>,
190 pub function: Function,
192}
193
194pub struct SymbolicInvariantCandidateInput<'a, FEN: FoundryEvmNetwork> {
196 pub executor: &'a Executor<FEN>,
198 pub invariant_address: Address,
200 pub invariants: &'a [&'a Function],
202 pub after_invariant: Option<&'a Function>,
204 pub target: &'a SymbolicInvariantTarget,
206 pub handler_sender: Address,
208 pub ffi_enabled: bool,
210}
211
212#[derive(Clone, Debug)]
214pub struct SymbolicInvariantCandidate {
215 pub invariant_idx: usize,
217 pub step: SymbolicInvariantStep,
219 pub storage: Vec<SymbolicStorageAssignment>,
221}
222
223#[derive(Clone, Debug, PartialEq, Eq)]
225pub struct SymbolicInvariantSearchLimitation {
226 pub kind: SymbolicStopReason,
228 pub reason: String,
230}
231
232impl From<SymbolicError> for SymbolicInvariantSearchLimitation {
233 fn from(error: SymbolicError) -> Self {
234 Self { kind: error.stop_reason(), reason: error.to_string() }
235 }
236}
237
238#[derive(Clone, Debug)]
240pub struct SymbolicInvariantCandidateSearchResult {
241 pub candidates: Vec<SymbolicInvariantCandidate>,
243 pub limitation: Option<SymbolicInvariantSearchLimitation>,
246}
247
248pub struct SymbolicInvariantRunInput<'a, FEN: FoundryEvmNetwork> {
250 pub executor: &'a Executor<FEN>,
252 pub invariant_address: Address,
254 pub sender: Address,
256 pub invariant: &'a Function,
258 pub after_invariant: Option<&'a Function>,
260 pub targets: Vec<SymbolicInvariantTarget>,
262 pub senders: Vec<Address>,
264 pub excluded_senders: Vec<Address>,
266 pub depth: usize,
268 pub check_interval: u32,
270 pub fail_on_revert: bool,
272 pub ffi_enabled: bool,
274}
275
276#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
278pub struct SymbolicStorageAssignment {
279 pub address: Address,
281 pub slot: U256,
283 pub value: U256,
285}
286
287#[derive(Clone, Debug)]
289pub enum SymbolicInvariantRunResult {
290 Safe(SymbolicStats),
292 Counterexample {
294 kind: SymbolicInvariantCounterexampleKind,
296 sequence: Vec<SymbolicInvariantStep>,
298 storage: Vec<SymbolicStorageAssignment>,
300 stats: SymbolicStats,
302 },
303 Incomplete {
305 kind: SymbolicStopReason,
307 reason: String,
309 stats: SymbolicStats,
311 },
312}
313
314#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316pub enum SymbolicInvariantCounterexampleKind {
317 Predicate,
319 Handler,
321}
322
323#[derive(Clone, Debug)]
325pub struct SymbolicInvariantStep {
326 pub sender: Address,
328 pub address: Address,
330 pub contract_name: Option<String>,
332 pub function_name: String,
334 pub signature: String,
336 pub args: Vec<DynSolValue>,
338 pub calldata: Bytes,
340}
341
342#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
344pub enum SymbolicStopReason {
345 Stuck,
347 RevertAll,
349 Timeout,
351 Error,
353}
354
355#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
357pub struct SymbolicStats {
358 pub paths: usize,
360 pub solver_queries: usize,
362 #[serde(default)]
364 pub smt_queries: usize,
365 #[serde(default)]
367 pub sat_queries: usize,
368 #[serde(default)]
370 pub model_queries: usize,
371 #[serde(default)]
373 pub sat_cache_hits: usize,
374 #[serde(default)]
376 pub model_cache_hits: usize,
377 #[serde(default)]
379 pub heuristic_witnesses: usize,
380 #[serde(default)]
382 pub solver_time_ms: u64,
383 #[serde(default)]
385 pub smt_input_bytes: u64,
386 #[serde(default)]
388 pub smt_max_query_bytes: u64,
389 #[serde(default)]
391 pub smt_build_time_ms: u64,
392 #[serde(default)]
394 pub smt_max_query_time_ms: u64,
395}
396
397pub struct SymbolicExecutor {
404 config: SymbolicConfig,
405 cx: runtime::SymCx,
406 solver: runtime::SmtLibSubprocessSolver,
407 deferred_incomplete: Option<DeferredIncomplete>,
408 deadline: Option<Instant>,
409 nested_deferred_mode: DeferredPathMode,
410 stateless_retry_safe: bool,
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
414enum DeferredPathMode {
415 Skip,
416 Yield,
417 Drain,
418}
419
420#[derive(Clone, Copy, Debug, PartialEq, Eq)]
421enum DeferredIncomplete {
422 Unsupported(&'static str),
423 SolverUnknown,
424 HardArithmetic,
425}