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