revm_database/states/bundle_account.rs
1use super::{
2 reverts::AccountInfoRevert, AccountRevert, AccountStatus, RevertToSlot, StorageSlot,
3 StorageWithOriginalValues, TransitionAccount,
4};
5use primitives::{HashMap, StorageKey, StorageValue};
6use state::AccountInfo;
7
8/// Account information focused on creating of database changesets
9/// and Reverts.
10///
11/// Status is needed as to know from what state we are applying the TransitionAccount.
12///
13/// Original account info is needed to know if there was a change.
14///
15/// Same thing for storage with original value.
16///
17/// On selfdestruct storage original value is ignored.
18#[derive(Clone, Debug, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct BundleAccount {
21 /// Current account information.
22 pub info: Option<AccountInfo>,
23 /// Original account information before modifications.
24 pub original_info: Option<AccountInfo>,
25 /// Contains both original and present state.
26 /// When extracting changeset we compare if original value is different from present value.
27 /// If it is different we add it to changeset.
28 ///
29 /// If Account was destroyed we ignore original value and compare present state with StorageValue::ZERO.
30 pub storage: StorageWithOriginalValues,
31 /// Account status.
32 pub status: AccountStatus,
33}
34
35impl BundleAccount {
36 /// Create new BundleAccount.
37 pub fn new(
38 original_info: Option<AccountInfo>,
39 present_info: Option<AccountInfo>,
40 storage: StorageWithOriginalValues,
41 status: AccountStatus,
42 ) -> Self {
43 Self {
44 info: present_info,
45 original_info,
46 storage,
47 status,
48 }
49 }
50
51 /// The approximate size of changes needed to store this account.
52 ///
53 /// `1 + storage_len`
54 pub fn size_hint(&self) -> usize {
55 1 + self.storage.len()
56 }
57
58 /// Return storage slot if it exists.
59 ///
60 /// In case we know that account is newly created or destroyed, return `Some(StorageValue::ZERO)`
61 pub fn storage_slot(&self, slot: StorageKey) -> Option<StorageValue> {
62 let slot = self.storage.get(&slot).map(|s| s.present_value);
63 if slot.is_some() {
64 slot
65 } else if self.status.is_storage_known() {
66 Some(StorageValue::ZERO)
67 } else {
68 None
69 }
70 }
71
72 /// Fetch account info if it exists.
73 pub fn account_info(&self) -> Option<AccountInfo> {
74 self.info.clone()
75 }
76
77 /// Was this account destroyed.
78 pub fn was_destroyed(&self) -> bool {
79 self.status.was_destroyed()
80 }
81
82 /// Return true of account info was changed.
83 pub fn is_info_changed(&self) -> bool {
84 self.info != self.original_info
85 }
86
87 /// Return true if contract was changed
88 pub fn is_contract_changed(&self) -> bool {
89 self.info.as_ref().map(|a| a.code_hash) != self.original_info.as_ref().map(|a| a.code_hash)
90 }
91
92 /// Revert account to previous state and return true if account can be removed.
93 pub fn revert(&mut self, revert: AccountRevert) -> bool {
94 self.status = revert.previous_status;
95
96 match revert.account {
97 AccountInfoRevert::DoNothing => (),
98 AccountInfoRevert::DeleteIt => {
99 self.info = None;
100 if self.original_info.is_none() {
101 self.storage = HashMap::default();
102 return true;
103 } else {
104 // Set all storage to zero but preserve original values.
105 self.storage.iter_mut().for_each(|(_, v)| {
106 v.present_value = StorageValue::ZERO;
107 });
108 return false;
109 }
110 }
111 AccountInfoRevert::RevertTo(info) => self.info = Some(info),
112 };
113 // Revert storage
114 for (key, slot) in revert.storage {
115 match slot {
116 RevertToSlot::Some(value) => {
117 // Don't overwrite original values if present
118 // if storage is not present set original value as current value.
119 self.storage
120 .entry(key)
121 .or_insert(StorageSlot::new(value))
122 .present_value = value;
123 }
124 RevertToSlot::Destroyed => {
125 // If it was destroyed this means that storage was created and we need to remove it.
126 self.storage.remove(&key);
127 }
128 }
129 }
130 false
131 }
132
133 /// Update to new state and generate AccountRevert that if applied to new state will
134 /// revert it to previous state.
135 ///
136 /// If no revert is present, update is noop.
137 pub fn update_and_create_revert(
138 &mut self,
139 transition: TransitionAccount,
140 ) -> Option<AccountRevert> {
141 let updated_info = transition.info;
142 let updated_storage = transition.storage;
143 let updated_status = transition.status;
144
145 // The helper that extends this storage but preserves original value.
146 let extend_storage =
147 |this_storage: &mut StorageWithOriginalValues,
148 storage_update: StorageWithOriginalValues| {
149 for (key, value) in storage_update {
150 this_storage.entry(key).or_insert(value).present_value = value.present_value;
151 }
152 };
153
154 let previous_storage_from_update =
155 |updated_storage: &StorageWithOriginalValues| -> HashMap<StorageKey, RevertToSlot> {
156 updated_storage
157 .iter()
158 .filter(|s| s.1.is_changed())
159 .map(|(key, value)| {
160 (*key, RevertToSlot::Some(value.previous_or_original_value))
161 })
162 .collect()
163 };
164
165 // Needed for some reverts.
166 let info_revert = if self.info != updated_info {
167 AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default())
168 } else {
169 AccountInfoRevert::DoNothing
170 };
171
172 let account_revert = match updated_status {
173 AccountStatus::Changed => {
174 let previous_storage = previous_storage_from_update(&updated_storage);
175 match self.status {
176 AccountStatus::Changed | AccountStatus::Loaded => {
177 // Extend the storage. original values is not used inside bundle.
178 extend_storage(&mut self.storage, updated_storage);
179 }
180 AccountStatus::LoadedEmptyEIP161 => {
181 // Do nothing.
182 // Only change that can happen from LoadedEmpty to Changed is if balance
183 // is send to account. So we are only checking account change here.
184 }
185 _ => unreachable!("Invalid state transfer to Changed from {self:?}"),
186 };
187 let previous_status = self.status;
188 self.status = AccountStatus::Changed;
189 self.info = updated_info;
190 Some(AccountRevert {
191 account: info_revert,
192 storage: previous_storage,
193 previous_status,
194 wipe_storage: false,
195 })
196 }
197 AccountStatus::InMemoryChange => {
198 let previous_storage = previous_storage_from_update(&updated_storage);
199 let in_memory_info_revert = match self.status {
200 AccountStatus::Loaded | AccountStatus::InMemoryChange => {
201 // From loaded (Or LoadedEmpty) to InMemoryChange can happen if there is balance change
202 // or new created account but Loaded didn't have contract.
203 extend_storage(&mut self.storage, updated_storage);
204 info_revert
205 }
206 AccountStatus::LoadedEmptyEIP161 => {
207 self.storage = updated_storage;
208 info_revert
209 }
210 AccountStatus::LoadedNotExisting => {
211 self.storage = updated_storage;
212 AccountInfoRevert::DeleteIt
213 }
214 _ => unreachable!("Invalid change to InMemoryChange from {self:?}"),
215 };
216 let previous_status = self.status;
217 self.status = AccountStatus::InMemoryChange;
218 self.info = updated_info;
219 Some(AccountRevert {
220 account: in_memory_info_revert,
221 storage: previous_storage,
222 previous_status,
223 wipe_storage: false,
224 })
225 }
226 AccountStatus::Loaded
227 | AccountStatus::LoadedNotExisting
228 | AccountStatus::LoadedEmptyEIP161 => {
229 // No changeset, maybe just update data
230 // Do nothing for now.
231 None
232 }
233 AccountStatus::Destroyed => {
234 // Clear this storage and move it to the Revert.
235 let this_storage = self.storage.drain().collect();
236 let ret = match self.status {
237 AccountStatus::InMemoryChange | AccountStatus::Changed | AccountStatus::Loaded | AccountStatus::LoadedEmptyEIP161 => {
238 Some(AccountRevert::new_selfdestructed(self.status, info_revert, this_storage))
239 }
240 AccountStatus::LoadedNotExisting => {
241 // Do nothing as we have LoadedNotExisting -> Destroyed (It is noop)
242 None
243 }
244 _ => unreachable!("Invalid transition to Destroyed account from: {self:?} to {updated_info:?} {updated_status:?}"),
245 };
246
247 if ret.is_some() {
248 self.status = AccountStatus::Destroyed;
249 self.info = None;
250 }
251
252 // Set present to destroyed.
253 ret
254 }
255 AccountStatus::DestroyedChanged => {
256 // Previous block created account or changed.
257 // (It was destroyed on previous block or one before).
258
259 // Check common pre destroy paths.
260 // If common path is there it will drain the storage.
261 if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
262 info_revert.clone(),
263 self,
264 &updated_storage,
265 ) {
266 // Set to destroyed and revert state.
267 self.status = AccountStatus::DestroyedChanged;
268 self.info = updated_info;
269 self.storage = updated_storage;
270
271 Some(revert_state)
272 } else {
273 let ret = match self.status {
274 AccountStatus::Destroyed | AccountStatus::LoadedNotExisting => {
275 // From destroyed state new account is made
276 Some(AccountRevert {
277 account: AccountInfoRevert::DeleteIt,
278 storage: previous_storage_from_update(&updated_storage),
279 previous_status: self.status,
280 wipe_storage: false,
281 })
282 }
283 AccountStatus::DestroyedChanged => {
284 // Account was destroyed in this transition. So we should clear present storage
285 // and insert it inside revert.
286
287 let previous_storage = if transition.storage_was_destroyed {
288 let mut storage = core::mem::take(&mut self.storage)
289 .into_iter()
290 .map(|t| (t.0, RevertToSlot::Some(t.1.present_value)))
291 .collect::<HashMap<_, _>>();
292 for key in updated_storage.keys() {
293 // As it is not existing inside Destroyed storage this means
294 // that previous values must be zero
295 storage.entry(*key).or_insert(RevertToSlot::Destroyed);
296 }
297 storage
298 } else {
299 previous_storage_from_update(&updated_storage)
300 };
301
302 Some(AccountRevert {
303 account: info_revert,
304 storage: previous_storage,
305 previous_status: AccountStatus::DestroyedChanged,
306 wipe_storage: false,
307 })
308 }
309 AccountStatus::DestroyedAgain => {
310 Some(AccountRevert::new_selfdestructed_again(
311 // Destroyed again will set empty account.
312 AccountStatus::DestroyedAgain,
313 AccountInfoRevert::DeleteIt,
314 HashMap::default(),
315 updated_storage.clone(),
316 ))
317 }
318 _ => unreachable!("Invalid state transfer to DestroyedNew from {self:?}"),
319 };
320 self.status = AccountStatus::DestroyedChanged;
321 self.info = updated_info;
322 // Extends current storage.
323 extend_storage(&mut self.storage, updated_storage);
324
325 ret
326 }
327 }
328 AccountStatus::DestroyedAgain => {
329 // Previous block created account
330 // (It was destroyed on previous block or one before).
331
332 // Check common pre destroy paths.
333 // This will drain the storage if it is common transition.
334 let ret = if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
335 info_revert,
336 self,
337 &HashMap::default(),
338 ) {
339 Some(revert_state)
340 } else {
341 match self.status {
342 AccountStatus::Destroyed
343 | AccountStatus::DestroyedAgain
344 | AccountStatus::LoadedNotExisting => {
345 // From destroyed to destroyed again is noop
346 //
347 // From DestroyedAgain to DestroyedAgain is noop
348 //
349 // From LoadedNotExisting to DestroyedAgain is noop
350 // as account is destroyed again
351 None
352 }
353 AccountStatus::DestroyedChanged => {
354 // From destroyed changed to destroyed again.
355 Some(AccountRevert::new_selfdestructed_again(
356 // Destroyed again will set empty account.
357 AccountStatus::DestroyedChanged,
358 AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default()),
359 self.storage.drain().collect(),
360 HashMap::default(),
361 ))
362 }
363 _ => unreachable!("Invalid state to DestroyedAgain from {self:?}"),
364 }
365 };
366 // Set to destroyed and revert state.
367 self.status = AccountStatus::DestroyedAgain;
368 self.info = None;
369 self.storage.clear();
370 ret
371 }
372 };
373
374 account_revert.and_then(|acc| if acc.is_empty() { None } else { Some(acc) })
375 }
376}