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:
parent
6379bcc15a
commit
865bd6c9cc
12 changed files with 399 additions and 4 deletions
|
@ -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")
|
||||
})
|
||||
}
|
||||
|
|
7
cli/smartcontract/testdata/invalid5/invalid.go
vendored
Normal file
7
cli/smartcontract/testdata/invalid5/invalid.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package invalid5
|
||||
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
|
||||
func Main() {
|
||||
runtime.Notify("Non declared event")
|
||||
}
|
1
cli/smartcontract/testdata/invalid5/invalid.yml
vendored
Normal file
1
cli/smartcontract/testdata/invalid5/invalid.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
name: Test undeclared event
|
7
cli/smartcontract/testdata/invalid6/invalid.go
vendored
Normal file
7
cli/smartcontract/testdata/invalid6/invalid.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package invalid6
|
||||
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
|
||||
func Main() {
|
||||
runtime.Notify("SomeEvent", "p1", "p2")
|
||||
}
|
6
cli/smartcontract/testdata/invalid6/invalid.yml
vendored
Normal file
6
cli/smartcontract/testdata/invalid6/invalid.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: Test undeclared event
|
||||
events:
|
||||
- name: SomeEvent
|
||||
parameters:
|
||||
- name: p1
|
||||
type: String
|
7
cli/smartcontract/testdata/invalid7/invalid.go
vendored
Normal file
7
cli/smartcontract/testdata/invalid7/invalid.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package invalid7
|
||||
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
|
||||
func Main() {
|
||||
runtime.Notify("SomeEvent", "p1", 5)
|
||||
}
|
8
cli/smartcontract/testdata/invalid7/invalid.yml
vendored
Normal file
8
cli/smartcontract/testdata/invalid7/invalid.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: Test undeclared event
|
||||
events:
|
||||
- name: SomeEvent
|
||||
parameters:
|
||||
- name: p1
|
||||
type: String
|
||||
- name: p2
|
||||
type: String
|
17
cli/smartcontract/testdata/invalid8/invalid.go
vendored
Normal file
17
cli/smartcontract/testdata/invalid8/invalid.go
vendored
Normal 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"})
|
||||
}
|
6
cli/smartcontract/testdata/invalid8/invalid.yml
vendored
Normal file
6
cli/smartcontract/testdata/invalid8/invalid.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: Test undeclared event
|
||||
events:
|
||||
- name: SomeEvent
|
||||
parameters:
|
||||
- name: p1
|
||||
type: Array
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
229
pkg/smartcontract/binding/generate_test.go
Normal file
229
pkg/smartcontract/binding/generate_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue