-
Notifications
You must be signed in to change notification settings - Fork 158
/
contract.go
209 lines (177 loc) · 5.49 KB
/
contract.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package core
import (
"errors"
"github.com/NethermindEth/juno/core/crypto"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/juno/core/trie"
"github.com/NethermindEth/juno/db"
)
// contract storage has fixed height at 251
const ContractStorageTrieHeight = 251
var (
ErrContractNotDeployed = errors.New("contract not deployed")
ErrContractAlreadyDeployed = errors.New("contract already deployed")
)
// NewContractUpdater creates an updater for the contract instance at the given address.
// Deploy should be called for contracts that were just deployed to the network.
func NewContractUpdater(addr *felt.Felt, txn db.Transaction) (*ContractUpdater, error) {
contractDeployed, err := deployed(addr, txn)
if err != nil {
return nil, err
}
if !contractDeployed {
return nil, ErrContractNotDeployed
}
return &ContractUpdater{
Address: addr,
txn: txn,
}, nil
}
// DeployContract sets up the database for a new contract.
func DeployContract(addr, classHash *felt.Felt, txn db.Transaction) (*ContractUpdater, error) {
contractDeployed, err := deployed(addr, txn)
if err != nil {
return nil, err
}
if contractDeployed {
return nil, ErrContractAlreadyDeployed
}
err = setClassHash(txn, addr, classHash)
if err != nil {
return nil, err
}
c, err := NewContractUpdater(addr, txn)
if err != nil {
return nil, err
}
err = c.UpdateNonce(&felt.Zero)
if err != nil {
return nil, err
}
return c, nil
}
// ContractAddress computes the address of a Starknet contract.
func ContractAddress(callerAddress, classHash, salt *felt.Felt, constructorCallData []*felt.Felt) *felt.Felt {
prefix := new(felt.Felt).SetBytes([]byte("STARKNET_CONTRACT_ADDRESS"))
callDataHash := crypto.PedersenArray(constructorCallData...)
// https://docs.starknet.io/documentation/architecture_and_concepts/Contracts/contract-address
return crypto.PedersenArray(
prefix,
callerAddress,
salt,
classHash,
callDataHash,
)
}
func deployed(addr *felt.Felt, txn db.Transaction) (bool, error) {
_, err := ContractClassHash(addr, txn)
if errors.Is(err, db.ErrKeyNotFound) {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
// ContractUpdater is a helper to update an existing contract instance.
type ContractUpdater struct {
// Address that this contract instance is deployed to
Address *felt.Felt
// txn to access the database
txn db.Transaction
}
// Purge eliminates the contract instance, deleting all associated data from storage
// assumes storage is cleared in revert process
func (c *ContractUpdater) Purge() error {
addrBytes := c.Address.Marshal()
buckets := []db.Bucket{db.ContractNonce, db.ContractClassHash}
for _, bucket := range buckets {
if err := c.txn.Delete(bucket.Key(addrBytes)); err != nil {
return err
}
}
return nil
}
// ContractNonce returns the amount transactions sent from this contract.
// Only account contracts can have a non-zero nonce.
func ContractNonce(addr *felt.Felt, txn db.Transaction) (*felt.Felt, error) {
key := db.ContractNonce.Key(addr.Marshal())
var nonce *felt.Felt
if err := txn.Get(key, func(val []byte) error {
nonce = new(felt.Felt)
nonce.SetBytes(val)
return nil
}); err != nil {
return nil, err
}
return nonce, nil
}
// UpdateNonce updates the nonce value in the database.
func (c *ContractUpdater) UpdateNonce(nonce *felt.Felt) error {
nonceKey := db.ContractNonce.Key(c.Address.Marshal())
return c.txn.Set(nonceKey, nonce.Marshal())
}
// ContractRoot returns the root of the contract storage.
func ContractRoot(addr *felt.Felt, txn db.Transaction) (*felt.Felt, error) {
cStorage, err := storage(addr, txn)
if err != nil {
return nil, err
}
return cStorage.Root()
}
type OnValueChanged = func(location, oldValue *felt.Felt) error
// UpdateStorage applies a change-set to the contract storage.
func (c *ContractUpdater) UpdateStorage(diff map[felt.Felt]*felt.Felt, cb OnValueChanged) error {
cStorage, err := storage(c.Address, c.txn)
if err != nil {
return err
}
// apply the diff
for key, value := range diff {
oldValue, pErr := cStorage.Put(&key, value)
if pErr != nil {
return pErr
}
if oldValue != nil {
if err = cb(&key, oldValue); err != nil {
return err
}
}
}
return cStorage.Commit()
}
func ContractStorage(addr, key *felt.Felt, txn db.Transaction) (*felt.Felt, error) {
cStorage, err := storage(addr, txn)
if err != nil {
return nil, err
}
return cStorage.Get(key)
}
// ContractClassHash returns hash of the class that the contract at the given address instantiates.
func ContractClassHash(addr *felt.Felt, txn db.Transaction) (*felt.Felt, error) {
key := db.ContractClassHash.Key(addr.Marshal())
var classHash *felt.Felt
if err := txn.Get(key, func(val []byte) error {
classHash = new(felt.Felt)
classHash.SetBytes(val)
return nil
}); err != nil {
return nil, err
}
return classHash, nil
}
func setClassHash(txn db.Transaction, addr, classHash *felt.Felt) error {
classHashKey := db.ContractClassHash.Key(addr.Marshal())
return txn.Set(classHashKey, classHash.Marshal())
}
// Replace replaces the class that the contract instantiates
func (c *ContractUpdater) Replace(classHash *felt.Felt) error {
return setClassHash(c.txn, c.Address, classHash)
}
// storage returns the [core.Trie] that represents the
// storage of the contract.
func storage(addr *felt.Felt, txn db.Transaction) (*trie.Trie, error) {
addrBytes := addr.Marshal()
trieTxn := trie.NewStorage(txn, db.ContractStorage.Key(addrBytes))
return trie.NewTriePedersen(trieTxn, ContractStorageTrieHeight)
}