2019-10-11 14:00:11 +00:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
2020-07-13 09:59:41 +00:00
|
|
|
"crypto/elliptic"
|
2019-10-11 14:00:11 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
2020-04-08 10:35:39 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
2021-02-03 19:01:20 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
2020-06-16 08:25:30 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
2020-06-03 12:55:06 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2019-10-11 14:00:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// MaxStorageKeyLen is the maximum length of a key for storage items.
|
2020-07-22 08:05:10 +00:00
|
|
|
MaxStorageKeyLen = 64
|
|
|
|
// MaxStorageValueLen is the maximum length of a value for storage items.
|
|
|
|
// It is set to be the maximum value for uint16.
|
|
|
|
MaxStorageValueLen = 65535
|
2019-10-11 14:00:11 +00:00
|
|
|
)
|
|
|
|
|
2020-06-18 10:50:30 +00:00
|
|
|
// StorageContext contains storing id and read/write flag, it's used as
|
2019-10-11 14:00:11 +00:00
|
|
|
// a context for storage manipulation functions.
|
|
|
|
type StorageContext struct {
|
2020-06-18 10:50:30 +00:00
|
|
|
ID int32
|
|
|
|
ReadOnly bool
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 08:22:58 +00:00
|
|
|
// StorageFlag represents storage flag which denotes whether the stored value is
|
|
|
|
// a constant.
|
|
|
|
type StorageFlag byte
|
|
|
|
|
|
|
|
const (
|
|
|
|
// None is a storage flag for non-constant items.
|
|
|
|
None StorageFlag = 0
|
|
|
|
// Constant is a storage flag for constant items.
|
|
|
|
Constant StorageFlag = 0x01
|
|
|
|
)
|
|
|
|
|
2020-07-13 10:05:31 +00:00
|
|
|
// engineGetScriptContainer returns transaction or block that contains the script
|
|
|
|
// being run.
|
2020-08-07 11:37:49 +00:00
|
|
|
func engineGetScriptContainer(ic *interop.Context) error {
|
2020-07-13 10:05:31 +00:00
|
|
|
var item stackitem.Item
|
|
|
|
switch t := ic.Container.(type) {
|
|
|
|
case *transaction.Transaction:
|
2021-02-03 19:01:20 +00:00
|
|
|
item = native.TransactionToStackItem(t)
|
2020-07-13 10:05:31 +00:00
|
|
|
case *block.Block:
|
2021-02-03 19:01:20 +00:00
|
|
|
item = native.BlockToStackItem(t)
|
2020-07-13 10:05:31 +00:00
|
|
|
default:
|
|
|
|
return errors.New("unknown script container")
|
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
ic.VM.Estack().PushVal(item)
|
2019-10-11 14:00:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// storageDelete deletes stored key-value pair.
|
2020-08-07 11:37:49 +00:00
|
|
|
func storageDelete(ic *interop.Context) error {
|
|
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
2019-10-11 14:00:11 +00:00
|
|
|
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")
|
|
|
|
}
|
2020-12-14 09:41:23 +00:00
|
|
|
ic.VM.AddGas(ic.Chain.GetPolicer().GetStoragePrice())
|
2020-08-07 11:37:49 +00:00
|
|
|
key := ic.VM.Estack().Pop().Bytes()
|
2020-06-18 10:50:30 +00:00
|
|
|
si := ic.DAO.GetStorageItem(stc.ID, key)
|
2019-10-11 14:00:11 +00:00
|
|
|
if si != nil && si.IsConst {
|
|
|
|
return errors.New("storage item is constant")
|
|
|
|
}
|
2020-06-18 10:50:30 +00:00
|
|
|
return ic.DAO.DeleteStorageItem(stc.ID, key)
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// storageGet returns stored key-value pair.
|
2020-08-07 11:37:49 +00:00
|
|
|
func storageGet(ic *interop.Context) error {
|
|
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
2019-10-11 14:00:11 +00:00
|
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
key := ic.VM.Estack().Pop().Bytes()
|
2020-06-18 10:50:30 +00:00
|
|
|
si := ic.DAO.GetStorageItem(stc.ID, key)
|
2019-10-11 14:00:11 +00:00
|
|
|
if si != nil && si.Value != nil {
|
2020-08-07 11:37:49 +00:00
|
|
|
ic.VM.Estack().PushVal(si.Value)
|
2019-10-11 14:00:11 +00:00
|
|
|
} else {
|
2020-08-07 11:37:49 +00:00
|
|
|
ic.VM.Estack().PushVal(stackitem.Null{})
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// storageGetContext returns storage context (scripthash).
|
2020-08-07 11:37:49 +00:00
|
|
|
func storageGetContext(ic *interop.Context) error {
|
|
|
|
return storageGetContextInternal(ic, false)
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// storageGetReadOnlyContext returns read-only context (scripthash).
|
2020-08-07 11:37:49 +00:00
|
|
|
func storageGetReadOnlyContext(ic *interop.Context) error {
|
|
|
|
return storageGetContextInternal(ic, true)
|
2020-07-22 07:46:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// storageGetContextInternal is internal version of storageGetContext and
|
|
|
|
// storageGetReadOnlyContext which allows to specify ReadOnly context flag.
|
2020-08-07 11:37:49 +00:00
|
|
|
func storageGetContextInternal(ic *interop.Context, isReadOnly bool) error {
|
2020-12-13 15:26:35 +00:00
|
|
|
contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
|
2020-06-18 10:50:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-11 14:00:11 +00:00
|
|
|
sc := &StorageContext{
|
2020-06-18 10:50:30 +00:00
|
|
|
ID: contract.ID,
|
2020-07-22 07:46:28 +00:00
|
|
|
ReadOnly: isReadOnly,
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
ic.VM.Estack().PushVal(stackitem.NewInterop(sc))
|
2019-10-11 14:00:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-07 11:37:49 +00:00
|
|
|
func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte, value []byte, isConst bool) error {
|
2019-10-11 14:00:11 +00:00
|
|
|
if len(key) > MaxStorageKeyLen {
|
|
|
|
return errors.New("key is too big")
|
|
|
|
}
|
2020-07-22 08:05:10 +00:00
|
|
|
if len(value) > MaxStorageValueLen {
|
|
|
|
return errors.New("value is too big")
|
|
|
|
}
|
2019-10-11 14:00:11 +00:00
|
|
|
if stc.ReadOnly {
|
|
|
|
return errors.New("StorageContext is read only")
|
|
|
|
}
|
2020-06-18 10:50:30 +00:00
|
|
|
si := ic.DAO.GetStorageItem(stc.ID, key)
|
2020-10-05 09:32:04 +00:00
|
|
|
if si != nil && si.IsConst {
|
2019-10-11 14:00:11 +00:00
|
|
|
return errors.New("storage item exists and is read-only")
|
|
|
|
}
|
2020-06-15 08:39:15 +00:00
|
|
|
sizeInc := 1
|
2020-10-05 09:32:04 +00:00
|
|
|
if si == nil {
|
|
|
|
si = &state.StorageItem{}
|
|
|
|
sizeInc = len(key) + len(value)
|
2020-11-03 19:50:40 +00:00
|
|
|
} else if len(value) != 0 {
|
|
|
|
if len(value) <= len(si.Value) {
|
|
|
|
sizeInc = (len(value)-1)/4 + 1
|
|
|
|
} else {
|
|
|
|
sizeInc = (len(si.Value)-1)/4 + 1 + len(value) - len(si.Value)
|
|
|
|
}
|
2020-06-15 08:39:15 +00:00
|
|
|
}
|
2020-12-14 09:41:23 +00:00
|
|
|
if !ic.VM.AddGas(int64(sizeInc) * ic.Chain.GetPolicer().GetStoragePrice()) {
|
2020-06-15 08:39:15 +00:00
|
|
|
return errGasLimitExceeded
|
|
|
|
}
|
2019-10-11 14:00:11 +00:00
|
|
|
si.Value = value
|
|
|
|
si.IsConst = isConst
|
2020-06-18 10:50:30 +00:00
|
|
|
return ic.DAO.PutStorageItem(stc.ID, key, si)
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// storagePutInternal is a unified implementation of storagePut and storagePutEx.
|
2020-08-07 11:37:49 +00:00
|
|
|
func storagePutInternal(ic *interop.Context, getFlag bool) error {
|
|
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
2019-10-11 14:00:11 +00:00
|
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
key := ic.VM.Estack().Pop().Bytes()
|
|
|
|
value := ic.VM.Estack().Pop().Bytes()
|
2019-10-11 14:00:11 +00:00
|
|
|
var flag int
|
|
|
|
if getFlag {
|
2020-08-07 11:37:49 +00:00
|
|
|
flag = int(ic.VM.Estack().Pop().BigInt().Int64())
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
return putWithContextAndFlags(ic, stc, key, value, int(Constant)&flag != 0)
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// storagePut puts key-value pair into the storage.
|
2020-08-07 11:37:49 +00:00
|
|
|
func storagePut(ic *interop.Context) error {
|
|
|
|
return storagePutInternal(ic, false)
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// storagePutEx puts key-value pair with given flags into the storage.
|
2020-08-07 11:37:49 +00:00
|
|
|
func storagePutEx(ic *interop.Context) error {
|
|
|
|
return storagePutInternal(ic, true)
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// storageContextAsReadOnly sets given context to read-only mode.
|
2020-08-07 11:37:49 +00:00
|
|
|
func storageContextAsReadOnly(ic *interop.Context) error {
|
|
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
2019-10-11 14:00:11 +00:00
|
|
|
stc, ok := stcInterface.(*StorageContext)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
|
|
}
|
|
|
|
if !stc.ReadOnly {
|
|
|
|
stx := &StorageContext{
|
2020-06-18 10:50:30 +00:00
|
|
|
ID: stc.ID,
|
|
|
|
ReadOnly: true,
|
2019-10-11 14:00:11 +00:00
|
|
|
}
|
|
|
|
stc = stx
|
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
ic.VM.Estack().PushVal(stackitem.NewInterop(stc))
|
2019-10-11 14:00:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-16 08:17:18 +00:00
|
|
|
// contractIsStandard checks if contract is standard (sig or multisig) contract.
|
2020-08-07 11:37:49 +00:00
|
|
|
func contractIsStandard(ic *interop.Context) error {
|
|
|
|
h := ic.VM.Estack().Pop().Bytes()
|
2020-06-16 08:17:18 +00:00
|
|
|
u, err := util.Uint160DecodeBytesBE(h)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var result bool
|
2020-12-13 15:26:35 +00:00
|
|
|
cs, _ := ic.GetContract(u)
|
2020-07-16 06:56:57 +00:00
|
|
|
if cs != nil {
|
2021-01-13 12:34:10 +00:00
|
|
|
result = vm.IsStandardContract(cs.NEF.Script)
|
2020-07-16 06:56:57 +00:00
|
|
|
} else {
|
|
|
|
if tx, ok := ic.Container.(*transaction.Transaction); ok {
|
|
|
|
for _, witness := range tx.Scripts {
|
|
|
|
if witness.ScriptHash() == u {
|
|
|
|
result = vm.IsStandardContract(witness.VerificationScript)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-16 08:17:18 +00:00
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
ic.VM.Estack().PushVal(result)
|
2020-06-16 08:17:18 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-06-16 08:25:30 +00:00
|
|
|
|
|
|
|
// contractCreateStandardAccount calculates contract scripthash for a given public key.
|
2020-08-07 11:37:49 +00:00
|
|
|
func contractCreateStandardAccount(ic *interop.Context) error {
|
|
|
|
h := ic.VM.Estack().Pop().Bytes()
|
2020-07-13 09:59:41 +00:00
|
|
|
p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256())
|
2020-06-16 08:25:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-07 11:37:49 +00:00
|
|
|
ic.VM.Estack().PushVal(p.GetScriptHash().BytesBE())
|
2020-06-16 08:25:30 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-07-16 09:13:55 +00:00
|
|
|
|
|
|
|
// contractGetCallFlags returns current context calling flags.
|
2020-08-07 11:37:49 +00:00
|
|
|
func contractGetCallFlags(ic *interop.Context) error {
|
|
|
|
ic.VM.Estack().PushVal(ic.VM.Context().GetCallFlags())
|
2020-07-16 09:13:55 +00:00
|
|
|
return nil
|
|
|
|
}
|