Skip to content

Commit 81d1e20

Browse files
test(state): add unit tests for state accessors (#3752)
1 parent a4c193e commit 81d1e20

1 file changed

Lines changed: 320 additions & 0 deletions

File tree

core/state/accessors_test.go

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
// Package state_test exercises the state package via its exported accessors.
2+
package state_test
3+
4+
import (
5+
"testing"
6+
7+
"github.com/NethermindEth/juno/core"
8+
"github.com/NethermindEth/juno/core/felt"
9+
"github.com/NethermindEth/juno/core/state"
10+
"github.com/NethermindEth/juno/core/trie2/triedb"
11+
"github.com/NethermindEth/juno/db"
12+
"github.com/NethermindEth/juno/db/memory"
13+
_ "github.com/NethermindEth/juno/encoder/registry"
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestContractAccessors(t *testing.T) {
19+
addr := felt.NewUnsafeFromString[felt.Felt]("0x123")
20+
21+
// storeContract stores a contract with the given values and fails the test on error.
22+
storeContract := func(
23+
t *testing.T, disk db.KeyValueWriter, nonce, classHash string, height uint64,
24+
) {
25+
t.Helper()
26+
require.NoError(t, state.WriteContract(
27+
disk, addr,
28+
felt.UnsafeFromString[felt.Felt](nonce),
29+
felt.UnsafeFromString[felt.Felt](classHash),
30+
height,
31+
))
32+
}
33+
34+
t.Run("absent before write", func(t *testing.T) {
35+
disk := memory.New()
36+
37+
exists, err := state.HasContract(disk, addr)
38+
require.NoError(t, err)
39+
assert.False(t, exists)
40+
41+
_, err = state.GetContract(disk, addr)
42+
assert.ErrorIs(t, err, db.ErrKeyNotFound)
43+
})
44+
45+
t.Run("write then read round-trip", func(t *testing.T) {
46+
disk := memory.New()
47+
storeContract(t, disk, "0x1", "0xabc", 42)
48+
49+
exists, err := state.HasContract(disk, addr)
50+
require.NoError(t, err)
51+
assert.True(t, exists)
52+
53+
contract, err := state.GetContract(disk, addr)
54+
require.NoError(t, err)
55+
assert.Equal(t, felt.UnsafeFromString[felt.Felt]("0x1"), contract.Nonce)
56+
assert.Equal(t, felt.UnsafeFromString[felt.Felt]("0xabc"), contract.ClassHash)
57+
assert.Equal(t, uint64(42), contract.DeployedHeight)
58+
})
59+
60+
t.Run("overwrite reflects latest values", func(t *testing.T) {
61+
disk := memory.New()
62+
storeContract(t, disk, "0x1", "0xabc", 42)
63+
storeContract(t, disk, "0x2", "0xdef", 99)
64+
65+
contract, err := state.GetContract(disk, addr)
66+
require.NoError(t, err)
67+
assert.Equal(t, felt.UnsafeFromString[felt.Felt]("0x2"), contract.Nonce)
68+
assert.Equal(t, felt.UnsafeFromString[felt.Felt]("0xdef"), contract.ClassHash)
69+
assert.Equal(t, uint64(99), contract.DeployedHeight)
70+
})
71+
72+
t.Run("delete removes the contract", func(t *testing.T) {
73+
disk := memory.New()
74+
storeContract(t, disk, "0x1", "0xabc", 42)
75+
76+
require.NoError(t, state.DeleteContract(disk, addr))
77+
78+
exists, err := state.HasContract(disk, addr)
79+
require.NoError(t, err)
80+
assert.False(t, exists)
81+
82+
_, err = state.GetContract(disk, addr)
83+
assert.ErrorIs(t, err, db.ErrKeyNotFound)
84+
})
85+
}
86+
87+
func TestClassAccessors(t *testing.T) {
88+
classHash := felt.NewUnsafeFromString[felt.Felt]("0xabc")
89+
newClass := func() *core.DeclaredClassDefinition {
90+
return &core.DeclaredClassDefinition{
91+
At: 12,
92+
Class: &core.SierraClass{
93+
SemanticVersion: "0.1.0",
94+
Program: []*felt.Felt{felt.NewUnsafeFromString[felt.Felt]("0x1")},
95+
},
96+
}
97+
}
98+
99+
t.Run("absent before write", func(t *testing.T) {
100+
disk := memory.New()
101+
102+
exists, err := state.HasClass(disk, classHash)
103+
require.NoError(t, err)
104+
assert.False(t, exists)
105+
106+
_, err = state.GetClass(disk, classHash)
107+
assert.ErrorIs(t, err, db.ErrKeyNotFound)
108+
})
109+
110+
t.Run("write then read round-trips the encoded class", func(t *testing.T) {
111+
disk := memory.New()
112+
class := newClass()
113+
require.NoError(t, state.WriteClass(disk, classHash, class))
114+
115+
exists, err := state.HasClass(disk, classHash)
116+
require.NoError(t, err)
117+
assert.True(t, exists)
118+
119+
got, err := state.GetClass(disk, classHash)
120+
require.NoError(t, err)
121+
assert.Equal(t, class.At, got.At)
122+
sierra, ok := got.Class.(*core.SierraClass)
123+
require.True(t, ok)
124+
assert.Equal(t, "0.1.0", sierra.SemanticVersion)
125+
assert.Equal(t, []*felt.Felt{felt.NewUnsafeFromString[felt.Felt]("0x1")}, sierra.Program)
126+
})
127+
128+
t.Run("overwrite reflects latest values", func(t *testing.T) {
129+
disk := memory.New()
130+
require.NoError(t, state.WriteClass(disk, classHash, newClass()))
131+
132+
updated := &core.DeclaredClassDefinition{
133+
At: 99,
134+
Class: &core.SierraClass{
135+
SemanticVersion: "0.2.0",
136+
Program: []*felt.Felt{felt.NewUnsafeFromString[felt.Felt]("0x2")},
137+
},
138+
}
139+
require.NoError(t, state.WriteClass(disk, classHash, updated))
140+
141+
got, err := state.GetClass(disk, classHash)
142+
require.NoError(t, err)
143+
assert.Equal(t, updated.At, got.At)
144+
sierra, ok := got.Class.(*core.SierraClass)
145+
require.True(t, ok)
146+
assert.Equal(t, "0.2.0", sierra.SemanticVersion)
147+
assert.Equal(t, []*felt.Felt{felt.NewUnsafeFromString[felt.Felt]("0x2")}, sierra.Program)
148+
})
149+
150+
t.Run("delete removes the class", func(t *testing.T) {
151+
disk := memory.New()
152+
require.NoError(t, state.WriteClass(disk, classHash, newClass()))
153+
154+
require.NoError(t, state.DeleteClass(disk, classHash))
155+
156+
exists, err := state.HasClass(disk, classHash)
157+
require.NoError(t, err)
158+
assert.False(t, exists)
159+
160+
_, err = state.GetClass(disk, classHash)
161+
assert.ErrorIs(t, err, db.ErrKeyNotFound)
162+
})
163+
}
164+
165+
// Reads values back through the StateReader history readers, which use a different key path
166+
// than the accessors, so the round-trip tests behavior rather than the accessor's own keys.
167+
func TestHistoryAccessors(t *testing.T) {
168+
addr := felt.NewUnsafeFromString[felt.Felt]("0x123")
169+
storageKey := felt.NewUnsafeFromString[felt.Felt]("0x456")
170+
171+
type historyKind struct {
172+
name string
173+
write func(w db.KeyValueWriter, blockNum uint64, value *felt.Felt) error
174+
del func(w db.KeyValueWriter, blockNum uint64) error
175+
readAt func(r *state.StateReader, blockNum uint64) (felt.Felt, error)
176+
valueOne *felt.Felt
177+
valueTwo *felt.Felt
178+
}
179+
180+
kinds := []historyKind{
181+
{
182+
name: "nonce",
183+
write: func(w db.KeyValueWriter, blockNum uint64, value *felt.Felt) error {
184+
return state.WriteNonceHistory(w, addr, blockNum, value)
185+
},
186+
del: func(w db.KeyValueWriter, blockNum uint64) error {
187+
return state.DeleteNonceHistory(w, addr, blockNum)
188+
},
189+
readAt: func(r *state.StateReader, blockNum uint64) (felt.Felt, error) {
190+
return r.ContractNonceAt(addr, blockNum)
191+
},
192+
valueOne: felt.NewUnsafeFromString[felt.Felt]("0x7"),
193+
valueTwo: felt.NewUnsafeFromString[felt.Felt]("0x2a"),
194+
},
195+
{
196+
name: "class hash",
197+
write: func(w db.KeyValueWriter, blockNum uint64, value *felt.Felt) error {
198+
return state.WriteClassHashHistory(w, addr, blockNum, value)
199+
},
200+
del: func(w db.KeyValueWriter, blockNum uint64) error {
201+
return state.DeleteClassHashHistory(w, addr, blockNum)
202+
},
203+
readAt: func(r *state.StateReader, blockNum uint64) (felt.Felt, error) {
204+
return r.ContractClassHashAt(addr, blockNum)
205+
},
206+
valueOne: felt.NewUnsafeFromString[felt.Felt]("0xaaa"),
207+
valueTwo: felt.NewUnsafeFromString[felt.Felt]("0xbbb"),
208+
},
209+
{
210+
name: "storage",
211+
write: func(w db.KeyValueWriter, blockNum uint64, value *felt.Felt) error {
212+
return state.WriteStorageHistory(w, addr, storageKey, blockNum, value)
213+
},
214+
del: func(w db.KeyValueWriter, blockNum uint64) error {
215+
return state.DeleteStorageHistory(w, addr, storageKey, blockNum)
216+
},
217+
readAt: func(r *state.StateReader, blockNum uint64) (felt.Felt, error) {
218+
return r.ContractStorageAt(addr, storageKey, blockNum)
219+
},
220+
valueOne: felt.NewUnsafeFromString[felt.Felt]("0xdef"),
221+
valueTwo: felt.NewUnsafeFromString[felt.Felt]("0xfed"),
222+
},
223+
}
224+
225+
const blockOne, blockTwo = uint64(7), uint64(15)
226+
227+
for _, k := range kinds {
228+
t.Run(k.name, func(t *testing.T) {
229+
// newReader returns a fresh disk store and a reader over it, so each subtest
230+
// starts from an empty history and is independent of execution order.
231+
newReader := func(t *testing.T) (db.KeyValueStore, *state.StateReader) {
232+
t.Helper()
233+
disk := memory.New()
234+
stateDB := state.NewStateDB(disk, triedb.New(disk, nil))
235+
reader, err := state.NewStateReader(&felt.Zero, stateDB)
236+
require.NoError(t, err)
237+
return disk, reader
238+
}
239+
240+
t.Run("reads the written value at its block", func(t *testing.T) {
241+
disk, reader := newReader(t)
242+
require.NoError(t, k.write(disk, blockOne, k.valueOne))
243+
244+
got, err := k.readAt(reader, blockOne)
245+
require.NoError(t, err)
246+
assert.Equal(t, *k.valueOne, got)
247+
})
248+
249+
t.Run("steps back to the last entry for a later block", func(t *testing.T) {
250+
disk, reader := newReader(t)
251+
require.NoError(t, k.write(disk, blockOne, k.valueOne))
252+
253+
got, err := k.readAt(reader, blockOne+3)
254+
require.NoError(t, err)
255+
assert.Equal(t, *k.valueOne, got)
256+
})
257+
258+
t.Run("reads zero before the first entry", func(t *testing.T) {
259+
disk, reader := newReader(t)
260+
require.NoError(t, k.write(disk, blockOne, k.valueOne))
261+
262+
got, err := k.readAt(reader, blockOne-1)
263+
require.NoError(t, err)
264+
assert.Equal(t, felt.Zero, got)
265+
})
266+
267+
t.Run("honours block boundaries across two entries", func(t *testing.T) {
268+
disk, reader := newReader(t)
269+
require.NoError(t, k.write(disk, blockOne, k.valueOne))
270+
require.NoError(t, k.write(disk, blockTwo, k.valueTwo))
271+
272+
between, err := k.readAt(reader, blockTwo-1)
273+
require.NoError(t, err)
274+
assert.Equal(t, *k.valueOne, between)
275+
276+
after, err := k.readAt(reader, blockTwo)
277+
require.NoError(t, err)
278+
assert.Equal(t, *k.valueTwo, after)
279+
})
280+
281+
t.Run("delete drops the entry", func(t *testing.T) {
282+
disk, reader := newReader(t)
283+
require.NoError(t, k.write(disk, blockOne, k.valueOne))
284+
require.NoError(t, k.write(disk, blockTwo, k.valueTwo))
285+
286+
require.NoError(t, k.del(disk, blockOne))
287+
require.NoError(t, k.del(disk, blockTwo))
288+
289+
got, err := k.readAt(reader, blockTwo+1)
290+
require.NoError(t, err)
291+
assert.Equal(t, felt.Zero, got)
292+
})
293+
})
294+
}
295+
}
296+
297+
func TestGetStateObject(t *testing.T) {
298+
addr := felt.NewUnsafeFromString[felt.Felt]("0x123")
299+
300+
t.Run("propagates not-found for a missing contract", func(t *testing.T) {
301+
disk := memory.New()
302+
303+
_, err := state.GetStateObject(disk, nil, addr)
304+
assert.ErrorIs(t, err, db.ErrKeyNotFound)
305+
})
306+
307+
t.Run("returns a state object for an existing contract", func(t *testing.T) {
308+
disk := memory.New()
309+
require.NoError(t, state.WriteContract(
310+
disk, addr,
311+
felt.UnsafeFromString[felt.Felt]("0x1"),
312+
felt.UnsafeFromString[felt.Felt]("0xabc"),
313+
42,
314+
))
315+
316+
obj, err := state.GetStateObject(disk, nil, addr)
317+
require.NoError(t, err)
318+
assert.NotNil(t, obj)
319+
})
320+
}

0 commit comments

Comments
 (0)