neorpc: adjust SignerWithWitness scopes parsing
Ensure that Scopes can be properly parsed not only from the string representation, but also from a single byte. transaction.Signer is not affected (checked against the C# implementation), only RPC-related signer scopes are allowed to be unmarshalled from byte. Close #3059. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
parent
e30e262e66
commit
8db997c58a
4 changed files with 185 additions and 25 deletions
|
@ -3,6 +3,7 @@ package transaction
|
|||
//go:generate stringer -type=WitnessScope -linecomment -output=witness_scope_string.go
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
@ -29,6 +30,19 @@ const (
|
|||
Global WitnessScope = 0x80
|
||||
)
|
||||
|
||||
// ScopesFromByte converts byte to a set of WitnessScopes and performs validity
|
||||
// check.
|
||||
func ScopesFromByte(b byte) (WitnessScope, error) {
|
||||
var res = WitnessScope(b)
|
||||
if (res&Global != 0) && (res&(None|CalledByEntry|CustomContracts|CustomGroups|Rules) != 0) {
|
||||
return 0, errors.New("Global scope can not be combined with other scopes")
|
||||
}
|
||||
if res&^(None|CalledByEntry|CustomContracts|CustomGroups|Rules|Global) != 0 {
|
||||
return 0, fmt.Errorf("invalid scope %d", res)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ScopesFromString converts string of comma-separated scopes to a set of scopes
|
||||
// (case-sensitive). String can combine several scopes, e.g. be any of: 'Global',
|
||||
// 'CalledByEntry,CustomGroups' etc. In case of an empty string an error will be
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -51,3 +52,64 @@ func TestScopesFromString(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, CalledByEntry|CustomGroups|CustomContracts, s)
|
||||
}
|
||||
|
||||
func TestScopesFromByte(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in byte
|
||||
expected WitnessScope
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
in: 0,
|
||||
expected: None,
|
||||
},
|
||||
{
|
||||
in: 1,
|
||||
expected: CalledByEntry,
|
||||
},
|
||||
{
|
||||
in: 16,
|
||||
expected: CustomContracts,
|
||||
},
|
||||
{
|
||||
in: 32,
|
||||
expected: CustomGroups,
|
||||
},
|
||||
{
|
||||
in: 64,
|
||||
expected: Rules,
|
||||
},
|
||||
{
|
||||
in: 128,
|
||||
expected: Global,
|
||||
},
|
||||
{
|
||||
in: 17,
|
||||
expected: CalledByEntry | CustomContracts,
|
||||
},
|
||||
{
|
||||
in: 48,
|
||||
expected: CustomContracts | CustomGroups,
|
||||
},
|
||||
{
|
||||
in: 128 + 1, // Global can't be combined with others.
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
in: 2, // No such scope.
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(strconv.Itoa(int(tc.in)), func(t *testing.T) {
|
||||
actual, err := ScopesFromByte(tc.in)
|
||||
if tc.shouldFail {
|
||||
require.Error(t, err, tc.in)
|
||||
} else {
|
||||
require.NoError(t, err, tc.in)
|
||||
require.Equal(t, tc.expected, actual, tc.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ type (
|
|||
// DisallowUnknownFields JSON marshaller setting.
|
||||
type signerWithWitnessAux struct {
|
||||
Account string `json:"account"`
|
||||
Scopes transaction.WitnessScope `json:"scopes"`
|
||||
Scopes json.RawMessage `json:"scopes"`
|
||||
AllowedContracts []util.Uint160 `json:"allowedcontracts,omitempty"`
|
||||
AllowedGroups []*keys.PublicKey `json:"allowedgroups,omitempty"`
|
||||
Rules []transaction.WitnessRule `json:"rules,omitempty"`
|
||||
|
@ -92,9 +92,13 @@ type signerWithWitnessAux struct {
|
|||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (s *SignerWithWitness) MarshalJSON() ([]byte, error) {
|
||||
sc, err := s.Scopes.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal scopes: %w", err)
|
||||
}
|
||||
signer := &signerWithWitnessAux{
|
||||
Account: s.Account.StringLE(),
|
||||
Scopes: s.Scopes,
|
||||
Scopes: sc,
|
||||
AllowedContracts: s.AllowedContracts,
|
||||
AllowedGroups: s.AllowedGroups,
|
||||
Rules: s.Rules,
|
||||
|
@ -118,9 +122,31 @@ func (s *SignerWithWitness) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("not a signer: %w", err)
|
||||
}
|
||||
var (
|
||||
jStr string
|
||||
jByte byte
|
||||
scopes transaction.WitnessScope
|
||||
)
|
||||
if len(aux.Scopes) != 0 {
|
||||
if err := json.Unmarshal(aux.Scopes, &jStr); err == nil {
|
||||
scopes, err = transaction.ScopesFromString(jStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve scopes from string: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := json.Unmarshal(aux.Scopes, &jByte)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal scopes from byte: %w", err)
|
||||
}
|
||||
scopes, err = transaction.ScopesFromByte(jByte)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve scopes from byte: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Signer = transaction.Signer{
|
||||
Account: acc,
|
||||
Scopes: aux.Scopes,
|
||||
Scopes: scopes,
|
||||
AllowedContracts: aux.AllowedContracts,
|
||||
AllowedGroups: aux.AllowedGroups,
|
||||
Rules: aux.Rules,
|
||||
|
|
|
@ -243,38 +243,96 @@ func TestGetWitness(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
raw string
|
||||
expected neorpc.SignerWithWitness
|
||||
raw string
|
||||
expected neorpc.SignerWithWitness
|
||||
shouldFail bool
|
||||
}{
|
||||
{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`, neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.None,
|
||||
}},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`,
|
||||
expected: neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.None,
|
||||
},
|
||||
},
|
||||
},
|
||||
{`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`, neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: addrHash,
|
||||
Scopes: transaction.Global,
|
||||
}},
|
||||
{
|
||||
raw: `{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`,
|
||||
expected: neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: addrHash,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
},
|
||||
},
|
||||
{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`, neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.Global,
|
||||
}},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`,
|
||||
expected: neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 128}`,
|
||||
expected: neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`,
|
||||
expected: neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.None,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 1}`,
|
||||
expected: neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 17}`,
|
||||
expected: neorpc.SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.CalledByEntry | transaction.CustomContracts,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 178}`,
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 2}`,
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
p := Param{RawMessage: json.RawMessage(tc.raw)}
|
||||
actual, err := p.GetSignerWithWitness()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
if tc.shouldFail {
|
||||
require.Error(t, err, tc.raw)
|
||||
} else {
|
||||
require.NoError(t, err, tc.raw)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
|
||||
actual, err = p.GetSignerWithWitness() // valid second invocation.
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
actual, err = p.GetSignerWithWitness() // valid second invocation.
|
||||
require.NoError(t, err, tc.raw)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue