compiler, cli: introduce *.manifest.json

Add ability to generate NEO3-compatable *.manifest.json into compiler.
This file represets contract manifest and includes ABI information, so
we don't need to create separate *.abi.json file. NEO3 debugger also
needs *.manifest.json only. So, switched from *.abi.json to
*.manifest.json file.
This commit is contained in:
Anna Shaleva 2020-06-25 16:10:08 +03:00
parent 1c1818d97e
commit 2f6065f541
4 changed files with 175 additions and 50 deletions

View file

@ -123,8 +123,8 @@ func NewCommands() []cli.Command {
Usage: "Emit debug info in a separate file", Usage: "Emit debug info in a separate file",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "abi, a", Name: "manifest, m",
Usage: "Emit application binary interface (.abi.json) file into separate file using configuration input file (*.yml)", Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "config, c", Name: "config, c",
@ -349,17 +349,17 @@ func contractCompile(ctx *cli.Context) error {
if len(src) == 0 { if len(src) == 0 {
return cli.NewExitError(errNoInput, 1) return cli.NewExitError(errNoInput, 1)
} }
abi := ctx.String("abi") manifestFile := ctx.String("manifest")
confFile := ctx.String("config") confFile := ctx.String("config")
if len(abi) != 0 && len(confFile) == 0 { if len(manifestFile) != 0 && len(confFile) == 0 {
return cli.NewExitError(errNoConfFile, 1) return cli.NewExitError(errNoConfFile, 1)
} }
o := &compiler.Options{ o := &compiler.Options{
Outfile: ctx.String("out"), Outfile: ctx.String("out"),
DebugInfo: ctx.String("debug"), DebugInfo: ctx.String("debug"),
ABIInfo: abi, ManifestFile: manifestFile,
} }
if len(confFile) != 0 { if len(confFile) != 0 {

View file

@ -29,8 +29,8 @@ type Options struct {
// The name of the output for debug info. // The name of the output for debug info.
DebugInfo string DebugInfo string
// The name of the output for application binary interface info. // The name of the output for contract manifest file.
ABIInfo string ManifestFile string
// Contract metadata. // Contract metadata.
ContractFeatures smartcontract.PropertyState ContractFeatures smartcontract.PropertyState
@ -124,13 +124,16 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
if err := ioutil.WriteFile(o.DebugInfo, data, os.ModePerm); err != nil { if err := ioutil.WriteFile(o.DebugInfo, data, os.ModePerm); err != nil {
return b, err return b, err
} }
if o.ABIInfo == "" { if o.ManifestFile == "" {
return b, err return b, err
} }
abi := di.convertToABI(o.ContractFeatures) m, err := di.convertToManifest(o.ContractFeatures)
abiData, err := json.Marshal(abi)
if err != nil { if err != nil {
return b, err return b, err
} }
return b, ioutil.WriteFile(o.ABIInfo, abiData, os.ModePerm) mData, err := json.Marshal(m)
if err != nil {
return b, err
}
return b, ioutil.WriteFile(o.ManifestFile, mData, os.ModePerm)
} }

View file

@ -11,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -267,6 +268,59 @@ func (d *DebugParam) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// ToManifestParameter converts DebugParam to manifest.Parameter
func (d *DebugParam) ToManifestParameter() (manifest.Parameter, error) {
pType, err := smartcontract.ParseParamType(d.Type)
if err != nil {
return manifest.Parameter{}, err
}
return manifest.Parameter{
Name: d.Name,
Type: pType,
}, nil
}
// ToManifestMethod converts MethodDebugInfo to manifest.Method
func (m *MethodDebugInfo) ToManifestMethod() (manifest.Method, error) {
var (
result manifest.Method
err error
)
parameters := make([]manifest.Parameter, len(m.Parameters))
for i, p := range m.Parameters {
parameters[i], err = p.ToManifestParameter()
if err != nil {
return result, err
}
}
returnType, err := smartcontract.ParseParamType(m.ReturnType)
if err != nil {
return result, err
}
result.Name = m.Name.Name
result.Parameters = parameters
result.ReturnType = returnType
return result, nil
}
// ToManifestEvent converts EventDebugInfo to manifest.Event
func (e *EventDebugInfo) ToManifestEvent() (manifest.Event, error) {
var (
result manifest.Event
err error
)
parameters := make([]manifest.Parameter, len(e.Parameters))
for i, p := range e.Parameters {
parameters[i], err = p.ToManifestParameter()
if err != nil {
return result, err
}
}
result.Name = e.Name
result.Parameters = parameters
return result, nil
}
// MarshalJSON implements json.Marshaler interface. // MarshalJSON implements json.Marshaler interface.
func (d *DebugMethodName) MarshalJSON() ([]byte, error) { func (d *DebugMethodName) MarshalJSON() ([]byte, error) {
return []byte(`"` + d.Namespace + `,` + d.Name + `"`), nil return []byte(`"` + d.Namespace + `,` + d.Name + `"`), nil
@ -311,35 +365,58 @@ func parsePairJSON(data []byte, sep string) (string, string, error) {
return ss[0], ss[1], nil return ss[0], ss[1], nil
} }
// convertToABI converts contract to the ABI struct for debugger. // convertToManifest converts contract to the manifest.Manifest struct for debugger.
// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038. // Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038.
func (di *DebugInfo) convertToABI(fs smartcontract.PropertyState) ABI { func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) {
methods := make([]Method, 0) var (
entryPoint manifest.Method
err error
)
for _, method := range di.Methods { for _, method := range di.Methods {
if method.Name.Name == mainIdent { if method.Name.Name == mainIdent {
methods = append(methods, Method{ entryPoint, err = method.ToManifestMethod()
Name: method.Name.Name, if err != nil {
Parameters: method.Parameters, return nil, err
ReturnType: method.ReturnType, }
})
break break
} }
} }
events := make([]Event, len(di.Events)) if entryPoint.Name == "" {
for i, event := range di.Events { return nil, errors.New("no Main method was found")
events[i] = Event{ }
Name: event.Name, methods := make([]manifest.Method, 0, len(di.Methods)-1)
Parameters: event.Parameters, for _, method := range di.Methods {
if method.Name.Name != mainIdent {
mMethod, err := method.ToManifestMethod()
if err != nil {
return nil, err
}
methods = append(methods, mMethod)
} }
} }
return ABI{ events := make([]manifest.Event, len(di.Events))
Hash: di.Hash, for i, event := range di.Events {
Metadata: Metadata{ events[i], err = event.ToManifestEvent()
HasStorage: fs&smartcontract.HasStorage != 0, if err != nil {
IsPayable: fs&smartcontract.IsPayable != 0, return nil, err
}, }
EntryPoint: mainIdent, }
Functions: methods,
result := manifest.NewManifest(di.Hash)
result.Features = fs
result.ABI = manifest.ABI{
Hash: di.Hash,
EntryPoint: entryPoint,
Methods: methods,
Events: events, Events: events,
} }
result.Permissions = []manifest.Permission{
{
Contract: manifest.PermissionDesc{
Type: manifest.PermissionWildcard,
},
Methods: manifest.WildStrings{},
},
}
return result, nil
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -121,29 +122,73 @@ func methodStruct() struct{} { return struct{}{} }
require.EqualValues(t, opcode.RET, buf[index]) require.EqualValues(t, opcode.RET, buf[index])
} }
t.Run("convert to ABI", func(t *testing.T) { t.Run("convert to Manifest", func(t *testing.T) {
actual := d.convertToABI(smartcontract.HasStorage) actual, err := d.convertToManifest(smartcontract.HasStorage)
expected := ABI{ require.NoError(t, err)
Hash: hash.Hash160(buf), expected := &manifest.Manifest{
Metadata: Metadata{ ABI: manifest.ABI{
HasStorage: true, Hash: hash.Hash160(buf),
HasDynamicInvocation: false, EntryPoint: manifest.Method{
IsPayable: false, Name: "Main",
}, Parameters: []manifest.Parameter{
EntryPoint: mainIdent,
Functions: []Method{
{
Name: mainIdent,
Parameters: []DebugParam{
{ {
Name: "op", Name: "op",
Type: "String", Type: smartcontract.StringType,
}, },
}, },
ReturnType: "Boolean", ReturnType: smartcontract.BoolType,
},
Methods: []manifest.Method{
{
Name: "methodInt",
Parameters: []manifest.Parameter{
{
Name: "a",
Type: smartcontract.StringType,
},
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "methodString",
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.StringType,
},
{
Name: "methodByteArray",
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.ByteArrayType,
},
{
Name: "methodArray",
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.ArrayType,
},
{
Name: "methodStruct",
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.ArrayType,
},
},
Events: []manifest.Event{},
},
Groups: []manifest.Group{},
Features: smartcontract.HasStorage,
Permissions: []manifest.Permission{
{
Contract: manifest.PermissionDesc{
Type: manifest.PermissionWildcard,
},
Methods: manifest.WildStrings{},
}, },
}, },
Events: []Event{}, Trusts: manifest.WildUint160s{
Value: []util.Uint160{},
},
SafeMethods: manifest.WildStrings{
Value: []string{},
},
Extra: nil,
} }
require.True(t, expected.ABI.Hash.Equals(actual.ABI.Hash)) require.True(t, expected.ABI.Hash.Equals(actual.ABI.Hash))
require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods) require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods)