@@ -699,6 +699,120 @@ func TestRevert(t *testing.T) {
699699 })
700700}
701701
702+ func TestStateUpdateWritesHistoryOnlyOnFieldChange (t * testing.T ) {
703+ addr := felt.NewFromUint64 [felt.Felt ](16 )
704+ classH0 := felt.NewFromUint64 [felt.Felt ](192 )
705+ classH1 := felt.NewFromUint64 [felt.Felt ](193 )
706+ storageKey := felt.NewFromUint64 [felt.Felt ](16 )
707+ val1 := felt.NewFromUint64 [felt.Felt ](66 )
708+ val2 := felt.NewFromUint64 [felt.Felt ](67 )
709+ nonce1 := felt.NewFromUint64 [felt.Felt ](1 )
710+ nonce2 := felt.NewFromUint64 [felt.Felt ](2 )
711+
712+ updates := []* core.StateUpdate {
713+ // block 0: deploy A with classHash H0 — classHash entry expected
714+ {
715+ OldRoot : & felt .Zero ,
716+ NewRoot : felt.NewUnsafeFromString [felt.Felt ](
717+ "0x15fc57d3df218117c4baa5b894357bfe195a27874f52efee3914d8594756d6c" ,
718+ ),
719+ StateDiff : & core.StateDiff {
720+ DeployedContracts : map [felt.Felt ]* felt.Felt {* addr : classH0 },
721+ },
722+ },
723+ // block 1: nonce change only — nonce entry expected
724+ {
725+ OldRoot : & felt .Zero ,
726+ NewRoot : felt.NewUnsafeFromString [felt.Felt ](
727+ "0x19be1c130f862c06576c14163ffd597ef49de2dd3eed0cd0329fc24a972f40d" ,
728+ ),
729+ StateDiff : & core.StateDiff {
730+ Nonces : map [felt.Felt ]* felt.Felt {* addr : nonce1 },
731+ },
732+ },
733+ // block 2: storage only — must NOT produce nonce or classHash entries
734+ {
735+ OldRoot : & felt .Zero ,
736+ NewRoot : felt.NewUnsafeFromString [felt.Felt ](
737+ "0x7b5f15dda7999552e8af72b55965c0f033c77f0e2d3b54a448ebbfdec22d7c4" ,
738+ ),
739+ StateDiff : & core.StateDiff {
740+ StorageDiffs : map [felt.Felt ]map [felt.Felt ]* felt.Felt {
741+ * addr : {* storageKey : val1 },
742+ },
743+ },
744+ },
745+ // block 3: nonce change only — nonce entry expected
746+ {
747+ OldRoot : & felt .Zero ,
748+ NewRoot : felt.NewUnsafeFromString [felt.Felt ](
749+ "0xa6e0bf8b99f39da84226ab00e8ede81133f60c72a86215f8c62eb2e9458d95" ,
750+ ),
751+ StateDiff : & core.StateDiff {
752+ Nonces : map [felt.Felt ]* felt.Felt {* addr : nonce2 },
753+ },
754+ },
755+ // block 4: classHash replacement — classHash entry expected
756+ {
757+ OldRoot : & felt .Zero ,
758+ NewRoot : felt.NewUnsafeFromString [felt.Felt ](
759+ "0x66e9a93ed05f4561da6527bcb1c98c1a6724a585c18167711291ef6e56d3875" ,
760+ ),
761+ StateDiff : & core.StateDiff {
762+ ReplacedClasses : map [felt.Felt ]* felt.Felt {* addr : classH1 },
763+ },
764+ },
765+ // block 5: storage only — must NOT produce nonce or classHash entries
766+ {
767+ OldRoot : & felt .Zero ,
768+ NewRoot : felt.NewUnsafeFromString [felt.Felt ](
769+ "0x4829eceed3f9be83ece51f354dbbb028172410e17ea7c4d5e98ad7c0e475e2a" ,
770+ ),
771+ StateDiff : & core.StateDiff {
772+ StorageDiffs : map [felt.Felt ]map [felt.Felt ]* felt.Felt {
773+ * addr : {* storageKey : val2 },
774+ },
775+ },
776+ },
777+ }
778+
779+ stateDB := setupState (t , updates , uint64 (len (updates )))
780+
781+ t .Run ("total entry counts match exact field-change count" , func (t * testing.T ) {
782+ assert .Equal (t , 2 , countNonceHistory (t , stateDB , addr ),
783+ "contract should have exactly 2 nonce history entries (blocks 1, 3)" )
784+ assert .Equal (t , 2 , countClassHashHistory (t , stateDB , addr ),
785+ "contract should have exactly 2 classHash history entries (blocks 0, 4)" )
786+ assert .Equal (t , 2 , countStorageHistory (t , stateDB , addr , storageKey ),
787+ "storage key should have exactly 2 history entries (blocks 2, 5)" )
788+ })
789+
790+ t .Run ("entries exist on the change blocks" , func (t * testing.T ) {
791+ assert .True (t , hasNonceHistory (t , stateDB , addr , 1 ))
792+ assert .True (t , hasNonceHistory (t , stateDB , addr , 3 ))
793+ assert .True (t , hasClassHashHistory (t , stateDB , addr , 0 ))
794+ assert .True (t , hasClassHashHistory (t , stateDB , addr , 4 ))
795+ assert .True (t , hasStorageHistory (t , stateDB , addr , storageKey , 2 ))
796+ assert .True (t , hasStorageHistory (t , stateDB , addr , storageKey , 5 ))
797+ })
798+
799+ t .Run ("no nonce / classHash entries on storage-only blocks" , func (t * testing.T ) {
800+ assert .False (t , hasNonceHistory (t , stateDB , addr , 2 ),
801+ "storage-only block must not write nonce history" )
802+ assert .False (t , hasNonceHistory (t , stateDB , addr , 5 ),
803+ "storage-only block must not write nonce history" )
804+ assert .False (t , hasClassHashHistory (t , stateDB , addr , 2 ),
805+ "storage-only block must not write classHash history" )
806+ assert .False (t , hasClassHashHistory (t , stateDB , addr , 5 ),
807+ "storage-only block must not write classHash history" )
808+ })
809+
810+ t .Run ("no nonce entry at deployment block (initial nonce is zero by default)" , func (t * testing.T ) {
811+ assert .False (t , hasNonceHistory (t , stateDB , addr , 0 ),
812+ "deployment block must not write a redundant zero-nonce history entry" )
813+ })
814+ }
815+
702816func BenchmarkStateUpdate (b * testing.B ) {
703817 client := feeder .NewTestClient (b , & networks .Mainnet )
704818 gw := adaptfeeder .New (client )
@@ -780,3 +894,51 @@ func newTestStateDB() *StateDB {
780894 trieDB := triedb .New (memDB , nil )
781895 return NewStateDB (memDB , trieDB )
782896}
897+
898+ func hasNonceHistory (t * testing.T , stateDB * StateDB , addr * felt.Felt , blockNum uint64 ) bool {
899+ t .Helper ()
900+ ok , err := stateDB .disk .Has (db .ContractNonceHistoryAtBlockKey (addr , blockNum ))
901+ require .NoError (t , err )
902+ return ok
903+ }
904+
905+ func hasClassHashHistory (t * testing.T , stateDB * StateDB , addr * felt.Felt , blockNum uint64 ) bool {
906+ t .Helper ()
907+ ok , err := stateDB .disk .Has (db .ContractClassHashHistoryAtBlockKey (addr , blockNum ))
908+ require .NoError (t , err )
909+ return ok
910+ }
911+
912+ func hasStorageHistory (t * testing.T , stateDB * StateDB , addr , key * felt.Felt , blockNum uint64 ) bool {
913+ t .Helper ()
914+ ok , err := stateDB .disk .Has (db .ContractStorageHistoryAtBlockKey (addr , key , blockNum ))
915+ require .NoError (t , err )
916+ return ok
917+ }
918+
919+ func countByPrefix (t * testing.T , stateDB * StateDB , prefix []byte ) int {
920+ t .Helper ()
921+ it , err := stateDB .disk .NewIterator (prefix , true )
922+ require .NoError (t , err )
923+ defer it .Close ()
924+ count := 0
925+ for it .First (); it .Valid (); it .Next () {
926+ count ++
927+ }
928+ return count
929+ }
930+
931+ func countNonceHistory (t * testing.T , stateDB * StateDB , addr * felt.Felt ) int {
932+ t .Helper ()
933+ return countByPrefix (t , stateDB , db .ContractNonceHistoryKey (addr ))
934+ }
935+
936+ func countClassHashHistory (t * testing.T , stateDB * StateDB , addr * felt.Felt ) int {
937+ t .Helper ()
938+ return countByPrefix (t , stateDB , db .ContractClassHashHistoryKey (addr ))
939+ }
940+
941+ func countStorageHistory (t * testing.T , stateDB * StateDB , addr , key * felt.Felt ) int {
942+ t .Helper ()
943+ return countByPrefix (t , stateDB , db .ContractStorageHistoryKey (addr , key ))
944+ }
0 commit comments