mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-26 19:42:23 +00:00
8055952bbc
Fantastic Beasts and Where to Find Them
270 lines
8.2 KiB
Go
270 lines
8.2 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"crypto/elliptic"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
var (
|
|
errGasLimitExceeded = errors.New("gas limit exceeded")
|
|
errFindInvalidOptions = errors.New("invalid Find options")
|
|
)
|
|
|
|
// StorageContext contains storing id and read/write flag, it's used as
|
|
// a context for storage manipulation functions.
|
|
type StorageContext struct {
|
|
ID int32
|
|
ReadOnly bool
|
|
}
|
|
|
|
// engineGetScriptContainer returns transaction or block that contains the script
|
|
// being run.
|
|
func engineGetScriptContainer(ic *interop.Context) error {
|
|
var item stackitem.Item
|
|
switch t := ic.Container.(type) {
|
|
case *transaction.Transaction:
|
|
item = native.TransactionToStackItem(t)
|
|
case *block.Block:
|
|
item = native.BlockToStackItem(t)
|
|
default:
|
|
return errors.New("unknown script container")
|
|
}
|
|
ic.VM.Estack().PushItem(item)
|
|
return nil
|
|
}
|
|
|
|
// storageDelete deletes stored key-value pair.
|
|
func storageDelete(ic *interop.Context) error {
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
if !ok {
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
}
|
|
if stc.ReadOnly {
|
|
return errors.New("StorageContext is read only")
|
|
}
|
|
key := ic.VM.Estack().Pop().Bytes()
|
|
ic.DAO.DeleteStorageItem(stc.ID, key)
|
|
return nil
|
|
}
|
|
|
|
// storageGet returns stored key-value pair.
|
|
func storageGet(ic *interop.Context) error {
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
if !ok {
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
}
|
|
key := ic.VM.Estack().Pop().Bytes()
|
|
si := ic.DAO.GetStorageItem(stc.ID, key)
|
|
if si != nil {
|
|
ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte(si)))
|
|
} else {
|
|
ic.VM.Estack().PushItem(stackitem.Null{})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// storageGetContext returns storage context (scripthash).
|
|
func storageGetContext(ic *interop.Context) error {
|
|
return storageGetContextInternal(ic, false)
|
|
}
|
|
|
|
// storageGetReadOnlyContext returns read-only context (scripthash).
|
|
func storageGetReadOnlyContext(ic *interop.Context) error {
|
|
return storageGetContextInternal(ic, true)
|
|
}
|
|
|
|
// storageGetContextInternal is internal version of storageGetContext and
|
|
// storageGetReadOnlyContext which allows to specify ReadOnly context flag.
|
|
func storageGetContextInternal(ic *interop.Context, isReadOnly bool) error {
|
|
contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sc := &StorageContext{
|
|
ID: contract.ID,
|
|
ReadOnly: isReadOnly,
|
|
}
|
|
ic.VM.Estack().PushItem(stackitem.NewInterop(sc))
|
|
return nil
|
|
}
|
|
|
|
func putWithContext(ic *interop.Context, stc *StorageContext, key []byte, value []byte) error {
|
|
if len(key) > storage.MaxStorageKeyLen {
|
|
return errors.New("key is too big")
|
|
}
|
|
if len(value) > storage.MaxStorageValueLen {
|
|
return errors.New("value is too big")
|
|
}
|
|
if stc.ReadOnly {
|
|
return errors.New("StorageContext is read only")
|
|
}
|
|
si := ic.DAO.GetStorageItem(stc.ID, key)
|
|
sizeInc := len(value)
|
|
if si == nil {
|
|
sizeInc = len(key) + len(value)
|
|
} else if len(value) != 0 {
|
|
if len(value) <= len(si) {
|
|
sizeInc = (len(value)-1)/4 + 1
|
|
} else if len(si) != 0 {
|
|
sizeInc = (len(si)-1)/4 + 1 + len(value) - len(si)
|
|
}
|
|
}
|
|
if !ic.VM.AddGas(int64(sizeInc) * ic.BaseStorageFee()) {
|
|
return errGasLimitExceeded
|
|
}
|
|
ic.DAO.PutStorageItem(stc.ID, key, value)
|
|
return nil
|
|
}
|
|
|
|
// storagePut puts key-value pair into the storage.
|
|
func storagePut(ic *interop.Context) error {
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
if !ok {
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
}
|
|
key := ic.VM.Estack().Pop().Bytes()
|
|
value := ic.VM.Estack().Pop().Bytes()
|
|
return putWithContext(ic, stc, key, value)
|
|
}
|
|
|
|
// storageContextAsReadOnly sets given context to read-only mode.
|
|
func storageContextAsReadOnly(ic *interop.Context) error {
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
if !ok {
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
}
|
|
if !stc.ReadOnly {
|
|
stx := &StorageContext{
|
|
ID: stc.ID,
|
|
ReadOnly: true,
|
|
}
|
|
stc = stx
|
|
}
|
|
ic.VM.Estack().PushItem(stackitem.NewInterop(stc))
|
|
return nil
|
|
}
|
|
|
|
// storageFind finds stored key-value pair.
|
|
func storageFind(ic *interop.Context) error {
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
if !ok {
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
}
|
|
prefix := ic.VM.Estack().Pop().Bytes()
|
|
opts := ic.VM.Estack().Pop().BigInt().Int64()
|
|
if opts&^istorage.FindAll != 0 {
|
|
return fmt.Errorf("%w: unknown flag", errFindInvalidOptions)
|
|
}
|
|
if opts&istorage.FindKeysOnly != 0 &&
|
|
opts&(istorage.FindDeserialize|istorage.FindPick0|istorage.FindPick1) != 0 {
|
|
return fmt.Errorf("%w KeysOnly conflicts with other options", errFindInvalidOptions)
|
|
}
|
|
if opts&istorage.FindValuesOnly != 0 &&
|
|
opts&(istorage.FindKeysOnly|istorage.FindRemovePrefix) != 0 {
|
|
return fmt.Errorf("%w: KeysOnly conflicts with ValuesOnly", errFindInvalidOptions)
|
|
}
|
|
if opts&istorage.FindPick0 != 0 && opts&istorage.FindPick1 != 0 {
|
|
return fmt.Errorf("%w: Pick0 conflicts with Pick1", errFindInvalidOptions)
|
|
}
|
|
if opts&istorage.FindDeserialize == 0 && (opts&istorage.FindPick0 != 0 || opts&istorage.FindPick1 != 0) {
|
|
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
seekres := ic.DAO.SeekAsync(ctx, stc.ID, storage.SeekRange{Prefix: prefix})
|
|
item := istorage.NewIterator(seekres, prefix, opts)
|
|
ic.VM.Estack().PushItem(stackitem.NewInterop(item))
|
|
ic.RegisterCancelFunc(func() {
|
|
cancel()
|
|
// Underlying persistent store is likely to be a private MemCachedStore. Thus,
|
|
// to avoid concurrent map iteration and map write we need to wait until internal
|
|
// seek goroutine is finished, because it can access underlying persistent store.
|
|
for range seekres {
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// contractCreateMultisigAccount calculates multisig contract scripthash for a
|
|
// given m and a set of public keys.
|
|
func contractCreateMultisigAccount(ic *interop.Context) error {
|
|
m := ic.VM.Estack().Pop().BigInt()
|
|
mu64 := m.Uint64()
|
|
if !m.IsUint64() || mu64 > math.MaxInt32 {
|
|
return errors.New("m must be positive and fit int32")
|
|
}
|
|
arr := ic.VM.Estack().Pop().Array()
|
|
pubs := make(keys.PublicKeys, len(arr))
|
|
for i, pk := range arr {
|
|
p, err := keys.NewPublicKeyFromBytes(pk.Value().([]byte), elliptic.P256())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pubs[i] = p
|
|
}
|
|
var invokeFee int64
|
|
if ic.IsHardforkEnabled(config.HFAspidochelone) {
|
|
invokeFee = fee.ECDSAVerifyPrice * int64(len(pubs))
|
|
} else {
|
|
invokeFee = 1 << 8
|
|
}
|
|
invokeFee *= ic.BaseExecFee()
|
|
if !ic.VM.AddGas(invokeFee) {
|
|
return errors.New("gas limit exceeded")
|
|
}
|
|
script, err := smartcontract.CreateMultiSigRedeemScript(int(mu64), pubs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ic.VM.Estack().PushItem(stackitem.NewByteArray(hash.Hash160(script).BytesBE()))
|
|
return nil
|
|
}
|
|
|
|
// contractCreateStandardAccount calculates contract scripthash for a given public key.
|
|
func contractCreateStandardAccount(ic *interop.Context) error {
|
|
h := ic.VM.Estack().Pop().Bytes()
|
|
p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var invokeFee int64
|
|
if ic.IsHardforkEnabled(config.HFAspidochelone) {
|
|
invokeFee = fee.ECDSAVerifyPrice
|
|
} else {
|
|
invokeFee = 1 << 8
|
|
}
|
|
invokeFee *= ic.BaseExecFee()
|
|
if !ic.VM.AddGas(invokeFee) {
|
|
return errors.New("gas limit exceeded")
|
|
}
|
|
ic.VM.Estack().PushItem(stackitem.NewByteArray(p.GetScriptHash().BytesBE()))
|
|
return nil
|
|
}
|
|
|
|
// contractGetCallFlags returns current context calling flags.
|
|
func contractGetCallFlags(ic *interop.Context) error {
|
|
ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.VM.Context().GetCallFlags()))))
|
|
return nil
|
|
}
|