anvil/eth/backend/mem/
cache.rs1use crate::config::anvil_tmp_dir;
2use alloy_primitives::B256;
3use foundry_evm::backend::StateSnapshot;
4use std::{
5 io,
6 path::{Path, PathBuf},
7};
8use tempfile::TempDir;
9
10pub struct DiskStateCache {
14 pub(crate) temp_path: Option<PathBuf>,
16 pub(crate) temp_dir: Option<TempDir>,
18}
19
20impl DiskStateCache {
21 pub fn with_path(self, temp_path: PathBuf) -> Self {
23 Self { temp_path: Some(temp_path), temp_dir: None }
24 }
25 fn with_cache_file<F, R>(&mut self, hash: B256, f: F) -> Option<R>
27 where
28 F: FnOnce(PathBuf) -> R,
29 {
30 if self.temp_dir.is_none() {
31 let tmp_dir = self
32 .temp_path
33 .as_ref()
34 .map(|p| -> io::Result<TempDir> {
35 std::fs::create_dir_all(p)?;
36 build_tmp_dir(Some(p))
37 })
38 .unwrap_or_else(|| build_tmp_dir(None));
39
40 match tmp_dir {
41 Ok(temp_dir) => {
42 trace!(target: "backend", path=?temp_dir.path(), "created disk state cache dir");
43 self.temp_dir = Some(temp_dir);
44 }
45 Err(err) => {
46 error!(target: "backend", %err, "failed to create disk state cache dir");
47 }
48 }
49 }
50 if let Some(temp_dir) = &self.temp_dir {
51 let path = temp_dir.path().join(format!("{hash:?}.json"));
52 Some(f(path))
53 } else {
54 None
55 }
56 }
57
58 pub fn write(&mut self, hash: B256, state: &StateSnapshot) -> bool {
62 self.with_cache_file(hash, |file| match foundry_common::fs::write_json_file(&file, state) {
63 Ok(_) => {
64 trace!(target: "backend", ?hash, "wrote state json file");
65 true
66 }
67 Err(err) => {
68 error!(target: "backend", %err, ?hash, "Failed to write state snapshot");
69 false
70 }
71 })
72 .unwrap_or(false)
73 }
74
75 pub fn read(&mut self, hash: B256) -> Option<StateSnapshot> {
79 self.with_cache_file(hash, |file| {
80 match foundry_common::fs::read_json_file::<StateSnapshot>(&file) {
81 Ok(state) => {
82 trace!(target: "backend", ?hash,"loaded cached state");
83 Some(state)
84 }
85 Err(err) => {
86 error!(target: "backend", %err, ?hash, "Failed to load state snapshot");
87 None
88 }
89 }
90 })
91 .flatten()
92 }
93
94 pub fn remove(&mut self, hash: B256) {
96 self.with_cache_file(hash, |file| {
97 foundry_common::fs::remove_file(file).map_err(|err| {
98 error!(target: "backend", %err, %hash, "Failed to remove state snapshot");
99 })
100 });
101 }
102}
103
104impl Default for DiskStateCache {
105 fn default() -> Self {
106 Self { temp_path: anvil_tmp_dir(), temp_dir: None }
107 }
108}
109
110fn build_tmp_dir(p: Option<&Path>) -> io::Result<TempDir> {
114 let mut builder = tempfile::Builder::new();
115 let now = chrono::offset::Utc::now();
116 let prefix = now.format("anvil-state-%d-%m-%Y-%H-%M").to_string();
117 builder.prefix(&prefix);
118
119 if let Some(p) = p { builder.tempdir_in(p) } else { builder.tempdir() }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use tempfile::tempdir;
126
127 #[test]
128 fn can_build_temp_dir() {
129 let dir = tempdir().unwrap();
130 let p = dir.path();
131 let cache_dir = build_tmp_dir(Some(p)).unwrap();
132 assert!(
133 cache_dir.path().file_name().unwrap().to_str().unwrap().starts_with("anvil-state-")
134 );
135 let cache_dir = build_tmp_dir(None).unwrap();
136 assert!(
137 cache_dir.path().file_name().unwrap().to_str().unwrap().starts_with("anvil-state-")
138 );
139 }
140}