@@ -78,7 +78,6 @@ impl BatchStateDiff {
78
78
}
79
79
80
80
let original_acc_storage = self . original_storage_slots . entry ( * addr) . or_default ( ) ;
81
-
82
81
let cur_acc_storage = self . storage_slots . entry ( * addr) . or_default ( ) ;
83
82
84
83
for ( key, value) in & acc. storage {
@@ -97,6 +96,221 @@ impl BatchStateDiff {
97
96
cur_acc_storage. insert ( * key, cur_storage_value) ;
98
97
}
99
98
}
99
+
100
+ // It can happen that the storage slots for a certain account has been completely
101
+ // reverted, so remove the account from the map.
102
+ if cur_acc_storage. is_empty ( ) {
103
+ self . storage_slots . remove ( addr) ;
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ #[ cfg( test) ]
110
+ mod tests {
111
+ use revm:: db:: { states:: StorageSlot , BundleAccount } ;
112
+ use revm_primitives:: {
113
+ alloy_primitives:: map:: HashMap , map:: DefaultHashBuilder , AccountInfo , Address , FixedBytes ,
114
+ KECCAK_EMPTY , U256 ,
115
+ } ;
116
+
117
+ use crate :: { BatchStateDiff , BlockStateDiff } ;
118
+
119
+ const fn account1 ( ) -> Address {
120
+ Address :: new ( [ 0x60 ; 20 ] )
121
+ }
122
+
123
+ const fn account2 ( ) -> Address {
124
+ Address :: new ( [ 0x61 ; 20 ] )
125
+ }
126
+
127
+ fn acc_info1 ( ) -> AccountInfo {
128
+ AccountInfo {
129
+ nonce : 1 ,
130
+ balance : U256 :: from ( 10 ) ,
131
+ code_hash : KECCAK_EMPTY ,
132
+ code : None ,
133
+ }
134
+ }
135
+
136
+ fn acc_info2 ( ) -> AccountInfo {
137
+ AccountInfo {
138
+ nonce : 3 ,
139
+ balance : U256 :: from ( 20 ) ,
140
+ code_hash : KECCAK_EMPTY ,
141
+ code : None ,
100
142
}
101
143
}
144
+
145
+ fn slot1 ( ) -> U256 {
146
+ U256 :: from ( 5 )
147
+ }
148
+
149
+ fn slot2 ( ) -> U256 {
150
+ U256 :: from ( 7 )
151
+ }
152
+
153
+ fn slot_changes ( ) -> HashMap < U256 , StorageSlot > {
154
+ HashMap :: from_iter ( [
155
+ (
156
+ slot1 ( ) ,
157
+ StorageSlot :: new_changed ( U256 :: from ( 0 ) , U256 :: from ( 10 ) ) ,
158
+ ) ,
159
+ (
160
+ slot2 ( ) ,
161
+ StorageSlot :: new_changed ( U256 :: from ( 10 ) , U256 :: from ( 15 ) ) ,
162
+ ) ,
163
+ ] )
164
+ }
165
+
166
+ fn revert_slot_changes (
167
+ slot_changes : & HashMap < U256 , StorageSlot > ,
168
+ ) -> HashMap < U256 , StorageSlot > {
169
+ HashMap :: < U256 , StorageSlot , DefaultHashBuilder > :: from_iter ( slot_changes. iter ( ) . map (
170
+ |( k, v) | {
171
+ (
172
+ * k,
173
+ StorageSlot :: new_changed ( v. present_value , v. previous_or_original_value ) ,
174
+ )
175
+ } ,
176
+ ) )
177
+ }
178
+
179
+ fn test_state_diff_acc1 ( ) -> BlockStateDiff {
180
+ let mut test_diff = BlockStateDiff {
181
+ state : HashMap :: default ( ) ,
182
+ contracts : HashMap :: default ( ) ,
183
+ } ;
184
+
185
+ test_diff. state . insert (
186
+ account1 ( ) ,
187
+ BundleAccount :: new (
188
+ None ,
189
+ Some ( acc_info1 ( ) ) ,
190
+ slot_changes ( ) ,
191
+ revm:: db:: AccountStatus :: Changed ,
192
+ ) ,
193
+ ) ;
194
+
195
+ test_diff
196
+ }
197
+
198
+ fn test_state_diff_acc_old_slots1 ( ) -> BlockStateDiff {
199
+ let mut test_diff = BlockStateDiff {
200
+ state : HashMap :: default ( ) ,
201
+ contracts : HashMap :: default ( ) ,
202
+ } ;
203
+
204
+ test_diff. state . insert (
205
+ account1 ( ) ,
206
+ BundleAccount :: new (
207
+ Some ( acc_info1 ( ) ) ,
208
+ Some ( AccountInfo {
209
+ nonce : 2 ,
210
+ balance : U256 :: from ( 9 ) ,
211
+ code_hash : KECCAK_EMPTY ,
212
+ code : None ,
213
+ } ) ,
214
+ revert_slot_changes ( & slot_changes ( ) ) ,
215
+ revm:: db:: AccountStatus :: Changed ,
216
+ ) ,
217
+ ) ;
218
+
219
+ test_diff
220
+ }
221
+
222
+ fn test_state_diff_acc2 ( ) -> BlockStateDiff {
223
+ let mut test_diff = BlockStateDiff {
224
+ state : HashMap :: default ( ) ,
225
+ contracts : HashMap :: default ( ) ,
226
+ } ;
227
+
228
+ test_diff. state . insert (
229
+ account2 ( ) ,
230
+ BundleAccount :: new (
231
+ None ,
232
+ Some ( acc_info2 ( ) ) ,
233
+ HashMap :: default ( ) ,
234
+ revm:: db:: AccountStatus :: Changed ,
235
+ ) ,
236
+ ) ;
237
+
238
+ test_diff
239
+ }
240
+
241
+ #[ test]
242
+ fn basic_batch_state_diff ( ) {
243
+ let mut batch_diff = BatchStateDiff :: new ( ) ;
244
+ batch_diff. apply ( test_state_diff_acc1 ( ) ) ;
245
+ batch_diff. apply ( test_state_diff_acc2 ( ) ) ;
246
+
247
+ let acc1 = batch_diff
248
+ . accounts
249
+ . get ( & account1 ( ) )
250
+ . expect ( "account1 should be present" )
251
+ . clone ( ) ;
252
+ let info1 = acc1. unwrap ( ) ;
253
+ assert ! ( info1 == acc_info1( ) ) ;
254
+
255
+ let acc2 = batch_diff
256
+ . accounts
257
+ . get ( & account2 ( ) )
258
+ . expect ( "account2 should be present" )
259
+ . clone ( ) ;
260
+ let info2 = acc2. unwrap ( ) ;
261
+ assert ! ( info2 == acc_info2( ) ) ;
262
+
263
+ assert ! ( batch_diff. storage_slots. contains_key( & account1( ) ) ) ;
264
+ }
265
+
266
+ #[ test]
267
+ fn multiple_slot_writes ( ) {
268
+ let mut batch_diff = BatchStateDiff :: new ( ) ;
269
+ batch_diff. apply ( test_state_diff_acc1 ( ) ) ;
270
+ batch_diff. apply ( test_state_diff_acc_old_slots1 ( ) ) ;
271
+
272
+ let acc1 = batch_diff
273
+ . accounts
274
+ . get ( & account1 ( ) )
275
+ . expect ( "account1 should be present" )
276
+ . clone ( ) ;
277
+ let info1 = acc1. unwrap ( ) ;
278
+ let expected_info = AccountInfo {
279
+ nonce : 2 ,
280
+ balance : U256 :: from ( 9 ) ,
281
+ code_hash : KECCAK_EMPTY ,
282
+ code : None ,
283
+ } ;
284
+ assert ! ( info1 == expected_info) ;
285
+ // Slots were reverted to initial values, so the map should be empty.
286
+ assert ! ( batch_diff. storage_slots. is_empty( ) ) ;
287
+
288
+ // Apply slots again and check it was recorded.
289
+ batch_diff. apply ( test_state_diff_acc1 ( ) ) ;
290
+ assert ! (
291
+ batch_diff
292
+ . storage_slots
293
+ . get( & account1( ) )
294
+ . unwrap( )
295
+ . get( & slot1( ) )
296
+ . unwrap( )
297
+ == & slot_changes( ) . get( & slot1( ) ) . unwrap( ) . present_value( )
298
+ )
299
+ }
300
+
301
+ #[ test]
302
+ fn smart_contract_diff ( ) {
303
+ let mut test_diff = BlockStateDiff {
304
+ state : HashMap :: default ( ) ,
305
+ contracts : HashMap :: default ( ) ,
306
+ } ;
307
+ test_diff. contracts . insert (
308
+ FixedBytes :: default ( ) ,
309
+ revm_primitives:: Bytecode :: LegacyRaw ( b"123" . into ( ) ) ,
310
+ ) ;
311
+
312
+ let mut batch_diff = BatchStateDiff :: new ( ) ;
313
+ batch_diff. apply ( test_diff) ;
314
+ assert ! ( !batch_diff. contracts. is_empty( ) )
315
+ }
102
316
}
0 commit comments