Merge pull request #1108 from nspcc-dev/neo3/compiler/nef

compiler: support *.nef and *.manifest.json generation
This commit is contained in:
Roman Khimov 2020-06-29 20:55:47 +03:00 committed by GitHub
commit 372dd71708
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 654 additions and 147 deletions

View file

@ -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{
@ -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",
@ -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)",
},
},
},
@ -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 {
@ -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
}

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
```
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

View file

@ -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

View file

@ -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 [<n>]
## 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
```

View file

@ -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.
@ -28,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
@ -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,11 +99,22 @@ 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)
if o.DebugInfo == "" {
err = ioutil.WriteFile(out, bytes, os.ModePerm)
if err != nil {
return b, err
}
if o.DebugInfo == "" {
return b, nil
}
p, err := filepath.Abs(src)
if err != nil {
return b, err
@ -115,13 +127,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)
}

View file

@ -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() {

View file

@ -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"
)
@ -32,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"`
@ -49,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.
@ -80,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
@ -267,6 +234,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 +331,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
}

View file

@ -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,31 +122,84 @@ 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,
}
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)
})
}

View file

@ -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"`
}

View file

@ -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"`
}

View file

@ -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"
@ -84,7 +85,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.

View file

@ -18,7 +18,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"
)
@ -216,7 +215,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",
},

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -67,12 +67,12 @@ var commands = []*ishell.Cmd{
Func: handleXStack,
},
{
Name: "loadavm",
Help: "Load an avm script into the VM",
LongHelp: `Usage: loadavm <file>
Name: "loadnef",
Help: "Load a NEF-consistent script into the VM",
LongHelp: `Usage: loadnef <file>
<file> is mandatory parameter, example:
> load /path/to/script.avm`,
Func: handleLoadAVM,
> loadnef /path/to/script.nef`,
Func: handleLoadNEF,
},
{
Name: "loadbase64",
@ -87,15 +87,15 @@ var commands = []*ishell.Cmd{
Help: "Load a hex-encoded script string into the VM",
LongHelp: `Usage: loadhex <string>
<string> 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 <file>
LongHelp: `Usage: loadgo <file>
<file> is mandatory parameter, example:
> load /path/to/file.go`,
> loadgo /path/to/file.go`,
Func: handleLoadGo,
},
{
@ -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 <file>"))

View file

@ -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)
}
@ -248,13 +249,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
}