cli: allow to specify CustomContracts/Groups signer

The syntax is `CalledByEntry,CustomContracts:hash1:hash2`.
This commit is contained in:
Evgeniy Stratonikov 2021-05-24 16:21:53 +03:00
parent 93a331818e
commit e3e0e2a8a8
3 changed files with 78 additions and 9 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"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/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/rpc/client" "github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -53,11 +54,52 @@ func parseCosigner(c string) (transaction.Signer, error) {
if err != nil { if err != nil {
return res, err 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 { if err != nil {
return transaction.Signer{}, err 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 return res, nil
} }

View file

@ -1,10 +1,13 @@
package cmdargs package cmdargs
import ( import (
"encoding/hex"
"strings" "strings"
"testing" "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/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/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -12,6 +15,10 @@ import (
func TestParseCosigner(t *testing.T) { func TestParseCosigner(t *testing.T) {
acc := util.Uint160{1, 3, 5, 7} 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{ testCases := map[string]transaction.Signer{
acc.StringLE(): { acc.StringLE(): {
Account: acc, Account: acc,
@ -33,25 +40,36 @@ func TestParseCosigner(t *testing.T) {
Account: acc, Account: acc,
Scopes: transaction.None, Scopes: transaction.None,
}, },
acc.StringLE() + ":CalledByEntry,CustomContracts": { acc.StringLE() + ":CalledByEntry,CustomContracts:" + c1.StringLE() + ":0x" + c2.StringLE(): {
Account: acc, Account: acc,
Scopes: transaction.CalledByEntry | transaction.CustomContracts, 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 { for s, expected := range testCases {
actual, err := parseCosigner(s) actual, err := parseCosigner(s)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, actual) require.Equal(t, expected, actual, s)
} }
errorCases := []string{ errorCases := []string{
acc.StringLE() + "0", acc.StringLE() + "0",
acc.StringLE() + ":Unknown", acc.StringLE() + ":Unknown",
acc.StringLE() + ":Global,CustomContracts", acc.StringLE() + ":Global,CustomContracts",
acc.StringLE() + ":Global,None", 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 { for _, s := range errorCases {
_, err := parseCosigner(s) _, err := parseCosigner(s)
require.Error(t, err) require.Error(t, err, s)
} }
} }

View file

@ -277,7 +277,13 @@ func NewCommands() []cli.Command {
entering deeper internal invokes. This can be default entering deeper internal invokes. This can be default
safe choice for native NEO/GAS. safe choice for native NEO/GAS.
- 'CustomContracts' - define valid custom contract hashes for witness check. - '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 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 specified, no array is passed. Note that scopes are properly handled by
@ -287,7 +293,10 @@ func NewCommands() []cli.Command {
* 'NNQk4QXsxvsrr3GSozoWBUxEmfag7B6hz5' * 'NNQk4QXsxvsrr3GSozoWBUxEmfag7B6hz5'
* 'NVquyZHoPirw6zAEPvY1ZezxM493zMWQqs:Global' * 'NVquyZHoPirw6zAEPvY1ZezxM493zMWQqs:Global'
* '0x0000000009070e030d0f0e020d0c06050e030c02' * '0x0000000009070e030d0f0e020d0c06050e030c02'
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,CustomGroups' * '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
`CustomGroups:0206d7495ceb34c197093b5fc1cccf1996ada05e69ef67e765462a7f5d88ee14d0'
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
`CustomContracts:1011120009070e030d0f0e020d0c06050e030c02:0x1211100009070e030d0f0e020d0c06050e030c02'
`, `,
Action: testInvokeFunction, Action: testInvokeFunction,
Flags: options.RPC, Flags: options.RPC,