mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-22 19:29:39 +00:00
smartcontract: add smartcontract manifest
Manifest contains all of smartcontract's metadata including parameters, return value, permissions etc.
This commit is contained in:
parent
e29d881901
commit
dd38e3ec3b
7 changed files with 823 additions and 0 deletions
106
pkg/smartcontract/manifest/container.go
Normal file
106
pkg/smartcontract/manifest/container.go
Normal 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
|
||||
}
|
112
pkg/smartcontract/manifest/container_test.go
Normal file
112
pkg/smartcontract/manifest/container_test.go
Normal 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))
|
||||
})
|
||||
})
|
||||
}
|
128
pkg/smartcontract/manifest/manifest.go
Normal file
128
pkg/smartcontract/manifest/manifest.go
Normal 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
|
||||
}
|
121
pkg/smartcontract/manifest/manifest_test.go
Normal file
121
pkg/smartcontract/manifest/manifest_test.go
Normal 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"))
|
||||
})
|
||||
}
|
89
pkg/smartcontract/manifest/method.go
Normal file
89
pkg/smartcontract/manifest/method.go
Normal 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
|
||||
}
|
173
pkg/smartcontract/manifest/permission.go
Normal file
173
pkg/smartcontract/manifest/permission.go
Normal 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")
|
||||
}
|
94
pkg/smartcontract/manifest/permission_test.go
Normal file
94
pkg/smartcontract/manifest/permission_test.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue