66package solidity
77
88import (
9- "reflect"
10-
119 "github.com/ethereum/go-ethereum/rlp"
1210
1311 "github.com/vechain/thor/v2/thor"
@@ -19,44 +17,99 @@ type Key interface {
1917
2018// Mapping is a key/value storage abstraction for built-in contracts, similar to the mapping in Solidity.
2119// It DOES NOT (TBD) allow for direct access to values if declared in the same `pos` in the built-in contract.
22- type Mapping [K Key , V any ] struct {
20+ //
21+ // Mapping behavior:
22+ //
23+ // - Setting the default type value means removing the value from storage
24+ //
25+ // - If Mapping[thor.Bytes32, *thor.Address]:
26+ // ex: SET (k:0x123, v:nil) -> stores nil
27+ // ex: SET (k:0x123, v:&thor.Address{}) -> stores nil
28+ //
29+ // - If Mapping[thor.Bytes32, thor.Address]:
30+ // ex: SET (k:0x123, v:thor.Address{}) -> stores nil (default empty value)
31+ //
32+ // - Getting a nil storage value will always return default value of V not an empty pointer of the instance
33+ //
34+ // - If Mapping[thor.Bytes32, *thor.Address]:
35+ // ex: SET (k:0x123, v:nil) GET (k:0x123)-> returns nil
36+ // ex: SET (k:0x123, v:&thor.Address{}) GET (k:0x123)-> returns nil
37+ //
38+ // - If Mapping[thor.Bytes32, thor.Address]:
39+ // ex: SET (k:0x123, v:nil) GET (k:0x123)-> returns thor.Address{} (default empty value)
40+ // ex: SET (k:0x123, v:thor.Address{}) GET (k:0x123)-> returns thor.Address{} (default empty value)
41+ //
42+ // - Getting a non-existing storage will always return the default value of the type defined in the V comparable mapping
43+ type Mapping [K Key , V comparable ] struct {
2344 context * Context
2445 basePos thor.Bytes32
2546}
2647
27- func NewMapping [K Key , V any ](context * Context , pos thor.Bytes32 ) * Mapping [K , V ] {
48+ // NewMapping creates a new persistent mapping at the given storage position.
49+ func NewMapping [K Key , V comparable ](context * Context , pos thor.Bytes32 ) * Mapping [K , V ] {
2850 return & Mapping [K , V ]{context : context , basePos : pos }
2951}
3052
31- func (m * Mapping [K , V ]) Get (key K ) (value V , err error ) {
53+ // Get retrieves the value for the given key, charging SloadGas per 32-byte word.
54+ // If no value is present, returns the zero-value of V.
55+ func (m * Mapping [K , V ]) Get (key K ) (V , error ) {
56+ // compute the storage slot from key + base position
3257 position := thor .Blake2b (key .Bytes (), m .basePos .Bytes ())
33- err = m .context .state .DecodeStorage (m .context .address , position , func (raw []byte ) error {
34- if reflect .ValueOf (value ).Kind () == reflect .Ptr {
35- value = reflect .New (reflect .TypeOf (value ).Elem ()).Interface ().(V )
36- }
37- if len (raw ) == 0 {
38- return nil
39- }
40- slots := (uint64 (len (raw )) + 31 ) / 32
41- m .context .UseGas (slots * thor .SloadGas )
42- return rlp .DecodeBytes (raw , & value )
43- })
44- return
58+
59+ // prepare a zero-value container for decoding
60+ var value V
61+
62+ // attempt to decode storage into value
63+ err := m .context .state .DecodeStorage (
64+ m .context .address , position , func (raw []byte ) error {
65+ if len (raw ) == 0 {
66+ // no data, leave value as zero
67+ return nil
68+ }
69+
70+ // charge gas per 32-byte word
71+ slots := (uint64 (len (raw )) + 31 ) / 32
72+ m .context .UseGas (slots * thor .SloadGas )
73+
74+ // decode RLP in-place
75+ return rlp .DecodeBytes (raw , & value )
76+ })
77+ if err != nil {
78+ // return zero type value
79+ var zero V
80+ return zero , err
81+ }
82+
83+ return value , nil
4584}
4685
86+ // Set stores the given value at key, charging Sstore gas per 32-byte word.
87+ // Passing the zero-value of V clears the storage slot.
4788func (m * Mapping [K , V ]) Set (key K , value V , newValue bool ) error {
4889 position := thor .Blake2b (key .Bytes (), m .basePos .Bytes ())
90+
91+ // do not RLP-encode nil values, instead set raw storage to nil
92+ var zero V
93+ if value == zero {
94+ m .context .state .SetRawStorage (m .context .address , position , nil )
95+ return nil
96+ }
97+
98+ // encode and store
4999 return m .context .state .EncodeStorage (m .context .address , position , func () ([]byte , error ) {
50- val , err := rlp .EncodeToBytes (value )
100+ // encode via rlp library's internal pooling
101+ buf , err := rlp .EncodeToBytes (value )
51102 if err != nil {
52103 return nil , err
53104 }
54- slots := (uint64 (len (val )) + 31 ) / 32
105+
106+ // charge gas per 32-byte word
107+ slots := (uint64 (len (buf )) + 31 ) / 32
55108 if newValue {
56109 m .context .UseGas (slots * thor .SstoreSetGas )
57110 } else {
58111 m .context .UseGas (slots * thor .SstoreResetGas )
59112 }
60- return rlp . EncodeToBytes ( value )
113+ return buf , nil
61114 })
62115}
0 commit comments