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  }