Merge pull request #1988 from nspcc-dev/cli-permission
cli,compiler: allow to specify manifest permissions
This commit is contained in:
commit
352450d25a
11 changed files with 241 additions and 19 deletions
129
cli/smartcontract/permission.go
Normal file
129
cli/smartcontract/permission.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type permission manifest.Permission
|
||||||
|
|
||||||
|
const (
|
||||||
|
permHashKey = "hash"
|
||||||
|
permGroupKey = "group"
|
||||||
|
permMethodKey = "methods"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p permission) MarshalYAML() (interface{}, error) {
|
||||||
|
m := make(yaml.MapSlice, 0, 2)
|
||||||
|
switch p.Contract.Type {
|
||||||
|
case manifest.PermissionWildcard:
|
||||||
|
case manifest.PermissionHash:
|
||||||
|
m = append(m, yaml.MapItem{
|
||||||
|
Key: permHashKey,
|
||||||
|
Value: p.Contract.Value.(util.Uint160).StringLE(),
|
||||||
|
})
|
||||||
|
case manifest.PermissionGroup:
|
||||||
|
bs := p.Contract.Value.(*keys.PublicKey).Bytes()
|
||||||
|
m = append(m, yaml.MapItem{
|
||||||
|
Key: permGroupKey,
|
||||||
|
Value: hex.EncodeToString(bs),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid permission type: %d", p.Contract.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
var val interface{} = "*"
|
||||||
|
if !p.Methods.IsWildcard() {
|
||||||
|
val = p.Methods.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
m = append(m, yaml.MapItem{
|
||||||
|
Key: permMethodKey,
|
||||||
|
Value: val,
|
||||||
|
})
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *permission) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var m map[string]interface{}
|
||||||
|
if err := unmarshal(&m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.fillType(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.fillMethods(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *permission) fillType(m map[string]interface{}) error {
|
||||||
|
vh, ok1 := m[permHashKey]
|
||||||
|
vg, ok2 := m[permGroupKey]
|
||||||
|
switch {
|
||||||
|
case ok1 && ok2:
|
||||||
|
return errors.New("permission must have either 'hash' or 'group' field")
|
||||||
|
case ok1:
|
||||||
|
s, ok := vh.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid 'hash' type")
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := util.Uint160DecodeStringLE(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Contract.Type = manifest.PermissionHash
|
||||||
|
p.Contract.Value = u
|
||||||
|
case ok2:
|
||||||
|
s, ok := vg.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid 'hash' type")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := keys.NewPublicKeyFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Contract.Type = manifest.PermissionGroup
|
||||||
|
p.Contract.Value = pub
|
||||||
|
default:
|
||||||
|
p.Contract.Type = manifest.PermissionWildcard
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *permission) fillMethods(m map[string]interface{}) error {
|
||||||
|
methods, ok := m[permMethodKey]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("'methods' field is missing from permission")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mt := methods.(type) {
|
||||||
|
case string:
|
||||||
|
if mt == "*" {
|
||||||
|
p.Methods.Value = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
ms := make([]string, len(mt))
|
||||||
|
for i := range mt {
|
||||||
|
ms[i], ok = mt[i].(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid permission method name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Methods.Value = ms
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return errors.New("'methods' field is invalid")
|
||||||
|
}
|
|
@ -401,6 +401,7 @@ func initSmartContract(ctx *cli.Context) error {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Permissions: []permission{permission(*manifest.NewPermission(manifest.PermissionWildcard))},
|
||||||
}
|
}
|
||||||
b, err := yaml.Marshal(m)
|
b, err := yaml.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -450,6 +451,10 @@ func contractCompile(ctx *cli.Context) error {
|
||||||
o.Name = conf.Name
|
o.Name = conf.Name
|
||||||
o.ContractEvents = conf.Events
|
o.ContractEvents = conf.Events
|
||||||
o.ContractSupportedStandards = conf.SupportedStandards
|
o.ContractSupportedStandards = conf.SupportedStandards
|
||||||
|
o.Permissions = make([]manifest.Permission, len(conf.Permissions))
|
||||||
|
for i := range conf.Permissions {
|
||||||
|
o.Permissions[i] = manifest.Permission(conf.Permissions[i])
|
||||||
|
}
|
||||||
o.SafeMethods = conf.SafeMethods
|
o.SafeMethods = conf.SafeMethods
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,6 +679,7 @@ type ProjectConfig struct {
|
||||||
SafeMethods []string
|
SafeMethods []string
|
||||||
SupportedStandards []string
|
SupportedStandards []string
|
||||||
Events []manifest.Event
|
Events []manifest.Event
|
||||||
|
Permissions []permission
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspect(ctx *cli.Context) error {
|
func inspect(ctx *cli.Context) error {
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
package smartcontract
|
package smartcontract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"flag"
|
"flag"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInitSmartContract(t *testing.T) {
|
func TestInitSmartContract(t *testing.T) {
|
||||||
|
@ -64,5 +69,73 @@ events:
|
||||||
parameters:
|
parameters:
|
||||||
- name: args
|
- name: args
|
||||||
type: Array
|
type: Array
|
||||||
|
permissions:
|
||||||
|
- methods: '*'
|
||||||
`, string(manifest))
|
`, string(manifest))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testPermissionMarshal(t *testing.T, p *manifest.Permission, expected string) {
|
||||||
|
out, err := yaml.Marshal((*permission)(p))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(out))
|
||||||
|
|
||||||
|
t.Run("Unmarshal", func(t *testing.T) {
|
||||||
|
actual := new(permission)
|
||||||
|
require.NoError(t, yaml.Unmarshal(out, actual))
|
||||||
|
require.Equal(t, p, (*manifest.Permission)(actual))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionMarshal(t *testing.T) {
|
||||||
|
t.Run("Wildcard", func(t *testing.T) {
|
||||||
|
p := manifest.NewPermission(manifest.PermissionWildcard)
|
||||||
|
testPermissionMarshal(t, p, "methods: '*'\n")
|
||||||
|
})
|
||||||
|
t.Run("no allowed methods", func(t *testing.T) {
|
||||||
|
p := manifest.NewPermission(manifest.PermissionWildcard)
|
||||||
|
p.Methods.Restrict()
|
||||||
|
testPermissionMarshal(t, p, "methods: []\n")
|
||||||
|
})
|
||||||
|
t.Run("hash", func(t *testing.T) {
|
||||||
|
h := random.Uint160()
|
||||||
|
p := manifest.NewPermission(manifest.PermissionHash, h)
|
||||||
|
testPermissionMarshal(t, p,
|
||||||
|
"hash: "+h.StringLE()+"\n"+
|
||||||
|
"methods: '*'\n")
|
||||||
|
})
|
||||||
|
t.Run("group with some methods", func(t *testing.T) {
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
p := manifest.NewPermission(manifest.PermissionGroup, priv.PublicKey())
|
||||||
|
p.Methods.Add("abc")
|
||||||
|
p.Methods.Add("lamao")
|
||||||
|
testPermissionMarshal(t, p,
|
||||||
|
"group: "+hex.EncodeToString(priv.PublicKey().Bytes())+"\n"+
|
||||||
|
"methods:\n- abc\n- lamao\n")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionUnmarshalInvalid(t *testing.T) {
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pub := hex.EncodeToString(priv.PublicKey().Bytes())
|
||||||
|
u160 := random.Uint160().StringLE()
|
||||||
|
testCases := []string{
|
||||||
|
"hash: []\nmethods: '*'\n", // invalid hash type
|
||||||
|
"hash: notahex\nmethods: '*'\n", // invalid hash
|
||||||
|
"group: []\nmethods: '*'\n", // invalid group type
|
||||||
|
"group: notahex\nmethods: '*'\n", // invalid group
|
||||||
|
"hash: " + u160 + "\n", // missing methods
|
||||||
|
"group: " + pub + "\nhash: " + u160 + "\nmethods: '*'", // hash/group conflict
|
||||||
|
"hash: " + u160 + "\nmethods:\n a: b\n", // invalid methods type
|
||||||
|
"hash: " + u160 + "\nmethods:\n- []\n", // methods array, invalid single
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc, func(t *testing.T) {
|
||||||
|
require.Error(t, yaml.Unmarshal([]byte(tc), new(permission)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
3
cli/testdata/deploy/neo-go.yml
vendored
3
cli/testdata/deploy/neo-go.yml
vendored
|
@ -1 +1,4 @@
|
||||||
name: Test deploy
|
name: Test deploy
|
||||||
|
permissions:
|
||||||
|
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
|
||||||
|
methods: ["update"]
|
||||||
|
|
|
@ -13,3 +13,5 @@ events:
|
||||||
type: Integer
|
type: Integer
|
||||||
- name: tokenId
|
- name: tokenId
|
||||||
type: ByteArray
|
type: ByteArray
|
||||||
|
permissions:
|
||||||
|
- methods: ["onNEP11Transfer"]
|
||||||
|
|
|
@ -12,3 +12,5 @@ events:
|
||||||
type: Integer
|
type: Integer
|
||||||
- name: tokenId
|
- name: tokenId
|
||||||
type: ByteArray
|
type: ByteArray
|
||||||
|
permissions:
|
||||||
|
- methods: ["onNEP11Transfer"]
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
@ -78,6 +79,10 @@ func NewDeployTx(bc blockchainer.Blockchainer, name string, sender util.Uint160,
|
||||||
o.Name = conf.Name
|
o.Name = conf.Name
|
||||||
o.ContractEvents = conf.Events
|
o.ContractEvents = conf.Events
|
||||||
o.ContractSupportedStandards = conf.SupportedStandards
|
o.ContractSupportedStandards = conf.SupportedStandards
|
||||||
|
o.Permissions = make([]manifest.Permission, len(conf.Permissions))
|
||||||
|
for i := range conf.Permissions {
|
||||||
|
o.Permissions[i] = manifest.Permission(conf.Permissions[i])
|
||||||
|
}
|
||||||
o.SafeMethods = conf.SafeMethods
|
o.SafeMethods = conf.SafeMethods
|
||||||
}
|
}
|
||||||
m, err := compiler.CreateManifest(di, o)
|
m, err := compiler.CreateManifest(di, o)
|
||||||
|
|
|
@ -54,6 +54,9 @@ type Options struct {
|
||||||
|
|
||||||
// SafeMethods contains list of methods which will be marked as safe in manifest.
|
// SafeMethods contains list of methods which will be marked as safe in manifest.
|
||||||
SafeMethods []string
|
SafeMethods []string
|
||||||
|
|
||||||
|
// Permissions is a list of permissions for every contract method.
|
||||||
|
Permissions []manifest.Permission
|
||||||
}
|
}
|
||||||
|
|
||||||
type buildInfo struct {
|
type buildInfo struct {
|
||||||
|
|
|
@ -441,13 +441,6 @@ func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) {
|
||||||
if result.ABI.Events == nil {
|
if result.ABI.Events == nil {
|
||||||
result.ABI.Events = make([]manifest.Event, 0)
|
result.ABI.Events = make([]manifest.Event, 0)
|
||||||
}
|
}
|
||||||
result.Permissions = []manifest.Permission{
|
result.Permissions = o.Permissions
|
||||||
{
|
|
||||||
Contract: manifest.PermissionDesc{
|
|
||||||
Type: manifest.PermissionWildcard,
|
|
||||||
},
|
|
||||||
Methods: manifest.WildStrings{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,14 @@ func _deploy(data interface{}, isUpdate bool) { x := 1; _ = x }
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("convert to Manifest", func(t *testing.T) {
|
t.Run("convert to Manifest", func(t *testing.T) {
|
||||||
actual, err := d.ConvertToManifest(&Options{Name: "MyCTR", SafeMethods: []string{"methodInt", "methodString"}})
|
p := manifest.NewPermission(manifest.PermissionWildcard)
|
||||||
|
p.Methods.Add("randomMethod")
|
||||||
|
|
||||||
|
actual, err := d.ConvertToManifest(&Options{
|
||||||
|
Name: "MyCTR",
|
||||||
|
SafeMethods: []string{"methodInt", "methodString"},
|
||||||
|
Permissions: []manifest.Permission{*p},
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expected := &manifest.Manifest{
|
expected := &manifest.Manifest{
|
||||||
Name: "MyCTR",
|
Name: "MyCTR",
|
||||||
|
@ -267,15 +274,8 @@ func _deploy(data interface{}, isUpdate bool) { x := 1; _ = x }
|
||||||
},
|
},
|
||||||
Events: []manifest.Event{},
|
Events: []manifest.Event{},
|
||||||
},
|
},
|
||||||
Groups: []manifest.Group{},
|
Groups: []manifest.Group{},
|
||||||
Permissions: []manifest.Permission{
|
Permissions: []manifest.Permission{*p},
|
||||||
{
|
|
||||||
Contract: manifest.PermissionDesc{
|
|
||||||
Type: manifest.PermissionWildcard,
|
|
||||||
},
|
|
||||||
Methods: manifest.WildStrings{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Trusts: manifest.WildPermissionDescs{
|
Trusts: manifest.WildPermissionDescs{
|
||||||
Value: []manifest.PermissionDesc{},
|
Value: []manifest.PermissionDesc{},
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
cinterop "github.com/nspcc-dev/neo-go/pkg/interop"
|
cinterop "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -157,7 +158,12 @@ func TestAppCall(t *testing.T) {
|
||||||
|
|
||||||
inner, di, err := compiler.CompileWithDebugInfo("foo.go", strings.NewReader(srcInner))
|
inner, di, err := compiler.CompileWithDebugInfo("foo.go", strings.NewReader(srcInner))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
m, err := di.ConvertToManifest(&compiler.Options{Name: "Foo"})
|
m, err := di.ConvertToManifest(&compiler.Options{
|
||||||
|
Name: "Foo",
|
||||||
|
Permissions: []manifest.Permission{
|
||||||
|
*manifest.NewPermission(manifest.PermissionWildcard),
|
||||||
|
},
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ih := hash.Hash160(inner)
|
ih := hash.Hash160(inner)
|
||||||
|
|
Loading…
Reference in a new issue