2ec1d76320
A part of integration with NEO Blockchain Toolkit (see #902). To be able to deploy smart-contract compiled with neo-go compiler via NEO Express, we have to generate additional .abi.json file. This file contains the following information: - hash of the compiled contract - smart-contract metadata (title, description, version, author, email, has-storage, has-dynamic-invoke, is-payable) - smart-contract entry point - functions - events However, this .abi.json file is slightly different from the one, described in manifest.go, so we have to add auxilaury stractures for json marshalling. The .abi.json format used by NEO-Express is described [here](https://github.com/neo-project/neo-devpack-dotnet/blob/master/src/Neo.Compiler.MSIL/FuncExport.cs#L66).
230 lines
5 KiB
Go
230 lines
5 KiB
Go
package compiler
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"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
|
|
func Main(op string) bool {
|
|
var s string
|
|
_ = s
|
|
res := methodInt(op)
|
|
_ = methodString()
|
|
_ = methodByteArray()
|
|
_ = methodArray()
|
|
_ = methodStruct()
|
|
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{}{} }
|
|
`
|
|
|
|
info, err := getBuildInfo(src)
|
|
require.NoError(t, err)
|
|
|
|
pkg := info.program.Package(info.initialPackage)
|
|
c := newCodegen(info, pkg)
|
|
require.NoError(t, c.compile(info, pkg))
|
|
|
|
buf := c.prog.Bytes()
|
|
d := c.emitDebugInfo()
|
|
require.NotNil(t, d)
|
|
|
|
t.Run("return types", func(t *testing.T) {
|
|
returnTypes := map[string]string{
|
|
"methodInt": "Integer",
|
|
"methodConcat": "String",
|
|
"methodString": "String", "methodByteArray": "ByteArray",
|
|
"methodArray": "Array", "methodStruct": "Struct",
|
|
"Main": "Boolean",
|
|
}
|
|
for i := range d.Methods {
|
|
name := d.Methods[i].Name.Name
|
|
assert.Equal(t, returnTypes[name], d.Methods[i].ReturnType)
|
|
}
|
|
})
|
|
|
|
t.Run("variables", func(t *testing.T) {
|
|
vars := map[string][]string{
|
|
"Main": {"s,String", "res,Integer"},
|
|
}
|
|
for i := range d.Methods {
|
|
v, ok := vars[d.Methods[i].Name.Name]
|
|
if ok {
|
|
require.Equal(t, v, d.Methods[i].Variables)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("param types", func(t *testing.T) {
|
|
paramTypes := map[string][]DebugParam{
|
|
"methodInt": {{
|
|
Name: "a",
|
|
Type: "String",
|
|
}},
|
|
"methodConcat": {
|
|
{
|
|
Name: "a",
|
|
Type: "String",
|
|
},
|
|
{
|
|
Name: "b",
|
|
Type: "String",
|
|
},
|
|
{
|
|
Name: "c",
|
|
Type: "String",
|
|
},
|
|
},
|
|
"Main": {{
|
|
Name: "op",
|
|
Type: "String",
|
|
}},
|
|
}
|
|
for i := range d.Methods {
|
|
v, ok := paramTypes[d.Methods[i].Name.Name]
|
|
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(buf))
|
|
require.EqualValues(t, opcode.RET, buf[index])
|
|
}
|
|
|
|
t.Run("convert to ABI", func(t *testing.T) {
|
|
author := "Joe"
|
|
email := "Joe@ex.com"
|
|
version := "1.0"
|
|
title := "MyProj"
|
|
description := "Description"
|
|
actual := d.convertToABI(buf, &smartcontract.ContractDetails{
|
|
Author: author,
|
|
Email: email,
|
|
Version: version,
|
|
ProjectName: title,
|
|
Description: description,
|
|
HasStorage: true,
|
|
HasDynamicInvocation: false,
|
|
IsPayable: false,
|
|
ReturnType: smartcontract.BoolType,
|
|
Parameters: []smartcontract.ParamType{
|
|
smartcontract.StringType,
|
|
},
|
|
})
|
|
expected := ABI{
|
|
Hash: hash.Hash160(buf),
|
|
Metadata: Metadata{
|
|
Author: author,
|
|
Email: email,
|
|
Version: version,
|
|
Title: title,
|
|
Description: description,
|
|
HasStorage: true,
|
|
HasDynamicInvocation: false,
|
|
IsPayable: false,
|
|
},
|
|
EntryPoint: mainIdent,
|
|
Functions: []Method{
|
|
{
|
|
Name: mainIdent,
|
|
Parameters: []DebugParam{
|
|
{
|
|
Name: "op",
|
|
Type: "String",
|
|
},
|
|
},
|
|
ReturnType: "Boolean",
|
|
},
|
|
},
|
|
Events: []Event{},
|
|
}
|
|
assert.Equal(t, expected, actual)
|
|
})
|
|
}
|
|
|
|
func TestSequencePoints(t *testing.T) {
|
|
src := `package foo
|
|
func Main(op string) bool {
|
|
if op == "123" {
|
|
return true
|
|
}
|
|
return false
|
|
}`
|
|
|
|
info, err := getBuildInfo(src)
|
|
require.NoError(t, err)
|
|
|
|
pkg := info.program.Package(info.initialPackage)
|
|
c := newCodegen(info, pkg)
|
|
require.NoError(t, c.compile(info, pkg))
|
|
|
|
d := c.emitDebugInfo()
|
|
require.NotNil(t, d)
|
|
|
|
// 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{
|
|
EntryPoint: "main",
|
|
Documents: []string{"/path/to/file"},
|
|
Methods: []MethodDebugInfo{
|
|
{
|
|
ID: "id1",
|
|
Name: DebugMethodName{
|
|
Namespace: "default",
|
|
Name: "method1",
|
|
},
|
|
Range: DebugRange{Start: 10, End: 20},
|
|
Parameters: []DebugParam{
|
|
{"param1", "Integer"},
|
|
{"ok", "Boolean"},
|
|
},
|
|
ReturnType: "ByteArray",
|
|
Variables: []string{},
|
|
SeqPoints: []DebugSeqPoint{
|
|
{
|
|
Opcode: 123,
|
|
Document: 1,
|
|
StartLine: 2,
|
|
StartCol: 3,
|
|
EndLine: 4,
|
|
EndCol: 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Events: []EventDebugInfo{},
|
|
}
|
|
|
|
testserdes.MarshalUnmarshalJSON(t, d, new(DebugInfo))
|
|
}
|