cli: use manifest during contract deployment
This commit is contained in:
parent
a03af55732
commit
76a2f62fbd
6 changed files with 65 additions and 128 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
Loading…
Reference in a new issue