From 927127e5fb18348a0b9bdf44f96774001772b4ef Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 24 Jun 2020 16:47:57 +0300 Subject: [PATCH 1/9] smartcontract: add nef file It should match C# NEF3 specification in order to debug and deploy smart contracts via NEO3 Blockchain Toolkit. --- pkg/smartcontract/nef/nef.go | 236 ++++++++++++++++++++++++++++++ pkg/smartcontract/nef/nef_test.go | 140 ++++++++++++++++++ 2 files changed, 376 insertions(+) create mode 100644 pkg/smartcontract/nef/nef.go create mode 100644 pkg/smartcontract/nef/nef_test.go diff --git a/pkg/smartcontract/nef/nef.go b/pkg/smartcontract/nef/nef.go new file mode 100644 index 000000000..aff79f3e9 --- /dev/null +++ b/pkg/smartcontract/nef/nef.go @@ -0,0 +1,236 @@ +package nef + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// NEO Executable Format 3 (NEF3) +// Standard: https://github.com/neo-project/proposals/pull/121/files +// Implementation: https://github.com/neo-project/neo/blob/v3.0.0-preview2/src/neo/SmartContract/NefFile.cs#L8 +// +------------+-----------+------------------------------------------------------------+ +// | Field | Length | Comment | +// +------------+-----------+------------------------------------------------------------+ +// | Magic | 4 bytes | Magic header | +// | Compiler | 32 bytes | Compiler used | +// | Version | 16 bytes | Compiler version (Major, Minor, Build, Version) | +// | ScriptHash | 20 bytes | ScriptHash for the script (BE) | +// +------------+-----------+------------------------------------------------------------+ +// | Checksum | 4 bytes | Sha256 of the header (CRC) | +// +------------+-----------+------------------------------------------------------------+ +// | Script | Var bytes | Var bytes for the payload | +// +------------+-----------+------------------------------------------------------------+ + +const ( + // Magic is a magic File header constant. + Magic uint32 = 0x3346454E + // MaxScriptLength is the maximum allowed contract script length. + MaxScriptLength = 1024 * 1024 + // compilerFieldSize is the length of `Compiler` File header field in bytes. + compilerFieldSize = 32 +) + +// File represents compiled contract file structure according to the NEF3 standard. +type File struct { + Header Header + Checksum uint32 + Script []byte +} + +// Header represents File header. +type Header struct { + Magic uint32 + Compiler string + Version Version + ScriptHash util.Uint160 +} + +// Version represents compiler version. +type Version struct { + Major int32 + Minor int32 + Build int32 + Revision int32 +} + +// NewFile returns new NEF3 file with script specified. +func NewFile(script []byte) (File, error) { + file := File{ + Header: Header{ + Magic: Magic, + Compiler: "neo-go", + ScriptHash: hash.Hash160(script), + }, + Script: script, + } + v, err := GetVersion(config.Version) + if err != nil { + return file, err + } + file.Header.Version = v + file.Checksum = file.Header.CalculateChecksum() + return file, nil +} + +// GetVersion returns Version from the given string. It accepts the following formats: +// `major.minor.build-[...]` +// `major.minor.build.revision-[...]` +// where `major`, `minor`, `build` and `revision` are 32-bit integers with base=10 +func GetVersion(version string) (Version, error) { + var ( + result Version + ) + versions := strings.SplitN(version, ".", 4) + if len(versions) < 3 { + return result, errors.New("invalid version format") + } + major, err := strconv.ParseInt(versions[0], 10, 32) + if err != nil { + return result, fmt.Errorf("failed to parse major version: %v", err) + } + result.Major = int32(major) + + minor, err := strconv.ParseInt(versions[1], 10, 32) + if err != nil { + return result, fmt.Errorf("failed to parse minor version: %v", err) + + } + result.Minor = int32(minor) + + b := versions[2] + if len(versions) == 3 { + b = strings.SplitN(b, "-", 2)[0] + } + build, err := strconv.ParseInt(b, 10, 32) + if err != nil { + return result, fmt.Errorf("failed to parse build version: %v", err) + } + result.Build = int32(build) + + if len(versions) == 4 { + r := strings.SplitN(versions[3], "-", 2)[0] + revision, err := strconv.ParseInt(r, 10, 32) + if err != nil { + return result, fmt.Errorf("failed to parse revision version: %v", err) + } + result.Revision = int32(revision) + } + + return result, nil +} + +// EncodeBinary implements io.Serializable interface. +func (v *Version) EncodeBinary(w *io.BinWriter) { + w.WriteU32LE(uint32(v.Major)) + w.WriteU32LE(uint32(v.Minor)) + w.WriteU32LE(uint32(v.Build)) + w.WriteU32LE(uint32(v.Revision)) +} + +// DecodeBinary implements io.Serializable interface. +func (v *Version) DecodeBinary(r *io.BinReader) { + v.Major = int32(r.ReadU32LE()) + v.Minor = int32(r.ReadU32LE()) + v.Build = int32(r.ReadU32LE()) + v.Revision = int32(r.ReadU32LE()) +} + +// EncodeBinary implements io.Serializable interface. +func (h *Header) EncodeBinary(w *io.BinWriter) { + w.WriteU32LE(h.Magic) + if len(h.Compiler) > compilerFieldSize { + w.Err = errors.New("invalid compiler name length") + return + } + bytes := []byte(h.Compiler) + w.WriteBytes(bytes) + if len(bytes) < compilerFieldSize { + w.WriteBytes(make([]byte, compilerFieldSize-len(bytes))) + } + h.Version.EncodeBinary(w) + h.ScriptHash.EncodeBinary(w) +} + +// DecodeBinary implements io.Serializable interface. +func (h *Header) DecodeBinary(r *io.BinReader) { + h.Magic = r.ReadU32LE() + if h.Magic != Magic { + r.Err = errors.New("invalid Magic") + return + } + buf := make([]byte, compilerFieldSize) + r.ReadBytes(buf) + buf = bytes.TrimRightFunc(buf, func(r rune) bool { + return r == 0 + }) + h.Compiler = string(buf) + h.Version.DecodeBinary(r) + h.ScriptHash.DecodeBinary(r) +} + +// CalculateChecksum returns first 4 bytes of SHA256(Header) converted to uint32. +func (h *Header) CalculateChecksum() uint32 { + buf := io.NewBufBinWriter() + h.EncodeBinary(buf.BinWriter) + if buf.Err != nil { + panic(buf.Err) + } + return binary.LittleEndian.Uint32(hash.Sha256(buf.Bytes()).BytesBE()) +} + +// EncodeBinary implements io.Serializable interface. +func (n *File) EncodeBinary(w *io.BinWriter) { + n.Header.EncodeBinary(w) + w.WriteU32LE(n.Checksum) + w.WriteVarBytes(n.Script) +} + +// DecodeBinary implements io.Serializable interface. +func (n *File) DecodeBinary(r *io.BinReader) { + n.Header.DecodeBinary(r) + n.Checksum = r.ReadU32LE() + checksum := n.Header.CalculateChecksum() + if checksum != n.Checksum { + r.Err = errors.New("CRC verification fail") + return + } + n.Script = r.ReadVarBytes() + if len(n.Script) > MaxScriptLength { + r.Err = errors.New("invalid script length") + return + } + if !hash.Hash160(n.Script).Equals(n.Header.ScriptHash) { + r.Err = errors.New("script hashes mismatch") + return + } +} + +// Bytes returns byte array with serialized NEF File. +func (n File) Bytes() ([]byte, error) { + buf := io.NewBufBinWriter() + n.EncodeBinary(buf.BinWriter) + if buf.Err != nil { + return nil, buf.Err + } + return buf.Bytes(), nil +} + +// FileFromBytes returns NEF File deserialized from given bytes. +func FileFromBytes(source []byte) (File, error) { + result := File{} + r := io.NewBinReaderFromBuf(source) + result.DecodeBinary(r) + if r.Err != nil { + return result, r.Err + } + return result, nil +} diff --git a/pkg/smartcontract/nef/nef_test.go b/pkg/smartcontract/nef/nef_test.go new file mode 100644 index 000000000..b0de75e10 --- /dev/null +++ b/pkg/smartcontract/nef/nef_test.go @@ -0,0 +1,140 @@ +package nef + +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/util" + "github.com/stretchr/testify/require" +) + +func TestEncodeDecodeBinary(t *testing.T) { + script := []byte{12, 32, 84, 35, 14} + expected := &File{ + Header: Header{ + Magic: Magic, + Compiler: "the best compiler ever", + Version: Version{ + Major: 1, + Minor: 2, + Build: 3, + Revision: 4, + }, + ScriptHash: hash.Hash160(script), + }, + Script: script, + } + + t.Run("invalid Magic", func(t *testing.T) { + expected.Header.Magic = 123 + checkDecodeError(t, expected) + }) + + t.Run("invalid checksum", func(t *testing.T) { + expected.Header.Magic = Magic + expected.Checksum = 123 + checkDecodeError(t, expected) + }) + + t.Run("invalid script length", func(t *testing.T) { + newScript := make([]byte, MaxScriptLength+1) + expected.Script = newScript + expected.Header.ScriptHash = hash.Hash160(newScript) + expected.Checksum = expected.Header.CalculateChecksum() + checkDecodeError(t, expected) + }) + + t.Run("invalid scripthash", func(t *testing.T) { + expected.Script = script + expected.Header.ScriptHash = util.Uint160{1, 2, 3} + expected.Checksum = expected.Header.CalculateChecksum() + checkDecodeError(t, expected) + }) + + t.Run("positive", func(t *testing.T) { + expected.Script = script + expected.Header.ScriptHash = hash.Hash160(script) + expected.Checksum = expected.Header.CalculateChecksum() + expected.Header.Magic = Magic + testserdes.EncodeDecodeBinary(t, expected, &File{}) + }) +} + +func checkDecodeError(t *testing.T, expected *File) { + bytes, err := testserdes.EncodeBinary(expected) + require.NoError(t, err) + require.Error(t, testserdes.DecodeBinary(bytes, &File{})) +} + +func TestBytesFromBytes(t *testing.T) { + script := []byte{12, 32, 84, 35, 14} + expected := File{ + Header: Header{ + Magic: Magic, + Compiler: "the best compiler ever", + Version: Version{ + Major: 1, + Minor: 2, + Build: 3, + Revision: 4, + }, + ScriptHash: hash.Hash160(script), + }, + Script: script, + } + expected.Checksum = expected.Header.CalculateChecksum() + + bytes, err := expected.Bytes() + require.NoError(t, err) + actual, err := FileFromBytes(bytes) + require.NoError(t, err) + require.Equal(t, expected, actual) +} + +func TestGetVersion(t *testing.T) { + _, err := GetVersion("qwerty") + require.Error(t, err) + + _, err = GetVersion("1.pre") + require.Error(t, err) + + _, err = GetVersion("1.1.pre") + require.Error(t, err) + + _, err = GetVersion("1.1.1.pre") + require.Error(t, err) + + actual, err := GetVersion("1.1.1-pre") + require.NoError(t, err) + expected := Version{ + Major: 1, + Minor: 1, + Build: 1, + Revision: 0, + } + require.Equal(t, expected, actual) + + actual, err = GetVersion("0.90.0-pre") + require.NoError(t, err) + expected = Version{ + Major: 0, + Minor: 90, + Build: 0, + Revision: 0, + } + require.Equal(t, expected, actual) + + actual, err = GetVersion("1.1.1.1-pre") + require.NoError(t, err) + expected = Version{ + Major: 1, + Minor: 1, + Build: 1, + Revision: 1, + } + require.Equal(t, expected, actual) + + _, err = GetVersion("1.1.1.1.1") + require.Error(t, err) +} From 6b8957243aa1d33d570d6e50f33ca04a023646ec Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 26 Jun 2020 17:23:10 +0300 Subject: [PATCH 2/9] *: move wallet config from `wallet` to `config` package In order to avoid dependency cycle at the next commits: imports github.com/nspcc-dev/neo-go/pkg/config imports github.com/nspcc-dev/neo-go/pkg/wallet imports github.com/nspcc-dev/neo-go/pkg/vm imports github.com/nspcc-dev/neo-go/pkg/smartcontract/nef imports github.com/nspcc-dev/neo-go/pkg/config --- pkg/config/application_config.go | 3 +-- pkg/{wallet => config}/wallet_config.go | 6 +++--- pkg/consensus/consensus.go | 3 ++- pkg/consensus/consensus_test.go | 3 +-- pkg/network/server_config.go | 5 ++--- 5 files changed, 9 insertions(+), 11 deletions(-) rename pkg/{wallet => config}/wallet_config.go (51%) diff --git a/pkg/config/application_config.go b/pkg/config/application_config.go index 21637c887..9c61cb9a3 100644 --- a/pkg/config/application_config.go +++ b/pkg/config/application_config.go @@ -6,7 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/network/metrics" "github.com/nspcc-dev/neo-go/pkg/rpc" - "github.com/nspcc-dev/neo-go/pkg/wallet" ) // ApplicationConfiguration config specific to the node. @@ -26,5 +25,5 @@ type ApplicationConfiguration struct { ProtoTickInterval time.Duration `yaml:"ProtoTickInterval"` Relay bool `yaml:"Relay"` RPC rpc.Config `yaml:"RPC"` - UnlockWallet wallet.Config `yaml:"UnlockWallet"` + UnlockWallet Wallet `yaml:"UnlockWallet"` } diff --git a/pkg/wallet/wallet_config.go b/pkg/config/wallet_config.go similarity index 51% rename from pkg/wallet/wallet_config.go rename to pkg/config/wallet_config.go index 8b5d790b6..8edf42339 100644 --- a/pkg/wallet/wallet_config.go +++ b/pkg/config/wallet_config.go @@ -1,7 +1,7 @@ -package wallet +package config -// Config is a wallet info. -type Config struct { +// Wallet is a wallet info. +type Wallet struct { Path string `yaml:"Path"` Password string `yaml:"Password"` } diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 783501d7f..364e5308f 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/dbft/block" "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/dbft/payload" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" coreb "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" @@ -80,7 +81,7 @@ type Config struct { // TimePerBlock minimal time that should pass before next block is accepted. TimePerBlock time.Duration // Wallet is a local-node wallet configuration. - Wallet *wallet.Config + Wallet *config.Wallet } // NewService returns new consensus.Service instance. diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index 196bdd5cf..240ca6a7a 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -17,7 +17,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" - "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -202,7 +201,7 @@ func newTestService(t *testing.T) *service { Broadcast: func(*Payload) {}, Chain: newTestChain(t), RequestTx: func(...util.Uint256) {}, - Wallet: &wallet.Config{ + Wallet: &config.Wallet{ Path: "./testdata/wallet1.json", Password: "one", }, diff --git a/pkg/network/server_config.go b/pkg/network/server_config.go index a84458e3a..a1ccd0e07 100644 --- a/pkg/network/server_config.go +++ b/pkg/network/server_config.go @@ -5,7 +5,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" - "github.com/nspcc-dev/neo-go/pkg/wallet" "go.uber.org/zap/zapcore" ) @@ -63,7 +62,7 @@ type ( LogLevel zapcore.Level // Wallet is a wallet configuration. - Wallet *wallet.Config + Wallet *config.Wallet // TimePerBlock is an interval which should pass between two successive blocks. TimePerBlock time.Duration @@ -76,7 +75,7 @@ func NewServerConfig(cfg config.Config) ServerConfig { appConfig := cfg.ApplicationConfiguration protoConfig := cfg.ProtocolConfiguration - var wc *wallet.Config + var wc *config.Wallet if appConfig.UnlockWallet.Path != "" { wc = &appConfig.UnlockWallet } From c7746da0234c41c948c1f54ab4a0caefcd7a6587 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 25 Jun 2020 19:21:49 +0300 Subject: [PATCH 3/9] cli, compiler: switch from .avm to .nef We don't generate clear .avm instructions anymore. Instead, use .nef files with additional metadata. --- cli/smartcontract/smart_contract.go | 49 ++++++++++++++++++----------- docs/cli.md | 6 ++-- docs/compiler.md | 12 +++---- pkg/compiler/compiler.go | 17 +++++++--- pkg/compiler/compiler_test.go | 5 ++- pkg/compiler/debug_test.go | 11 ++++++- 6 files changed, 67 insertions(+), 33 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index a5a62e010..60999fdd8 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -16,12 +16,12 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -73,14 +73,14 @@ func NewCommands() []cli.Command { testInvokeScriptFlags := []cli.Flag{ cli.StringFlag{ Name: "in, i", - Usage: "Input location of the avm file that needs to be invoked", + Usage: "Input location of the .nef file that needs to be invoked", }, } testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...) deployFlags := []cli.Flag{ cli.StringFlag{ Name: "in, i", - Usage: "Input file for the smart contract (*.avm)", + Usage: "Input file for the smart contract (*.nef)", }, cli.StringFlag{ Name: "config, c", @@ -103,7 +103,7 @@ func NewCommands() []cli.Command { Subcommands: []cli.Command{ { Name: "compile", - Usage: "compile a smart contract to a .avm file", + Usage: "compile a smart contract to a .nef file", Action: contractCompile, Flags: []cli.Flag{ cli.StringFlag{ @@ -134,7 +134,7 @@ func NewCommands() []cli.Command { }, { Name: "deploy", - Usage: "deploy a smart contract (.avm with description)", + Usage: "deploy a smart contract (.nef with description)", Description: `Deploys given contract into the chain. The gas parameter is for additional gas to be added as a network fee to prioritize the transaction. It may also be required to add that to satisfy chain's policy regarding transaction size @@ -254,9 +254,9 @@ func NewCommands() []cli.Command { }, { Name: "testinvokescript", - Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)", - UsageText: "neo-go contract testinvokescript -r endpoint -i input.avm [cosigners...]", - Description: `Executes given compiled AVM instructions with the given set of + Usage: "Invoke compiled AVM code in NEF format on the blockchain (test mode, not creating a transaction for it)", + UsageText: "neo-go contract testinvokescript -r endpoint -i input.nef [cosigners...]", + Description: `Executes given compiled AVM instructions in NEF format with the given set of cosigners. See testinvokefunction documentation for the details about parameters. `, Action: testInvokeScript, @@ -288,7 +288,7 @@ func NewCommands() []cli.Command { }, cli.StringFlag{ Name: "in, i", - Usage: "input file of the program", + Usage: "input file of the program (either .go or .nef)", }, }, }, @@ -492,6 +492,10 @@ func testInvokeScript(ctx *cli.Context) error { if err != nil { return cli.NewExitError(err, 1) } + nefFile, err := nef.FileFromBytes(b) + if err != nil { + return cli.NewExitError(errors.Wrapf(err, "failed to restore .nef file"), 1) + } args := ctx.Args() var cosigners []transaction.Cosigner @@ -513,7 +517,7 @@ func testInvokeScript(ctx *cli.Context) error { return err } - scriptHex := hex.EncodeToString(b) + scriptHex := hex.EncodeToString(nefFile.Script) resp, err := c.InvokeScript(scriptHex, cosigners) if err != nil { return cli.NewExitError(err, 1) @@ -551,11 +555,10 @@ func (p *ProjectConfig) GetFeatures() smartcontract.PropertyState { } // ToManifest converts project config to the manifest. -func (p *ProjectConfig) ToManifest(script []byte) *manifest.Manifest { - h := hash.Hash160(script) - m := manifest.NewManifest(h) +func (p *ProjectConfig) ToManifest(file nef.File) *manifest.Manifest { + m := manifest.NewManifest(file.Header.ScriptHash) m.Features = p.GetFeatures() - m.ABI.Hash = h + m.ABI.Hash = file.Header.ScriptHash m.ABI.EntryPoint = p.EntryPoint m.ABI.Methods = p.Methods m.ABI.Events = p.Events @@ -577,6 +580,12 @@ func inspect(ctx *cli.Context) error { if err != nil { return cli.NewExitError(errors.Wrap(err, "failed to compile"), 1) } + } else { + nefFile, err := nef.FileFromBytes(b) + if err != nil { + return cli.NewExitError(errors.Wrapf(err, "failed to restore .nef file"), 1) + } + b = nefFile.Script } v := vm.New() v.LoadScript(b) @@ -638,10 +647,14 @@ func contractDeploy(ctx *cli.Context) error { if err != nil { return err } - avm, err := ioutil.ReadFile(in) + f, err := ioutil.ReadFile(in) if err != nil { return cli.NewExitError(err, 1) } + nefFile, err := nef.FileFromBytes(f) + if err != nil { + return cli.NewExitError(errors.Wrapf(err, "failed to restore .nef file"), 1) + } conf, err := parseContractConfig(confFile) if err != nil { return err @@ -655,8 +668,8 @@ func contractDeploy(ctx *cli.Context) error { return err } - m := conf.ToManifest(avm) - txScript, sysfee, err := request.CreateDeploymentScript(avm, m) + m := conf.ToManifest(nefFile) + txScript, sysfee, err := request.CreateDeploymentScript(nefFile.Script, m) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) } @@ -665,7 +678,7 @@ func contractDeploy(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) } - fmt.Printf("Sent deployment transaction %s for contract %s\n", txHash.StringLE(), hash.Hash160(avm).StringLE()) + fmt.Printf("Sent deployment transaction %s for contract %s\n", txHash.StringLE(), nefFile.Header.ScriptHash.StringLE()) return nil } diff --git a/docs/cli.md b/docs/cli.md index 8cff392e5..6eb7eba0a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -104,11 +104,11 @@ In case you don't want to provide details use `--skip-details, -skip`. ./bin/neo-go contract compile -i mycontract.go ``` -By default the output filename will be the name of your `.go` file with the `.avm` extension, the file will be located +By default the output filename will be the name of your `.go` file with the `.nef` extension, the file will be located in the same directory where you called the command from. If you want another location for your compiled contract: ``` -./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.avm +./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.nef ``` ### Deploy @@ -118,7 +118,7 @@ in the same directory where you called the command from. If you want another loc //Implemented in test mode. It means that it won't affect the blockchain ``` -./bin/neo-go contract testinvoke -i mycontract.avm +./bin/neo-go contract testinvoke -i mycontract.nef ``` ### Debug diff --git a/docs/compiler.md b/docs/compiler.md index 56f5367e5..64552285b 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -37,17 +37,17 @@ functionality as Neo .net Framework library. ./bin/neo-go contract compile -i mycontract.go ``` -By default the filename will be the name of your .go file with the .avm extension, the file will be located in the same directory where your Go contract is. If you want another location for your compiled contract: +By default the filename will be the name of your .go file with the .nef extension, the file will be located in the same directory where your Go contract is. If you want another location for your compiled contract: ``` -./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.avm +./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.nef ``` ### Debugging You can dump the opcodes generated by the compiler with the following command: ``` -./bin/neo-go contract inspect -i mycontract.go +./bin/neo-go contract inspect -i mycontract.go -c ``` This will result in something like this: @@ -116,7 +116,7 @@ Toolkit](https://github.com/neo-project/neo-blockchain-toolkit/). To do that you need to generate debug information using `--debug` option, like this: ``` -$ ./bin/neo-go contract compile -i contract.go -o contract.avm --debug contract.debug.json +$ ./bin/neo-go contract compile -i contract.go -o contract.nef --debug contract.debug.json ``` This file can then be used by debugger and set up to work just like for any @@ -144,7 +144,7 @@ project: It's passed to the `deploy` command via `-c` option: ``` -$ ./bin/neo-go contract deploy -i contract.avm -c contract.yml -r http://localhost:20331 -w wallet.json -g 0.001 +$ ./bin/neo-go contract deploy -i contract.nef -c contract.yml -r http://localhost:20331 -w wallet.json -g 0.001 ``` Deployment works via an RPC server, an address of which is passed via `-r` @@ -163,7 +163,7 @@ deployment with neo-go. It's done in the same step with compilation via support the command line will look like this: ``` -$ ./bin/neo-go contract compile -i contract.go --config contract.yml -o contract.avm --debug contract.debug.json --abi contract.abi.json +$ ./bin/neo-go contract compile -i contract.go --config contract.yml -o contract.nef --debug contract.debug.json --abi contract.abi.json ``` This file can then be used by toolkit to deploy contract the same way diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 9e60b710b..e211c0774 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -12,14 +12,15 @@ import ( "strings" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "golang.org/x/tools/go/loader" ) -const fileExt = "avm" +const fileExt = "nef" // Options contains all the parameters that affect the behaviour of the compiler. type Options struct { - // The extension of the output file default set to .avm + // The extension of the output file default set to .nef Ext string // The name of the output file. @@ -78,7 +79,7 @@ func CompileWithDebugInfo(r io.Reader) ([]byte, *DebugInfo, error) { return CodeGen(ctx) } -// CompileAndSave will compile and save the file to disk. +// CompileAndSave will compile and save the file to disk in the NEF format. func CompileAndSave(src string, o *Options) ([]byte, error) { if !strings.HasSuffix(src, ".go") { return nil, fmt.Errorf("%s is not a Go file", src) @@ -98,8 +99,16 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { if err != nil { return nil, fmt.Errorf("error while trying to compile smart contract file: %v", err) } + f, err := nef.NewFile(b) + if err != nil { + return nil, fmt.Errorf("error while trying to create .nef file: %v", err) + } + bytes, err := f.Bytes() + if err != nil { + return nil, fmt.Errorf("error while serializing .nef file: %v", err) + } out := fmt.Sprintf("%s.%s", o.Outfile, o.Ext) - err = ioutil.WriteFile(out, b, os.ModePerm) + err = ioutil.WriteFile(out, bytes, os.ModePerm) if o.DebugInfo == "" { return b, err } diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index 2d004da20..d3ab69f1b 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/stretchr/testify/require" ) @@ -20,6 +21,8 @@ type compilerTestCase struct { } func TestCompiler(t *testing.T) { + // CompileAndSave use config.Version for proper .nef generation. + config.Version = "0.90.0-test" testCases := []compilerTestCase{ { name: "TestCompile", @@ -44,7 +47,7 @@ func TestCompiler(t *testing.T) { require.NoError(t, err) err = os.MkdirAll(exampleSavePath, os.ModePerm) require.NoError(t, err) - outfile := exampleSavePath + "/test.avm" + outfile := exampleSavePath + "/test.nef" _, err = compiler.CompileAndSave(exampleCompilePath+"/"+infos[0].Name(), &compiler.Options{Outfile: outfile}) require.NoError(t, err) defer func() { diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 0c0acc70d..09af1421d 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -145,7 +145,16 @@ func methodStruct() struct{} { return struct{}{} } }, Events: []Event{}, } - assert.Equal(t, expected, actual) + require.True(t, expected.ABI.Hash.Equals(actual.ABI.Hash)) + require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods) + require.Equal(t, expected.ABI.EntryPoint, actual.ABI.EntryPoint) + require.Equal(t, expected.ABI.Events, actual.ABI.Events) + require.Equal(t, expected.Groups, actual.Groups) + require.Equal(t, expected.Features, actual.Features) + require.Equal(t, expected.Permissions, actual.Permissions) + require.Equal(t, expected.Trusts, actual.Trusts) + require.Equal(t, expected.SafeMethods, actual.SafeMethods) + require.Equal(t, expected.Extra, actual.Extra) }) } From b387deaa0562e86cb69848bf68b916bc6e68c4df Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 25 Jun 2020 10:36:21 +0300 Subject: [PATCH 4/9] vm: switch from .avm to .nef --- docs/vm.md | 8 ++++---- pkg/vm/cli/cli.go | 12 ++++++------ pkg/vm/vm.go | 11 ++++++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/vm.md b/docs/vm.md index c9454c3b5..73d062797 100644 --- a/docs/vm.md +++ b/docs/vm.md @@ -1,6 +1,6 @@ # NEO-GO-VM -A cross platform virtual machine implementation for `avm` compatible programs. +A cross platform virtual machine implementation for `NEF` compatible programs. # Installation @@ -46,7 +46,7 @@ Commands: help display help ip Show current instruction istack Show invocation stack contents - loadavm Load an avm script into the VM + loadnef Load an avm script in NEF format into the VM loadgo Compile and load a Go file into the VM loadhex Load a hex-encoded script string into the VM ops Dump opcodes of the current loaded program @@ -70,10 +70,10 @@ Usage: step [] ## Loading in your script -To load an avm script into the VM: +To load an avm script in NEF format into the VM: ``` -NEO-GO-VM > loadavm ../contract.avm +NEO-GO-VM > loadnef ../contract.nef READY: loaded 36 instructions ``` diff --git a/pkg/vm/cli/cli.go b/pkg/vm/cli/cli.go index efe793255..4724ff123 100644 --- a/pkg/vm/cli/cli.go +++ b/pkg/vm/cli/cli.go @@ -67,12 +67,12 @@ var commands = []*ishell.Cmd{ Func: handleXStack, }, { - Name: "loadavm", - Help: "Load an avm script into the VM", - LongHelp: `Usage: loadavm + Name: "loadnef", + Help: "Load a NEF-consistent script into the VM", + LongHelp: `Usage: loadnef is mandatory parameter, example: -> load /path/to/script.avm`, - Func: handleLoadAVM, +> load /path/to/script.nef`, + Func: handleLoadNEF, }, { Name: "loadbase64", @@ -241,7 +241,7 @@ func handleXStack(c *ishell.Context) { c.Println(v.Stack(c.Cmd.Name)) } -func handleLoadAVM(c *ishell.Context) { +func handleLoadNEF(c *ishell.Context) { v := getVMFromContext(c) if len(c.Args) < 1 { c.Err(errors.New("missing parameter ")) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 90d16014b..02b9bed71 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -86,7 +87,7 @@ type VM struct { keys map[string]*keys.PublicKey } -// New returns a new VM object ready to load .avm bytecode scripts. +// New returns a new VM object ready to load AVM bytecode scripts. func New() *VM { return NewWithTrigger(trigger.System) } @@ -235,13 +236,17 @@ func (v *VM) AddBreakPointRel(n int) { v.AddBreakPoint(ctx.ip + n) } -// LoadFile loads a program from the given path, ready to execute it. +// LoadFile loads a program in NEF format from the given path, ready to execute it. func (v *VM) LoadFile(path string) error { b, err := ioutil.ReadFile(path) if err != nil { return err } - v.Load(b) + f, err := nef.FileFromBytes(b) + if err != nil { + return err + } + v.Load(f.Script) return nil } From 1c1818d97e1b9867a40cd10f96973cb6a89edaf4 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 25 Jun 2020 10:38:53 +0300 Subject: [PATCH 5/9] vm: fix typoes in `cli` package --- pkg/vm/cli/cli.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/vm/cli/cli.go b/pkg/vm/cli/cli.go index 4724ff123..4c7527bca 100644 --- a/pkg/vm/cli/cli.go +++ b/pkg/vm/cli/cli.go @@ -71,7 +71,7 @@ var commands = []*ishell.Cmd{ Help: "Load a NEF-consistent script into the VM", LongHelp: `Usage: loadnef is mandatory parameter, example: -> load /path/to/script.nef`, +> loadnef /path/to/script.nef`, Func: handleLoadNEF, }, { @@ -87,15 +87,15 @@ var commands = []*ishell.Cmd{ Help: "Load a hex-encoded script string into the VM", LongHelp: `Usage: loadhex is mandatory parameter, example: -> load 0c0c48656c6c6f20776f726c6421`, +> loadhex 0c0c48656c6c6f20776f726c6421`, Func: handleLoadHex, }, { Name: "loadgo", Help: "Compile and load a Go file into the VM", - LongHelp: `Usage: loadhex + LongHelp: `Usage: loadgo is mandatory parameter, example: -> load /path/to/file.go`, +> loadgo /path/to/file.go`, Func: handleLoadGo, }, { From 2f6065f541abba32627ac3a15f80f3a89dc1966b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 25 Jun 2020 16:10:08 +0300 Subject: [PATCH 6/9] 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) From 45213dda49445e88a4c4612a64fbcc999a17b32e Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 25 Jun 2020 16:22:23 +0300 Subject: [PATCH 7/9] compiler: remove unnecessary structures --- pkg/compiler/debug.go | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index b496d016b..d24935b97 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -81,40 +81,6 @@ type DebugParam struct { Type string `json:"type"` } -// ABI represents ABI contract info in compatible with NEO Blockchain Toolkit format -type ABI struct { - Hash util.Uint160 `json:"hash"` - Metadata Metadata `json:"metadata"` - EntryPoint string `json:"entrypoint"` - Functions []Method `json:"functions"` - Events []Event `json:"events"` -} - -// Metadata represents ABI contract metadata -type Metadata struct { - Author string `json:"author"` - Email string `json:"email"` - Version string `json:"version"` - Title string `json:"title"` - Description string `json:"description"` - HasStorage bool `json:"has-storage"` - HasDynamicInvocation bool `json:"has-dynamic-invoke"` - IsPayable bool `json:"is-payable"` -} - -// Method represents ABI method's metadata. -type Method struct { - Name string `json:"name"` - Parameters []DebugParam `json:"parameters"` - ReturnType string `json:"returntype"` -} - -// Event represents ABI event's metadata. -type Event struct { - Name string `json:"name"` - Parameters []DebugParam `json:"parameters"` -} - func (c *codegen) saveSequencePoint(n ast.Node) { if c.scope == nil { // do not save globals for now From 66b6a27b09e6c08179256c87e1e16318b76bc0de Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 26 Jun 2020 12:08:59 +0300 Subject: [PATCH 8/9] compiler: fix DebugInfo JSON marshalling MethodDebugInfo.Parameters should be marshalled as `params` MethodDebugInfo.ReturnType --> `return` EventDebugInfo.Parameters --> `params` (see https://github.com/ngdseattle/design-notes/blob/master/NDX-DN11%20-%20NEO%20Debug%20Info%20Specification.md#v11-format) --- pkg/compiler/debug.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index d24935b97..7747cae4f 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -33,7 +33,7 @@ type MethodDebugInfo struct { // Parameters is a list of method's parameters. Parameters []DebugParam `json:"params"` // ReturnType is method's return type. - ReturnType string `json:"return-type"` + ReturnType string `json:"return"` Variables []string `json:"variables"` // SeqPoints is a map between source lines and byte-code instruction offsets. SeqPoints []DebugSeqPoint `json:"sequence-points"` @@ -50,7 +50,7 @@ type EventDebugInfo struct { ID string `json:"id"` // Name is a human-readable event name in a format "{namespace}-{name}". Name string `json:"name"` - Parameters []DebugParam `json:"parameters"` + Parameters []DebugParam `json:"params"` } // DebugSeqPoint represents break-point for debugger. From dc5da4f37ea087318b3298f035bb923064ff2fa3 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 26 Jun 2020 13:40:19 +0300 Subject: [PATCH 9/9] compiler: fix unhandled error --- pkg/compiler/compiler.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 292b5d7bd..c17f33e2f 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -109,9 +109,12 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { } out := fmt.Sprintf("%s.%s", o.Outfile, o.Ext) err = ioutil.WriteFile(out, bytes, os.ModePerm) - if o.DebugInfo == "" { + if err != nil { return b, err } + if o.DebugInfo == "" { + return b, nil + } p, err := filepath.Abs(src) if err != nil { return b, err