foundry_evm_symbolic/runtime/
precompiles.rs1use super::*;
2
3pub(crate) fn precompile_number(address: Address) -> Option<u8> {
4 let bytes = address.as_slice();
5 if bytes[..PRECOMPILE_ADDRESS_LEADING_ZEROS].iter().any(|byte| *byte != 0) {
6 return None;
7 }
8 match bytes[PRECOMPILE_ADDRESS_LEADING_ZEROS] {
9 1..=10 => Some(bytes[PRECOMPILE_ADDRESS_LEADING_ZEROS]),
10 _ => None,
11 }
12}
13
14pub(crate) fn precompile_number_for_spec(address: Address, spec_id: SpecId) -> Option<u8> {
15 match precompile_number(address)? {
16 5..=8 if spec_id < SpecId::BYZANTIUM => None,
17 9 if spec_id < SpecId::ISTANBUL => None,
18 10 if spec_id < SpecId::CANCUN => None,
19 number => Some(number),
20 }
21}
22
23pub(crate) fn active_precompiles(spec_id: SpecId) -> &'static Precompiles {
25 Precompiles::new(PrecompileSpecId::from_spec_id(spec_id))
26}
27
28pub(crate) fn is_precompile_for_spec(address: Address, spec_id: SpecId) -> bool {
30 active_precompiles(spec_id).contains(&address)
31}
32
33pub(crate) fn execute_precompile(
37 cx: &mut SymCx,
38 address: Address,
39 input: &[u8],
40 spec_id: SpecId,
41) -> Result<Option<SymReturnData>, SymbolicError> {
42 let Some(precompile) = active_precompiles(spec_id).get(&address) else {
43 return Err(SymbolicError::Unsupported("unsupported precompile"));
44 };
45 match precompile.execute(input, u64::MAX, 0) {
46 Ok(output) if output.is_success() => {
47 Ok(Some(SymReturnData::from_concrete_bytes(cx, output.bytes.to_vec())))
48 }
49 _ => Ok(None),
50 }
51}
52
53pub(crate) fn execute_symbolic_precompile(
54 cx: &mut SymCx,
55 address: Address,
56 input: SymBytes,
57 input_len: SymExpr,
58 spec_id: SpecId,
59) -> Result<Option<SymReturnData>, SymbolicError> {
60 if let Some(input_len) = input_len.as_const()
61 && let Ok(input_len) = usize::try_from(input_len)
62 && input_len <= input.len()
63 && let Ok(input) =
64 input.slice_concrete(cx, 0, input_len).concrete_bytes(cx, "symbolic precompile input")
65 {
66 return execute_precompile(cx, address, &input, spec_id);
67 }
68
69 match precompile_number_for_spec(address, spec_id) {
70 Some(1) => {
71 let input = SymBytes::sized(cx, input, input_len, 128);
73 if let Ok(input) = input.concrete_bytes(cx, "symbolic ecrecover input") {
74 return execute_precompile(cx, address, &input, spec_id);
75 }
76 let v = input.word_at(cx, 32);
77 let v27 = SymBoolExpr::eq_word_const(cx, &v, U256::from(27));
78 let v28 = SymBoolExpr::eq_word_const(cx, &v, U256::from(28));
79 let valid_v = SymBoolExpr::or(cx, vec![v27, v28]);
80 if valid_v.as_const() == Some(false) {
81 return Ok(Some(SymReturnData::empty(cx)));
82 }
83
84 let input = input.materialize(cx);
85 let input_len = SymExpr::constant(cx, U256::from(128));
86 let word = symbolic_hash_word_with_len(cx, "ecrecover", input, input_len);
87 let recovered = byte_word(cx, U256::ZERO, word.clone()).nonzero_bool(cx);
90 let recovered = SymBoolExpr::and(cx, vec![valid_v, recovered]);
91 let full_len = SymExpr::constant(cx, U256::from(32));
92 let empty_len = SymExpr::zero(cx);
93 let len = SymExpr::ite(cx, recovered, full_len, empty_len);
94 let mut bytes = vec![SymExpr::zero(cx); 12];
95 bytes.extend((12..32).map(|idx| byte_word(cx, U256::from(idx), word.clone())));
96 let bytes = SymBytes::exprs(cx, bytes);
97 Ok(Some(SymReturnData { len_word: len, bytes }))
98 }
99 Some(2) => {
100 let input = input.materialize(cx);
101 let word = symbolic_hash_word_with_len(cx, "sha256", input, input_len);
102 let bytes = word.into_byte_exprs(cx);
103 Ok(Some(SymReturnData::from_byte_exprs(cx, bytes)))
104 }
105 Some(3) => {
106 let input = input.materialize(cx);
107 let word = symbolic_hash_word_with_len(cx, "ripemd160", input, input_len);
108 let mut bytes = vec![SymExpr::zero(cx); 12];
109 bytes.extend((12..32).map(|idx| byte_word(cx, U256::from(idx), word.clone())));
110 Ok(Some(SymReturnData::from_byte_exprs(cx, bytes)))
111 }
112 Some(4) => Ok(Some(SymReturnData { len_word: input_len, bytes: input })),
113 Some(5) => symbolic_modexp_precompile(cx, &input, input_len),
114 Some(6) => {
115 let input_len = input_len.as_usize_or("symbolic precompile input")?;
116 if input_len > input.len() {
117 return Err(SymbolicError::Unsupported("out-of-bounds symbolic precompile input"));
118 }
119 if (0..input_len).any(|idx| input.byte(cx, idx).as_const().is_none()) {
120 return Err(SymbolicError::Unsupported(
121 "symbolic bn254 precompile validity not modeled",
122 ));
123 }
124 Ok(Some(symbolic_fixed_len_precompile_output(cx, "bn254_add", &input, input_len, 64)))
125 }
126 Some(7) => {
127 let input_len = input_len.as_usize_or("symbolic precompile input")?;
128 if input_len > input.len() {
129 return Err(SymbolicError::Unsupported("out-of-bounds symbolic precompile input"));
130 }
131 if (0..input_len).any(|idx| input.byte(cx, idx).as_const().is_none()) {
132 return Err(SymbolicError::Unsupported(
133 "symbolic bn254 precompile validity not modeled",
134 ));
135 }
136 Ok(Some(symbolic_fixed_len_precompile_output(cx, "bn254_mul", &input, input_len, 64)))
137 }
138 Some(8) => {
139 let input_len = input_len.as_usize_or("symbolic precompile input")?;
140 if input_len % 192 != 0 {
141 return Ok(None);
142 }
143 if input_len > input.len() {
144 return Err(SymbolicError::Unsupported("out-of-bounds symbolic precompile input"));
145 }
146 if (0..input_len).any(|idx| input.byte(cx, idx).as_const().is_none()) {
147 return Err(SymbolicError::Unsupported(
148 "symbolic bn254 precompile validity not modeled",
149 ));
150 }
151 Ok(Some(symbolic_fixed_len_precompile_output(
152 cx,
153 "bn254_pairing",
154 &input,
155 input_len,
156 32,
157 )))
158 }
159 Some(9) => {
160 let input_len = input_len.as_usize_or("symbolic precompile input")?;
161 if input_len != 213 {
162 return Ok(None);
163 }
164 if input_len > input.len() {
165 return Err(SymbolicError::Unsupported("out-of-bounds symbolic precompile input"));
166 }
167 let flag = input.byte(cx, 212);
168 match flag.as_const() {
169 Some(flag) if flag.is_zero() || flag == U256::ONE => {}
170 Some(_) => return Ok(None),
171 None => {
172 return Err(SymbolicError::Unsupported(
173 "symbolic blake2f precompile final flag not modeled",
174 ));
175 }
176 }
177 Ok(Some(symbolic_fixed_len_precompile_output(cx, "blake2f", &input, input_len, 64)))
178 }
179 Some(10) => Err(SymbolicError::Unsupported("KZG handled by execute_kzg_precompile_call")),
180 _ => {
181 let input_len = input_len.as_usize_or("symbolic precompile input")?;
182 if input_len > input.len() {
183 return Err(SymbolicError::Unsupported("out-of-bounds symbolic precompile input"));
184 }
185 let input = input
186 .slice_concrete(cx, 0, input_len)
187 .concrete_bytes(cx, "symbolic precompile input")?;
188 execute_precompile(cx, address, &input, spec_id)
189 }
190 }
191}
192
193pub(crate) fn symbolic_modexp_precompile(
194 cx: &mut SymCx,
195 input: &SymBytes,
196 input_len: SymExpr,
197) -> Result<Option<SymReturnData>, SymbolicError> {
198 let input_len = input_len.as_usize_or("symbolic precompile input")?;
199 if input_len > input.len() {
200 return Err(SymbolicError::Unsupported("out-of-bounds symbolic precompile input"));
201 }
202
203 let modulus_len = concrete_precompile_word_at(cx, input, 64)?;
204 let modulus_len = usize::try_from(modulus_len)
205 .ok()
206 .ok_or(SymbolicError::Unsupported("symbolic modexp output length"))?;
207 if modulus_len > 4096 {
208 return Err(SymbolicError::Unsupported("symbolic modexp output length"));
209 }
210 Ok(Some(symbolic_fixed_len_precompile_output(cx, "modexp", input, input_len, modulus_len)))
211}
212
213pub(crate) fn concrete_precompile_word_at(
214 cx: &mut SymCx,
215 input: &SymBytes,
216 offset: usize,
217) -> Result<U256, SymbolicError> {
218 let mut bytes = [0u8; 32];
219 for (idx, byte) in bytes.iter_mut().enumerate() {
220 let word = input.byte(cx, offset + idx);
221 *byte = word.as_const_or("symbolic precompile length header")?.to::<u8>();
222 }
223 Ok(U256::from_be_bytes(bytes))
224}
225
226pub(crate) fn symbolic_fixed_len_precompile_output(
227 cx: &mut SymCx,
228 algorithm: &'static str,
229 input: &SymBytes,
230 input_len: usize,
231 output_len: usize,
232) -> SymReturnData {
233 let input_len_word = SymExpr::constant(cx, U256::from(input_len));
234 let input = input.materialize(cx);
235 let mut bytes = Vec::with_capacity(output_len);
236 for chunk in 0..output_len.div_ceil(32) {
237 let mut chunk_input = Vec::with_capacity(input.len() + 1);
238 chunk_input.push(SymExpr::constant(cx, U256::from(chunk)));
239 chunk_input.extend(input.iter().cloned());
240 bytes.extend(
241 symbolic_hash_word_with_len(cx, algorithm, chunk_input, input_len_word.clone())
242 .into_byte_exprs(cx),
243 );
244 }
245 bytes.truncate(output_len);
246 SymReturnData::from_byte_exprs(cx, bytes)
247}