cli, compiler: switch from .avm to .nef

We don't generate clear .avm instructions anymore. Instead, use .nef
files with additional metadata.
This commit is contained in:
Anna Shaleva 2020-06-25 19:21:49 +03:00
parent 6b8957243a
commit c7746da023
6 changed files with 67 additions and 33 deletions

View file

@ -16,12 +16,12 @@ import (
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/request" "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/rpc/response/result"
"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/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/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
@ -73,14 +73,14 @@ func NewCommands() []cli.Command {
testInvokeScriptFlags := []cli.Flag{ testInvokeScriptFlags := []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "in, i", 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...) testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...)
deployFlags := []cli.Flag{ deployFlags := []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "in, i", Name: "in, i",
Usage: "Input file for the smart contract (*.avm)", Usage: "Input file for the smart contract (*.nef)",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "config, c", Name: "config, c",
@ -103,7 +103,7 @@ func NewCommands() []cli.Command {
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
Name: "compile", Name: "compile",
Usage: "compile a smart contract to a .avm file", Usage: "compile a smart contract to a .nef file",
Action: contractCompile, Action: contractCompile,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
@ -134,7 +134,7 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "deploy", 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 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 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 be required to add that to satisfy chain's policy regarding transaction size
@ -254,9 +254,9 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "testinvokescript", Name: "testinvokescript",
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)", 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.avm [cosigners...]", UsageText: "neo-go contract testinvokescript -r endpoint -i input.nef [cosigners...]",
Description: `Executes given compiled AVM instructions with the given set of Description: `Executes given compiled AVM instructions in NEF format with the given set of
cosigners. See testinvokefunction documentation for the details about parameters. cosigners. See testinvokefunction documentation for the details about parameters.
`, `,
Action: testInvokeScript, Action: testInvokeScript,
@ -288,7 +288,7 @@ func NewCommands() []cli.Command {
}, },
cli.StringFlag{ cli.StringFlag{
Name: "in, i", 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 { if err != nil {
return cli.NewExitError(err, 1) 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() args := ctx.Args()
var cosigners []transaction.Cosigner var cosigners []transaction.Cosigner
@ -513,7 +517,7 @@ func testInvokeScript(ctx *cli.Context) error {
return err return err
} }
scriptHex := hex.EncodeToString(b) scriptHex := hex.EncodeToString(nefFile.Script)
resp, err := c.InvokeScript(scriptHex, cosigners) resp, err := c.InvokeScript(scriptHex, cosigners)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
@ -551,11 +555,10 @@ func (p *ProjectConfig) GetFeatures() smartcontract.PropertyState {
} }
// ToManifest converts project config to the manifest. // ToManifest converts project config to the manifest.
func (p *ProjectConfig) ToManifest(script []byte) *manifest.Manifest { func (p *ProjectConfig) ToManifest(file nef.File) *manifest.Manifest {
h := hash.Hash160(script) m := manifest.NewManifest(file.Header.ScriptHash)
m := manifest.NewManifest(h)
m.Features = p.GetFeatures() m.Features = p.GetFeatures()
m.ABI.Hash = h m.ABI.Hash = file.Header.ScriptHash
m.ABI.EntryPoint = p.EntryPoint m.ABI.EntryPoint = p.EntryPoint
m.ABI.Methods = p.Methods m.ABI.Methods = p.Methods
m.ABI.Events = p.Events m.ABI.Events = p.Events
@ -577,6 +580,12 @@ func inspect(ctx *cli.Context) error {
if err != nil { if err != nil {
return cli.NewExitError(errors.Wrap(err, "failed to compile"), 1) 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 := vm.New()
v.LoadScript(b) v.LoadScript(b)
@ -638,10 +647,14 @@ func contractDeploy(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
avm, err := ioutil.ReadFile(in) f, err := ioutil.ReadFile(in)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) 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) conf, err := parseContractConfig(confFile)
if err != nil { if err != nil {
return err return err
@ -655,8 +668,8 @@ func contractDeploy(ctx *cli.Context) error {
return err return err
} }
m := conf.ToManifest(avm) m := conf.ToManifest(nefFile)
txScript, sysfee, err := request.CreateDeploymentScript(avm, m) txScript, sysfee, err := request.CreateDeploymentScript(nefFile.Script, m)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) 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 { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) 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 return nil
} }

View file

@ -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 ./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: 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 ### 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 //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 ### Debug

View file

@ -37,17 +37,17 @@ functionality as Neo .net Framework library.
./bin/neo-go contract compile -i mycontract.go ./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 ### Debugging
You can dump the opcodes generated by the compiler with the following command: 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: 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: 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 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: 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` 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: 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 This file can then be used by toolkit to deploy contract the same way

View file

@ -12,14 +12,15 @@ import (
"strings" "strings"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
) )
const fileExt = "avm" const fileExt = "nef"
// Options contains all the parameters that affect the behaviour of the compiler. // Options contains all the parameters that affect the behaviour of the compiler.
type Options struct { 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 Ext string
// The name of the output file. // The name of the output file.
@ -78,7 +79,7 @@ func CompileWithDebugInfo(r io.Reader) ([]byte, *DebugInfo, error) {
return CodeGen(ctx) 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) { func CompileAndSave(src string, o *Options) ([]byte, error) {
if !strings.HasSuffix(src, ".go") { if !strings.HasSuffix(src, ".go") {
return nil, fmt.Errorf("%s is not a Go file", src) 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 { if err != nil {
return nil, fmt.Errorf("error while trying to compile smart contract file: %v", err) 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) 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 == "" { if o.DebugInfo == "" {
return b, err return b, err
} }

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -20,6 +21,8 @@ type compilerTestCase struct {
} }
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
// CompileAndSave use config.Version for proper .nef generation.
config.Version = "0.90.0-test"
testCases := []compilerTestCase{ testCases := []compilerTestCase{
{ {
name: "TestCompile", name: "TestCompile",
@ -44,7 +47,7 @@ func TestCompiler(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
err = os.MkdirAll(exampleSavePath, os.ModePerm) err = os.MkdirAll(exampleSavePath, os.ModePerm)
require.NoError(t, err) require.NoError(t, err)
outfile := exampleSavePath + "/test.avm" outfile := exampleSavePath + "/test.nef"
_, err = compiler.CompileAndSave(exampleCompilePath+"/"+infos[0].Name(), &compiler.Options{Outfile: outfile}) _, err = compiler.CompileAndSave(exampleCompilePath+"/"+infos[0].Name(), &compiler.Options{Outfile: outfile})
require.NoError(t, err) require.NoError(t, err)
defer func() { defer func() {

View file

@ -145,7 +145,16 @@ func methodStruct() struct{} { return struct{}{} }
}, },
Events: []Event{}, 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)
}) })
} }