1use alloy_primitives::{Address, B256, Bytes};
2use evm_fork_cache::cache::{CodeMismatch, EvmCache};
3
4use crate::OracleError;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum OracleCodeInstallMode {
9 VerifyCanonical,
11 EtchSimulation,
13}
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct OracleCodeSeed {
18 pub address: Address,
20 pub code: Bytes,
22 pub mode: OracleCodeInstallMode,
24}
25
26impl OracleCodeSeed {
27 pub fn verify(address: Address, code: Bytes) -> Self {
29 Self {
30 address,
31 code,
32 mode: OracleCodeInstallMode::VerifyCanonical,
33 }
34 }
35
36 pub fn etch(address: Address, code: Bytes) -> Self {
38 Self {
39 address,
40 code,
41 mode: OracleCodeInstallMode::EtchSimulation,
42 }
43 }
44}
45
46#[derive(Clone, Debug, Default, PartialEq, Eq)]
48pub struct OracleCodeRegistry {
49 seeds: Vec<OracleCodeSeed>,
50}
51
52impl OracleCodeRegistry {
53 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn seed(mut self, address: Address, code: Bytes) -> Self {
60 self.seeds.push(OracleCodeSeed::verify(address, code));
61 self
62 }
63
64 pub fn seed_many<I>(mut self, seeds: I) -> Self
66 where
67 I: IntoIterator<Item = (Address, Bytes)>,
68 {
69 self.seeds.extend(
70 seeds
71 .into_iter()
72 .map(|(address, code)| OracleCodeSeed::verify(address, code)),
73 );
74 self
75 }
76
77 pub fn etch(mut self, address: Address, code: Bytes) -> Self {
79 self.seeds.push(OracleCodeSeed::etch(address, code));
80 self
81 }
82
83 pub fn etch_many<I>(mut self, etches: I) -> Self
85 where
86 I: IntoIterator<Item = (Address, Bytes)>,
87 {
88 self.seeds.extend(
89 etches
90 .into_iter()
91 .map(|(address, code)| OracleCodeSeed::etch(address, code)),
92 );
93 self
94 }
95
96 pub fn with_seed(mut self, seed: OracleCodeSeed) -> Self {
98 self.seeds.push(seed);
99 self
100 }
101
102 pub fn is_empty(&self) -> bool {
104 self.seeds.is_empty()
105 }
106
107 pub fn seeds(&self) -> &[OracleCodeSeed] {
109 &self.seeds
110 }
111
112 pub fn apply_to_cache(&self, cache: &mut EvmCache) -> OracleCodeWarmupReport {
114 let mut report = OracleCodeWarmupReport::default();
115 let mut has_canonical_seed = false;
116
117 for seed in &self.seeds {
118 match seed.mode {
119 OracleCodeInstallMode::VerifyCanonical => {
120 has_canonical_seed = true;
121 match cache.seed_account_code(seed.address, seed.code.clone()) {
122 Ok(code_hash) => report.seeded.push(OracleCodeInstall {
123 address: seed.address,
124 code_hash,
125 }),
126 Err(error) => report.install_errors.push(OracleCodeInstallError {
127 address: seed.address,
128 mode: seed.mode,
129 reason: error.to_string(),
130 }),
131 }
132 }
133 OracleCodeInstallMode::EtchSimulation => {
134 match cache.etch_account_code(seed.address, seed.code.clone()) {
135 Ok(code_hash) => report.etched.push(OracleCodeInstall {
136 address: seed.address,
137 code_hash,
138 }),
139 Err(error) => report.install_errors.push(OracleCodeInstallError {
140 address: seed.address,
141 mode: seed.mode,
142 reason: error.to_string(),
143 }),
144 }
145 }
146 }
147 }
148
149 if has_canonical_seed {
150 match cache.verify_code_seeds() {
151 Ok(verification) => {
152 report.verified = verification.verified;
153 report.mismatched = verification
154 .mismatched
155 .into_iter()
156 .map(Into::into)
157 .collect();
158 report.not_deployed = verification.not_deployed;
159 report.codeless = verification.codeless;
160 report.unverifiable = verification
161 .unverifiable
162 .into_iter()
163 .map(|(address, reason)| OracleCodeUnverifiable { address, reason })
164 .collect();
165 }
166 Err(error) => report.verify_error = Some(error.to_string()),
167 }
168 }
169
170 report
171 }
172
173 pub fn apply_to_cache_with_policy(
175 &self,
176 cache: &mut EvmCache,
177 policy: OracleCodeWarmupPolicy,
178 ) -> Result<OracleCodeWarmupReport, OracleError> {
179 let report = self.apply_to_cache(cache);
180 report.enforce_policy(policy)?;
181 Ok(report)
182 }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub struct OracleCodeWarmupPolicy {
188 pub fail_on_install_error: bool,
190 pub fail_on_mismatch: bool,
192 pub fail_on_not_deployed: bool,
194 pub fail_on_codeless: bool,
196 pub fail_on_unverifiable: bool,
198 pub fail_on_verify_error: bool,
200}
201
202impl OracleCodeWarmupPolicy {
203 pub fn strict() -> Self {
205 Self {
206 fail_on_install_error: true,
207 fail_on_mismatch: true,
208 fail_on_not_deployed: true,
209 fail_on_codeless: true,
210 fail_on_unverifiable: true,
211 fail_on_verify_error: true,
212 }
213 }
214
215 pub fn allow_unverifiable() -> Self {
217 Self {
218 fail_on_unverifiable: false,
219 fail_on_verify_error: false,
220 ..Self::strict()
221 }
222 }
223
224 pub fn best_effort() -> Self {
226 Self {
227 fail_on_install_error: false,
228 fail_on_mismatch: false,
229 fail_on_not_deployed: false,
230 fail_on_codeless: false,
231 fail_on_unverifiable: false,
232 fail_on_verify_error: false,
233 }
234 }
235}
236
237impl Default for OracleCodeWarmupPolicy {
238 fn default() -> Self {
239 Self::allow_unverifiable()
240 }
241}
242
243#[derive(Clone, Debug, PartialEq, Eq)]
245pub struct OracleCodeInstall {
246 pub address: Address,
248 pub code_hash: B256,
250}
251
252#[derive(Clone, Debug, PartialEq, Eq)]
254pub struct OracleCodeInstallError {
255 pub address: Address,
257 pub mode: OracleCodeInstallMode,
259 pub reason: String,
261}
262
263#[derive(Clone, Debug, PartialEq, Eq)]
265pub struct OracleCodeMismatch {
266 pub address: Address,
268 pub expected: B256,
270 pub actual: B256,
272}
273
274impl From<CodeMismatch> for OracleCodeMismatch {
275 fn from(value: CodeMismatch) -> Self {
276 Self {
277 address: value.address,
278 expected: value.expected,
279 actual: value.actual,
280 }
281 }
282}
283
284#[derive(Clone, Debug, PartialEq, Eq)]
286pub struct OracleCodeUnverifiable {
287 pub address: Address,
289 pub reason: String,
291}
292
293#[derive(Clone, Debug, Default, PartialEq, Eq)]
295pub struct OracleCodeWarmupReport {
296 pub seeded: Vec<OracleCodeInstall>,
298 pub etched: Vec<OracleCodeInstall>,
300 pub install_errors: Vec<OracleCodeInstallError>,
302 pub verified: Vec<Address>,
304 pub mismatched: Vec<OracleCodeMismatch>,
306 pub not_deployed: Vec<Address>,
308 pub codeless: Vec<Address>,
310 pub unverifiable: Vec<OracleCodeUnverifiable>,
312 pub verify_error: Option<String>,
314}
315
316impl OracleCodeWarmupReport {
317 pub fn is_empty(&self) -> bool {
319 self.seeded.is_empty()
320 && self.etched.is_empty()
321 && self.install_errors.is_empty()
322 && self.verified.is_empty()
323 && self.mismatched.is_empty()
324 && self.not_deployed.is_empty()
325 && self.codeless.is_empty()
326 && self.unverifiable.is_empty()
327 && self.verify_error.is_none()
328 }
329
330 pub fn enforce_policy(&self, policy: OracleCodeWarmupPolicy) -> Result<(), OracleError> {
332 let violation = OracleCodePolicyViolation {
333 install_errors: if policy.fail_on_install_error {
334 self.install_errors.len()
335 } else {
336 0
337 },
338 mismatched: if policy.fail_on_mismatch {
339 self.mismatched.len()
340 } else {
341 0
342 },
343 not_deployed: if policy.fail_on_not_deployed {
344 self.not_deployed.len()
345 } else {
346 0
347 },
348 codeless: if policy.fail_on_codeless {
349 self.codeless.len()
350 } else {
351 0
352 },
353 unverifiable: if policy.fail_on_unverifiable {
354 self.unverifiable.len()
355 } else {
356 0
357 },
358 verify_error: if policy.fail_on_verify_error {
359 self.verify_error.clone()
360 } else {
361 None
362 },
363 };
364 if violation.is_empty() {
365 Ok(())
366 } else {
367 Err(OracleError::CodePolicy(Box::new(violation)))
368 }
369 }
370}
371
372#[non_exhaustive]
380#[derive(Clone, Debug, Default, PartialEq, Eq)]
381pub struct OracleCodePolicyViolation {
382 pub install_errors: usize,
384 pub mismatched: usize,
386 pub not_deployed: usize,
388 pub codeless: usize,
390 pub unverifiable: usize,
392 pub verify_error: Option<String>,
394}
395
396impl OracleCodePolicyViolation {
397 fn is_empty(&self) -> bool {
398 self.install_errors == 0
399 && self.mismatched == 0
400 && self.not_deployed == 0
401 && self.codeless == 0
402 && self.unverifiable == 0
403 && self.verify_error.is_none()
404 }
405}
406
407impl std::fmt::Display for OracleCodePolicyViolation {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 let mut failures = Vec::new();
410 if self.install_errors > 0 {
411 failures.push(format!("{} install error(s)", self.install_errors));
412 }
413 if self.mismatched > 0 {
414 failures.push(format!("{} code hash mismatch(es)", self.mismatched));
415 }
416 if self.not_deployed > 0 {
417 failures.push(format!("{} not deployed address(es)", self.not_deployed));
418 }
419 if self.codeless > 0 {
420 failures.push(format!("{} codeless address(es)", self.codeless));
421 }
422 if self.unverifiable > 0 {
423 failures.push(format!("{} unverifiable claim(s)", self.unverifiable));
424 }
425 if let Some(error) = &self.verify_error {
426 failures.push(format!("verification error: {error}"));
427 }
428 write!(
429 f,
430 "oracle code warmup failed policy: {}",
431 failures.join(", ")
432 )
433 }
434}