Merge pull request #1988 from nspcc-dev/cli-permission

cli,compiler: allow to specify manifest permissions
This commit is contained in:
Roman Khimov 2021-06-07 10:27:29 +03:00 committed by GitHub
commit 352450d25a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 241 additions and 19 deletions

View 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")
}

View file

@ -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 {

View file

@ -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)))
})
}
}

View file

@ -1 +1,4 @@
name: Test deploy name: Test deploy
permissions:
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
methods: ["update"]

View file

@ -13,3 +13,5 @@ events:
type: Integer type: Integer
- name: tokenId - name: tokenId
type: ByteArray type: ByteArray
permissions:
- methods: ["onNEP11Transfer"]

View file

@ -12,3 +12,5 @@ events:
type: Integer type: Integer
- name: tokenId - name: tokenId
type: ByteArray type: ByteArray
permissions:
- methods: ["onNEP11Transfer"]

View file

@ -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)

View file

@ -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 {

View file

@ -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
} }

View file

@ -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",
@ -268,14 +275,7 @@ 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{},
}, },

View file

@ -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)