diff --git a/cli/cmdargs/parser.go b/cli/cmdargs/parser.go index 6118061d5..cddb51cb7 100644 --- a/cli/cmdargs/parser.go +++ b/cli/cmdargs/parser.go @@ -164,6 +164,16 @@ func ParseParams(args []string, calledFromMain bool) (int, []smartcontract.Param default: param, err := smartcontract.NewParameterFromString(s) if err != nil { + // '--' argument is skipped by urfave/cli library, which leads + // to [--, addr:scope] being transformed to [addr:scope] and + // interpreted as a parameter if other positional arguments are not present. + // Here we fallback to parsing cosigners in this specific case to + // create a better user experience ('-- addr:scope' vs '-- -- addr:scope'). + if k == 0 { + if _, err := parseCosigner(s); err == nil { + return 0, nil, nil + } + } return 0, nil, fmt.Errorf("failed to parse argument #%d: %w", k+1, err) } res = append(res, *param) diff --git a/cli/contract_test.go b/cli/contract_test.go index 19e103896..37aef9a68 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/fixedn" @@ -157,6 +158,83 @@ func TestDeployBigContract(t *testing.T) { } func TestContractDeployWithData(t *testing.T) { + eCompile := newExecutor(t, false) + + // For proper nef generation. + config.Version = "0.90.0-test" + tmpDir := t.TempDir() + + nefName := path.Join(tmpDir, "deploy.nef") + manifestName := path.Join(tmpDir, "deploy.manifest.json") + eCompile.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", nefName, "--manifest", manifestName) + + deployContract := func(t *testing.T, haveData bool, scope string) { + e := newExecutor(t, true) + cmd := []string{ + "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://" + e.RPC.Addr, + "--wallet", validatorWallet, "--address", validatorAddr, + "--in", nefName, "--manifest", manifestName, + "--force", + } + + if haveData { + cmd = append(cmd, "[", "key1", "12", "key2", "take_me_to_church", "]") + } + if scope != "" { + cmd = append(cmd, "--", validatorAddr+":"+scope) + } else { + scope = "CalledByEntry" + } + e.In.WriteString("one\r") + e.Run(t, cmd...) + + tx, _ := e.checkTxPersisted(t, "Sent invocation transaction ") + require.Equal(t, scope, tx.Signers[0].Scopes.String()) + if !haveData { + return + } + + 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.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addr, + h.StringLE(), + "getValueWithKey", "key1", + ) + + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vm.HaltState.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, []byte{12}, res.Stack[0].Value()) + + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addr, + h.StringLE(), + "getValueWithKey", "key2", + ) + + res = new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vm.HaltState.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, []byte("take_me_to_church"), res.Stack[0].Value()) + } + + deployContract(t, true, "") + deployContract(t, false, "Global") + deployContract(t, true, "Global") +} + +func TestDeployWithSigners(t *testing.T) { e := newExecutor(t, true) // For proper nef generation. @@ -166,7 +244,7 @@ func TestContractDeployWithData(t *testing.T) { nefName := path.Join(tmpDir, "deploy.nef") manifestName := path.Join(tmpDir, "deploy.manifest.json") e.Run(t, "neo-go", "contract", "compile", - "--in", "testdata/deploy/main.go", // compile single file + "--in", "testdata/deploy/main.go", "--config", "testdata/deploy/neo-go.yml", "--out", nefName, "--manifest", manifestName) @@ -176,38 +254,9 @@ func TestContractDeployWithData(t *testing.T) { "--wallet", validatorWallet, "--address", validatorAddr, "--in", nefName, "--manifest", manifestName, "--force", - "[", "key1", "12", "key2", "take_me_to_church", "]") - - e.checkTxPersisted(t, "Sent invocation transaction ") - 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.Run(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, - h.StringLE(), - "getValueWithKey", "key1", - ) - - res := new(result.Invoke) - require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) - require.Equal(t, vm.HaltState.String(), res.State, res.FaultException) - require.Len(t, res.Stack, 1) - require.Equal(t, []byte{12}, res.Stack[0].Value()) - - e.Run(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, - h.StringLE(), - "getValueWithKey", "key2", - ) - - res = new(result.Invoke) - require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) - require.Equal(t, vm.HaltState.String(), res.State, res.FaultException) - require.Len(t, res.Stack, 1) - require.Equal(t, []byte("take_me_to_church"), res.Stack[0].Value()) + "--", validatorAddr+":Global") + tx, _ := e.checkTxPersisted(t, "Sent invocation transaction ") + require.Equal(t, transaction.Global, tx.Signers[0].Scopes) } func TestContractManifestGroups(t *testing.T) { diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 6a076de84..c66659d66 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -843,7 +843,7 @@ func contractDeploy(ctx *cli.Context) error { Value: manifestBytes, }, } - _, data, err := cmdargs.ParseParams(ctx.Args(), true) + signOffset, data, err := cmdargs.ParseParams(ctx.Args(), true) if err != nil { return cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) } @@ -872,10 +872,15 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1) } - cosigners := []transaction.Signer{{ - Account: acc.Contract.ScriptHash(), - Scopes: transaction.CalledByEntry, - }} + cosigners, sgnErr := cmdargs.GetSignersFromContext(ctx, signOffset) + if sgnErr != nil { + return err + } else if len(cosigners) == 0 { + cosigners = []transaction.Signer{{ + Account: acc.Contract.ScriptHash(), + Scopes: transaction.CalledByEntry, + }} + } sender, extErr := invokeWithArgs(ctx, acc, w, mgmtHash, "deploy", appCallParams, cosigners) if extErr != nil {