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