anvil/eth/backend/mem/
cache.rs
1use 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) {
64 self.with_cache_file(hash, |file| {
65 tokio::task::spawn(async move {
66 match foundry_common::fs::write_json_file(&file, &state) {
67 Ok(_) => {
68 trace!(target: "backend", ?hash, "wrote state json file");
69 }
70 Err(err) => {
71 error!(target: "backend", %err, ?hash, "Failed to load state snapshot");
72 }
73 };
74 });
75 });
76 }
77
78 pub fn read(&mut self, hash: B256) -> Option<StateSnapshot> {
82 self.with_cache_file(hash, |file| {
83 match foundry_common::fs::read_json_file::<StateSnapshot>(&file) {
84 Ok(state) => {
85 trace!(target: "backend", ?hash,"loaded cached state");
86 Some(state)
87 }
88 Err(err) => {
89 error!(target: "backend", %err, ?hash, "Failed to load state snapshot");
90 None
91 }
92 }
93 })
94 .flatten()
95 }
96
97 pub fn remove(&mut self, hash: B256) {
99 self.with_cache_file(hash, |file| {
100 foundry_common::fs::remove_file(file).map_err(|err| {
101 error!(target: "backend", %err, %hash, "Failed to remove state snapshot");
102 })
103 });
104 }
105}
106
107impl Default for DiskStateCache {
108 fn default() -> Self {
109 Self { temp_path: anvil_tmp_dir(), temp_dir: None }
110 }
111}
112
113fn build_tmp_dir(p: Option<&Path>) -> io::Result<TempDir> {
117 let mut builder = tempfile::Builder::new();
118 let now = chrono::offset::Utc::now();
119 let prefix = now.format("anvil-state-%d-%m-%Y-%H-%M").to_string();
120 builder.prefix(&prefix);
121
122 if let Some(p) = p {
123 builder.tempdir_in(p)
124 } else {
125 builder.tempdir()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use tempfile::tempdir;
133
134 #[test]
135 fn can_build_temp_dir() {
136 let dir = tempdir().unwrap();
137 let p = dir.path();
138 let cache_dir = build_tmp_dir(Some(p)).unwrap();
139 assert!(cache_dir
140 .path()
141 .file_name()
142 .unwrap()
143 .to_str()
144 .unwrap()
145 .starts_with("anvil-state-"));
146 let cache_dir = build_tmp_dir(None).unwrap();
147 assert!(cache_dir
148 .path()
149 .file_name()
150 .unwrap()
151 .to_str()
152 .unwrap()
153 .starts_with("anvil-state-"));
154 }
155}