Merge pull request #1108 from nspcc-dev/neo3/compiler/nef
compiler: support *.nef and *.manifest.json generation
This commit is contained in:
commit
372dd71708
17 changed files with 654 additions and 147 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
236
pkg/smartcontract/nef/nef.go
Normal file
236
pkg/smartcontract/nef/nef.go
Normal 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
|
||||
}
|
140
pkg/smartcontract/nef/nef_test.go
Normal file
140
pkg/smartcontract/nef/nef_test.go
Normal 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)
|
||||
}
|
|
@ -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>"))
|
||||
|
|
11
pkg/vm/vm.go
11
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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue