cli: use manifest during contract deployment

This commit is contained in:
Evgenii Stratonikov 2020-06-09 16:12:58 +03:00
parent a03af55732
commit 76a2f62fbd
6 changed files with 65 additions and 128 deletions

View file

@ -1,7 +1,6 @@
package smartcontract package smartcontract
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"encoding/hex" "encoding/hex"
@ -22,6 +21,7 @@ import (
"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/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"
@ -323,23 +323,22 @@ func initSmartContract(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
// Ask contract information and write a neo-go.yml file unless the -skip-details flag is set. m := ProjectConfig{
// TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. EntryPoint: manifest.Method{
if !ctx.Bool("skip-details") { Name: "Main",
details := parseContractDetails() Parameters: []manifest.Parameter{
details.ReturnType = smartcontract.ByteArrayType manifest.NewParameter("Method", smartcontract.StringType),
details.Parameters = make([]smartcontract.ParamType, 2) manifest.NewParameter("Arguments", smartcontract.ArrayType),
details.Parameters[0] = smartcontract.StringType },
details.Parameters[1] = smartcontract.ArrayType ReturnType: smartcontract.ByteArrayType,
},
project := &ProjectConfig{Contract: details} }
b, err := yaml.Marshal(project) b, err := yaml.Marshal(m)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
}
} }
data := []byte(fmt.Sprintf(smartContractTmpl, contractName)) data := []byte(fmt.Sprintf(smartContractTmpl, contractName))
@ -375,7 +374,7 @@ func contractCompile(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
o.ContractDetails = &conf.Contract o.ContractFeatures = conf.GetFeatures()
} }
result, err := compiler.CompileAndSave(src, o) result, err := compiler.CompileAndSave(src, o)
@ -526,30 +525,35 @@ func testInvokeScript(ctx *cli.Context) error {
// ProjectConfig contains project metadata. // ProjectConfig contains project metadata.
type ProjectConfig struct { type ProjectConfig struct {
Version uint HasStorage bool
Contract smartcontract.ContractDetails `yaml:"project"` IsPayable bool
EntryPoint manifest.Method
Methods []manifest.Method
Events []manifest.Event
} }
func parseContractDetails() smartcontract.ContractDetails { // GetFeatures returns smartcontract features from the config.
details := smartcontract.ContractDetails{} func (p *ProjectConfig) GetFeatures() smartcontract.PropertyState {
reader := bufio.NewReader(os.Stdin) var fs smartcontract.PropertyState
if p.IsPayable {
fs |= smartcontract.IsPayable
}
if p.HasStorage {
fs |= smartcontract.HasStorage
}
return fs
}
fmt.Print("Author: ") // ToManifest converts project config to the manifest.
details.Author, _ = reader.ReadString('\n') func (p *ProjectConfig) ToManifest(script []byte) *manifest.Manifest {
h := hash.Hash160(script)
fmt.Print("Email: ") m := manifest.NewManifest(h)
details.Email, _ = reader.ReadString('\n') m.Features = p.GetFeatures()
m.ABI.Hash = h
fmt.Print("Version: ") m.ABI.EntryPoint = p.EntryPoint
details.Version, _ = reader.ReadString('\n') m.ABI.Methods = p.Methods
m.ABI.Events = p.Events
fmt.Print("Project name: ") return m
details.ProjectName, _ = reader.ReadString('\n')
fmt.Print("Description: ")
details.Description, _ = reader.ReadString('\n')
return details
} }
func inspect(ctx *cli.Context) error { func inspect(ctx *cli.Context) error {
@ -646,13 +650,12 @@ func contractDeploy(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
txScript, err := request.CreateDeploymentScript(avm, &conf.Contract) m := conf.ToManifest(avm)
txScript, sysfee, err := request.CreateDeploymentScript(avm, 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)
} }
sysfee := smartcontract.GetDeploymentPrice(request.DetailsToSCProperties(&conf.Contract))
txHash, err := c.SignAndPushInvocationTx(txScript, acc, sysfee, gas) txHash, err := c.SignAndPushInvocationTx(txScript, acc, sysfee, gas)
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)

View file

@ -32,7 +32,7 @@ type Options struct {
ABIInfo string ABIInfo string
// Contract metadata. // Contract metadata.
ContractDetails *smartcontract.ContractDetails ContractFeatures smartcontract.PropertyState
} }
type buildInfo struct { type buildInfo struct {
@ -118,7 +118,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
if o.ABIInfo == "" { if o.ABIInfo == "" {
return b, err return b, err
} }
abi := di.convertToABI(b, o.ContractDetails) abi := di.convertToABI(b, o.ContractFeatures)
abiData, err := json.Marshal(abi) abiData, err := json.Marshal(abi)
if err != nil { if err != nil {
return b, err return b, err

View file

@ -311,14 +311,16 @@ func parsePairJSON(data []byte, sep string) (string, string, error) {
return ss[0], ss[1], nil return ss[0], ss[1], nil
} }
func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDetails) ABI { // convertToABI converts contract to the ABI struct for debugger.
// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038.
func (di *DebugInfo) convertToABI(contract []byte, fs smartcontract.PropertyState) ABI {
methods := make([]Method, 0) methods := make([]Method, 0)
for _, method := range di.Methods { for _, method := range di.Methods {
if method.Name.Name == di.EntryPoint { if method.Name.Name == di.EntryPoint {
methods = append(methods, Method{ methods = append(methods, Method{
Name: method.Name.Name, Name: method.Name.Name,
Parameters: method.Parameters, Parameters: method.Parameters,
ReturnType: cd.ReturnType.String(), ReturnType: method.ReturnType,
}) })
break break
} }
@ -333,14 +335,8 @@ func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDet
return ABI{ return ABI{
Hash: hash.Hash160(contract), Hash: hash.Hash160(contract),
Metadata: Metadata{ Metadata: Metadata{
Author: cd.Author, HasStorage: fs&smartcontract.HasStorage != 0,
Email: cd.Email, IsPayable: fs&smartcontract.IsPayable != 0,
Version: cd.Version,
Title: cd.ProjectName,
Description: cd.Description,
HasStorage: cd.HasStorage,
HasDynamicInvocation: cd.HasDynamicInvocation,
IsPayable: cd.IsPayable,
}, },
EntryPoint: di.EntryPoint, EntryPoint: di.EntryPoint,
Functions: methods, Functions: methods,

View file

@ -117,33 +117,10 @@ func methodStruct() struct{} { return struct{}{} }
} }
t.Run("convert to ABI", func(t *testing.T) { t.Run("convert to ABI", func(t *testing.T) {
author := "Joe" actual := d.convertToABI(buf, smartcontract.HasStorage)
email := "Joe@ex.com"
version := "1.0"
title := "MyProj"
description := "Description"
actual := d.convertToABI(buf, &smartcontract.ContractDetails{
Author: author,
Email: email,
Version: version,
ProjectName: title,
Description: description,
HasStorage: true,
HasDynamicInvocation: false,
IsPayable: false,
ReturnType: smartcontract.BoolType,
Parameters: []smartcontract.ParamType{
smartcontract.StringType,
},
})
expected := ABI{ expected := ABI{
Hash: hash.Hash160(buf), Hash: hash.Hash160(buf),
Metadata: Metadata{ Metadata: Metadata{
Author: author,
Email: email,
Version: version,
Title: title,
Description: description,
HasStorage: true, HasStorage: true,
HasDynamicInvocation: false, HasDynamicInvocation: false,
IsPayable: false, IsPayable: false,

View file

@ -5,49 +5,28 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"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/util" "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/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
) )
// DetailsToSCProperties extract the fields needed from ContractDetails
// and converts them to smartcontract.PropertyState.
func DetailsToSCProperties(contract *smartcontract.ContractDetails) smartcontract.PropertyState {
var props smartcontract.PropertyState
if contract.HasStorage {
props |= smartcontract.HasStorage
}
if contract.HasDynamicInvocation {
props |= smartcontract.HasDynamicInvoke
}
if contract.IsPayable {
props |= smartcontract.IsPayable
}
return props
}
// CreateDeploymentScript returns a script that deploys given smart contract // CreateDeploymentScript returns a script that deploys given smart contract
// with its metadata. // with its metadata and system fee require for this.
func CreateDeploymentScript(avm []byte, contract *smartcontract.ContractDetails) ([]byte, error) { func CreateDeploymentScript(avm []byte, manif *manifest.Manifest) ([]byte, util.Fixed8, error) {
script := io.NewBufBinWriter() script := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte(contract.Description)) w := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte(contract.Email)) manif.EncodeBinary(w.BinWriter)
emit.Bytes(script.BinWriter, []byte(contract.Author)) rawManifest := w.Bytes()
emit.Bytes(script.BinWriter, []byte(contract.Version)) emit.Bytes(script.BinWriter, rawManifest)
emit.Bytes(script.BinWriter, []byte(contract.ProjectName))
emit.Int(script.BinWriter, int64(DetailsToSCProperties(contract)))
emit.Int(script.BinWriter, int64(contract.ReturnType))
params := make([]byte, len(contract.Parameters))
for k := range contract.Parameters {
params[k] = byte(contract.Parameters[k])
}
emit.Bytes(script.BinWriter, params)
emit.Bytes(script.BinWriter, avm) emit.Bytes(script.BinWriter, avm)
emit.Syscall(script.BinWriter, "Neo.Contract.Create") emit.Syscall(script.BinWriter, "Neo.Contract.Create")
return script.Bytes(), nil sysfee := util.Fixed8(core.StoragePrice * (len(avm) + len(rawManifest)))
return script.Bytes(), sysfee, nil
} }
// expandArrayIntoScript pushes all FuncParam parameters from the given array // expandArrayIntoScript pushes all FuncParam parameters from the given array

View file

@ -1,18 +0,0 @@
package smartcontract
import "github.com/nspcc-dev/neo-go/pkg/util"
// GetDeploymentPrice returns contract deployment price based on its properties.
func GetDeploymentPrice(props PropertyState) util.Fixed8 {
fee := int64(100)
if props&HasStorage != 0 {
fee += 400
}
if props&HasDynamicInvoke != 0 {
fee += 500
}
return util.Fixed8FromInt64(fee)
}