Merge pull request #1915 from nspcc-dev/cli/deploy_refactoring

cli: unify 'contract deploy' and 'contract invoke*'
This commit is contained in:
Roman Khimov 2021-04-23 12:48:37 +03:00 committed by GitHub
commit b0e8e37b99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 124 deletions

View file

@ -194,12 +194,12 @@ func TestContractDeployWithData(t *testing.T) {
"--in", nefName, "--manifest", manifestName, "--in", nefName, "--manifest", manifestName,
"[", "key1", "12", "key2", "take_me_to_church", "]") "[", "key1", "12", "key2", "take_me_to_church", "]")
e.checkTxPersisted(t, "Sent invocation transaction ")
line, err := e.Out.ReadString('\n') line, err := e.Out.ReadString('\n')
require.NoError(t, err) require.NoError(t, err)
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: ")) line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
h, err := util.Uint160DecodeStringLE(line) h, err := util.Uint160DecodeStringLE(line)
require.NoError(t, err) require.NoError(t, err)
e.checkTxPersisted(t)
e.Run(t, "neo-go", "contract", "testinvokefunction", e.Run(t, "neo-go", "contract", "testinvokefunction",
"--rpc-endpoint", "http://"+e.RPC.Addr, "--rpc-endpoint", "http://"+e.RPC.Addr,
@ -244,12 +244,12 @@ func deployVerifyContract(t *testing.T, e *executor) util.Uint160 {
"--rpc-endpoint", "http://"+e.RPC.Addr, "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet, "--address", validatorAddr, "--wallet", validatorWallet, "--address", validatorAddr,
"--in", nefName, "--manifest", manifestName) "--in", nefName, "--manifest", manifestName)
e.checkTxPersisted(t, "Sent invocation transaction ")
line, err := e.Out.ReadString('\n') line, err := e.Out.ReadString('\n')
require.NoError(t, err) require.NoError(t, err)
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: ")) line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
hVerify, err := util.Uint160DecodeStringLE(line) hVerify, err := util.Uint160DecodeStringLE(line)
require.NoError(t, err) require.NoError(t, err)
e.checkTxPersisted(t)
return hVerify return hVerify
} }
@ -288,12 +288,12 @@ func TestComlileAndInvokeFunction(t *testing.T) {
"--wallet", validatorWallet, "--address", validatorAddr, "--wallet", validatorWallet, "--address", validatorAddr,
"--in", nefName, "--manifest", manifestName) "--in", nefName, "--manifest", manifestName)
e.checkTxPersisted(t, "Sent invocation transaction ")
line, err := e.Out.ReadString('\n') line, err := e.Out.ReadString('\n')
require.NoError(t, err) require.NoError(t, err)
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: ")) line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
h, err := util.Uint160DecodeStringLE(line) h, err := util.Uint160DecodeStringLE(line)
require.NoError(t, err) require.NoError(t, err)
e.checkTxPersisted(t)
t.Run("check calc hash", func(t *testing.T) { t.Run("check calc hash", func(t *testing.T) {
// missing sender // missing sender

View file

@ -5,14 +5,12 @@ import (
"math/big" "math/big"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"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/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -110,19 +108,7 @@ func TestSignMultisigTx(t *testing.T) {
require.Equal(t, big.NewInt(3), b) require.Equal(t, big.NewInt(3), b)
t.Run("via invokefunction", func(t *testing.T) { t.Run("via invokefunction", func(t *testing.T) {
e.In.WriteString("one\r") h := deployVerifyContract(t, e)
e.Run(t, "neo-go", "contract", "deploy",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet, "--address", validatorAddr,
"--in", "testdata/verify.nef", "--manifest", "testdata/verify.manifest.json")
line, err := e.Out.ReadString('\n')
require.NoError(t, err)
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
h, err := util.Uint160DecodeStringLE(line)
require.NoError(t, err)
e.checkTxPersisted(t)
e.In.WriteString("acc\rpass\rpass\r") e.In.WriteString("acc\rpass\rpass\r")
e.Run(t, "neo-go", "wallet", "import-deployed", e.Run(t, "neo-go", "wallet", "import-deployed",

View file

@ -22,16 +22,13 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/rpc/client"
"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/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "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/smartcontract/nef"
"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/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -98,21 +95,6 @@ func NewCommands() []cli.Command {
}, },
} }
testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...) testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...)
deployFlags := []cli.Flag{
cli.StringFlag{
Name: "in, i",
Usage: "Input file for the smart contract (*.nef)",
},
cli.StringFlag{
Name: "manifest, m",
Usage: "Manifest input file (*.manifest.json)",
},
walletFlag,
addressFlag,
outFlag,
gasFlag,
}
deployFlags = append(deployFlags, options.RPC...)
invokeFunctionFlags := []cli.Flag{ invokeFunctionFlags := []cli.Flag{
walletFlag, walletFlag,
addressFlag, addressFlag,
@ -121,6 +103,16 @@ func NewCommands() []cli.Command {
forceFlag, forceFlag,
} }
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
deployFlags := append(invokeFunctionFlags, []cli.Flag{
cli.StringFlag{
Name: "in, i",
Usage: "Input file for the smart contract (*.nef)",
},
cli.StringFlag{
Name: "manifest, m",
Usage: "Manifest input file (*.manifest.json)",
},
}...)
return []cli.Command{{ return []cli.Command{{
Name: "contract", Name: "contract",
Usage: "compile - debug - deploy smart contracts", Usage: "compile - debug - deploy smart contracts",
@ -167,7 +159,7 @@ func NewCommands() []cli.Command {
{ {
Name: "deploy", Name: "deploy",
Usage: "deploy a smart contract (.nef with description)", Usage: "deploy a smart contract (.nef with description)",
UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] --in contract.nef --manifest contract.manifest.json [--out file] [data]", UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [data]",
Description: `Deploys given contract into the chain. The gas parameter is for additional 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. The data gas to be added as a network fee to prioritize the transaction. The data
parameter is an optional parameter to be passed to '_deploy' method. parameter is an optional parameter to be passed to '_deploy' method.
@ -178,7 +170,7 @@ func NewCommands() []cli.Command {
{ {
Name: "invokefunction", Name: "invokefunction",
Usage: "invoke deployed contract on the blockchain", Usage: "invoke deployed contract on the blockchain",
UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [--out file] scripthash [method] [arguments...] [--] [signers...]", UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [--out file] [--force] scripthash [method] [arguments...] [--] [signers...]",
Description: `Executes given (as a script hash) deployed script with the given method, Description: `Executes given (as a script hash) deployed script with the given method,
arguments and signers. Sender is included in the list of signers by default arguments and signers. Sender is included in the list of signers by default
with None witness scope. If you'd like to change default sender's scope, with None witness scope. If you'd like to change default sender's scope,
@ -508,18 +500,13 @@ func invokeFunction(ctx *cli.Context) error {
func invokeInternal(ctx *cli.Context, signAndPush bool) error { func invokeInternal(ctx *cli.Context, signAndPush bool) error {
var ( var (
err error err error
exitErr *cli.ExitError exitErr *cli.ExitError
gas fixedn.Fixed8 operation string
operation string params = make([]smartcontract.Parameter, 0)
params = make([]smartcontract.Parameter, 0) paramsStart = 1
paramsStart = 1 cosigners []transaction.Signer
cosigners []transaction.Signer cosignersOffset = 0
cosignersAccounts []client.SignerAccount
cosignersOffset = 0
resp *result.Invoke
acc *wallet.Account
wall *wallet.Wallet
) )
args := ctx.Args() args := ctx.Args()
@ -549,15 +536,33 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
return exitErr return exitErr
} }
_, err = invokeWithArgs(ctx, signAndPush, script, operation, params, cosigners)
return err
}
func invokeWithArgs(ctx *cli.Context, signAndPush bool, script util.Uint160, operation string, params []smartcontract.Parameter, cosigners []transaction.Signer) (util.Uint160, error) {
var (
err error
gas fixedn.Fixed8
cosignersAccounts []client.SignerAccount
resp *result.Invoke
acc *wallet.Account
wall *wallet.Wallet
sender util.Uint160
)
if signAndPush { if signAndPush {
gas = flags.Fixed8FromContext(ctx, "gas") gas = flags.Fixed8FromContext(ctx, "gas")
acc, wall, err = getAccFromContext(ctx) acc, wall, err = getAccFromContext(ctx)
if err != nil { if err != nil {
return err return sender, err
}
sender, err = address.StringToUint160(acc.Address)
if err != nil {
return sender, err
} }
cosignersAccounts, err = cmdargs.GetSignersAccounts(wall, cosigners) cosignersAccounts, err = cmdargs.GetSignersAccounts(wall, cosigners)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to calculate network fee: %w", err), 1) return sender, cli.NewExitError(fmt.Errorf("failed to calculate network fee: %w", err), 1)
} }
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
@ -565,50 +570,50 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return sender, err
} }
resp, err = c.InvokeFunction(script, operation, params, cosigners) resp, err = c.InvokeFunction(script, operation, params, cosigners)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return sender, cli.NewExitError(err, 1)
} }
if signAndPush && resp.State != "HALT" { if signAndPush && resp.State != "HALT" {
errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s\n", resp.State, resp.FaultException) errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s\n", resp.State, resp.FaultException)
if !ctx.Bool("force") { if !ctx.Bool("force") {
return cli.NewExitError(errText+". Use --force flag to send the transaction anyway.", 1) return sender, cli.NewExitError(errText+". Use --force flag to send the transaction anyway.", 1)
} }
fmt.Fprintln(ctx.App.Writer, errText+". Sending transaction...") fmt.Fprintln(ctx.App.Writer, errText+". Sending transaction...")
} }
if out := ctx.String("out"); out != "" { if out := ctx.String("out"); out != "" {
tx, err := c.CreateTxFromScript(resp.Script, acc, resp.GasConsumed, int64(gas), cosignersAccounts) tx, err := c.CreateTxFromScript(resp.Script, acc, resp.GasConsumed, int64(gas), cosignersAccounts)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) return sender, cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
} }
if err := paramcontext.InitAndSave(c.GetNetwork(), tx, acc, out); err != nil { if err := paramcontext.InitAndSave(c.GetNetwork(), tx, acc, out); err != nil {
return cli.NewExitError(err, 1) return sender, cli.NewExitError(err, 1)
} }
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
return nil return sender, nil
} }
if signAndPush { if signAndPush {
if len(resp.Script) == 0 { if len(resp.Script) == 0 {
return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) return sender, cli.NewExitError(errors.New("no script returned from the RPC node"), 1)
} }
txHash, err := c.SignAndPushInvocationTx(resp.Script, acc, resp.GasConsumed, gas, cosignersAccounts) txHash, err := c.SignAndPushInvocationTx(resp.Script, acc, resp.GasConsumed, gas, cosignersAccounts)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1) return sender, cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
} }
fmt.Fprintf(ctx.App.Writer, "Sent invocation transaction %s\n", txHash.StringLE()) fmt.Fprintf(ctx.App.Writer, "Sent invocation transaction %s\n", txHash.StringLE())
} else { } else {
b, err := json.MarshalIndent(resp, "", " ") b, err := json.MarshalIndent(resp, "", " ")
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return sender, cli.NewExitError(err, 1)
} }
fmt.Fprintln(ctx.App.Writer, string(b)) fmt.Fprintln(ctx.App.Writer, string(b))
} }
return nil return sender, nil
} }
func testInvokeScript(ctx *cli.Context) error { func testInvokeScript(ctx *cli.Context) error {
@ -741,16 +746,7 @@ func contractDeploy(ctx *cli.Context) error {
if len(manifestFile) == 0 { if len(manifestFile) == 0 {
return cli.NewExitError(errNoManifestFile, 1) return cli.NewExitError(errNoManifestFile, 1)
} }
gas := flags.Fixed8FromContext(ctx, "gas")
acc, _, err := getAccFromContext(ctx)
if err != nil {
return err
}
sender, err := address.StringToUint160(acc.Address)
if err != nil {
return cli.NewExitError(err, 1)
}
f, err := ioutil.ReadFile(in) f, err := ioutil.ReadFile(in)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
@ -771,13 +767,25 @@ func contractDeploy(ctx *cli.Context) error {
return cli.NewExitError(fmt.Errorf("failed to restore manifest file: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to restore manifest file: %w", err), 1)
} }
_, data, extErr := cmdargs.GetDataFromContext(ctx) appCallParams := []smartcontract.Parameter{
if extErr != nil { {
return extErr Type: smartcontract.ByteArrayType,
Value: f,
},
{
Type: smartcontract.ByteArrayType,
Value: manifestBytes,
},
} }
appCallParams := []interface{}{f, manifestBytes} _, data, err := cmdargs.ParseParams(ctx.Args(), true)
if data != nil { if err != nil {
appCallParams = append(appCallParams, data) return cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
}
if len(data) > 1 {
return cli.NewExitError("'data' should be represented as a single parameter", 1)
}
if len(data) != 0 {
appCallParams = append(appCallParams, data[0])
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
@ -792,42 +800,13 @@ func contractDeploy(ctx *cli.Context) error {
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to get management contract's hash: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to get management contract's hash: %w", err), 1)
} }
buf := io.NewBufBinWriter() sender, extErr := invokeWithArgs(ctx, true, mgmtHash, "deploy", appCallParams, nil)
emit.AppCall(buf.BinWriter, mgmtHash, "deploy", if extErr != nil {
callflag.ReadStates|callflag.WriteStates|callflag.AllowNotify, return extErr
appCallParams...)
if buf.Err != nil {
return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", buf.Err), 1)
}
txScript := buf.Bytes()
// It doesn't require any signers.
invRes, err := c.InvokeScript(txScript, nil)
if err == nil && invRes.FaultException != "" {
err = errors.New(invRes.FaultException)
}
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to test-invoke deployment script: %w", err), 1)
} }
var txHash util.Uint256
if out := ctx.String("out"); out != "" {
tx, err := c.CreateTxFromScript(txScript, acc, invRes.GasConsumed, int64(gas), nil)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
}
if err := paramcontext.InitAndSave(c.GetNetwork(), tx, acc, out); err != nil {
return cli.NewExitError(err, 1)
}
txHash = tx.Hash()
} else {
txHash, err = c.SignAndPushInvocationTx(txScript, acc, invRes.GasConsumed, gas, nil)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
}
}
hash := state.CreateContractHash(sender, nefFile.Checksum, m.Name) hash := state.CreateContractHash(sender, nefFile.Checksum, m.Name)
fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", hash.StringLE()) fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", hash.StringLE())
fmt.Fprintln(ctx.App.Writer, txHash.StringLE())
return nil return nil
} }

View file

@ -14,7 +14,6 @@ import (
"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/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -243,20 +242,7 @@ func TestClaimGas(t *testing.T) {
func TestImportDeployed(t *testing.T) { func TestImportDeployed(t *testing.T) {
e := newExecutor(t, true) e := newExecutor(t, true)
e.In.WriteString("one\r") h := deployVerifyContract(t, e)
e.Run(t, "neo-go", "contract", "deploy",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet, "--address", validatorAddr,
"--in", "testdata/verify.nef", "--manifest", "testdata/verify.manifest.json")
line, err := e.Out.ReadString('\n')
require.NoError(t, err)
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
h, err := util.Uint160DecodeStringLE(line)
require.NoError(t, err)
e.checkTxPersisted(t)
tmpDir := os.TempDir() tmpDir := os.TempDir()
walletPath := path.Join(tmpDir, "wallet.json") walletPath := path.Join(tmpDir, "wallet.json")
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)