From 2f6065f541abba32627ac3a15f80f3a89dc1966b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 25 Jun 2020 16:10:08 +0300 Subject: [PATCH] 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. --- cli/smartcontract/smart_contract.go | 12 +-- pkg/compiler/compiler.go | 15 ++-- pkg/compiler/debug.go | 119 +++++++++++++++++++++++----- pkg/compiler/debug_test.go | 79 ++++++++++++++---- 4 files changed, 175 insertions(+), 50 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 60999fdd8..1006bf17e 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -123,8 +123,8 @@ func NewCommands() []cli.Command { Usage: "Emit debug info in a separate file", }, cli.StringFlag{ - Name: "abi, a", - Usage: "Emit application binary interface (.abi.json) file into separate file using configuration input file (*.yml)", + Name: "manifest, m", + Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)", }, cli.StringFlag{ Name: "config, c", @@ -349,17 +349,17 @@ func contractCompile(ctx *cli.Context) error { if len(src) == 0 { return cli.NewExitError(errNoInput, 1) } - abi := ctx.String("abi") + manifestFile := ctx.String("manifest") confFile := ctx.String("config") - if len(abi) != 0 && len(confFile) == 0 { + if len(manifestFile) != 0 && len(confFile) == 0 { return cli.NewExitError(errNoConfFile, 1) } o := &compiler.Options{ Outfile: ctx.String("out"), - DebugInfo: ctx.String("debug"), - ABIInfo: abi, + DebugInfo: ctx.String("debug"), + ManifestFile: manifestFile, } if len(confFile) != 0 { diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index e211c0774..292b5d7bd 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -29,8 +29,8 @@ type Options struct { // The name of the output for debug info. DebugInfo string - // The name of the output for application binary interface info. - ABIInfo string + // The name of the output for contract manifest file. + ManifestFile string // Contract metadata. 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 { return b, err } - if o.ABIInfo == "" { + if o.ManifestFile == "" { return b, err } - abi := di.convertToABI(o.ContractFeatures) - abiData, err := json.Marshal(abi) + m, err := di.convertToManifest(o.ContractFeatures) if err != nil { 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) } diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 96fdebcbe..b496d016b 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -11,6 +11,7 @@ import ( "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/manifest" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -267,6 +268,59 @@ func (d *DebugParam) UnmarshalJSON(data []byte) error { 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. func (d *DebugMethodName) MarshalJSON() ([]byte, error) { 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 } -// 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. -func (di *DebugInfo) convertToABI(fs smartcontract.PropertyState) ABI { - methods := make([]Method, 0) +func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) { + var ( + entryPoint manifest.Method + err error + ) for _, method := range di.Methods { if method.Name.Name == mainIdent { - methods = append(methods, Method{ - Name: method.Name.Name, - Parameters: method.Parameters, - ReturnType: method.ReturnType, - }) + entryPoint, err = method.ToManifestMethod() + if err != nil { + return nil, err + } break } } - events := make([]Event, len(di.Events)) - for i, event := range di.Events { - events[i] = Event{ - Name: event.Name, - Parameters: event.Parameters, + if entryPoint.Name == "" { + return nil, errors.New("no Main method was found") + } + methods := make([]manifest.Method, 0, len(di.Methods)-1) + 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{ - Hash: di.Hash, - Metadata: Metadata{ - HasStorage: fs&smartcontract.HasStorage != 0, - IsPayable: fs&smartcontract.IsPayable != 0, - }, - EntryPoint: mainIdent, - Functions: methods, + events := make([]manifest.Event, len(di.Events)) + for i, event := range di.Events { + events[i], err = event.ToManifestEvent() + if err != nil { + return nil, err + } + } + + result := manifest.NewManifest(di.Hash) + result.Features = fs + result.ABI = manifest.ABI{ + Hash: di.Hash, + EntryPoint: entryPoint, + Methods: methods, Events: events, } + result.Permissions = []manifest.Permission{ + { + Contract: manifest.PermissionDesc{ + Type: manifest.PermissionWildcard, + }, + Methods: manifest.WildStrings{}, + }, + } + return result, nil } diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 09af1421d..c614a670a 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -6,6 +6,7 @@ import ( "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/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" @@ -121,29 +122,73 @@ func methodStruct() struct{} { return struct{}{} } require.EqualValues(t, opcode.RET, buf[index]) } - t.Run("convert to ABI", func(t *testing.T) { - actual := d.convertToABI(smartcontract.HasStorage) - expected := ABI{ - Hash: hash.Hash160(buf), - Metadata: Metadata{ - HasStorage: true, - HasDynamicInvocation: false, - IsPayable: false, - }, - EntryPoint: mainIdent, - Functions: []Method{ - { - Name: mainIdent, - Parameters: []DebugParam{ + t.Run("convert to Manifest", func(t *testing.T) { + actual, err := d.convertToManifest(smartcontract.HasStorage) + require.NoError(t, err) + expected := &manifest.Manifest{ + ABI: manifest.ABI{ + Hash: hash.Hash160(buf), + EntryPoint: manifest.Method{ + Name: "Main", + Parameters: []manifest.Parameter{ { 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.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods)