package compiler import ( "encoding/json" "strings" "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCodeGen_DebugInfo(t *testing.T) { src := `package foo import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop/storage" import "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" var staticVar int func init() { a := 1 _ = a } func init() { x := "" _ = x staticVar = 1 } func Main(op string) bool { var s string _ = s res := MethodInt(op) _ = MethodString() _ = MethodByteArray() _ = MethodArray() _ = MethodStruct() _ = MethodConcat("a", "b", "c") _ = unexportedMethod() return res == 42 } func MethodInt(a string) int { if a == "get42" { return 42 } return 3 } func MethodConcat(a, b string, c string) string{ return a + b + c } func MethodString() string { return "" } func MethodByteArray() []byte { return nil } func MethodArray() []bool { return nil } func MethodStruct() struct{} { return struct{}{} } func unexportedMethod() int { return 1 } func MethodParams(addr interop.Hash160, h interop.Hash256, sig interop.Signature, pub interop.PublicKey, inter interop.Interface, ctx storage.Context, tx ledger.Transaction) bool { return true } type MyStruct struct {} func (ms MyStruct) MethodOnStruct() { } func (ms *MyStruct) MethodOnPointerToStruct() { } func _deploy(data any, isUpdate bool) { x := 1; _ = x } ` ne, d, err := CompileWithOptions("foo.go", strings.NewReader(src), nil) require.NoError(t, err) require.NotNil(t, d) t.Run("return types", func(t *testing.T) { returnTypes := map[string]string{ "MethodInt": "Integer", "MethodConcat": "ByteString", "MethodString": "ByteString", "MethodByteArray": "ByteString", "MethodArray": "Array", "MethodStruct": "Struct", "Main": "Boolean", "unexportedMethod": "Integer", "MethodOnStruct": "Void", "MethodOnPointerToStruct": "Void", "MethodParams": "Boolean", "_deploy": "Void", manifest.MethodInit: "Void", } for i := range d.Methods { name := d.Methods[i].ID assert.Equal(t, returnTypes[name], d.Methods[i].ReturnType) } }) t.Run("variables", func(t *testing.T) { vars := map[string][]string{ "Main": {"s,ByteString", "res,Integer"}, manifest.MethodInit: {"a,Integer", "x,ByteString"}, manifest.MethodDeploy: {"x,Integer"}, } for i := range d.Methods { v, ok := vars[d.Methods[i].ID] if ok { require.Equal(t, v, d.Methods[i].Variables) } } }) t.Run("static variables", func(t *testing.T) { require.Equal(t, []string{"staticVar,Integer"}, d.StaticVariables) }) t.Run("param types", func(t *testing.T) { paramTypes := map[string][]DebugParam{ "_deploy": { { Name: "data", Type: "Any", TypeSC: smartcontract.AnyType, }, { Name: "isUpdate", Type: "Boolean", TypeSC: smartcontract.BoolType, }, }, "MethodInt": {{ Name: "a", Type: "ByteString", RealType: binding.Override{ TypeName: "string", }, TypeSC: smartcontract.StringType, }}, "MethodConcat": { { Name: "a", Type: "ByteString", RealType: binding.Override{ TypeName: "string", }, TypeSC: smartcontract.StringType, }, { Name: "b", Type: "ByteString", RealType: binding.Override{ TypeName: "string", }, TypeSC: smartcontract.StringType, }, { Name: "c", Type: "ByteString", RealType: binding.Override{ TypeName: "string", }, TypeSC: smartcontract.StringType, }, }, "Main": {{ Name: "op", Type: "ByteString", RealType: binding.Override{ TypeName: "string", }, TypeSC: smartcontract.StringType, }}, } for i := range d.Methods { v, ok := paramTypes[d.Methods[i].ID] if ok { require.Equal(t, v, d.Methods[i].Parameters) } } }) // basic check that last instruction of every method is indeed RET for i := range d.Methods { index := d.Methods[i].Range.End require.True(t, int(index) < len(ne.Script)) require.EqualValues(t, opcode.RET, ne.Script[index]) } t.Run("convert to Manifest", func(t *testing.T) { p := manifest.NewPermission(manifest.PermissionWildcard) p.Methods.Add("randomMethod") actual, err := d.ConvertToManifest(&Options{ Name: "MyCTR", SafeMethods: []string{"methodInt", "methodString"}, Permissions: []manifest.Permission{*p}, }) require.NoError(t, err) expected := &manifest.Manifest{ Name: "MyCTR", ABI: manifest.ABI{ Methods: []manifest.Method{ { Name: manifest.MethodInit, Parameters: []manifest.Parameter{}, ReturnType: smartcontract.VoidType, }, { Name: "_deploy", Parameters: []manifest.Parameter{ manifest.NewParameter("data", smartcontract.AnyType), manifest.NewParameter("isUpdate", smartcontract.BoolType), }, ReturnType: smartcontract.VoidType, }, { Name: "main", Parameters: []manifest.Parameter{ manifest.NewParameter("op", smartcontract.StringType), }, ReturnType: smartcontract.BoolType, }, { Name: "methodInt", Parameters: []manifest.Parameter{ { Name: "a", Type: smartcontract.StringType, }, }, ReturnType: smartcontract.IntegerType, Safe: true, }, { Name: "methodString", Parameters: []manifest.Parameter{}, ReturnType: smartcontract.StringType, Safe: true, }, { Name: "methodByteArray", Parameters: []manifest.Parameter{}, ReturnType: smartcontract.ByteArrayType, }, { Name: "methodArray", Parameters: []manifest.Parameter{}, ReturnType: smartcontract.ArrayType, }, { Name: "methodStruct", Parameters: []manifest.Parameter{}, ReturnType: smartcontract.ArrayType, }, { Name: "methodConcat", Parameters: []manifest.Parameter{ { Name: "a", Type: smartcontract.StringType, }, { Name: "b", Type: smartcontract.StringType, }, { Name: "c", Type: smartcontract.StringType, }, }, ReturnType: smartcontract.StringType, }, { Name: "methodParams", Parameters: []manifest.Parameter{ manifest.NewParameter("addr", smartcontract.Hash160Type), manifest.NewParameter("h", smartcontract.Hash256Type), manifest.NewParameter("sig", smartcontract.SignatureType), manifest.NewParameter("pub", smartcontract.PublicKeyType), manifest.NewParameter("inter", smartcontract.InteropInterfaceType), manifest.NewParameter("ctx", smartcontract.InteropInterfaceType), manifest.NewParameter("tx", smartcontract.ArrayType), }, ReturnType: smartcontract.BoolType, }, }, Events: []manifest.Event{}, }, Groups: []manifest.Group{}, Permissions: []manifest.Permission{*p}, Trusts: manifest.WildPermissionDescs{ Value: []manifest.PermissionDesc{}, }, Extra: json.RawMessage("null"), } require.Equal(t, len(expected.ABI.Methods), len(actual.ABI.Methods)) for _, exp := range expected.ABI.Methods { md := actual.ABI.GetMethod(exp.Name, len(exp.Parameters)) require.NotNil(t, md) require.Equal(t, exp.Name, md.Name) require.Equal(t, exp.Parameters, md.Parameters) require.Equal(t, exp.ReturnType, md.ReturnType) require.Equal(t, exp.Safe, md.Safe) } require.Equal(t, expected.ABI.Events, actual.ABI.Events) require.Equal(t, expected.Groups, actual.Groups) require.Equal(t, expected.Permissions, actual.Permissions) require.Equal(t, expected.Trusts, actual.Trusts) require.Equal(t, expected.Extra, actual.Extra) }) } func TestSequencePoints(t *testing.T) { src := `package foo func Main(op string) bool { if op == "123" { return true } return false }` _, d, err := CompileWithOptions("foo.go", strings.NewReader(src), nil) require.NoError(t, err) require.NotNil(t, d) require.Equal(t, 1, len(d.Documents)) require.True(t, strings.HasSuffix(d.Documents[0], "foo.go")) // Main func has 2 return on 4-th and 6-th lines. ps := d.Methods[0].SeqPoints require.Equal(t, 2, len(ps)) require.Equal(t, 4, ps[0].StartLine) require.Equal(t, 6, ps[1].StartLine) } func TestDebugInfo_MarshalJSON(t *testing.T) { d := &DebugInfo{ Documents: []string{"/path/to/file"}, Methods: []MethodDebugInfo{ { ID: "id1", Name: DebugMethodName{ Namespace: "default", Name: "method1", }, Range: DebugRange{Start: 10, End: 20}, Parameters: []DebugParam{ {Name: "param1", Type: "Integer"}, {Name: "ok", Type: "Boolean"}, }, ReturnType: "ByteString", Variables: []string{}, SeqPoints: []DebugSeqPoint{ { Opcode: 123, Document: 1, StartLine: 2, StartCol: 3, EndLine: 4, EndCol: 5, }, }, }, }, Events: []EventDebugInfo{}, } testserdes.MarshalUnmarshalJSON(t, d, new(DebugInfo)) } func TestManifestOverload(t *testing.T) { src := `package foo func Main() int { return 1 } func Add3() int { return Add3Aux(0) } func Add3Aux(a int) int { return a + 3 } func Add3Aux2(b int) int { return b + 3 } func Add4() int { return 4 }` _, di, err := CompileWithOptions("foo.go", strings.NewReader(src), nil) require.NoError(t, err) m, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add3Aux": "add3"}}) require.NoError(t, err) require.NoError(t, m.ABI.IsValid()) require.NotNil(t, m.ABI.GetMethod("add3", 0)) require.NotNil(t, m.ABI.GetMethod("add3", 1)) require.Nil(t, m.ABI.GetMethod("add3Aux", 1)) t.Run("missing method", func(t *testing.T) { _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"miss": "add3"}}) require.Error(t, err) }) t.Run("parameter conflict", func(t *testing.T) { _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add4": "add3"}}) require.Error(t, err) }) t.Run("parameter conflict, overload", func(t *testing.T) { _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{ "add3Aux": "add3", "add3Aux2": "add3", }}) require.Error(t, err) }) t.Run("missing target method", func(t *testing.T) { _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add4": "add5"}}) require.Error(t, err) }) }