smartcontract: use permission descriptors for manifest's trusts

This commit is contained in:
Anna Shaleva 2021-05-04 14:48:50 +03:00
parent 4fb421738b
commit 50fc9bf766
7 changed files with 200 additions and 145 deletions

View file

@ -7,7 +7,6 @@ import (
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -263,8 +262,8 @@ func _deploy(data interface{}, isUpdate bool) {}
Methods: manifest.WildStrings{},
},
},
Trusts: manifest.WildUint160s{
Value: []util.Uint160{},
Trusts: manifest.WildPermissionDescs{
Value: []manifest.PermissionDesc{},
},
Extra: json.RawMessage("null"),
}

View file

@ -7,8 +7,6 @@ package manifest
import (
"bytes"
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// WildStrings represents string set which can be wildcard.
@ -16,9 +14,9 @@ type WildStrings struct {
Value []string
}
// WildUint160s represents Uint160 set which can be wildcard.
type WildUint160s struct {
Value []util.Uint160
// WildPermissionDescs represents PermissionDescriptor set which can be wildcard.
type WildPermissionDescs struct {
Value []PermissionDesc
}
// Contains checks if v is in the container.
@ -35,7 +33,7 @@ func (c *WildStrings) Contains(v string) bool {
}
// Contains checks if v is in the container.
func (c *WildUint160s) Contains(v util.Uint160) bool {
func (c *WildPermissionDescs) Contains(v PermissionDesc) bool {
if c.IsWildcard() {
return true
}
@ -51,19 +49,19 @@ func (c *WildUint160s) Contains(v util.Uint160) bool {
func (c *WildStrings) IsWildcard() bool { return c.Value == nil }
// IsWildcard returns true iff container is wildcard.
func (c *WildUint160s) IsWildcard() bool { return c.Value == nil }
func (c *WildPermissionDescs) IsWildcard() bool { return c.Value == nil }
// Restrict transforms container into an empty one.
func (c *WildStrings) Restrict() { c.Value = []string{} }
// Restrict transforms container into an empty one.
func (c *WildUint160s) Restrict() { c.Value = []util.Uint160{} }
func (c *WildPermissionDescs) Restrict() { c.Value = []PermissionDesc{} }
// Add adds v to the container.
func (c *WildStrings) Add(v string) { c.Value = append(c.Value, v) }
// Add adds v to the container.
func (c *WildUint160s) Add(v util.Uint160) { c.Value = append(c.Value, v) }
func (c *WildPermissionDescs) Add(v PermissionDesc) { c.Value = append(c.Value, v) }
// MarshalJSON implements json.Marshaler interface.
func (c WildStrings) MarshalJSON() ([]byte, error) {
@ -74,7 +72,7 @@ func (c WildStrings) MarshalJSON() ([]byte, error) {
}
// MarshalJSON implements json.Marshaler interface.
func (c WildUint160s) MarshalJSON() ([]byte, error) {
func (c WildPermissionDescs) MarshalJSON() ([]byte, error) {
if c.IsWildcard() {
return []byte(`"*"`), nil
}
@ -94,9 +92,9 @@ func (c *WildStrings) UnmarshalJSON(data []byte) error {
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (c *WildUint160s) UnmarshalJSON(data []byte) error {
func (c *WildPermissionDescs) UnmarshalJSON(data []byte) error {
if !bytes.Equal(data, []byte(`"*"`)) {
us := []util.Uint160{}
us := []PermissionDesc{}
if err := json.Unmarshal(data, &us); err != nil {
return err
}

View file

@ -6,7 +6,7 @@ import (
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require"
)
@ -21,15 +21,30 @@ func TestContainer_Restrict(t *testing.T) {
require.Equal(t, 0, len(c.Value))
})
t.Run("uint160", func(t *testing.T) {
c := new(WildUint160s)
u := random.Uint160()
t.Run("PermissionDesc", func(t *testing.T) {
check := func(t *testing.T, u PermissionDesc) {
c := new(WildPermissionDescs)
require.True(t, c.IsWildcard())
require.True(t, c.Contains(u))
c.Restrict()
require.False(t, c.IsWildcard())
require.False(t, c.Contains(u))
require.Equal(t, 0, len(c.Value))
}
t.Run("Hash", func(t *testing.T) {
check(t, PermissionDesc{
Type: PermissionHash,
Value: random.Uint160(),
})
})
t.Run("Group", func(t *testing.T) {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
check(t, PermissionDesc{
Type: PermissionGroup,
Value: pk.PublicKey(),
})
})
})
}
@ -44,17 +59,24 @@ func TestContainer_Add(t *testing.T) {
})
t.Run("uint160", func(t *testing.T) {
c := new(WildUint160s)
require.Equal(t, []util.Uint160(nil), c.Value)
exp := []util.Uint160{random.Uint160(), random.Uint160()}
c := new(WildPermissionDescs)
require.Equal(t, []PermissionDesc(nil), c.Value)
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
exp := []PermissionDesc{
{Type: PermissionHash, Value: random.Uint160()},
{Type: PermissionGroup, Value: pk.PublicKey()},
}
for i := range exp {
c.Add(exp[i])
}
for i := range exp {
require.True(t, c.Contains(exp[i]))
}
require.False(t, c.Contains(random.Uint160()))
pkRand, err := keys.NewPrivateKey()
require.NoError(t, err)
require.False(t, c.Contains(PermissionDesc{Type: PermissionHash, Value: random.Uint160()}))
require.False(t, c.Contains(PermissionDesc{Type: PermissionGroup, Value: pkRand.PublicKey()}))
})
}
@ -85,27 +107,30 @@ func TestContainer_MarshalJSON(t *testing.T) {
})
})
t.Run("uint160", func(t *testing.T) {
t.Run("PermissionDesc", func(t *testing.T) {
t.Run("wildcard", func(t *testing.T) {
expected := new(WildUint160s)
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
expected := new(WildPermissionDescs)
testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs))
})
t.Run("empty", func(t *testing.T) {
expected := new(WildUint160s)
expected := new(WildPermissionDescs)
expected.Restrict()
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs))
})
t.Run("non-empty", func(t *testing.T) {
expected := new(WildUint160s)
expected.Add(random.Uint160())
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
expected := new(WildPermissionDescs)
expected.Add(PermissionDesc{
Type: PermissionHash,
Value: random.Uint160(),
})
testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs))
})
t.Run("invalid", func(t *testing.T) {
js := []byte(`["notahex"]`)
c := new(WildUint160s)
c := new(WildPermissionDescs)
require.Error(t, json.Unmarshal(js, c))
})
})

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"math"
"sort"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -38,7 +37,7 @@ type Manifest struct {
// SupportedStandards is a list of standards supported by the contract.
SupportedStandards []string `json:"supportedstandards"`
// Trusts is a set of hashes to a which contract trusts.
Trusts WildUint160s `json:"trusts"`
Trusts WildPermissionDescs `json:"trusts"`
// Extra is an implementation-defined user data.
Extra json.RawMessage `json:"extra"`
}
@ -109,20 +108,12 @@ func (m *Manifest) IsValid(hash util.Uint160) error {
return err
}
if len(m.Trusts.Value) > 1 {
hashes := make([]util.Uint160, len(m.Trusts.Value))
hashes := make([]PermissionDesc, len(m.Trusts.Value))
copy(hashes, m.Trusts.Value)
sort.Slice(hashes, func(i, j int) bool {
return hashes[i].Less(hashes[j])
})
for i := range hashes {
if i == 0 {
continue
}
if hashes[i] == hashes[i-1] {
if permissionDescsHaveDups(hashes) {
return errors.New("duplicate trusted contracts")
}
}
}
return Permissions(m.Permissions).AreValid()
}
@ -144,8 +135,8 @@ func (m *Manifest) ToStackItem() (stackitem.Item, error) {
trusts := stackitem.Item(stackitem.Null{})
if !m.Trusts.IsWildcard() {
tItems := make([]stackitem.Item, len(m.Trusts.Value))
for i := range m.Trusts.Value {
tItems[i] = stackitem.NewByteArray(m.Trusts.Value[i].BytesBE())
for i, v := range m.Trusts.Value {
tItems[i] = v.ToStackItem()
}
trusts = stackitem.Make(tItems)
}
@ -231,16 +222,14 @@ func (m *Manifest) FromStackItem(item stackitem.Item) error {
return errors.New("invalid Trusts stackitem type")
}
trusts := str[6].Value().([]stackitem.Item)
m.Trusts = WildUint160s{Value: make([]util.Uint160, len(trusts))}
m.Trusts = WildPermissionDescs{Value: make([]PermissionDesc, len(trusts))}
for i := range trusts {
bytes, err := trusts[i].TryBytes()
if err != nil {
return err
}
m.Trusts.Value[i], err = util.Uint160DecodeBytesBE(bytes)
v := new(PermissionDesc)
err = v.FromStackItem(trusts[i])
if err != nil {
return err
}
m.Trusts.Value[i] = *v
}
}
extra, err := str[7].TryBytes()

View file

@ -181,17 +181,18 @@ func TestIsValid(t *testing.T) {
})
m.SupportedStandards = m.SupportedStandards[:1]
m.Trusts.Add(util.Uint160{1, 2, 3})
d := PermissionDesc{Type: PermissionHash, Value: util.Uint160{1, 2, 3}}
m.Trusts.Add(d)
t.Run("valid, with trust", func(t *testing.T) {
require.NoError(t, m.IsValid(contractHash))
})
m.Trusts.Add(util.Uint160{3, 2, 1})
m.Trusts.Add(PermissionDesc{Type: PermissionHash, Value: util.Uint160{3, 2, 1}})
t.Run("valid, with trusts", func(t *testing.T) {
require.NoError(t, m.IsValid(contractHash))
})
m.Trusts.Add(util.Uint160{1, 2, 3})
m.Trusts.Add(d)
t.Run("invalid, with trusts", func(t *testing.T) {
require.Error(t, m.IsValid(contractHash))
})
@ -276,8 +277,17 @@ func TestManifestToStackItem(t *testing.T) {
}},
Permissions: []Permission{*NewPermission(PermissionWildcard)},
SupportedStandards: []string{"NEP-17"},
Trusts: WildUint160s{
Value: []util.Uint160{{1, 2, 3}},
Trusts: WildPermissionDescs{
Value: []PermissionDesc{
{
Type: PermissionHash,
Value: util.Uint160{1, 2, 3},
},
{
Type: PermissionGroup,
Value: pk.PublicKey(),
},
},
},
Extra: []byte(`even not a json allowed`),
}

View file

@ -104,3 +104,32 @@ func stringsHaveDups(strings []string) bool {
}
return false
}
// permissionDescsHaveDups checks given set of strings for duplicates. It modifies the slice given!
func permissionDescsHaveDups(descs []PermissionDesc) bool {
sort.Slice(descs, func(i, j int) bool {
return descs[i].Less(descs[j])
})
for i := range descs {
if i == 0 {
continue
}
j := i - 1
if descs[i].Type != descs[j].Type {
continue
}
switch descs[i].Type {
case PermissionWildcard:
return true
case PermissionHash:
if descs[i].Hash() == descs[j].Hash() {
return true
}
case PermissionGroup:
if descs[i].Group().Cmp(descs[j].Group()) == 0 {
return true
}
}
}
return false
}

View file

@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"sort"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
@ -89,6 +88,37 @@ func (d *PermissionDesc) Group() *keys.PublicKey {
return d.Value.(*keys.PublicKey)
}
// Less returns true if this value is less than given PermissionDesc value.
func (d *PermissionDesc) Less(d1 PermissionDesc) bool {
if d.Type < d1.Type {
return true
}
if d.Type != d1.Type {
return false
}
switch d.Type {
case PermissionHash:
return d.Hash().Less(d1.Hash())
case PermissionGroup:
return d.Group().Cmp(d1.Group()) < 0
}
return false
}
// Equals returns true if both PermissionDesc values are the same.
func (d *PermissionDesc) Equals(v PermissionDesc) bool {
if d.Type != v.Type {
return false
}
switch d.Type {
case PermissionHash:
return d.Hash().Equals(v.Hash())
case PermissionGroup:
return d.Group().Cmp(v.Group()) == 0
}
return false
}
// IsValid checks if Permission is correct.
func (p *Permission) IsValid() error {
for i := range p.Methods.Value {
@ -122,45 +152,8 @@ func (ps Permissions) AreValid() error {
for i := range ps {
contracts = append(contracts, ps[i].Contract)
}
sort.Slice(contracts, func(i, j int) bool {
if contracts[i].Type < contracts[j].Type {
return true
}
if contracts[i].Type != contracts[j].Type {
return false
}
switch contracts[i].Type {
case PermissionHash:
return contracts[i].Hash().Less(contracts[j].Hash())
case PermissionGroup:
return contracts[i].Group().Cmp(contracts[j].Group()) < 0
}
return false
})
for i := range contracts {
if i == 0 {
continue
}
j := i - 1
if contracts[i].Type != contracts[j].Type {
continue
}
var bad bool
switch contracts[i].Type {
case PermissionWildcard:
bad = true
case PermissionHash:
if contracts[i].Hash() == contracts[j].Hash() {
bad = true
}
case PermissionGroup:
if contracts[i].Group().Cmp(contracts[j].Group()) == 0 {
bad = true
}
}
if bad {
return errors.New("duplicate contracts")
}
if permissionDescsHaveDups(contracts) {
return errors.New("contracts have duplicates")
}
return nil
}
@ -254,20 +247,55 @@ func (d *PermissionDesc) UnmarshalJSON(data []byte) error {
return errors.New("unknown permission")
}
// ToStackItem converts PermissionDesc to stackitem.Item.
func (d *PermissionDesc) ToStackItem() stackitem.Item {
switch d.Type {
case PermissionWildcard:
return stackitem.Null{}
case PermissionHash:
return stackitem.NewByteArray(d.Hash().BytesBE())
case PermissionGroup:
return stackitem.NewByteArray(d.Group().Bytes())
default:
panic("unsupported PermissionDesc type")
}
}
// FromStackItem converts stackitem.Item to PermissionDesc.
func (d *PermissionDesc) FromStackItem(item stackitem.Item) error {
if _, ok := item.(stackitem.Null); ok {
d.Type = PermissionWildcard
return nil
}
if item.Type() != stackitem.ByteArrayT {
return fmt.Errorf("unsupported permission descriptor type: %s", item.Type())
}
byteArr, err := item.TryBytes()
if err != nil {
return err
}
switch len(byteArr) {
case util.Uint160Size:
hash, _ := util.Uint160DecodeBytesBE(byteArr)
d.Type = PermissionHash
d.Value = hash
case 33:
pKey, err := keys.NewPublicKeyFromBytes(byteArr, elliptic.P256())
if err != nil {
return err
}
d.Type = PermissionGroup
d.Value = pKey
default:
return errors.New("invalid ByteArray length")
}
return nil
}
// ToStackItem converts Permission to stackitem.Item.
func (p *Permission) ToStackItem() stackitem.Item {
var (
contract stackitem.Item
methods stackitem.Item
)
switch p.Contract.Type {
case PermissionWildcard:
contract = stackitem.Null{}
case PermissionHash:
contract = stackitem.NewByteArray(p.Contract.Hash().BytesBE())
case PermissionGroup:
contract = stackitem.NewByteArray(p.Contract.Group().Bytes())
}
var methods stackitem.Item
contract := p.Contract.ToStackItem()
if p.Methods.IsWildcard() {
methods = stackitem.Null{}
} else {
@ -293,35 +321,12 @@ func (p *Permission) FromStackItem(item stackitem.Item) error {
if len(str) != 2 {
return errors.New("invalid Permission stackitem length")
}
if _, ok := str[0].(stackitem.Null); ok {
p.Contract = PermissionDesc{
Type: PermissionWildcard,
}
} else {
byteArr, err := str[0].TryBytes()
desc := new(PermissionDesc)
err = desc.FromStackItem(str[0])
if err != nil {
return err
}
switch len(byteArr) {
case util.Uint160Size:
hash, _ := util.Uint160DecodeBytesBE(byteArr)
p.Contract = PermissionDesc{
Type: PermissionHash,
Value: hash,
}
case 33:
pKey, err := keys.NewPublicKeyFromBytes(byteArr, elliptic.P256())
if err != nil {
return err
}
p.Contract = PermissionDesc{
Type: PermissionGroup,
Value: pKey,
}
default:
return errors.New("invalid Contract ByteArray length")
}
return fmt.Errorf("invalid Contract stackitem: %w", err)
}
p.Contract = *desc
if _, ok := str[1].(stackitem.Null); ok {
p.Methods = WildStrings{Value: nil}
} else {