github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/management.go (about) 1 package native 2 3 import ( 4 "context" 5 "encoding/binary" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "math" 10 "math/big" 11 "unicode/utf8" 12 13 "github.com/nspcc-dev/neo-go/pkg/config" 14 "github.com/nspcc-dev/neo-go/pkg/core/dao" 15 "github.com/nspcc-dev/neo-go/pkg/core/interop" 16 "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" 17 istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" 18 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 19 "github.com/nspcc-dev/neo-go/pkg/core/state" 20 "github.com/nspcc-dev/neo-go/pkg/core/storage" 21 "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" 22 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 23 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 24 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 25 "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" 26 "github.com/nspcc-dev/neo-go/pkg/util" 27 "github.com/nspcc-dev/neo-go/pkg/util/bitfield" 28 "github.com/nspcc-dev/neo-go/pkg/vm" 29 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 30 ) 31 32 // Management is a contract-managing native contract. 33 type Management struct { 34 interop.ContractMD 35 NEO *NEO 36 Policy *Policy 37 } 38 39 type ManagementCache struct { 40 contracts map[util.Uint160]*state.Contract 41 // nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist. 42 nep11 map[util.Uint160]struct{} 43 // nep17 is a map of NEP-17-compliant contracts which is updated with every PostPersist. 44 nep17 map[util.Uint160]struct{} 45 } 46 47 const ( 48 ManagementContractID = -1 49 50 // PrefixContract is a prefix used to store contract states inside Management native contract. 51 PrefixContract = 8 52 prefixContractHash = 12 53 54 defaultMinimumDeploymentFee = 10_00000000 55 contractDeployNotificationName = "Deploy" 56 contractUpdateNotificationName = "Update" 57 contractDestroyNotificationName = "Destroy" 58 ) 59 60 var ( 61 errGasLimitExceeded = errors.New("gas limit exceeded") 62 63 keyNextAvailableID = []byte{15} 64 keyMinimumDeploymentFee = []byte{20} 65 ) 66 67 var ( 68 _ interop.Contract = (*Management)(nil) 69 _ dao.NativeContractCache = (*ManagementCache)(nil) 70 ) 71 72 // Copy implements NativeContractCache interface. 73 func (c *ManagementCache) Copy() dao.NativeContractCache { 74 cp := &ManagementCache{ 75 contracts: make(map[util.Uint160]*state.Contract), 76 nep11: make(map[util.Uint160]struct{}), 77 nep17: make(map[util.Uint160]struct{}), 78 } 79 // Copy the whole set of contracts is too expensive. We will create a separate map 80 // holding the same set of pointers to contracts, and in case if some contract is 81 // supposed to be changed, Management will create the copy in-place. 82 for hash, ctr := range c.contracts { 83 cp.contracts[hash] = ctr 84 } 85 for hash := range c.nep17 { 86 cp.nep17[hash] = struct{}{} 87 } 88 for hash := range c.nep11 { 89 cp.nep11[hash] = struct{}{} 90 } 91 return cp 92 } 93 94 // MakeContractKey creates a key from the account script hash. 95 func MakeContractKey(h util.Uint160) []byte { 96 return makeUint160Key(PrefixContract, h) 97 } 98 99 // newManagement creates a new Management native contract. 100 func newManagement() *Management { 101 var m = &Management{ 102 ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID), 103 } 104 defer m.BuildHFSpecificMD(m.ActiveIn()) 105 106 desc := newDescriptor("getContract", smartcontract.ArrayType, 107 manifest.NewParameter("hash", smartcontract.Hash160Type)) 108 md := newMethodAndPrice(m.getContract, 1<<15, callflag.ReadStates) 109 m.AddMethod(md, desc) 110 111 desc = newDescriptor("deploy", smartcontract.ArrayType, 112 manifest.NewParameter("nefFile", smartcontract.ByteArrayType), 113 manifest.NewParameter("manifest", smartcontract.ByteArrayType)) 114 md = newMethodAndPrice(m.deploy, 0, callflag.All) 115 m.AddMethod(md, desc) 116 117 desc = newDescriptor("deploy", smartcontract.ArrayType, 118 manifest.NewParameter("nefFile", smartcontract.ByteArrayType), 119 manifest.NewParameter("manifest", smartcontract.ByteArrayType), 120 manifest.NewParameter("data", smartcontract.AnyType)) 121 md = newMethodAndPrice(m.deployWithData, 0, callflag.All) 122 m.AddMethod(md, desc) 123 124 desc = newDescriptor("update", smartcontract.VoidType, 125 manifest.NewParameter("nefFile", smartcontract.ByteArrayType), 126 manifest.NewParameter("manifest", smartcontract.ByteArrayType)) 127 md = newMethodAndPrice(m.update, 0, callflag.All) 128 m.AddMethod(md, desc) 129 130 desc = newDescriptor("update", smartcontract.VoidType, 131 manifest.NewParameter("nefFile", smartcontract.ByteArrayType), 132 manifest.NewParameter("manifest", smartcontract.ByteArrayType), 133 manifest.NewParameter("data", smartcontract.AnyType)) 134 md = newMethodAndPrice(m.updateWithData, 0, callflag.All) 135 m.AddMethod(md, desc) 136 137 desc = newDescriptor("destroy", smartcontract.VoidType) 138 md = newMethodAndPrice(m.destroy, 1<<15, callflag.States|callflag.AllowNotify) 139 m.AddMethod(md, desc) 140 141 desc = newDescriptor("getMinimumDeploymentFee", smartcontract.IntegerType) 142 md = newMethodAndPrice(m.getMinimumDeploymentFee, 1<<15, callflag.ReadStates) 143 m.AddMethod(md, desc) 144 145 desc = newDescriptor("setMinimumDeploymentFee", smartcontract.VoidType, 146 manifest.NewParameter("value", smartcontract.IntegerType)) 147 md = newMethodAndPrice(m.setMinimumDeploymentFee, 1<<15, callflag.States) 148 m.AddMethod(md, desc) 149 150 desc = newDescriptor("hasMethod", smartcontract.BoolType, 151 manifest.NewParameter("hash", smartcontract.Hash160Type), 152 manifest.NewParameter("method", smartcontract.StringType), 153 manifest.NewParameter("pcount", smartcontract.IntegerType)) 154 md = newMethodAndPrice(m.hasMethod, 1<<15, callflag.ReadStates) 155 m.AddMethod(md, desc) 156 157 desc = newDescriptor("getContractById", smartcontract.ArrayType, 158 manifest.NewParameter("id", smartcontract.IntegerType)) 159 md = newMethodAndPrice(m.getContractByID, 1<<15, callflag.ReadStates) 160 m.AddMethod(md, desc) 161 162 desc = newDescriptor("getContractHashes", smartcontract.InteropInterfaceType) 163 md = newMethodAndPrice(m.getContractHashes, 1<<15, callflag.ReadStates) 164 m.AddMethod(md, desc) 165 166 hashParam := manifest.NewParameter("Hash", smartcontract.Hash160Type) 167 eDesc := newEventDescriptor(contractDeployNotificationName, hashParam) 168 eMD := newEvent(eDesc) 169 m.AddEvent(eMD) 170 171 eDesc = newEventDescriptor(contractUpdateNotificationName, hashParam) 172 eMD = newEvent(eDesc) 173 m.AddEvent(eMD) 174 175 eDesc = newEventDescriptor(contractDestroyNotificationName, hashParam) 176 eMD = newEvent(eDesc) 177 m.AddEvent(eMD) 178 return m 179 } 180 181 func toHash160(si stackitem.Item) util.Uint160 { 182 hashBytes, err := si.TryBytes() 183 if err != nil { 184 panic(err) 185 } 186 hash, err := util.Uint160DecodeBytesBE(hashBytes) 187 if err != nil { 188 panic(err) 189 } 190 return hash 191 } 192 193 // getContract is an implementation of public getContract method, it's run under 194 // VM protections, so it's OK for it to panic instead of returning errors. 195 func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) stackitem.Item { 196 hash := toHash160(args[0]) 197 ctr, err := GetContract(ic.DAO, hash) 198 if err != nil { 199 if errors.Is(err, storage.ErrKeyNotFound) { 200 return stackitem.Null{} 201 } 202 panic(err) 203 } 204 return contractToStack(ctr) 205 } 206 207 // getContractByID is an implementation of public getContractById method, it's run under 208 // VM protections, so it's OK for it to panic instead of returning errors. 209 func (m *Management) getContractByID(ic *interop.Context, args []stackitem.Item) stackitem.Item { 210 idBig, err := args[0].TryInteger() 211 if err != nil { 212 panic(err) 213 } 214 id := idBig.Int64() 215 if !idBig.IsInt64() || id < math.MinInt32 || id > math.MaxInt32 { 216 panic("id is not a correct int32") 217 } 218 ctr, err := GetContractByID(ic.DAO, int32(id)) 219 if err != nil { 220 if errors.Is(err, storage.ErrKeyNotFound) { 221 return stackitem.Null{} 222 } 223 panic(err) 224 } 225 return contractToStack(ctr) 226 } 227 228 // GetContract returns a contract with the given hash from the given DAO. 229 func GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) { 230 cache := d.GetROCache(ManagementContractID).(*ManagementCache) 231 return getContract(cache, hash) 232 } 233 234 // getContract returns a contract with the given hash from provided RO or RW cache. 235 func getContract(cache *ManagementCache, hash util.Uint160) (*state.Contract, error) { 236 cs, ok := cache.contracts[hash] 237 if !ok { 238 return nil, storage.ErrKeyNotFound 239 } 240 return cs, nil 241 } 242 243 // GetContractByID returns a contract with the given ID from the given DAO. 244 func GetContractByID(d *dao.Simple, id int32) (*state.Contract, error) { 245 hash, err := GetContractScriptHash(d, id) 246 if err != nil { 247 return nil, err 248 } 249 return GetContract(d, hash) 250 } 251 252 // GetContractScriptHash returns a contract hash associated with the given ID from the given DAO. 253 func GetContractScriptHash(d *dao.Simple, id int32) (util.Uint160, error) { 254 key := make([]byte, 5) 255 key = putHashKey(key, id) 256 si := d.GetStorageItem(ManagementContractID, key) 257 if si == nil { 258 return util.Uint160{}, storage.ErrKeyNotFound 259 } 260 return util.Uint160DecodeBytesBE(si) 261 } 262 263 func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) { 264 _, isNull := arg.(stackitem.Null) 265 if isNull { 266 return nil, nil 267 } 268 b, err := arg.TryBytes() 269 if err != nil { 270 return nil, err 271 } 272 l := len(b) 273 if l == 0 { 274 return nil, errors.New("empty") 275 } else if l > max { 276 return nil, fmt.Errorf("len is %d (max %d)", l, max) 277 } 278 279 return b, nil 280 } 281 282 func (m *Management) getContractHashes(ic *interop.Context, _ []stackitem.Item) stackitem.Item { 283 ctx, cancel := context.WithCancel(context.Background()) 284 prefix := []byte{prefixContractHash} 285 seekres := ic.DAO.SeekAsync(ctx, ManagementContractID, storage.SeekRange{Prefix: prefix}) 286 filteredRes := make(chan storage.KeyValue) 287 go func() { 288 for kv := range seekres { 289 if len(kv.Key) == 4 && binary.BigEndian.Uint32(kv.Key) < math.MaxInt32 { 290 filteredRes <- kv 291 } 292 } 293 close(filteredRes) 294 }() 295 opts := istorage.FindRemovePrefix 296 item := istorage.NewIterator(filteredRes, prefix, int64(opts)) 297 ic.RegisterCancelFunc(func() { 298 cancel() 299 for range seekres { //nolint:revive //empty-block 300 } 301 }) 302 return stackitem.NewInterop(item) 303 } 304 305 // getNefAndManifestFromItems converts input arguments into NEF and manifest 306 // adding an appropriate deployment GAS price and sanitizing inputs. 307 func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item, isDeploy bool) (*nef.File, *manifest.Manifest, error) { 308 nefBytes, err := getLimitedSlice(args[0], math.MaxInt32) // Upper limits are checked during NEF deserialization. 309 if err != nil { 310 return nil, nil, fmt.Errorf("invalid NEF file: %w", err) 311 } 312 manifestBytes, err := getLimitedSlice(args[1], manifest.MaxManifestSize) 313 if err != nil { 314 return nil, nil, fmt.Errorf("invalid manifest: %w", err) 315 } 316 317 gas := ic.BaseStorageFee() * int64(len(nefBytes)+len(manifestBytes)) 318 if isDeploy { 319 fee := m.minimumDeploymentFee(ic.DAO) 320 if fee > gas { 321 gas = fee 322 } 323 } 324 if !ic.VM.AddGas(gas) { 325 return nil, nil, errGasLimitExceeded 326 } 327 var resManifest *manifest.Manifest 328 var resNef *nef.File 329 if nefBytes != nil { 330 nf, err := nef.FileFromBytes(nefBytes) 331 if err != nil { 332 return nil, nil, fmt.Errorf("invalid NEF file: %w", err) 333 } 334 resNef = &nf 335 } 336 if manifestBytes != nil { 337 if !utf8.Valid(manifestBytes) { 338 return nil, nil, errors.New("manifest is not UTF-8 compliant") 339 } 340 resManifest = new(manifest.Manifest) 341 err := json.Unmarshal(manifestBytes, resManifest) 342 if err != nil { 343 return nil, nil, fmt.Errorf("invalid manifest: %w", err) 344 } 345 } 346 return resNef, resManifest, nil 347 } 348 349 // deploy is an implementation of public 2-argument deploy method. 350 func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item { 351 return m.deployWithData(ic, append(args, stackitem.Null{})) 352 } 353 354 // deployWithData is an implementation of public 3-argument deploy method. 355 // It's run under VM protections, so it's OK for it to panic instead of returning errors. 356 func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item { 357 neff, manif, err := m.getNefAndManifestFromItems(ic, args, true) 358 if err != nil { 359 panic(err) 360 } 361 if neff == nil { 362 panic(errors.New("no valid NEF provided")) 363 } 364 if manif == nil { 365 panic(errors.New("no valid manifest provided")) 366 } 367 if ic.Tx == nil { 368 panic(errors.New("no transaction provided")) 369 } 370 newcontract, err := m.Deploy(ic, ic.Tx.Sender(), neff, manif) 371 if err != nil { 372 panic(err) 373 } 374 m.callDeploy(ic, newcontract, args[2], false) 375 m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash) 376 return contractToStack(newcontract) 377 } 378 379 func markUpdated(d *dao.Simple, hash util.Uint160, cs *state.Contract) { 380 cache := d.GetRWCache(ManagementContractID).(*ManagementCache) 381 delete(cache.nep11, hash) 382 delete(cache.nep17, hash) 383 if cs == nil { 384 delete(cache.contracts, hash) 385 return 386 } 387 updateContractCache(cache, cs) 388 } 389 390 // Deploy creates a contract's hash/ID and saves a new contract into the given DAO. 391 // It doesn't run _deploy method and doesn't emit notification. 392 func (m *Management) Deploy(ic *interop.Context, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) { 393 h := state.CreateContractHash(sender, neff.Checksum, manif.Name) 394 if m.Policy.IsBlocked(ic.DAO, h) { 395 return nil, fmt.Errorf("the contract %s has been blocked", h.StringLE()) 396 } 397 _, err := GetContract(ic.DAO, h) 398 if err == nil { 399 return nil, errors.New("contract already exists") 400 } 401 id, err := m.getNextContractID(ic.DAO) 402 if err != nil { 403 return nil, err 404 } 405 err = manif.IsValid(h, false) // do not check manifest size, the whole state.Contract will be checked later. 406 if err != nil { 407 return nil, fmt.Errorf("invalid manifest: %w", err) 408 } 409 err = checkScriptAndMethods(ic, neff.Script, manif.ABI.Methods) 410 if err != nil { 411 return nil, err 412 } 413 newcontract := &state.Contract{ 414 ContractBase: state.ContractBase{ 415 ID: id, 416 Hash: h, 417 NEF: *neff, 418 Manifest: *manif, 419 }, 420 } 421 err = PutContractState(ic.DAO, newcontract) 422 if err != nil { 423 return nil, err 424 } 425 return newcontract, nil 426 } 427 428 func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item { 429 return m.updateWithData(ic, append(args, stackitem.Null{})) 430 } 431 432 // update is an implementation of public update method, it's run under 433 // VM protections, so it's OK for it to panic instead of returning errors. 434 func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item { 435 neff, manif, err := m.getNefAndManifestFromItems(ic, args, false) 436 if err != nil { 437 panic(err) 438 } 439 if neff == nil && manif == nil { 440 panic(errors.New("both NEF and manifest are nil")) 441 } 442 contract, err := m.Update(ic, ic.VM.GetCallingScriptHash(), neff, manif) 443 if err != nil { 444 panic(err) 445 } 446 m.callDeploy(ic, contract, args[2], true) 447 m.emitNotification(ic, contractUpdateNotificationName, contract.Hash) 448 return stackitem.Null{} 449 } 450 451 // Update updates contract's script and/or manifest in the given DAO. 452 // It doesn't run _deploy method and doesn't emit notification. 453 func (m *Management) Update(ic *interop.Context, hash util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) { 454 var contract state.Contract 455 456 oldcontract, err := GetContract(ic.DAO, hash) 457 if err != nil { 458 return nil, errors.New("contract doesn't exist") 459 } 460 if oldcontract.UpdateCounter == math.MaxUint16 { 461 return nil, errors.New("the contract reached the maximum number of updates") 462 } 463 464 contract = *oldcontract // Make a copy, don't ruin (potentially) cached contract. 465 // if NEF was provided, update the contract script 466 if neff != nil { 467 contract.NEF = *neff 468 } 469 // if manifest was provided, update the contract manifest 470 if manif != nil { 471 if manif.Name != contract.Manifest.Name { 472 return nil, errors.New("contract name can't be changed") 473 } 474 err = manif.IsValid(contract.Hash, false) // do not check manifest size, the whole state.Contract will be checked later. 475 if err != nil { 476 return nil, fmt.Errorf("invalid manifest: %w", err) 477 } 478 contract.Manifest = *manif 479 } 480 err = checkScriptAndMethods(ic, contract.NEF.Script, contract.Manifest.ABI.Methods) 481 if err != nil { 482 return nil, err 483 } 484 contract.UpdateCounter++ 485 err = PutContractState(ic.DAO, &contract) 486 if err != nil { 487 return nil, err 488 } 489 return &contract, nil 490 } 491 492 // destroy is an implementation of destroy update method, it's run under 493 // VM protections, so it's OK for it to panic instead of returning errors. 494 func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackitem.Item { 495 hash := ic.VM.GetCallingScriptHash() 496 err := m.Destroy(ic.DAO, hash) 497 if err != nil { 498 panic(err) 499 } 500 m.emitNotification(ic, contractDestroyNotificationName, hash) 501 return stackitem.Null{} 502 } 503 504 // Destroy drops the given contract from DAO along with its storage. It doesn't emit notification. 505 func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error { 506 contract, err := GetContract(d, hash) 507 if err != nil { 508 return err 509 } 510 key := MakeContractKey(hash) 511 d.DeleteStorageItem(m.ID, key) 512 key = putHashKey(key, contract.ID) 513 d.DeleteStorageItem(ManagementContractID, key) 514 515 d.Seek(contract.ID, storage.SeekRange{}, func(k, _ []byte) bool { 516 d.DeleteStorageItem(contract.ID, k) 517 return true 518 }) 519 m.Policy.blockAccountInternal(d, hash) 520 markUpdated(d, hash, nil) 521 return nil 522 } 523 524 func (m *Management) getMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item { 525 return stackitem.NewBigInteger(big.NewInt(m.minimumDeploymentFee(ic.DAO))) 526 } 527 528 // minimumDeploymentFee returns the minimum required fee for contract deploy. 529 func (m *Management) minimumDeploymentFee(dao *dao.Simple) int64 { 530 return getIntWithKey(m.ID, dao, keyMinimumDeploymentFee) 531 } 532 533 func (m *Management) setMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item { 534 value := toBigInt(args[0]) 535 if value.Sign() < 0 { 536 panic("MinimumDeploymentFee cannot be negative") 537 } 538 if !m.NEO.checkCommittee(ic) { 539 panic("invalid committee signature") 540 } 541 ic.DAO.PutStorageItem(m.ID, keyMinimumDeploymentFee, bigint.ToBytes(value)) 542 return stackitem.Null{} 543 } 544 545 func (m *Management) callDeploy(ic *interop.Context, cs *state.Contract, data stackitem.Item, isUpdate bool) { 546 md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy, 2) 547 if md != nil { 548 err := contract.CallFromNative(ic, m.Hash, cs, manifest.MethodDeploy, 549 []stackitem.Item{data, stackitem.NewBool(isUpdate)}, false) 550 if err != nil { 551 panic(err) 552 } 553 } 554 } 555 556 func contractToStack(cs *state.Contract) stackitem.Item { 557 si, err := cs.ToStackItem() 558 if err != nil { 559 panic(fmt.Errorf("contract to stack item: %w", err)) 560 } 561 return si 562 } 563 564 func (m *Management) hasMethod(ic *interop.Context, args []stackitem.Item) stackitem.Item { 565 cHash := toHash160(args[0]) 566 method, err := stackitem.ToString(args[1]) 567 if err != nil { 568 panic(err) 569 } 570 pcount := int(toInt64((args[2]))) 571 cs, err := GetContract(ic.DAO, cHash) 572 if err != nil { 573 return stackitem.NewBool(false) 574 } 575 return stackitem.NewBool(cs.Manifest.ABI.GetMethod(method, pcount) != nil) 576 } 577 578 // Metadata implements the Contract interface. 579 func (m *Management) Metadata() *interop.ContractMD { 580 return &m.ContractMD 581 } 582 583 // updateContractCache saves the contract in the common and NEP-related caches. It's 584 // an internal method that must be called with m.mtx lock taken. 585 func updateContractCache(cache *ManagementCache, cs *state.Contract) { 586 cache.contracts[cs.Hash] = cs 587 if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) { 588 cache.nep11[cs.Hash] = struct{}{} 589 } 590 if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) { 591 cache.nep17[cs.Hash] = struct{}{} 592 } 593 } 594 595 // OnPersist implements the Contract interface. 596 func (m *Management) OnPersist(ic *interop.Context) error { 597 var cache *ManagementCache 598 for _, native := range ic.Natives { 599 var ( 600 activeIn = native.ActiveIn() 601 isDeploy bool 602 isUpdate bool 603 latestHF config.Hardfork 604 currentActiveHFs []config.Hardfork 605 ) 606 activeHFs := native.Metadata().ActiveHFs 607 isDeploy = activeIn == nil && ic.Block.Index == 0 || 608 activeIn != nil && ic.IsHardforkActivation(*activeIn) 609 if !isDeploy { 610 for _, hf := range config.Hardforks { 611 if _, ok := activeHFs[hf]; ok && ic.IsHardforkActivation(hf) { 612 isUpdate = true 613 activation := hf // avoid loop variable pointer exporting. 614 activeIn = &activation // reuse ActiveIn variable for the initialization hardfork. 615 // Break immediately since native Initialize should be called starting from the first hardfork in a raw 616 // (if there are multiple hardforks with the same enabling height). 617 break 618 } 619 } 620 } 621 // Search for the latest active hardfork to properly construct manifest and 622 // initialize natives for the range of active hardforks. 623 for _, hf := range config.Hardforks { 624 if _, ok := activeHFs[hf]; ok && ic.IsHardforkActivation(hf) { 625 latestHF = hf 626 currentActiveHFs = append(currentActiveHFs, hf) 627 } 628 } 629 if !(isDeploy || isUpdate) { 630 continue 631 } 632 md := native.Metadata() 633 hfSpecificMD := md.HFSpecificContractMD(&latestHF) 634 base := hfSpecificMD.ContractBase 635 var cs *state.Contract 636 switch { 637 case isDeploy: 638 cs = &state.Contract{ 639 ContractBase: base, 640 } 641 case isUpdate: 642 if cache == nil { 643 cache = ic.DAO.GetRWCache(m.ID).(*ManagementCache) 644 } 645 oldcontract, err := getContract(cache, md.Hash) 646 if err != nil { 647 return fmt.Errorf("failed to retrieve native %s from cache: %w", md.Name, err) 648 } 649 650 contract := *oldcontract // Make a copy, don't ruin cached contract and cache. 651 contract.NEF = base.NEF 652 contract.Manifest = base.Manifest 653 contract.UpdateCounter++ 654 cs = &contract 655 } 656 err := putContractState(ic.DAO, cs, false) // Perform cache update manually. 657 if err != nil { 658 return fmt.Errorf("failed to put contract state: %w", err) 659 } 660 661 // Deploy hardfork (contract's ActiveIn) is not a part of contract's active hardforks and 662 // allowed to be nil, this, a special initialization call for it. 663 if isDeploy { 664 if err := native.Initialize(ic, activeIn, hfSpecificMD); err != nil { 665 return fmt.Errorf("initializing %s native contract at HF %v: %w", md.Name, activeIn, err) 666 } 667 } 668 // The rest of activating hardforks also require initialization. 669 for _, hf := range currentActiveHFs { 670 if err := native.Initialize(ic, &hf, hfSpecificMD); err != nil { 671 return fmt.Errorf("initializing %s native contract at HF %d: %w", md.Name, activeIn, err) 672 } 673 } 674 675 if cache == nil { 676 cache = ic.DAO.GetRWCache(m.ID).(*ManagementCache) 677 } 678 updateContractCache(cache, cs) 679 680 ntfName := contractDeployNotificationName 681 if isUpdate { 682 ntfName = contractUpdateNotificationName 683 } 684 m.emitNotification(ic, ntfName, cs.Hash) 685 } 686 687 return nil 688 } 689 690 // InitializeCache initializes contract cache with the proper values from storage. 691 // Cache initialization should be done apart from Initialize because Initialize is 692 // called only when deploying native contracts. 693 func (m *Management) InitializeCache(blockHeight uint32, d *dao.Simple) error { 694 cache := &ManagementCache{ 695 contracts: make(map[util.Uint160]*state.Contract), 696 nep11: make(map[util.Uint160]struct{}), 697 nep17: make(map[util.Uint160]struct{}), 698 } 699 700 var initErr error 701 d.Seek(m.ID, storage.SeekRange{Prefix: []byte{PrefixContract}}, func(_, v []byte) bool { 702 var cs = new(state.Contract) 703 initErr = stackitem.DeserializeConvertible(v, cs) 704 if initErr != nil { 705 return false 706 } 707 updateContractCache(cache, cs) 708 return true 709 }) 710 if initErr != nil { 711 return initErr 712 } 713 d.SetCache(m.ID, cache) 714 return nil 715 } 716 717 // PostPersist implements the Contract interface. 718 func (m *Management) PostPersist(ic *interop.Context) error { 719 return nil 720 } 721 722 // GetNEP11Contracts returns hashes of all deployed contracts that support NEP-11 standard. The list 723 // is updated every PostPersist, so until PostPersist is called, the result for the previous block 724 // is returned. 725 func (m *Management) GetNEP11Contracts(d *dao.Simple) []util.Uint160 { 726 cache := d.GetROCache(m.ID).(*ManagementCache) 727 result := make([]util.Uint160, 0, len(cache.nep11)) 728 for h := range cache.nep11 { 729 result = append(result, h) 730 } 731 return result 732 } 733 734 // GetNEP17Contracts returns hashes of all deployed contracts that support NEP-17 standard. The list 735 // is updated every PostPersist, so until PostPersist is called, the result for the previous block 736 // is returned. 737 func (m *Management) GetNEP17Contracts(d *dao.Simple) []util.Uint160 { 738 cache := d.GetROCache(m.ID).(*ManagementCache) 739 result := make([]util.Uint160, 0, len(cache.nep17)) 740 for h := range cache.nep17 { 741 result = append(result, h) 742 } 743 return result 744 } 745 746 // Initialize implements the Contract interface. 747 func (m *Management) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { 748 if hf != m.ActiveIn() { 749 return nil 750 } 751 752 setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee) 753 setIntWithKey(m.ID, ic.DAO, keyNextAvailableID, 1) 754 755 cache := &ManagementCache{ 756 contracts: make(map[util.Uint160]*state.Contract), 757 nep11: make(map[util.Uint160]struct{}), 758 nep17: make(map[util.Uint160]struct{}), 759 } 760 ic.DAO.SetCache(m.ID, cache) 761 return nil 762 } 763 764 // ActiveIn implements the Contract interface. 765 func (m *Management) ActiveIn() *config.Hardfork { 766 return nil 767 } 768 769 // PutContractState saves given contract state into given DAO. 770 func PutContractState(d *dao.Simple, cs *state.Contract) error { 771 return putContractState(d, cs, true) 772 } 773 774 // putContractState is an internal PutContractState representation. 775 func putContractState(d *dao.Simple, cs *state.Contract, updateCache bool) error { 776 key := MakeContractKey(cs.Hash) 777 if err := putConvertibleToDAO(ManagementContractID, d, key, cs); err != nil { 778 return err 779 } 780 if updateCache { 781 markUpdated(d, cs.Hash, cs) 782 } 783 if cs.UpdateCounter != 0 { // Update. 784 return nil 785 } 786 key = putHashKey(key, cs.ID) 787 d.PutStorageItem(ManagementContractID, key, cs.Hash.BytesBE()) 788 return nil 789 } 790 791 func putHashKey(buf []byte, id int32) []byte { 792 buf[0] = prefixContractHash 793 binary.BigEndian.PutUint32(buf[1:], uint32(id)) 794 return buf[:5] 795 } 796 797 func (m *Management) getNextContractID(d *dao.Simple) (int32, error) { 798 si := d.GetStorageItem(m.ID, keyNextAvailableID) 799 if si == nil { 800 return 0, errors.New("nextAvailableID is not initialized") 801 } 802 id := bigint.FromBytes(si) 803 ret := int32(id.Int64()) 804 id.Add(id, intOne) 805 d.PutBigInt(m.ID, keyNextAvailableID, id) 806 return ret, nil 807 } 808 809 func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) { 810 ic.AddNotification(m.Hash, name, stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)})) 811 } 812 813 func checkScriptAndMethods(ic *interop.Context, script []byte, methods []manifest.Method) error { 814 l := len(script) 815 offsets := bitfield.New(l) 816 for i := range methods { 817 if methods[i].Offset >= l { 818 return fmt.Errorf("method %s/%d: offset is out of the script range", methods[i].Name, len(methods[i].Parameters)) 819 } 820 offsets.Set(methods[i].Offset) 821 } 822 if !ic.IsHardforkEnabled(config.HFBasilisk) { 823 return nil 824 } 825 err := vm.IsScriptCorrect(script, offsets) 826 if err != nil { 827 return fmt.Errorf("invalid contract script: %w", err) 828 } 829 830 return nil 831 }