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")
|
"--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 o.GuessEventTypes {
|
||||||
if len(di.EmittedEvents) > 0 {
|
if len(di.EmittedEvents) > 0 {
|
||||||
for eventName, eventUsages := range di.EmittedEvents {
|
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)
|
eBindingName := rpcbinding.ToEventBindingName(eventName)
|
||||||
// Take into account the first usage only.
|
for typeName, extType := range exampleUsage.ExtTypes {
|
||||||
// TODO: extend it to the rest of invocations.
|
|
||||||
for typeName, extType := range eventUsages[0].ExtTypes {
|
|
||||||
if _, ok := cfg.NamedTypes[typeName]; !ok {
|
if _, ok := cfg.NamedTypes[typeName]; !ok {
|
||||||
cfg.NamedTypes[typeName] = extType
|
cfg.NamedTypes[typeName] = extType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range eventUsages[0].Params {
|
for _, p := range exampleUsage.Params {
|
||||||
pBindingName := rpcbinding.ToParameterBindingName(p.Name)
|
pBindingName := rpcbinding.ToParameterBindingName(p.Name)
|
||||||
pname := eBindingName + "." + pBindingName
|
pname := eBindingName + "." + pBindingName
|
||||||
if p.RealType.TypeName != "" {
|
if p.RealType.TypeName != "" {
|
||||||
|
|
|
@ -265,3 +265,34 @@ func TemplateFromManifest(cfg Config, scTypeConverter func(string, smartcontract
|
||||||
func upperFirst(s string) string {
|
func upperFirst(s string) string {
|
||||||
return strings.ToUpper(s[0:1]) + s[1:]
|
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