@@ -200,6 +200,179 @@ pub struct Cheatcodes {
200200 /// Breakpoints supplied by the `breakpoint` cheatcode.
201201 /// `char -> (address, pc)`
202202 pub breakpoints : Breakpoints ,
203+
204+ /// Track if cool cheatcode was called on each address
205+ pub addresses : HashMap < Address , AddressState > ,
206+ /// Track if an addresses storage slot is cool or not
207+ pub address_storage : HashMap < Address , HashMap < U256 , StorageSlotState > > ,
208+ /// How much gas to charge in the next step (op code) based on cool cheatcode calculations
209+ pub additional_gas_next_op : u64 ,
210+ }
211+
212+ /// Whether an Address is accessed or not
213+ #[ derive( Clone , PartialEq , Debug ) ]
214+ pub enum AddressState {
215+ /// if already accessed, then charge WARM_STORAGE_READ_COST (100)
216+ Warm ,
217+ /// charge COLD_ACCOUNT_ACCESS_COST (2600)
218+ Cool ,
219+ }
220+
221+ /// Whether a Storage Slot is warm or already been modified
222+ #[ derive( Clone , PartialEq , Debug ) ]
223+ pub enum StorageSlotState {
224+ /// charge extra based on SSTORE calculations
225+ WarmWithSLOAD ,
226+ /// if SSTORE already happened, don't charge extra
227+ WarmWithSSTORE ,
228+ /// same as if empty
229+ Cool ,
230+ }
231+
232+ /// Function to charge extra gas per opcode based on cool cheatcode
233+ fn add_gas_from_cool_cheatcode < DB : DatabaseExt > (
234+ state : & mut Cheatcodes ,
235+ interpreter : & mut Interpreter ,
236+ data : & mut EVMData < ' _ , DB > ,
237+ ) -> InstructionResult {
238+ if state. addresses . len ( ) == 0 {
239+ return InstructionResult :: Continue
240+ }
241+
242+ // For gas costs, see https://eips.ethereum.org/EIPS/eip-2200, https://eips.ethereum.org/EIPS/eip-2929
243+
244+ // if previous step added gas, add it once
245+ // note that all the opcodes will already have a cost (usually 100)
246+ // so adding until it hits the gas expected for a cold key/address
247+ if state. additional_gas_next_op > 0 {
248+ interpreter. gas . record_cost ( state. additional_gas_next_op ) ;
249+ state. additional_gas_next_op = 0 ;
250+ }
251+
252+ // if cool cheatcode was ever called on this address
253+ let contract_address = interpreter. contract ( ) . address ;
254+
255+ if state. addresses . get ( & contract_address) . is_some ( ) {
256+ // check target itself
257+ match interpreter. current_opcode ( ) {
258+ // via AccessListTracer
259+ opcode:: EXTCODECOPY |
260+ opcode:: EXTCODEHASH |
261+ opcode:: EXTCODESIZE |
262+ opcode:: BALANCE |
263+ opcode:: SELFDESTRUCT => {
264+ // address is first parameter
265+ if let Ok ( slot) = interpreter. stack ( ) . peek ( 0 ) {
266+ let addr: Address = Address :: from_word ( slot. into ( ) ) ;
267+
268+ // COLD_ACCOUNT_ACCESS_COST is 2600
269+ // check this is done once per address, unless cheatcode is called again
270+ // ignore if same as contract address
271+ if state. addresses . get ( & addr) == Some ( & AddressState :: Cool ) {
272+ state. additional_gas_next_op = 2500 ;
273+ state. addresses . insert ( addr, AddressState :: Warm ) ;
274+ }
275+ }
276+ }
277+ // via AccessListTracer
278+ opcode:: DELEGATECALL | opcode:: CALL | opcode:: STATICCALL | opcode:: CALLCODE => {
279+ // address is second parameter
280+ if let Ok ( slot) = interpreter. stack ( ) . peek ( 1 ) {
281+ let addr: Address = Address :: from_word ( slot. into ( ) ) ;
282+
283+ // COLD_ACCOUNT_ACCESS_COST is 2600
284+ // check this is done once per address, unless cheatcode is called again
285+ // ignore if same as contract address
286+ if state. addresses . get ( & addr) == Some ( & AddressState :: Cool ) {
287+ state. additional_gas_next_op = 2500 ;
288+ state. addresses . insert ( addr, AddressState :: Warm ) ;
289+ }
290+ }
291+ }
292+ _ => { }
293+ }
294+ // check target's slots
295+ if let Some ( contract_storage) = state. address_storage . get_mut ( & contract_address) {
296+ match interpreter. current_opcode ( ) {
297+ opcode:: SLOAD => {
298+ let key = try_or_continue ! ( interpreter. stack( ) . peek( 0 ) ) ;
299+
300+ let account = data. journaled_state . state ( ) . get ( & contract_address) . unwrap ( ) ;
301+ if account. storage . get ( & key) . is_some ( ) {
302+ match contract_storage. get ( & key) {
303+ None | Some ( StorageSlotState :: Cool ) => {
304+ // COLD_SLOAD_COST is 2100
305+ state. additional_gas_next_op = 2000 ;
306+ contract_storage. insert ( key, StorageSlotState :: WarmWithSLOAD ) ;
307+ }
308+ Some ( _) => { }
309+ }
310+ } else {
311+ contract_storage. insert ( key, StorageSlotState :: WarmWithSLOAD ) ;
312+ }
313+ }
314+ opcode:: SSTORE => {
315+ let key = try_or_continue ! ( interpreter. stack( ) . peek( 0 ) ) ;
316+ let val = try_or_continue ! ( interpreter. stack( ) . peek( 1 ) ) ;
317+
318+ let account = data. journaled_state . state ( ) . get ( & contract_address) . unwrap ( ) ;
319+ if account. storage . get ( & key) . is_some ( ) {
320+ // only add gas the first time the storage is touched again
321+ match contract_storage. get ( & key) {
322+ Some ( StorageSlotState :: WarmWithSLOAD ) => {
323+ // cool keeps the slot value changes
324+ // as if the previous_or_original_value = present_value`
325+ // so include the extra gas
326+ let slot = account. storage . get ( & key) . unwrap ( ) ;
327+ if val != slot. present_value &&
328+ slot. present_value != slot. previous_or_original_value
329+ {
330+ if slot. present_value == U256 :: ZERO {
331+ // SSTORE_SET_GAS is 20000
332+ state. additional_gas_next_op += 20000 - 100
333+ } else {
334+ // SSTORE_RESET_GAS is 5000 - COLD_SLOAD_COST (2100)
335+ state. additional_gas_next_op += 2900 - 100
336+ }
337+ }
338+
339+ // set slot is_warm to true
340+ contract_storage. insert ( key, StorageSlotState :: WarmWithSSTORE ) ;
341+ }
342+ None | Some ( StorageSlotState :: Cool ) => {
343+ // Means SSTORE was called without SLOAD before
344+ // COLD_SLOAD_COST is 2100
345+ state. additional_gas_next_op = 2100 ;
346+
347+ // cool keeps the slot value changes
348+ // as if the previous_or_original_value = present_value`
349+ // so include the extra gas
350+ let slot = account. storage . get ( & key) . unwrap ( ) ;
351+ if val != slot. present_value &&
352+ slot. present_value != slot. previous_or_original_value
353+ {
354+ if slot. present_value == U256 :: ZERO {
355+ // SSTORE_SET_GAS is 20000
356+ state. additional_gas_next_op += 20000 - 100
357+ } else {
358+ // SSTORE_RESET_GAS is 5000 - COLD_SLOAD_COST (2100)
359+ state. additional_gas_next_op += 2900 - 100
360+ }
361+ }
362+ contract_storage. insert ( key, StorageSlotState :: WarmWithSSTORE ) ;
363+ }
364+ Some ( StorageSlotState :: WarmWithSSTORE ) => { }
365+ }
366+ } else {
367+ contract_storage. insert ( key, StorageSlotState :: WarmWithSSTORE ) ;
368+ }
369+ }
370+ _ => { }
371+ }
372+ }
373+ }
374+
375+ InstructionResult :: Continue
203376}
204377
205378impl Cheatcodes {
@@ -523,6 +696,16 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
523696 InstructionResult :: Continue
524697 }
525698
699+ fn step_end (
700+ & mut self ,
701+ interpreter : & mut Interpreter ,
702+ data : & mut EVMData < ' _ , DB > ,
703+ eval : InstructionResult ,
704+ ) -> InstructionResult {
705+ add_gas_from_cool_cheatcode ( self , interpreter, data) ;
706+ eval
707+ }
708+
526709 fn log ( & mut self , _: & mut EVMData < ' _ , DB > , address : & Address , topics : & [ B256 ] , data : & Bytes ) {
527710 if !self . expected_emits . is_empty ( ) {
528711 expect:: handle_expect_emit ( self , address, topics, data) ;
0 commit comments