From c7746da0234c41c948c1f54ab4a0caefcd7a6587 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 25 Jun 2020 19:21:49 +0300 Subject: [PATCH] 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) }) }