@@ -142,6 +142,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
142142 delete (s .stateObjects , obj .address )
143143 }
144144 }
145+ // Invalidate journal because reverting across transactions is not allowed.
146+ s .clearJournalAndRefund ()
145147}
146148
147149// New creates a new state from a given trie.
@@ -331,6 +333,25 @@ func (s *StateDB) GetStateAndCommittedState(addr common.Address, hash common.Has
331333 return common.Hash {}, common.Hash {}
332334}
333335
336+ // SetStateOverride installs the provided storage value as part of the base state used by simulations.
337+ func (s * StateDB ) SetStateOverride (addr common.Address , key , value common.Hash ) {
338+ stateObject := s .getOrNewStateObject (addr )
339+ if stateObject != nil {
340+ stateObject .SetStateOverride (key , value )
341+ }
342+ }
343+
344+ func (s * StateDB ) clearJournalAndRefund () {
345+ if s .journal == nil {
346+ s .journal = newJournal ()
347+ } else {
348+ s .journal .reset ()
349+ }
350+ s .validRevisions = nil
351+ s .nextRevisionID = 0
352+ s .refund = 0
353+ }
354+
334355// GetRefund returns the current value of the refund counter.
335356func (s * StateDB ) GetRefund () uint64 {
336357 return s .refund
@@ -697,19 +718,19 @@ func (s *StateDB) Commit() error {
697718 if s .writeCache != nil {
698719 s .writeCache ()
699720 }
700- return s .commitWithCtx (s .ctx )
721+ return s .commitWithCtx (s .ctx , false )
701722}
702723
703724// CommitWithCacheCtx writes the dirty states to keeper using the cacheCtx.
704725// This function is used before any precompile call to make sure the cacheCtx
705726// is updated with the latest changes within the tx (StateDB's journal entries).
706727func (s * StateDB ) CommitWithCacheCtx () error {
707- return s .commitWithCtx (s .cacheCtx )
728+ return s .commitWithCtx (s .cacheCtx , true )
708729}
709730
710731// commitWithCtx writes the dirty states to keeper
711732// using the provided context
712- func (s * StateDB ) commitWithCtx (ctx sdk.Context ) error {
733+ func (s * StateDB ) commitWithCtx (ctx sdk.Context , keepDirty bool ) error {
713734 for _ , addr := range s .journal .sortedDirties () {
714735 obj := s .stateObjects [addr ]
715736 if obj .selfDestructed {
@@ -735,6 +756,32 @@ func (s *StateDB) commitWithCtx(ctx sdk.Context) error {
735756 } else {
736757 s .keeper .SetState (ctx , obj .Address (), key , valueBytes )
737758 }
759+
760+ // Track the persisted value as the new baseline so later writes compare
761+ // against the most recently flushed state. In go-ethereum the same happens
762+ // during commitStorage: originStorage follows the latest flush and act as
763+ // the reference for future dirty detection. The actual revert path still
764+ // consults the journal's origvalue, so keeping this cache in sync during
765+ // cacheCtx flushes is safe even if a precompile subsequently reverts.
766+ if obj .originStorage == nil {
767+ obj .originStorage = make (Storage )
768+ }
769+ if len (valueBytes ) == 0 {
770+ delete (obj .originStorage , key )
771+ } else {
772+ obj .originStorage [key ] = obj .dirtyStorage [key ]
773+ }
774+ // Only the final Commit against the root context should clear dirtyStorage.
775+ // During precompile execution we pass keepDirty=true so that the slots remain
776+ // marked dirty after cacheCtx flushes, letting the outer transaction still
777+ // persist those writes (or revert them via the journal) once execution finishes.
778+ //
779+ // This is essential after SetState started syncing originStorage: the cacheCtx
780+ // flush must leave the dirty slots intact so the final Commit() can push the
781+ // same updates into the keeper context.
782+ if ! keepDirty {
783+ delete (obj .dirtyStorage , key )
784+ }
738785 }
739786 }
740787 }
0 commit comments