compiler: compare emitted event params if --guess-eventtypes enabled

In this case emitted event parameters should match from invocation to
invocation. It's an error otherwise (and if the type is not Any).

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-05-25 19:17:49 +03:00
parent 6379bcc15a
commit 865bd6c9cc
12 changed files with 399 additions and 4 deletions

View file

@ -499,3 +499,52 @@ callflags:
"--config", cfgPath, "--out", "zzz")
})
}
func TestCompile_GuessEventTypes(t *testing.T) {
app := cli.NewApp()
app.Commands = NewCommands()
app.ExitErrHandler = func(*cli.Context, error) {}
checkError := func(t *testing.T, msg string, args ...string) {
// cli.ExitError doesn't implement wraping properly, so we check for an error message.
err := app.Run(args)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), msg), "got: %v", err)
}
check := func(t *testing.T, source string, expectedErrText string) {
tmpDir := t.TempDir()
configFile := filepath.Join(source, "invalid.yml")
manifestF := filepath.Join(tmpDir, "invalid.manifest.json")
bindingF := filepath.Join(tmpDir, "invalid.binding.yml")
nefF := filepath.Join(tmpDir, "invalid.out.nef")
cmd := []string{"", "contract", "compile",
"--in", source,
"--config", configFile,
"--manifest", manifestF,
"--bindings", bindingF,
"--out", nefF,
"--guess-eventtypes",
}
checkError(t, expectedErrText, cmd...)
}
t.Run("not declared in manifest", func(t *testing.T) {
check(t, filepath.Join("testdata", "invalid5"), "inconsistent usages of event `Non declared event`: not declared in the contract config")
})
t.Run("invalid number of params", func(t *testing.T) {
check(t, filepath.Join("testdata", "invalid6"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1")
})
/*
// TODO: this on is a controversial one. If event information is provided in the config file, then conversion code
// will be emitted by the compiler according to the parameter type provided via config. Thus, we can be sure that
// either event parameter has the type specified in the config file or the execution of the contract will fail.
// Thus, this testcase is always failing (no compilation error occures).
// Question: do we want to compare `RealType` of the emitted parameter with the one expected in the manifest?
t.Run("SC parameter type mismatch", func(t *testing.T) {
check(t, filepath.Join("testdata", "invalid7"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1")
})
*/
t.Run("extended types mismatch", func(t *testing.T) {
check(t, filepath.Join("testdata", "invalid8"), "inconsistent usages of event `SomeEvent`: extended type of param #0 mismatch")
})
}

View file

@ -0,0 +1,7 @@
package invalid5
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main() {
runtime.Notify("Non declared event")
}

View file

@ -0,0 +1 @@
name: Test undeclared event

View file

@ -0,0 +1,7 @@
package invalid6
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main() {
runtime.Notify("SomeEvent", "p1", "p2")
}

View file

@ -0,0 +1,6 @@
name: Test undeclared event
events:
- name: SomeEvent
parameters:
- name: p1
type: String

View file

@ -0,0 +1,7 @@
package invalid7
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main() {
runtime.Notify("SomeEvent", "p1", 5)
}

View file

@ -0,0 +1,8 @@
name: Test undeclared event
events:
- name: SomeEvent
parameters:
- name: p1
type: String
- name: p2
type: String

View file

@ -0,0 +1,17 @@
package invalid8
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
type SomeStruct1 struct {
Field1 int
}
type SomeStruct2 struct {
Field2 string
}
func Main() {
// Inconsistent event params usages (different named types throughout the usages).
runtime.Notify("SomeEvent", SomeStruct1{Field1: 123})
runtime.Notify("SomeEvent", SomeStruct2{Field2: "str"})
}

View file

@ -0,0 +1,6 @@
name: Test undeclared event
events:
- name: SomeEvent
parameters:
- name: p1
type: Array

View file

@ -356,16 +356,43 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
if o.GuessEventTypes {
if len(di.EmittedEvents) > 0 {
for eventName, eventUsages := range di.EmittedEvents {
var manifestEvent HybridEvent
for _, e := range o.ContractEvents {
if e.Name == eventName {
manifestEvent = e
break
}
}
if len(manifestEvent.Name) == 0 {
return nil, fmt.Errorf("inconsistent usages of event `%s`: not declared in the contract config", eventName)
}
exampleUsage := eventUsages[0]
for _, usage := range eventUsages {
if len(usage.Params) != len(manifestEvent.Parameters) {
return nil, fmt.Errorf("inconsistent usages of event `%s` against config: number of params mismatch: %d vs %d", eventName, len(exampleUsage.Params), len(manifestEvent.Parameters))
}
for i, actual := range usage.Params {
mParam := manifestEvent.Parameters[i]
// TODO: see the TestCompile_GuessEventTypes, "SC parameter type mismatch" section,
// do we want to compare with actual.RealType? The conversion code is emitted by the
// compiler for it, so we expect the parameter to be of the proper type.
if !(mParam.Type == smartcontract.AnyType || actual.TypeSC == mParam.Type) {
return nil, fmt.Errorf("inconsistent usages of event `%s` against config: SC type of param #%d mismatch: %s vs %s", eventName, i, actual.TypeSC, mParam.Type)
}
expected := exampleUsage.Params[i]
if !actual.ExtendedType.Equals(expected.ExtendedType) {
return nil, fmt.Errorf("inconsistent usages of event `%s`: extended type of param #%d mismatch", eventName, i)
}
}
}
eBindingName := rpcbinding.ToEventBindingName(eventName)
// Take into account the first usage only.
// TODO: extend it to the rest of invocations.
for typeName, extType := range eventUsages[0].ExtTypes {
for typeName, extType := range exampleUsage.ExtTypes {
if _, ok := cfg.NamedTypes[typeName]; !ok {
cfg.NamedTypes[typeName] = extType
}
}
for _, p := range eventUsages[0].Params {
for _, p := range exampleUsage.Params {
pBindingName := rpcbinding.ToParameterBindingName(p.Name)
pname := eBindingName + "." + pBindingName
if p.RealType.TypeName != "" {

View file

@ -265,3 +265,34 @@ func TemplateFromManifest(cfg Config, scTypeConverter func(string, smartcontract
func upperFirst(s string) string {
return strings.ToUpper(s[0:1]) + s[1:]
}
// Equals compares two extended types field-by-field and returns true if they are
// equal.
func (e *ExtendedType) Equals(other *ExtendedType) bool {
if e == nil && other == nil {
return true
}
if e != nil && other == nil ||
e == nil && other != nil {
return false
}
if !((e.Base == other.Base || (e.Base == smartcontract.ByteArrayType || e.Base == smartcontract.StringType) &&
(other.Base == smartcontract.ByteArrayType || other.Base == smartcontract.StringType)) &&
e.Name == other.Name &&
e.Interface == other.Interface &&
e.Key == other.Key) {
return false
}
if len(e.Fields) != len(other.Fields) {
return false
}
for i := range e.Fields {
if e.Fields[i].Field != other.Fields[i].Field {
return false
}
if !e.Fields[i].ExtendedType.Equals(&other.Fields[i].ExtendedType) {
return false
}
}
return (e.Value == nil && other.Value == nil) || (e.Value != nil && other.Value != nil && e.Value.Equals(other.Value))
}

View file

@ -0,0 +1,229 @@
package binding
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/stretchr/testify/assert"
)
func TestExtendedType_Equals(t *testing.T) {
crazyT := ExtendedType{
Base: smartcontract.StringType,
Name: "qwertyu",
Interface: "qwerty",
Key: smartcontract.BoolType,
Value: &ExtendedType{
Base: smartcontract.IntegerType,
},
Fields: []FieldExtendedType{
{
Field: "qwe",
ExtendedType: ExtendedType{
Base: smartcontract.IntegerType,
Name: "qwer",
Interface: "qw",
Key: smartcontract.ArrayType,
Fields: []FieldExtendedType{
{
Field: "as",
},
},
},
},
{
Field: "asf",
ExtendedType: ExtendedType{
Base: smartcontract.BoolType,
},
},
{
Field: "sffg",
ExtendedType: ExtendedType{
Base: smartcontract.AnyType,
},
},
},
}
tcs := map[string]struct {
a *ExtendedType
b *ExtendedType
expectedRes bool
}{
"both nil": {
a: nil,
b: nil,
expectedRes: true,
},
"a is nil": {
a: nil,
b: &ExtendedType{},
expectedRes: false,
},
"b is nil": {
a: &ExtendedType{},
b: nil,
expectedRes: false,
},
"base mismatch": {
a: &ExtendedType{
Base: smartcontract.StringType,
},
b: &ExtendedType{
Base: smartcontract.IntegerType,
},
expectedRes: false,
},
"name mismatch": {
a: &ExtendedType{
Base: smartcontract.ArrayType,
Name: "q",
},
b: &ExtendedType{
Base: smartcontract.ArrayType,
Name: "w",
},
expectedRes: false,
},
"number of fields mismatch": {
a: &ExtendedType{
Base: smartcontract.ArrayType,
Name: "q",
Fields: []FieldExtendedType{
{
Field: "IntField",
ExtendedType: ExtendedType{Base: smartcontract.IntegerType},
},
},
},
b: &ExtendedType{
Base: smartcontract.ArrayType,
Name: "w",
Fields: []FieldExtendedType{
{
Field: "IntField",
ExtendedType: ExtendedType{Base: smartcontract.IntegerType},
},
{
Field: "BoolField",
ExtendedType: ExtendedType{Base: smartcontract.BoolType},
},
},
},
expectedRes: false,
},
"field names mismatch": {
a: &ExtendedType{
Base: smartcontract.ArrayType,
Fields: []FieldExtendedType{
{
Field: "IntField",
ExtendedType: ExtendedType{Base: smartcontract.IntegerType},
},
},
},
b: &ExtendedType{
Base: smartcontract.ArrayType,
Fields: []FieldExtendedType{
{
Field: "BoolField",
ExtendedType: ExtendedType{Base: smartcontract.BoolType},
},
},
},
expectedRes: false,
},
"field types mismatch": {
a: &ExtendedType{
Base: smartcontract.ArrayType,
Fields: []FieldExtendedType{
{
Field: "Field",
ExtendedType: ExtendedType{Base: smartcontract.IntegerType},
},
},
},
b: &ExtendedType{
Base: smartcontract.ArrayType,
Fields: []FieldExtendedType{
{
Field: "Field",
ExtendedType: ExtendedType{Base: smartcontract.BoolType},
},
},
},
expectedRes: false,
},
"interface mismatch": {
a: &ExtendedType{Interface: "iterator"},
b: &ExtendedType{Interface: "unknown"},
expectedRes: false,
},
"value is nil": {
a: &ExtendedType{
Base: smartcontract.StringType,
},
b: &ExtendedType{
Base: smartcontract.StringType,
},
expectedRes: true,
},
"a value is not nil": {
a: &ExtendedType{
Base: smartcontract.ArrayType,
Value: &ExtendedType{},
},
b: &ExtendedType{
Base: smartcontract.ArrayType,
},
expectedRes: false,
},
"b value is not nil": {
a: &ExtendedType{
Base: smartcontract.ArrayType,
},
b: &ExtendedType{
Base: smartcontract.ArrayType,
Value: &ExtendedType{},
},
expectedRes: false,
},
"byte array tolerance for a": {
a: &ExtendedType{
Base: smartcontract.StringType,
},
b: &ExtendedType{
Base: smartcontract.ByteArrayType,
},
expectedRes: true,
},
"byte array tolerance for b": {
a: &ExtendedType{
Base: smartcontract.ByteArrayType,
},
b: &ExtendedType{
Base: smartcontract.StringType,
},
expectedRes: true,
},
"key mismatch": {
a: &ExtendedType{
Key: smartcontract.StringType,
},
b: &ExtendedType{
Key: smartcontract.IntegerType,
},
expectedRes: false,
},
"good nested": {
a: &crazyT,
b: &crazyT,
expectedRes: true,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
assert.Equal(t, tc.expectedRes, tc.a.Equals(tc.b))
})
}
}