smartcontract: add smartcontract manifest

Manifest contains all of smartcontract's metadata including
parameters, return value, permissions etc.
This commit is contained in:
Evgenii Stratonikov 2020-03-18 18:21:12 +03:00
parent e29d881901
commit dd38e3ec3b
7 changed files with 823 additions and 0 deletions

View file

@ -0,0 +1,106 @@
package manifest
// This file contains types and helper methods for wildcard containers.
// Wildcard container can contain either a finite set of elements or
// every possible element, in which case it is named `wildcard`.
import (
"bytes"
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// WildStrings represents string set which can be wildcard.
type WildStrings struct {
Value []string
}
// WildUint160s represents Uint160 set which can be wildcard.
type WildUint160s struct {
Value []util.Uint160
}
// Contains checks if v is in the container.
func (c *WildStrings) Contains(v string) bool {
if c.IsWildcard() {
return true
}
for _, s := range c.Value {
if v == s {
return true
}
}
return false
}
// Contains checks if v is in the container.
func (c *WildUint160s) Contains(v util.Uint160) bool {
if c.IsWildcard() {
return true
}
for _, u := range c.Value {
if u.Equals(v) {
return true
}
}
return false
}
// IsWildcard returns true iff container is wildcard.
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 }
// 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{} }
// 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) }
// MarshalJSON implements json.Marshaler interface.
func (c *WildStrings) MarshalJSON() ([]byte, error) {
if c.IsWildcard() {
return []byte(`"*"`), nil
}
return json.Marshal(c.Value)
}
// MarshalJSON implements json.Marshaler interface.
func (c *WildUint160s) MarshalJSON() ([]byte, error) {
if c.IsWildcard() {
return []byte(`"*"`), nil
}
return json.Marshal(c.Value)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (c *WildStrings) UnmarshalJSON(data []byte) error {
if !bytes.Equal(data, []byte(`"*"`)) {
ss := []string{}
if err := json.Unmarshal(data, &ss); err != nil {
return err
}
c.Value = ss
}
return nil
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (c *WildUint160s) UnmarshalJSON(data []byte) error {
if !bytes.Equal(data, []byte(`"*"`)) {
us := []util.Uint160{}
if err := json.Unmarshal(data, &us); err != nil {
return err
}
c.Value = us
}
return nil
}

View file

@ -0,0 +1,112 @@
package manifest
import (
"encoding/json"
"testing"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestContainer_Restrict(t *testing.T) {
t.Run("string", func(t *testing.T) {
c := new(WildStrings)
require.True(t, c.IsWildcard())
require.True(t, c.Contains("abc"))
c.Restrict()
require.False(t, c.IsWildcard())
require.False(t, c.Contains("abc"))
require.Equal(t, 0, len(c.Value))
})
t.Run("uint160", func(t *testing.T) {
c := new(WildUint160s)
u := random.Uint160()
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))
})
}
func TestContainer_Add(t *testing.T) {
t.Run("string", func(t *testing.T) {
c := new(WildStrings)
require.Equal(t, []string(nil), c.Value)
c.Add("abc")
require.True(t, c.Contains("abc"))
require.False(t, c.Contains("aaa"))
})
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()}
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()))
})
}
func TestContainer_MarshalJSON(t *testing.T) {
t.Run("string", func(t *testing.T) {
t.Run("wildcard", func(t *testing.T) {
expected := new(WildStrings)
testserdes.MarshalUnmarshalJSON(t, expected, new(WildStrings))
})
t.Run("empty", func(t *testing.T) {
expected := new(WildStrings)
expected.Restrict()
testserdes.MarshalUnmarshalJSON(t, expected, new(WildStrings))
})
t.Run("non-empty", func(t *testing.T) {
expected := new(WildStrings)
expected.Add("string1")
expected.Add("string2")
testserdes.MarshalUnmarshalJSON(t, expected, new(WildStrings))
})
t.Run("invalid", func(t *testing.T) {
js := []byte(`[123]`)
c := new(WildStrings)
require.Error(t, json.Unmarshal(js, c))
})
})
t.Run("uint160", func(t *testing.T) {
t.Run("wildcard", func(t *testing.T) {
expected := new(WildUint160s)
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
})
t.Run("empty", func(t *testing.T) {
expected := new(WildUint160s)
expected.Restrict()
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
})
t.Run("non-empty", func(t *testing.T) {
expected := new(WildUint160s)
expected.Add(random.Uint160())
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
})
t.Run("invalid", func(t *testing.T) {
js := []byte(`["notahex"]`)
c := new(WildUint160s)
require.Error(t, json.Unmarshal(js, c))
})
})
}

View file

@ -0,0 +1,128 @@
package manifest
import (
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// MaxManifestSize is a max length for a valid contract manifest.
const MaxManifestSize = 2048
// ABI represents a contract application binary interface.
type ABI struct {
Hash util.Uint160 `json:"hash"`
EntryPoint Method `json:"entryPoint"`
Methods []Method `json:"methods"`
Events []Event `json:"events"`
}
// Manifest represens contract metadata.
type Manifest struct {
// ABI is a contract's ABI.
ABI ABI
// Groups is a set of groups to which a contract belongs.
Groups []Group
// Features is a set of contract's features.
Features smartcontract.PropertyState
Permissions []Permission
// Trusts is a set of hashes to a which contract trusts.
Trusts WildUint160s
// SafeMethods is a set of names of safe methods.
SafeMethods WildStrings
// Extra is an implementation-defined user data.
Extra interface{}
}
type manifestAux struct {
ABI *ABI `json:"abi"`
Groups []Group `json:"groups"`
Features map[string]bool `json:"features"`
Permissions []Permission `json:"permissions"`
Trusts *WildUint160s `json:"trusts"`
SafeMethods *WildStrings `json:"safeMethods"`
Extra interface{} `json:"extra"`
}
// NewManifest returns new manifest with necessary fields initialized.
func NewManifest(h util.Uint160) *Manifest {
m := &Manifest{
ABI: ABI{
Hash: h,
Methods: []Method{},
Events: []Event{},
},
Groups: []Group{},
Features: smartcontract.NoProperties,
}
m.Trusts.Restrict()
m.SafeMethods.Restrict()
return m
}
// DefaultManifest returns default contract manifest.
func DefaultManifest(h util.Uint160) *Manifest {
m := NewManifest(h)
m.ABI.EntryPoint = *DefaultEntryPoint()
m.Permissions = []Permission{*NewPermission(PermissionWildcard)}
return m
}
// CanCall returns true is current contract is allowed to call
// method of another contract.
func (m *Manifest) CanCall(toCall *Manifest, method string) bool {
// this if is not present in the original code but should probably be here
if toCall.SafeMethods.Contains(method) {
return true
}
for i := range m.Permissions {
if m.Permissions[i].IsAllowed(toCall, method) {
return true
}
}
return false
}
// MarshalJSON implements json.Marshaler interface.
func (m *Manifest) MarshalJSON() ([]byte, error) {
features := make(map[string]bool)
features["storage"] = m.Features&smartcontract.HasStorage != 0
features["payable"] = m.Features&smartcontract.IsPayable != 0
aux := &manifestAux{
ABI: &m.ABI,
Groups: m.Groups,
Features: features,
Permissions: m.Permissions,
Trusts: &m.Trusts,
SafeMethods: &m.SafeMethods,
Extra: m.Extra,
}
return json.Marshal(aux)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (m *Manifest) UnmarshalJSON(data []byte) error {
aux := &manifestAux{
ABI: &m.ABI,
Trusts: &m.Trusts,
SafeMethods: &m.SafeMethods,
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
if aux.Features["storage"] {
m.Features |= smartcontract.HasStorage
}
if aux.Features["payable"] {
m.Features |= smartcontract.IsPayable
}
m.Groups = aux.Groups
m.Permissions = aux.Permissions
m.Extra = aux.Extra
return nil
}

View file

@ -0,0 +1,121 @@
package manifest
import (
"encoding/json"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
// Test vectors are taken from the main NEO repo
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs#L10
func TestManifest_MarshalJSON(t *testing.T) {
t.Run("default", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":null}`
m := testUnmarshalMarshalManifest(t, s)
require.Equal(t, DefaultManifest(util.Uint160{}), m)
})
// this vector is missing from original repo
t.Run("features", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":true,"payable":true},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("permissions", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("safe methods", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":["balanceOf"],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("trust", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("groups", func(t *testing.T) {
s := `{"groups":[{"pubKey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("extra", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":{"key":"value"}}`
testUnmarshalMarshalManifest(t, s)
})
}
func testUnmarshalMarshalManifest(t *testing.T, s string) *Manifest {
js := []byte(s)
c := NewManifest(util.Uint160{})
require.NoError(t, json.Unmarshal(js, c))
data, err := json.Marshal(c)
require.NoError(t, err)
require.JSONEq(t, s, string(data))
return c
}
func TestManifest_CanCall(t *testing.T) {
t.Run("safe methods", func(t *testing.T) {
man1 := NewManifest(util.Uint160{})
man2 := DefaultManifest(util.Uint160{})
require.False(t, man1.CanCall(man2, "method1"))
man2.SafeMethods.Add("method1")
require.True(t, man1.CanCall(man2, "method1"))
})
t.Run("wildcard permission", func(t *testing.T) {
man1 := DefaultManifest(util.Uint160{})
man2 := DefaultManifest(util.Uint160{})
require.True(t, man1.CanCall(man2, "method1"))
})
}
func TestPermission_IsAllowed(t *testing.T) {
manifest := DefaultManifest(util.Uint160{})
t.Run("wildcard", func(t *testing.T) {
perm := NewPermission(PermissionWildcard)
require.True(t, perm.IsAllowed(manifest, "AAA"))
})
t.Run("hash", func(t *testing.T) {
perm := NewPermission(PermissionHash, util.Uint160{})
require.True(t, perm.IsAllowed(manifest, "AAA"))
t.Run("restrict methods", func(t *testing.T) {
perm.Methods.Restrict()
require.False(t, perm.IsAllowed(manifest, "AAA"))
perm.Methods.Add("AAA")
require.True(t, perm.IsAllowed(manifest, "AAA"))
})
})
t.Run("invalid hash", func(t *testing.T) {
perm := NewPermission(PermissionHash, util.Uint160{1})
require.False(t, perm.IsAllowed(manifest, "AAA"))
})
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
manifest.Groups = []Group{{PublicKey: priv.PublicKey()}}
t.Run("group", func(t *testing.T) {
perm := NewPermission(PermissionGroup, priv.PublicKey())
require.True(t, perm.IsAllowed(manifest, "AAA"))
})
t.Run("invalid group", func(t *testing.T) {
priv2, err := keys.NewPrivateKey()
require.NoError(t, err)
perm := NewPermission(PermissionGroup, priv2.PublicKey())
require.False(t, perm.IsAllowed(manifest, "AAA"))
})
}

View file

@ -0,0 +1,89 @@
package manifest
import (
"encoding/hex"
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
)
// Parameter represents smartcontract's parameter's definition.
type Parameter struct {
Name string `json:"name"`
Type smartcontract.ParamType `json:"type"`
}
// Event is a description of a single event.
type Event struct {
Name string `json:"name"`
Parameters []Parameter `json:"parameters"`
}
// Group represents a group of smartcontracts identified by a public key.
// Every SC in a group must provide signature of it's hash to prove
// it belongs to a group.
type Group struct {
PublicKey *keys.PublicKey `json:"pubKey"`
Signature []byte `json:"signature"`
}
type groupAux struct {
PublicKey string `json:"pubKey"`
Signature []byte `json:"signature"`
}
// Method represents method's metadata.
type Method struct {
Name string `json:"name"`
Parameters []Parameter `json:"parameters"`
ReturnType smartcontract.ParamType `json:"returnType"`
}
// NewParameter returns new paramter with the specified name and type.
func NewParameter(name string, typ smartcontract.ParamType) Parameter {
return Parameter{
Name: name,
Type: typ,
}
}
// DefaultEntryPoint represents default entrypoint to a contract.
func DefaultEntryPoint() *Method {
return &Method{
Name: "Main",
Parameters: []Parameter{
NewParameter("operation", smartcontract.StringType),
NewParameter("args", smartcontract.ArrayType),
},
ReturnType: smartcontract.AnyType,
}
}
// MarshalJSON implements json.Marshaler interface.
func (g *Group) MarshalJSON() ([]byte, error) {
aux := &groupAux{
PublicKey: hex.EncodeToString(g.PublicKey.Bytes()),
Signature: g.Signature,
}
return json.Marshal(aux)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (g *Group) UnmarshalJSON(data []byte) error {
aux := new(groupAux)
if err := json.Unmarshal(data, aux); err != nil {
return err
}
b, err := hex.DecodeString(aux.PublicKey)
if err != nil {
return err
}
pub := new(keys.PublicKey)
if err := pub.DecodeBytes(b); err != nil {
return err
}
g.PublicKey = pub
g.Signature = aux.Signature
return nil
}

View file

@ -0,0 +1,173 @@
package manifest
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// PermissionType represents permission type.
type PermissionType uint8
const (
// PermissionWildcard allows everything.
PermissionWildcard PermissionType = 0
// PermissionHash restricts called contracts based on hash.
PermissionHash PermissionType = 1
// PermissionGroup restricts called contracts based on public key.
PermissionGroup PermissionType = 2
)
// PermissionDesc is a permission descriptor.
type PermissionDesc struct {
Type PermissionType
Value interface{}
}
// Permission describes which contracts may be invoked and which methods are called.
type Permission struct {
Contract PermissionDesc `json:"contract"`
Methods WildStrings `json:"methods"`
}
type permissionAux struct {
Contract PermissionDesc `json:"contract"`
Methods WildStrings `json:"methods"`
}
// NewPermission returns new permission of a given type.
func NewPermission(typ PermissionType, args ...interface{}) *Permission {
return &Permission{
Contract: *newPermissionDesc(typ, args...),
}
}
func newPermissionDesc(typ PermissionType, args ...interface{}) *PermissionDesc {
desc := &PermissionDesc{Type: typ}
switch typ {
case PermissionWildcard:
if len(args) != 0 {
panic("wildcard permission has no arguments")
}
case PermissionHash:
if len(args) == 0 {
panic("hash permission should have an argument")
} else if u, ok := args[0].(util.Uint160); !ok {
panic("hash permission should have util.Uint160 argument")
} else {
desc.Value = u
}
case PermissionGroup:
if len(args) == 0 {
panic("group permission should have an argument")
} else if pub, ok := args[0].(*keys.PublicKey); !ok {
panic("group permission should have a public key argument")
} else {
desc.Value = pub
}
}
return desc
}
// Hash returns hash for hash-permission.
func (d *PermissionDesc) Hash() util.Uint160 {
return d.Value.(util.Uint160)
}
// Group returns group's public key for group-permission.
func (d *PermissionDesc) Group() *keys.PublicKey {
return d.Value.(*keys.PublicKey)
}
// IsAllowed checks if method is allowed to be executed.
func (p *Permission) IsAllowed(m *Manifest, method string) bool {
switch p.Contract.Type {
case PermissionWildcard:
return true
case PermissionHash:
if !p.Contract.Hash().Equals(m.ABI.Hash) {
return false
}
case PermissionGroup:
g := p.Contract.Group()
for i := range m.Groups {
if !g.Equal(m.Groups[i].PublicKey) {
return false
}
}
default:
panic(fmt.Sprintf("unexpected permission: %d", p.Contract.Type))
}
if p.Methods.IsWildcard() {
return true
}
return p.Methods.Contains(method)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (p *Permission) UnmarshalJSON(data []byte) error {
aux := new(permissionAux)
if err := json.Unmarshal(data, aux); err != nil {
return err
}
p.Contract = aux.Contract
p.Methods = aux.Methods
return nil
}
// MarshalJSON implements json.Marshaler interface.
func (d *PermissionDesc) MarshalJSON() ([]byte, error) {
switch d.Type {
case PermissionHash:
return json.Marshal("0x" + d.Hash().StringLE())
case PermissionGroup:
return json.Marshal(hex.EncodeToString(d.Group().Bytes()))
default:
return []byte(`"*"`), nil
}
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (d *PermissionDesc) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
const uint160HexSize = 2 * util.Uint160Size
switch len(s) {
case 2 + uint160HexSize:
// allow to unmarshal both hex and 0xhex forms
if s[0] != '0' || s[1] != 'x' {
return errors.New("invalid uint160")
}
s = s[2:]
fallthrough
case uint160HexSize:
u, err := util.Uint160DecodeStringLE(s)
if err != nil {
return err
}
d.Type = PermissionHash
d.Value = u
return nil
case 66:
pub, err := keys.NewPublicKeyFromString(s)
if err != nil {
return err
}
d.Type = PermissionGroup
d.Value = pub
return nil
case 1:
if s == "*" {
d.Type = PermissionWildcard
return nil
}
}
return errors.New("unknown permission")
}

View file

@ -0,0 +1,94 @@
package manifest
import (
"encoding/json"
"fmt"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestNewPermission(t *testing.T) {
require.Panics(t, func() { NewPermission(PermissionWildcard, util.Uint160{}) })
require.Panics(t, func() { NewPermission(PermissionHash) })
require.Panics(t, func() { NewPermission(PermissionHash, 1) })
require.Panics(t, func() { NewPermission(PermissionGroup) })
require.Panics(t, func() { NewPermission(PermissionGroup, util.Uint160{}) })
}
func TestPermission_MarshalJSON(t *testing.T) {
t.Run("wildcard", func(t *testing.T) {
expected := NewPermission(PermissionWildcard)
expected.Methods.Restrict()
testMarshalUnmarshal(t, expected, NewPermission(PermissionWildcard))
})
t.Run("group", func(t *testing.T) {
expected := NewPermission(PermissionWildcard)
expected.Contract.Type = PermissionGroup
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
expected.Contract.Value = priv.PublicKey()
expected.Methods.Add("method1")
expected.Methods.Add("method2")
testMarshalUnmarshal(t, expected, NewPermission(PermissionWildcard))
})
t.Run("hash", func(t *testing.T) {
expected := NewPermission(PermissionWildcard)
expected.Contract.Type = PermissionHash
expected.Contract.Value = random.Uint160()
testMarshalUnmarshal(t, expected, NewPermission(PermissionWildcard))
})
}
func TestPermissionDesc_MarshalJSON(t *testing.T) {
t.Run("uint160 with 0x", func(t *testing.T) {
u := random.Uint160()
s := u.StringLE()
js := []byte(fmt.Sprintf(`"0x%s"`, s))
d := new(PermissionDesc)
require.NoError(t, json.Unmarshal(js, d))
require.Equal(t, u, d.Value.(util.Uint160))
})
t.Run("invalid uint160", func(t *testing.T) {
d := new(PermissionDesc)
s := random.String(util.Uint160Size * 2)
js := []byte(fmt.Sprintf(`"ok%s"`, s))
require.Error(t, json.Unmarshal(js, d))
js = []byte(fmt.Sprintf(`"%s"`, s))
require.Error(t, json.Unmarshal(js, d))
})
t.Run("invalid public key", func(t *testing.T) {
d := new(PermissionDesc)
s := random.String(65)
s = "k" + s // not a hex
js := []byte(fmt.Sprintf(`"%s"`, s))
require.Error(t, json.Unmarshal(js, d))
})
t.Run("not a string", func(t *testing.T) {
d := new(PermissionDesc)
js := []byte(`123`)
require.Error(t, json.Unmarshal(js, d))
})
t.Run("invalid string", func(t *testing.T) {
d := new(PermissionDesc)
js := []byte(`"invalid length"`)
require.Error(t, json.Unmarshal(js, d))
})
}
func testMarshalUnmarshal(t *testing.T, expected, actual interface{}) {
data, err := json.Marshal(expected)
require.NoError(t, err)
require.NoError(t, json.Unmarshal(data, actual))
require.Equal(t, expected, actual)
}