From 76a2f62fbd7e4a590c0fc3d672243671afda12b7 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 9 Jun 2020 16:12:58 +0300 Subject: [PATCH] cli: use manifest during contract deployment --- cli/smartcontract/smart_contract.go | 89 ++++++++++++++------------- pkg/compiler/compiler.go | 4 +- pkg/compiler/debug.go | 16 ++--- pkg/compiler/debug_test.go | 25 +------- pkg/rpc/request/txBuilder.go | 41 +++--------- pkg/smartcontract/deployment_price.go | 18 ------ 6 files changed, 65 insertions(+), 128 deletions(-) delete mode 100644 pkg/smartcontract/deployment_price.go diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index ccf9bb1bf..e22b986ac 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -1,7 +1,6 @@ package smartcontract import ( - "bufio" "bytes" "context" "encoding/hex" @@ -22,6 +21,7 @@ import ( "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/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -323,23 +323,22 @@ func initSmartContract(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - // Ask contract information and write a neo-go.yml file unless the -skip-details flag is set. - // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. - if !ctx.Bool("skip-details") { - details := parseContractDetails() - details.ReturnType = smartcontract.ByteArrayType - details.Parameters = make([]smartcontract.ParamType, 2) - details.Parameters[0] = smartcontract.StringType - details.Parameters[1] = smartcontract.ArrayType - - project := &ProjectConfig{Contract: details} - b, err := yaml.Marshal(project) - if err != nil { - return cli.NewExitError(err, 1) - } - if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { - return cli.NewExitError(err, 1) - } + m := ProjectConfig{ + EntryPoint: manifest.Method{ + Name: "Main", + Parameters: []manifest.Parameter{ + manifest.NewParameter("Method", smartcontract.StringType), + manifest.NewParameter("Arguments", smartcontract.ArrayType), + }, + ReturnType: smartcontract.ByteArrayType, + }, + } + b, err := yaml.Marshal(m) + if err != nil { + return cli.NewExitError(err, 1) + } + if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { + return cli.NewExitError(err, 1) } data := []byte(fmt.Sprintf(smartContractTmpl, contractName)) @@ -375,7 +374,7 @@ func contractCompile(ctx *cli.Context) error { if err != nil { return err } - o.ContractDetails = &conf.Contract + o.ContractFeatures = conf.GetFeatures() } result, err := compiler.CompileAndSave(src, o) @@ -526,30 +525,35 @@ func testInvokeScript(ctx *cli.Context) error { // ProjectConfig contains project metadata. type ProjectConfig struct { - Version uint - Contract smartcontract.ContractDetails `yaml:"project"` + HasStorage bool + IsPayable bool + EntryPoint manifest.Method + Methods []manifest.Method + Events []manifest.Event } -func parseContractDetails() smartcontract.ContractDetails { - details := smartcontract.ContractDetails{} - reader := bufio.NewReader(os.Stdin) +// GetFeatures returns smartcontract features from the config. +func (p *ProjectConfig) GetFeatures() smartcontract.PropertyState { + var fs smartcontract.PropertyState + if p.IsPayable { + fs |= smartcontract.IsPayable + } + if p.HasStorage { + fs |= smartcontract.HasStorage + } + return fs +} - fmt.Print("Author: ") - details.Author, _ = reader.ReadString('\n') - - fmt.Print("Email: ") - details.Email, _ = reader.ReadString('\n') - - fmt.Print("Version: ") - details.Version, _ = reader.ReadString('\n') - - fmt.Print("Project name: ") - details.ProjectName, _ = reader.ReadString('\n') - - fmt.Print("Description: ") - details.Description, _ = reader.ReadString('\n') - - return details +// ToManifest converts project config to the manifest. +func (p *ProjectConfig) ToManifest(script []byte) *manifest.Manifest { + h := hash.Hash160(script) + m := manifest.NewManifest(h) + m.Features = p.GetFeatures() + m.ABI.Hash = h + m.ABI.EntryPoint = p.EntryPoint + m.ABI.Methods = p.Methods + m.ABI.Events = p.Events + return m } func inspect(ctx *cli.Context) error { @@ -646,13 +650,12 @@ func contractDeploy(ctx *cli.Context) error { 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 { 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) if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index f72d7ba47..59abcacf8 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -32,7 +32,7 @@ type Options struct { ABIInfo string // Contract metadata. - ContractDetails *smartcontract.ContractDetails + ContractFeatures smartcontract.PropertyState } type buildInfo struct { @@ -118,7 +118,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { if o.ABIInfo == "" { return b, err } - abi := di.convertToABI(b, o.ContractDetails) + abi := di.convertToABI(b, o.ContractFeatures) abiData, err := json.Marshal(abi) if err != nil { return b, err diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 9f174b5cc..c6b4b6c69 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -311,14 +311,16 @@ func parsePairJSON(data []byte, sep string) (string, string, error) { 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) for _, method := range di.Methods { if method.Name.Name == di.EntryPoint { methods = append(methods, Method{ Name: method.Name.Name, Parameters: method.Parameters, - ReturnType: cd.ReturnType.String(), + ReturnType: method.ReturnType, }) break } @@ -333,14 +335,8 @@ func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDet return ABI{ Hash: hash.Hash160(contract), Metadata: Metadata{ - Author: cd.Author, - Email: cd.Email, - Version: cd.Version, - Title: cd.ProjectName, - Description: cd.Description, - HasStorage: cd.HasStorage, - HasDynamicInvocation: cd.HasDynamicInvocation, - IsPayable: cd.IsPayable, + HasStorage: fs&smartcontract.HasStorage != 0, + IsPayable: fs&smartcontract.IsPayable != 0, }, EntryPoint: di.EntryPoint, Functions: methods, diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 25cd7d035..7059e1384 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -117,33 +117,10 @@ func methodStruct() struct{} { return struct{}{} } } t.Run("convert to ABI", func(t *testing.T) { - author := "Joe" - 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, - }, - }) + actual := d.convertToABI(buf, smartcontract.HasStorage) expected := ABI{ Hash: hash.Hash160(buf), Metadata: Metadata{ - Author: author, - Email: email, - Version: version, - Title: title, - Description: description, HasStorage: true, HasDynamicInvocation: false, IsPayable: false, diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index 05def02a3..5c3cd1eed 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -5,49 +5,28 @@ import ( "fmt" "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/io" "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/emit" "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 -// with its metadata. -func CreateDeploymentScript(avm []byte, contract *smartcontract.ContractDetails) ([]byte, error) { +// with its metadata and system fee require for this. +func CreateDeploymentScript(avm []byte, manif *manifest.Manifest) ([]byte, util.Fixed8, error) { script := io.NewBufBinWriter() - emit.Bytes(script.BinWriter, []byte(contract.Description)) - emit.Bytes(script.BinWriter, []byte(contract.Email)) - emit.Bytes(script.BinWriter, []byte(contract.Author)) - emit.Bytes(script.BinWriter, []byte(contract.Version)) - 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) + w := io.NewBufBinWriter() + manif.EncodeBinary(w.BinWriter) + rawManifest := w.Bytes() + emit.Bytes(script.BinWriter, rawManifest) emit.Bytes(script.BinWriter, avm) 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 diff --git a/pkg/smartcontract/deployment_price.go b/pkg/smartcontract/deployment_price.go deleted file mode 100644 index 5ba9b8929..000000000 --- a/pkg/smartcontract/deployment_price.go +++ /dev/null @@ -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) -}