From e3e0e2a8a882949a396b44cff81dfe532b3737a8 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 24 May 2021 16:21:53 +0300 Subject: [PATCH] cli: allow to specify CustomContracts/Groups signer The syntax is `CalledByEntry,CustomContracts:hash1:hash2`. --- cli/cmdargs/parser.go | 46 +++++++++++++++++++++++++++-- cli/cmdargs/parser_test.go | 28 ++++++++++++++---- cli/smartcontract/smart_contract.go | 13 ++++++-- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/cli/cmdargs/parser.go b/cli/cmdargs/parser.go index a34a70397..6118061d5 100644 --- a/cli/cmdargs/parser.go +++ b/cli/cmdargs/parser.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/flags" "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/rpc/client" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -53,11 +54,52 @@ func parseCosigner(c string) (transaction.Signer, error) { if err != nil { return res, err } - if len(data) > 1 { - res.Scopes, err = transaction.ScopesFromString(data[1]) + + if len(data) == 1 { + return res, nil + } + + res.Scopes = 0 + scopes := strings.Split(data[1], ",") + for _, s := range scopes { + sub := strings.Split(s, ":") + scope, err := transaction.ScopesFromString(sub[0]) if err != nil { return transaction.Signer{}, err } + if scope == transaction.Global && res.Scopes&^transaction.Global != 0 || + scope != transaction.Global && res.Scopes&transaction.Global != 0 { + return transaction.Signer{}, errors.New("Global scope can not be combined with other scopes") + } + + res.Scopes |= scope + + switch scope { + case transaction.CustomContracts: + if len(sub) == 1 { + return transaction.Signer{}, errors.New("CustomContracts scope must refer to at least one contract") + } + for _, s := range sub[1:] { + addr, err := flags.ParseAddress(s) + if err != nil { + return transaction.Signer{}, err + } + + res.AllowedContracts = append(res.AllowedContracts, addr) + } + case transaction.CustomGroups: + if len(sub) == 1 { + return transaction.Signer{}, errors.New("CustomGroups scope must refer to at least one group") + } + for _, s := range sub[1:] { + pub, err := keys.NewPublicKeyFromString(s) + if err != nil { + return transaction.Signer{}, err + } + + res.AllowedGroups = append(res.AllowedGroups, pub) + } + } } return res, nil } diff --git a/cli/cmdargs/parser_test.go b/cli/cmdargs/parser_test.go index 7b09aa05d..c27df62f2 100644 --- a/cli/cmdargs/parser_test.go +++ b/cli/cmdargs/parser_test.go @@ -1,10 +1,13 @@ package cmdargs import ( + "encoding/hex" "strings" "testing" + "github.com/nspcc-dev/neo-go/internal/random" "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/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" @@ -12,6 +15,10 @@ import ( func TestParseCosigner(t *testing.T) { acc := util.Uint160{1, 3, 5, 7} + c1, c2 := random.Uint160(), random.Uint160() + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + testCases := map[string]transaction.Signer{ acc.StringLE(): { Account: acc, @@ -33,25 +40,36 @@ func TestParseCosigner(t *testing.T) { Account: acc, Scopes: transaction.None, }, - acc.StringLE() + ":CalledByEntry,CustomContracts": { - Account: acc, - Scopes: transaction.CalledByEntry | transaction.CustomContracts, + acc.StringLE() + ":CalledByEntry,CustomContracts:" + c1.StringLE() + ":0x" + c2.StringLE(): { + Account: acc, + Scopes: transaction.CalledByEntry | transaction.CustomContracts, + AllowedContracts: []util.Uint160{c1, c2}, + }, + acc.StringLE() + ":CustomGroups:" + hex.EncodeToString(priv.PublicKey().Bytes()): { + Account: acc, + Scopes: transaction.CustomGroups, + AllowedGroups: keys.PublicKeys{priv.PublicKey()}, }, } for s, expected := range testCases { actual, err := parseCosigner(s) require.NoError(t, err) - require.Equal(t, expected, actual) + require.Equal(t, expected, actual, s) } errorCases := []string{ acc.StringLE() + "0", acc.StringLE() + ":Unknown", acc.StringLE() + ":Global,CustomContracts", acc.StringLE() + ":Global,None", + acc.StringLE() + ":CustomContracts:" + acc.StringLE() + ",Global", + acc.StringLE() + ":CustomContracts", + acc.StringLE() + ":CustomContracts:xxx", + acc.StringLE() + ":CustomGroups", + acc.StringLE() + ":CustomGroups:xxx", } for _, s := range errorCases { _, err := parseCosigner(s) - require.Error(t, err) + require.Error(t, err, s) } } diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 070a9ef7c..c45194288 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -277,7 +277,13 @@ func NewCommands() []cli.Command { entering deeper internal invokes. This can be default safe choice for native NEO/GAS. - 'CustomContracts' - define valid custom contract hashes for witness check. - - 'CustomGroups' - define custom pubkey for group members. + Hashes are be provided as hex-encoded LE value string. + At lest one hash must be provided. Multiple hashes + are separated by ':'. + - 'CustomGroups' - define custom public keys for group members. Public keys are + provided as short-form (1-byte prefix + 32 bytes) hex-encoded + values. At least one key must be provided. Multiple keys + are separated by ':'. If no scopes were specified, 'CalledByEntry' used as default. If no signers were specified, no array is passed. Note that scopes are properly handled by @@ -287,7 +293,10 @@ func NewCommands() []cli.Command { * 'NNQk4QXsxvsrr3GSozoWBUxEmfag7B6hz5' * 'NVquyZHoPirw6zAEPvY1ZezxM493zMWQqs:Global' * '0x0000000009070e030d0f0e020d0c06050e030c02' - * '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,CustomGroups' + * '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` + + `CustomGroups:0206d7495ceb34c197093b5fc1cccf1996ada05e69ef67e765462a7f5d88ee14d0' + * '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` + + `CustomContracts:1011120009070e030d0f0e020d0c06050e030c02:0x1211100009070e030d0f0e020d0c06050e030c02' `, Action: testInvokeFunction, Flags: options.RPC,