core/types/bal: deep-copy borrowed storage map in Merge

When Merge encountered an address that was absent from the receiver it
assigned the source's inner StorageAccessList map by reference, so both
lists ended up sharing the same map. Any subsequent AddState on either
side would leak into the other.

Clone the borrowed map on adoption to keep the two lists independent,
mirroring the deep-copy discipline used by accessList, accessEvents and
the newly added StateAccessList.Copy.

The method currently has no production caller but is part of the public
surface being prepared for BAL construction, so trap the bug before it
wires up.

Add a small regression test that fails on the previous code.
This commit is contained in:
rayoo 2026-04-25 15:58:44 +08:00
parent b70d9a4b8e
commit 88866b2a4c
2 changed files with 27 additions and 1 deletions

View file

@ -72,7 +72,9 @@ func (s *StateAccessList) Merge(other *StateAccessList) {
for addr, otherSlots := range other.list {
slots, exists := s.list[addr]
if !exists {
s.list[addr] = otherSlots
// Clone the borrowed map so later mutations on either list
// don't leak into the other.
s.list[addr] = maps.Clone(otherSlots)
continue
}
maps.Copy(slots, otherSlots)

View file

@ -252,3 +252,27 @@ func TestBlockAccessListValidation(t *testing.T) {
t.Fatalf("Unexpected validation error: %v", err)
}
}
// TestStateAccessListMergeIsolation verifies that StateAccessList.Merge does
// not alias the source's inner storage map into the receiver. Regression test
// for a bug where mutations to a merged list leaked back into the source.
func TestStateAccessListMergeIsolation(t *testing.T) {
addr := common.BytesToAddress([]byte{0x01})
slotA := common.BytesToHash([]byte{0x0A})
slotB := common.BytesToHash([]byte{0x0B})
src := NewStateAccessList()
src.AddState(addr, slotA)
dst := NewStateAccessList()
dst.Merge(src)
// Mutate only the receiver. Before the fix this also mutated src because
// Merge assigned src's inner map by reference when the receiver lacked
// the address.
dst.AddState(addr, slotB)
if _, leaked := src.list[addr][slotB]; leaked {
t.Fatalf("Merge aliased source map: slotB leaked into src after dst-only mutation")
}
}