runtime: check notifications against ABI
Related to #2703, just a logged thing for now.
This commit is contained in:
parent
48567fbc61
commit
79887f9d78
7 changed files with 196 additions and 57 deletions
|
@ -73,9 +73,20 @@ func Notify(ic *interop.Context) error {
|
||||||
if len(name) > MaxEventNameLen {
|
if len(name) > MaxEventNameLen {
|
||||||
return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
|
return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
|
||||||
}
|
}
|
||||||
if !ic.VM.Context().IsDeployed() {
|
curHash := ic.VM.GetCurrentScriptHash()
|
||||||
|
ctr, err := ic.GetContract(curHash)
|
||||||
|
if err != nil {
|
||||||
return errors.New("notifications are not allowed in dynamic scripts")
|
return errors.New("notifications are not allowed in dynamic scripts")
|
||||||
}
|
}
|
||||||
|
ev := ctr.Manifest.ABI.GetEvent(name)
|
||||||
|
if ev == nil {
|
||||||
|
ic.Log.Info("bad notification", zap.String("contract", curHash.StringLE()), zap.String("event", name), zap.Error(fmt.Errorf("event %s does not exist", name)))
|
||||||
|
} else {
|
||||||
|
err = ev.CheckCompliance(args)
|
||||||
|
if err != nil {
|
||||||
|
ic.Log.Info("bad notification", zap.String("contract", curHash.StringLE()), zap.String("event", name), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// But it has to be serializable, otherwise we either have some broken
|
// But it has to be serializable, otherwise we either have some broken
|
||||||
// (recursive) structure inside or an interop item that can't be used
|
// (recursive) structure inside or an interop item that can't be used
|
||||||
|
@ -87,7 +98,7 @@ func Notify(ic *interop.Context) error {
|
||||||
if len(bytes) > MaxNotificationSize {
|
if len(bytes) > MaxNotificationSize {
|
||||||
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
|
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
|
||||||
}
|
}
|
||||||
ic.AddNotification(ic.VM.GetCurrentScriptHash(), name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
|
ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,9 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
@ -131,55 +129,3 @@ func TestLog(t *testing.T) {
|
||||||
require.Equal(t, h.StringLE(), logMsg["script"])
|
require.Equal(t, h.StringLE(), logMsg["script"])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotify(t *testing.T) {
|
|
||||||
h := random.Uint160()
|
|
||||||
caller := random.Uint160()
|
|
||||||
exe, err := nef.NewFile([]byte{1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
newIC := func(name string, args interface{}) *interop.Context {
|
|
||||||
ic := &interop.Context{VM: vm.New(), DAO: &dao.Simple{}}
|
|
||||||
ic.VM.LoadNEFMethod(exe, caller, h, callflag.NoneFlag, true, 0, -1, nil)
|
|
||||||
ic.VM.Estack().PushVal(args)
|
|
||||||
ic.VM.Estack().PushVal(name)
|
|
||||||
return ic
|
|
||||||
}
|
|
||||||
t.Run("big name", func(t *testing.T) {
|
|
||||||
ic := newIC(string(make([]byte, MaxEventNameLen+1)), stackitem.NewArray([]stackitem.Item{stackitem.Null{}}))
|
|
||||||
require.Error(t, Notify(ic))
|
|
||||||
})
|
|
||||||
t.Run("dynamic script", func(t *testing.T) {
|
|
||||||
ic := &interop.Context{VM: vm.New(), DAO: &dao.Simple{}}
|
|
||||||
ic.VM.LoadScriptWithHash([]byte{1}, h, callflag.NoneFlag)
|
|
||||||
ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(42)}))
|
|
||||||
ic.VM.Estack().PushVal("event")
|
|
||||||
require.Error(t, Notify(ic))
|
|
||||||
})
|
|
||||||
t.Run("recursive struct", func(t *testing.T) {
|
|
||||||
arr := stackitem.NewArray([]stackitem.Item{stackitem.Null{}})
|
|
||||||
arr.Append(arr)
|
|
||||||
ic := newIC("event", arr)
|
|
||||||
require.Error(t, Notify(ic))
|
|
||||||
})
|
|
||||||
t.Run("big notification", func(t *testing.T) {
|
|
||||||
bs := stackitem.NewByteArray(make([]byte, MaxNotificationSize+1))
|
|
||||||
arr := stackitem.NewArray([]stackitem.Item{bs})
|
|
||||||
ic := newIC("event", arr)
|
|
||||||
require.Error(t, Notify(ic))
|
|
||||||
})
|
|
||||||
t.Run("good", func(t *testing.T) {
|
|
||||||
arr := stackitem.NewArray([]stackitem.Item{stackitem.Make(42)})
|
|
||||||
ic := newIC("good event", arr)
|
|
||||||
require.NoError(t, Notify(ic))
|
|
||||||
require.Equal(t, 1, len(ic.Notifications))
|
|
||||||
|
|
||||||
arr.MarkAsReadOnly() // tiny hack for test to be able to compare object references.
|
|
||||||
ev := ic.Notifications[0]
|
|
||||||
require.Equal(t, "good event", ev.Name)
|
|
||||||
require.Equal(t, h, ev.ScriptHash)
|
|
||||||
require.Equal(t, arr, ev.Item)
|
|
||||||
// Check deep copy.
|
|
||||||
arr.Value().([]stackitem.Item)[0] = stackitem.Null{}
|
|
||||||
require.NotEqual(t, arr, ev.Item)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Ui
|
||||||
ic.VM.GasLimit = -1
|
ic.VM.GasLimit = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBurnGas(t *testing.T) {
|
func getDeployedInternal(t *testing.T) (*neotest.Executor, neotest.Signer, *core.Blockchain, *state.Contract) {
|
||||||
bc, acc := chain.NewSingle(t)
|
bc, acc := chain.NewSingle(t)
|
||||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||||
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
|
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
|
||||||
|
@ -92,6 +92,12 @@ func TestBurnGas(t *testing.T) {
|
||||||
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
|
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
|
||||||
e.AddNewBlock(t, tx)
|
e.AddNewBlock(t, tx)
|
||||||
e.CheckHalt(t, tx.Hash())
|
e.CheckHalt(t, tx.Hash())
|
||||||
|
|
||||||
|
return e, acc, bc, cs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBurnGas(t *testing.T) {
|
||||||
|
e, acc, _, cs := getDeployedInternal(t)
|
||||||
cInvoker := e.ValidatorInvoker(cs.Hash)
|
cInvoker := e.ValidatorInvoker(cs.Hash)
|
||||||
|
|
||||||
t.Run("good", func(t *testing.T) {
|
t.Run("good", func(t *testing.T) {
|
||||||
|
@ -539,3 +545,53 @@ func TestGetRandomCompatibility(t *testing.T) {
|
||||||
require.NoError(t, runtime.GetRandom(ic))
|
require.NoError(t, runtime.GetRandom(ic))
|
||||||
require.Equal(t, "247152297361212656635216876565962360375", ic.VM.Estack().Pop().BigInt().String())
|
require.Equal(t, "247152297361212656635216876565962360375", ic.VM.Estack().Pop().BigInt().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
caller := random.Uint160()
|
||||||
|
newIC := func(name string, args interface{}) *interop.Context {
|
||||||
|
_, _, bc, cs := getDeployedInternal(t)
|
||||||
|
ic := bc.GetTestVM(trigger.Application, nil, nil)
|
||||||
|
ic.VM.LoadNEFMethod(&cs.NEF, caller, cs.Hash, callflag.NoneFlag, true, 0, -1, nil)
|
||||||
|
ic.VM.Estack().PushVal(args)
|
||||||
|
ic.VM.Estack().PushVal(name)
|
||||||
|
return ic
|
||||||
|
}
|
||||||
|
t.Run("big name", func(t *testing.T) {
|
||||||
|
ic := newIC(string(make([]byte, runtime.MaxEventNameLen+1)), stackitem.NewArray([]stackitem.Item{stackitem.Null{}}))
|
||||||
|
require.Error(t, runtime.Notify(ic))
|
||||||
|
})
|
||||||
|
t.Run("dynamic script", func(t *testing.T) {
|
||||||
|
ic := newIC("some", stackitem.Null{})
|
||||||
|
ic.VM.LoadScriptWithHash([]byte{1}, random.Uint160(), callflag.NoneFlag)
|
||||||
|
ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(42)}))
|
||||||
|
ic.VM.Estack().PushVal("event")
|
||||||
|
require.Error(t, runtime.Notify(ic))
|
||||||
|
})
|
||||||
|
t.Run("recursive struct", func(t *testing.T) {
|
||||||
|
arr := stackitem.NewArray([]stackitem.Item{stackitem.Null{}})
|
||||||
|
arr.Append(arr)
|
||||||
|
ic := newIC("event", arr)
|
||||||
|
require.Error(t, runtime.Notify(ic))
|
||||||
|
})
|
||||||
|
t.Run("big notification", func(t *testing.T) {
|
||||||
|
bs := stackitem.NewByteArray(make([]byte, runtime.MaxNotificationSize+1))
|
||||||
|
arr := stackitem.NewArray([]stackitem.Item{bs})
|
||||||
|
ic := newIC("event", arr)
|
||||||
|
require.Error(t, runtime.Notify(ic))
|
||||||
|
})
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
arr := stackitem.NewArray([]stackitem.Item{stackitem.Make(42)})
|
||||||
|
ic := newIC("good event", arr)
|
||||||
|
require.NoError(t, runtime.Notify(ic))
|
||||||
|
require.Equal(t, 1, len(ic.Notifications))
|
||||||
|
|
||||||
|
arr.MarkAsReadOnly() // tiny hack for test to be able to compare object references.
|
||||||
|
ev := ic.Notifications[0]
|
||||||
|
require.Equal(t, "good event", ev.Name)
|
||||||
|
require.Equal(t, ic.VM.GetCurrentScriptHash(), ev.ScriptHash)
|
||||||
|
require.Equal(t, arr, ev.Item)
|
||||||
|
// Check deep copy.
|
||||||
|
arr.Value().([]stackitem.Item)[0] = stackitem.Null{}
|
||||||
|
require.NotEqual(t, arr, ev.Item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package manifest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
@ -60,3 +61,17 @@ func (e *Event) FromStackItem(item stackitem.Item) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckCompliance checks compliance of the given array of items with the
|
||||||
|
// current event.
|
||||||
|
func (e *Event) CheckCompliance(items []stackitem.Item) error {
|
||||||
|
if len(items) != len(e.Parameters) {
|
||||||
|
return errors.New("mismatch between the number of parameters and items")
|
||||||
|
}
|
||||||
|
for i := range items {
|
||||||
|
if !e.Parameters[i].Type.Match(items[i]) {
|
||||||
|
return fmt.Errorf("parameter %d type mismatch: %s vs %s", i, e.Parameters[i].Type.String(), items[i].Type().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -64,3 +64,13 @@ func TestEvent_FromStackItemErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEventCheckCompliance(t *testing.T) {
|
||||||
|
m := &Event{
|
||||||
|
Name: "mur",
|
||||||
|
Parameters: []Parameter{{Name: "p1", Type: smartcontract.BoolType}},
|
||||||
|
}
|
||||||
|
require.Error(t, m.CheckCompliance([]stackitem.Item{}))
|
||||||
|
require.Error(t, m.CheckCompliance([]stackitem.Item{stackitem.Make("something")}))
|
||||||
|
require.NoError(t, m.CheckCompliance([]stackitem.Item{stackitem.Make(true)}))
|
||||||
|
}
|
||||||
|
|
|
@ -170,6 +170,52 @@ func (pt ParamType) EncodeDefaultValue(w *io.BinWriter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkBytesWithLen(vt stackitem.Type, v stackitem.Item, l int) bool {
|
||||||
|
if vt == stackitem.AnyT {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if vt != stackitem.ByteArrayT && vt != stackitem.BufferT {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b, _ := v.TryBytes() // Can't fail, we know the type exactly.
|
||||||
|
return len(b) == l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt ParamType) Match(v stackitem.Item) bool {
|
||||||
|
vt := v.Type()
|
||||||
|
|
||||||
|
// Pointer can't be matched at all.
|
||||||
|
if vt == stackitem.PointerT {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch pt {
|
||||||
|
case AnyType:
|
||||||
|
return true
|
||||||
|
case BoolType:
|
||||||
|
return vt == stackitem.BooleanT
|
||||||
|
case IntegerType:
|
||||||
|
return vt == stackitem.IntegerT
|
||||||
|
case ByteArrayType, StringType:
|
||||||
|
return vt == stackitem.ByteArrayT || vt == stackitem.BufferT || vt == stackitem.AnyT
|
||||||
|
case Hash160Type:
|
||||||
|
return checkBytesWithLen(vt, v, 20)
|
||||||
|
case Hash256Type:
|
||||||
|
return checkBytesWithLen(vt, v, 32)
|
||||||
|
case PublicKeyType:
|
||||||
|
return checkBytesWithLen(vt, v, 33)
|
||||||
|
case SignatureType:
|
||||||
|
return checkBytesWithLen(vt, v, 64)
|
||||||
|
case ArrayType:
|
||||||
|
return vt == stackitem.AnyT || vt == stackitem.ArrayT || vt == stackitem.StructT
|
||||||
|
case MapType:
|
||||||
|
return vt == stackitem.AnyT || vt == stackitem.MapT
|
||||||
|
case InteropInterfaceType:
|
||||||
|
return vt == stackitem.AnyT || vt == stackitem.InteropT
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ParseParamType is a user-friendly string to ParamType converter, it's
|
// ParseParamType is a user-friendly string to ParamType converter, it's
|
||||||
// case-insensitive and makes the following conversions:
|
// case-insensitive and makes the following conversions:
|
||||||
//
|
//
|
||||||
|
|
|
@ -418,3 +418,58 @@ func TestConvertToStackitemType(t *testing.T) {
|
||||||
UnknownType.ConvertToStackitemType()
|
UnknownType.ConvertToStackitemType()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParamTypeMatch(t *testing.T) {
|
||||||
|
for itm, pt := range map[stackitem.Item]ParamType{
|
||||||
|
&stackitem.Pointer{}: BoolType,
|
||||||
|
&stackitem.Pointer{}: MapType,
|
||||||
|
stackitem.Make(0): BoolType,
|
||||||
|
stackitem.Make(0): ByteArrayType,
|
||||||
|
stackitem.Make(0): StringType,
|
||||||
|
stackitem.Make(false): ByteArrayType,
|
||||||
|
stackitem.Make(true): StringType,
|
||||||
|
stackitem.Make([]byte{1}): Hash160Type,
|
||||||
|
stackitem.Make([]byte{1}): Hash256Type,
|
||||||
|
stackitem.Make([]byte{1}): PublicKeyType,
|
||||||
|
stackitem.Make([]byte{1}): SignatureType,
|
||||||
|
stackitem.Make(0): Hash160Type,
|
||||||
|
stackitem.Make(0): Hash256Type,
|
||||||
|
stackitem.Make(0): PublicKeyType,
|
||||||
|
stackitem.Make(0): SignatureType,
|
||||||
|
stackitem.Make(0): ArrayType,
|
||||||
|
stackitem.Make(0): MapType,
|
||||||
|
stackitem.Make(0): InteropInterfaceType,
|
||||||
|
stackitem.Make(0): VoidType,
|
||||||
|
} {
|
||||||
|
require.Falsef(t, pt.Match(itm), "%s - %s", pt.String(), itm.String())
|
||||||
|
}
|
||||||
|
for itm, pt := range map[stackitem.Item]ParamType{
|
||||||
|
stackitem.Make(false): BoolType,
|
||||||
|
stackitem.Make(true): BoolType,
|
||||||
|
stackitem.Make(0): IntegerType,
|
||||||
|
stackitem.Make(100500): IntegerType,
|
||||||
|
stackitem.Make([]byte{1}): ByteArrayType,
|
||||||
|
stackitem.Make([]byte{1}): StringType,
|
||||||
|
stackitem.NewBuffer([]byte{1}): ByteArrayType,
|
||||||
|
stackitem.NewBuffer([]byte{1}): StringType,
|
||||||
|
stackitem.Null{}: ByteArrayType,
|
||||||
|
stackitem.Null{}: StringType,
|
||||||
|
stackitem.Make(util.Uint160{}.BytesBE()): Hash160Type,
|
||||||
|
stackitem.Make(util.Uint256{}.BytesBE()): Hash256Type,
|
||||||
|
stackitem.Null{}: Hash160Type,
|
||||||
|
stackitem.Null{}: Hash256Type,
|
||||||
|
stackitem.Make(make([]byte, 33)): PublicKeyType,
|
||||||
|
stackitem.Null{}: PublicKeyType,
|
||||||
|
stackitem.Make(make([]byte, 64)): SignatureType,
|
||||||
|
stackitem.Null{}: SignatureType,
|
||||||
|
stackitem.Make([]stackitem.Item{}): ArrayType,
|
||||||
|
stackitem.NewStruct([]stackitem.Item{}): ArrayType,
|
||||||
|
stackitem.Null{}: ArrayType,
|
||||||
|
stackitem.NewMap(): MapType,
|
||||||
|
stackitem.Null{}: MapType,
|
||||||
|
stackitem.NewInterop(true): InteropInterfaceType,
|
||||||
|
stackitem.Null{}: InteropInterfaceType,
|
||||||
|
} {
|
||||||
|
require.Truef(t, pt.Match(itm), "%s - %s", pt.String(), itm.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue