Skip to main content

evm_oracle_state/
code.rs

1use alloy_primitives::{Address, B256, Bytes};
2use evm_fork_cache::cache::{CodeMismatch, EvmCache};
3
4use crate::OracleError;
5
6/// How an oracle bytecode entry should be installed into the fork cache.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum OracleCodeInstallMode {
9    /// Seed a canonical bytecode claim and verify it against on-chain code hash.
10    VerifyCanonical,
11    /// Deliberately etch local simulation bytecode. This is never verified.
12    EtchSimulation,
13}
14
15/// One bytecode install request for an oracle-related contract.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct OracleCodeSeed {
18    /// Contract address whose code should be installed.
19    pub address: Address,
20    /// Runtime bytecode.
21    pub code: Bytes,
22    /// Installation mode.
23    pub mode: OracleCodeInstallMode,
24}
25
26impl OracleCodeSeed {
27    /// Create a canonical seed that must verify against chain code hash.
28    pub fn verify(address: Address, code: Bytes) -> Self {
29        Self {
30            address,
31            code,
32            mode: OracleCodeInstallMode::VerifyCanonical,
33        }
34    }
35
36    /// Create an explicit local etch for simulation-only bytecode.
37    pub fn etch(address: Address, code: Bytes) -> Self {
38        Self {
39            address,
40            code,
41            mode: OracleCodeInstallMode::EtchSimulation,
42        }
43    }
44}
45
46/// Registry of oracle bytecode claims to install before discovery or simulation.
47#[derive(Clone, Debug, Default, PartialEq, Eq)]
48pub struct OracleCodeRegistry {
49    seeds: Vec<OracleCodeSeed>,
50}
51
52impl OracleCodeRegistry {
53    /// Create an empty registry.
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    /// Add a canonical runtime bytecode claim that must verify once.
59    pub fn seed(mut self, address: Address, code: Bytes) -> Self {
60        self.seeds.push(OracleCodeSeed::verify(address, code));
61        self
62    }
63
64    /// Add multiple canonical runtime bytecode claims that must verify once.
65    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    /// Add an explicit simulation-only code etch.
78    pub fn etch(mut self, address: Address, code: Bytes) -> Self {
79        self.seeds.push(OracleCodeSeed::etch(address, code));
80        self
81    }
82
83    /// Add multiple explicit simulation-only code etches.
84    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    /// Append an already-built seed.
97    pub fn with_seed(mut self, seed: OracleCodeSeed) -> Self {
98        self.seeds.push(seed);
99        self
100    }
101
102    /// Return true when there is no code work to apply.
103    pub fn is_empty(&self) -> bool {
104        self.seeds.is_empty()
105    }
106
107    /// Return registered seed entries.
108    pub fn seeds(&self) -> &[OracleCodeSeed] {
109        &self.seeds
110    }
111
112    /// Install code entries into the cache and verify canonical seeds.
113    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    /// Install code entries, verify canonical seeds, and enforce `policy`.
174    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/// Failure policy for oracle bytecode warmup.
186#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub struct OracleCodeWarmupPolicy {
188    /// Fail when installing a seed or etch returns an error.
189    pub fail_on_install_error: bool,
190    /// Fail when canonical seeded bytecode contradicts on-chain code hash.
191    pub fail_on_mismatch: bool,
192    /// Fail when a canonical seed points at an address that is not deployed.
193    pub fail_on_not_deployed: bool,
194    /// Fail when a canonical seed points at an account with empty code.
195    pub fail_on_codeless: bool,
196    /// Fail when canonical verification cannot run or cannot settle.
197    pub fail_on_unverifiable: bool,
198    /// Fail when the whole verification pass errors.
199    pub fail_on_verify_error: bool,
200}
201
202impl OracleCodeWarmupPolicy {
203    /// Strict policy for production canonical bytecode claims.
204    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    /// Allow transient verification uncertainty, but fail on contradictions.
216    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    /// Best-effort policy that only records report fields.
225    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/// One successfully installed bytecode entry.
244#[derive(Clone, Debug, PartialEq, Eq)]
245pub struct OracleCodeInstall {
246    /// Contract address.
247    pub address: Address,
248    /// Installed bytecode hash.
249    pub code_hash: B256,
250}
251
252/// Code install failure for one entry.
253#[derive(Clone, Debug, PartialEq, Eq)]
254pub struct OracleCodeInstallError {
255    /// Contract address.
256    pub address: Address,
257    /// Requested installation mode.
258    pub mode: OracleCodeInstallMode,
259    /// Error message.
260    pub reason: String,
261}
262
263/// Code-hash mismatch for a canonical seed.
264#[derive(Clone, Debug, PartialEq, Eq)]
265pub struct OracleCodeMismatch {
266    /// Contract address.
267    pub address: Address,
268    /// Expected hash from the seeded bytecode.
269    pub expected: B256,
270    /// Actual on-chain code hash.
271    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/// Canonical code claim that could not be verified due to transport or provider limitations.
285#[derive(Clone, Debug, PartialEq, Eq)]
286pub struct OracleCodeUnverifiable {
287    /// Contract address.
288    pub address: Address,
289    /// Reason verification did not settle.
290    pub reason: String,
291}
292
293/// Report for code seeding, verification, and explicit etching.
294#[derive(Clone, Debug, Default, PartialEq, Eq)]
295pub struct OracleCodeWarmupReport {
296    /// Canonical code claims installed before verification.
297    pub seeded: Vec<OracleCodeInstall>,
298    /// Explicit local etches installed.
299    pub etched: Vec<OracleCodeInstall>,
300    /// Entries that could not be installed.
301    pub install_errors: Vec<OracleCodeInstallError>,
302    /// Canonical claims verified against chain code hash.
303    pub verified: Vec<Address>,
304    /// Canonical claims contradicted by chain code hash.
305    pub mismatched: Vec<OracleCodeMismatch>,
306    /// Addresses not deployed at the pinned block.
307    pub not_deployed: Vec<Address>,
308    /// Addresses with empty code at the pinned block.
309    pub codeless: Vec<Address>,
310    /// Canonical claims that could not be verified but remain pending.
311    pub unverifiable: Vec<OracleCodeUnverifiable>,
312    /// Whole-verification error, if verification itself could not run.
313    pub verify_error: Option<String>,
314}
315
316impl OracleCodeWarmupReport {
317    /// Return true when no code work was requested or performed.
318    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    /// Enforce a failure policy against this report.
331    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/// Summary of the code warmup report entries that violated an enforced
373/// [`OracleCodeWarmupPolicy`], carried by
374/// [`OracleError::CodePolicy`](crate::OracleError::CodePolicy).
375///
376/// Each count covers only the categories the policy actually enforced; the
377/// full per-entry detail remains on the [`OracleCodeWarmupReport`] returned by
378/// the `build_report` paths.
379#[non_exhaustive]
380#[derive(Clone, Debug, Default, PartialEq, Eq)]
381pub struct OracleCodePolicyViolation {
382    /// Enforced install errors.
383    pub install_errors: usize,
384    /// Enforced code hash mismatches.
385    pub mismatched: usize,
386    /// Enforced not-deployed addresses.
387    pub not_deployed: usize,
388    /// Enforced codeless addresses.
389    pub codeless: usize,
390    /// Enforced unverifiable claims.
391    pub unverifiable: usize,
392    /// Enforced verification transport error, when one occurred.
393    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}