[#48] frostfsid: Support Group updating

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2023-11-09 11:01:17 +03:00
parent 501b0c5e3c
commit e2e406932f
6 changed files with 965 additions and 199 deletions

View file

@ -57,21 +57,26 @@ type (
} }
Group struct { Group struct {
ID int64
Name string Name string
Namespace string Namespace string
KV map[string]string
} }
GroupExtended struct { GroupExtended struct {
ID int64
Name string Name string
Namespace string Namespace string
KV map[string]string
SubjectsCount int64 SubjectsCount int64
} }
) )
const ( const (
SubjectIAMPathKey = "iam-path" IAMPathKey = "iam-path"
SubjectCreatedTimeKey = "ctime" IAMARNKey = "iam-arn"
SubjectModifiedTimeKey = "mtime" IAMCreatedTimeKey = "ctime"
IAMModifiedTimeKey = "mtime"
) )
const iteratorBatchSize = 100 const iteratorBatchSize = 100
@ -107,6 +112,10 @@ const (
createGroupMethod = "createGroup" createGroupMethod = "createGroup"
getGroupMethod = "getGroup" getGroupMethod = "getGroup"
getGroupExtendedMethod = "getGroupExtended" getGroupExtendedMethod = "getGroupExtended"
getGroupIDByNameMethod = "getGroupIDByName"
setGroupNameMethod = "setGroupName"
setGroupKVMethod = "setGroupKV"
deleteGroupKVMethod = "deleteGroupKV"
listGroupsMethod = "listGroups" listGroupsMethod = "listGroups"
addSubjectToGroupMethod = "addSubjectToGroup" addSubjectToGroupMethod = "addSubjectToGroup"
removeSubjectFromGroupMethod = "removeSubjectFromGroup" removeSubjectFromGroupMethod = "removeSubjectFromGroup"
@ -240,13 +249,13 @@ func (c Client) RemoveSubjectKeyCall(addr util.Uint160, key *keys.PublicKey) (me
// SetSubjectKV updates subject kv map. // SetSubjectKV updates subject kv map.
// Must be invoked by contract owner. // Must be invoked by contract owner.
// You can use some predefined key constants: SubjectLoginKey, SubjectIAMPathKey, SubjectCreatedTimeKey, SubjectModifiedTimeKey. // You can use some predefined key constants: IAMPathKey, IAMARNKey, IAMCreatedTimeKey, IAMModifiedTimeKey.
func (c Client) SetSubjectKV(addr util.Uint160, key, val string) (tx util.Uint256, vub uint32, err error) { func (c Client) SetSubjectKV(addr util.Uint160, key, val string) (tx util.Uint256, vub uint32, err error) {
method, args := c.SetSubjectKVCall(addr, key, val) method, args := c.SetSubjectKVCall(addr, key, val)
return c.act.SendCall(c.contract, method, args...) return c.act.SendCall(c.contract, method, args...)
} }
// SetSubjectKVCall provides args for SetSubjectLogin to use in commonclient.Transaction. // SetSubjectKVCall provides args for SetSubjectKV to use in commonclient.Transaction.
func (c Client) SetSubjectKVCall(addr util.Uint160, key, val string) (method string, args []any) { func (c Client) SetSubjectKVCall(addr util.Uint160, key, val string) (method string, args []any) {
return setSubjectKVMethod, []any{addr, key, val} return setSubjectKVMethod, []any{addr, key, val}
} }
@ -258,7 +267,7 @@ func (c Client) SetSubjectName(addr util.Uint160, name string) (tx util.Uint256,
return c.act.SendCall(c.contract, method, args...) return c.act.SendCall(c.contract, method, args...)
} }
// SetSubjectNameCall provides args for SetSubjectLogin to use in commonclient.Transaction. // SetSubjectNameCall provides args for SetSubjectName to use in commonclient.Transaction.
func (c Client) SetSubjectNameCall(addr util.Uint160, name string) (method string, args []any) { func (c Client) SetSubjectNameCall(addr util.Uint160, name string) (method string, args []any) {
return setSubjectNameMethod, []any{addr, name} return setSubjectNameMethod, []any{addr, name}
} }
@ -270,7 +279,7 @@ func (c Client) DeleteSubjectKV(addr util.Uint160, key string) (tx util.Uint256,
return c.act.SendCall(c.contract, method, args...) return c.act.SendCall(c.contract, method, args...)
} }
// DeleteSubjectKVCall provides args for SetSubjectLogin to use in commonclient.Transaction. // DeleteSubjectKVCall provides args for DeleteSubjectKV to use in commonclient.Transaction.
func (c Client) DeleteSubjectKVCall(addr util.Uint160, key string) (method string, args []any) { func (c Client) DeleteSubjectKVCall(addr util.Uint160, key string) (method string, args []any) {
return deleteSubjectKVMethod, []any{addr, key} return deleteSubjectKVMethod, []any{addr, key}
} }
@ -386,8 +395,8 @@ func (c Client) CreateGroupCall(namespace, group string) (method string, args []
} }
// GetGroup gets group. // GetGroup gets group.
func (c Client) GetGroup(namespace, group string) (*Group, error) { func (c Client) GetGroup(namespace string, groupID int64) (*Group, error) {
items, err := unwrap.Array(c.act.Call(c.contract, getGroupMethod, namespace, group)) items, err := unwrap.Array(c.act.Call(c.contract, getGroupMethod, namespace, groupID))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -396,8 +405,8 @@ func (c Client) GetGroup(namespace, group string) (*Group, error) {
} }
// GetGroupExtended gets extended group. // GetGroupExtended gets extended group.
func (c Client) GetGroupExtended(namespace, group string) (*GroupExtended, error) { func (c Client) GetGroupExtended(namespace string, groupID int64) (*GroupExtended, error) {
items, err := unwrap.Array(c.act.Call(c.contract, getGroupExtendedMethod, namespace, group)) items, err := unwrap.Array(c.act.Call(c.contract, getGroupExtendedMethod, namespace, groupID))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -405,6 +414,48 @@ func (c Client) GetGroupExtended(namespace, group string) (*GroupExtended, error
return parseGroupExtended(items) return parseGroupExtended(items)
} }
// SetGroupName updates subject name.
// Must be invoked by contract owner.
func (c Client) SetGroupName(namespace string, groupID int64, name string) (tx util.Uint256, vub uint32, err error) {
method, args := c.SetGroupNameCall(namespace, groupID, name)
return c.act.SendCall(c.contract, method, args...)
}
// SetGroupNameCall provides args for SetGroupName to use in commonclient.Transaction.
func (c Client) SetGroupNameCall(namespace string, groupID int64, name string) (method string, args []any) {
return setGroupNameMethod, []any{namespace, groupID, name}
}
// SetGroupKV updates group kv map.
// Must be invoked by contract owner.
// You can use some predefined key constants: IAMPathKey, IAMARNKey, IAMCreatedTimeKey, IAMModifiedTimeKey.
func (c Client) SetGroupKV(namespace string, groupID int64, key, val string) (tx util.Uint256, vub uint32, err error) {
method, args := c.SetGroupKVCall(namespace, groupID, key, val)
return c.act.SendCall(c.contract, method, args...)
}
// SetGroupKVCall provides args for SetGroupKV to use in commonclient.Transaction.
func (c Client) SetGroupKVCall(namespace string, groupID int64, key, val string) (method string, args []any) {
return setGroupKVMethod, []any{namespace, groupID, key, val}
}
// DeleteGroupKV removes group kv map.
// Must be invoked by contract owner.
func (c Client) DeleteGroupKV(namespace string, groupID int64, key string) (tx util.Uint256, vub uint32, err error) {
method, args := c.DeleteGroupKVCall(namespace, groupID, key)
return c.act.SendCall(c.contract, method, args...)
}
// DeleteGroupKVCall provides args for DeleteGroupKV to use in commonclient.Transaction.
func (c Client) DeleteGroupKVCall(namespace string, groupID int64, key string) (method string, args []any) {
return deleteGroupKVMethod, []any{namespace, groupID, key}
}
// GetGroupIDByName gets group id its name (namespace scope).
func (c Client) GetGroupIDByName(namespace, groupName string) (int64, error) {
return unwrap.Int64(c.act.Call(c.contract, getGroupIDByNameMethod, namespace, groupName))
}
// ListGroups gets all groups in specific namespace. // ListGroups gets all groups in specific namespace.
func (c Client) ListGroups(namespace string) ([]*Group, error) { func (c Client) ListGroups(namespace string) ([]*Group, error) {
items, err := commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listGroupsMethod, namespace) items, err := commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listGroupsMethod, namespace)
@ -417,43 +468,43 @@ func (c Client) ListGroups(namespace string) ([]*Group, error) {
// AddSubjectToGroup adds a new subject to group. // AddSubjectToGroup adds a new subject to group.
// Must be invoked by contract owner. // Must be invoked by contract owner.
func (c Client) AddSubjectToGroup(addr util.Uint160, group string) (tx util.Uint256, vub uint32, err error) { func (c Client) AddSubjectToGroup(addr util.Uint160, groupID int64) (tx util.Uint256, vub uint32, err error) {
method, args := c.AddSubjectToGroupCall(addr, group) method, args := c.AddSubjectToGroupCall(addr, groupID)
return c.act.SendCall(c.contract, method, args...) return c.act.SendCall(c.contract, method, args...)
} }
// AddSubjectToGroupCall provides args for AddSubjectToGroup to use in commonclient.Transaction. // AddSubjectToGroupCall provides args for AddSubjectToGroup to use in commonclient.Transaction.
func (c Client) AddSubjectToGroupCall(addr util.Uint160, group string) (method string, args []any) { func (c Client) AddSubjectToGroupCall(addr util.Uint160, groupID int64) (method string, args []any) {
return addSubjectToGroupMethod, []any{addr, group} return addSubjectToGroupMethod, []any{addr, groupID}
} }
// RemoveSubjectFromGroup removes subject from group. // RemoveSubjectFromGroup removes subject from group.
// Must be invoked by contract owner. // Must be invoked by contract owner.
func (c Client) RemoveSubjectFromGroup(addr util.Uint160, namespace, group string) (tx util.Uint256, vub uint32, err error) { func (c Client) RemoveSubjectFromGroup(addr util.Uint160, groupID int64) (tx util.Uint256, vub uint32, err error) {
method, args := c.RemoveSubjectFromGroupCall(addr, namespace, group) method, args := c.RemoveSubjectFromGroupCall(addr, groupID)
return c.act.SendCall(c.contract, method, args...) return c.act.SendCall(c.contract, method, args...)
} }
// RemoveSubjectFromGroupCall provides args for RemoveSubjectFromGroup to use in commonclient.Transaction. // RemoveSubjectFromGroupCall provides args for RemoveSubjectFromGroup to use in commonclient.Transaction.
func (c Client) RemoveSubjectFromGroupCall(addr util.Uint160, namespace, group string) (method string, args []any) { func (c Client) RemoveSubjectFromGroupCall(addr util.Uint160, groupID int64) (method string, args []any) {
return removeSubjectFromGroupMethod, []any{addr, namespace, group} return removeSubjectFromGroupMethod, []any{addr, groupID}
} }
// ListGroupSubjects gets all subjects in specific group. // ListGroupSubjects gets all subjects in specific group.
func (c Client) ListGroupSubjects(namespace, group string) ([]util.Uint160, error) { func (c Client) ListGroupSubjects(namespace string, groupID int64) ([]util.Uint160, error) {
return unwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listGroupSubjectsMethod, namespace, group)) return unwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listGroupSubjectsMethod, namespace, groupID))
} }
// DeleteGroup deletes group. // DeleteGroup deletes group.
// Must be invoked by contract owner. // Must be invoked by contract owner.
func (c Client) DeleteGroup(namespace, group string) (tx util.Uint256, vub uint32, err error) { func (c Client) DeleteGroup(namespace string, groupID int64) (tx util.Uint256, vub uint32, err error) {
method, args := c.DeleteGroupCall(namespace, group) method, args := c.DeleteGroupCall(namespace, groupID)
return c.act.SendCall(c.contract, method, args...) return c.act.SendCall(c.contract, method, args...)
} }
// DeleteGroupCall provides args for DeleteGroup to use in commonclient.Transaction. // DeleteGroupCall provides args for DeleteGroup to use in commonclient.Transaction.
func (c Client) DeleteGroupCall(namespace, group string) (method string, args []any) { func (c Client) DeleteGroupCall(namespace string, groupID int64) (method string, args []any) {
return deleteGroupMethod, []any{namespace, group} return deleteGroupMethod, []any{namespace, groupID}
} }
// ListNonEmptyNamespaces gets namespaces that contain at least one subject. // ListNonEmptyNamespaces gets namespaces that contain at least one subject.
@ -484,6 +535,15 @@ func (c Client) Wait(tx util.Uint256, vub uint32, err error) (*state.AppExecResu
return c.act.Wait(tx, vub, err) return c.act.Wait(tx, vub, err)
} }
// ParseGroupID fetch groupID from stack after creating group method invocation.
func (c Client) ParseGroupID(res *state.AppExecResult, err error) (int64, error) {
if err != nil {
return 0, err
}
return unwrap.Int64(makeResFromAppExec(res))
}
// ListNonEmptyGroups gets groups that contain at least one subject. // ListNonEmptyGroups gets groups that contain at least one subject.
func (c Client) ListNonEmptyGroups(namespace string) ([]string, error) { func (c Client) ListNonEmptyGroups(namespace string) ([]string, error) {
groups, err := c.ListGroups(namespace) groups, err := c.ListGroups(namespace)
@ -494,7 +554,7 @@ func (c Client) ListNonEmptyGroups(namespace string) ([]string, error) {
var res []string var res []string
for _, group := range groups { for _, group := range groups {
groupExt, err := c.GetGroupExtended(namespace, group.Name) groupExt, err := c.GetGroupExtended(namespace, group.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -522,6 +582,13 @@ func makeValidRes(item stackitem.Item) (*result.Invoke, error) {
}, nil }, nil
} }
func makeResFromAppExec(res *state.AppExecResult) (*result.Invoke, error) {
return &result.Invoke{
Stack: res.Stack,
State: res.VMState.String(),
}, nil
}
func parseSubject(structArr []stackitem.Item) (*Subject, error) { func parseSubject(structArr []stackitem.Item) (*Subject, error) {
if len(structArr) < 5 { if len(structArr) < 5 {
return nil, errors.New("invalid response subject struct") return nil, errors.New("invalid response subject struct")
@ -710,49 +777,73 @@ func parseNamespaces(items []stackitem.Item) ([]*Namespace, error) {
} }
func parseGroup(structArr []stackitem.Item) (*Group, error) { func parseGroup(structArr []stackitem.Item) (*Group, error) {
if len(structArr) < 2 { if len(structArr) < 4 {
return nil, errors.New("invalid response group struct") return nil, errors.New("invalid response group struct")
} }
name, err := structArr[0].TryBytes() groupID, err := structArr[0].TryInteger()
if err != nil { if err != nil {
return nil, err return nil, err
} }
namespace, err := structArr[1].TryBytes() name, err := structArr[1].TryBytes()
if err != nil {
return nil, err
}
namespace, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
kvs, err := parseMap(structArr[3])
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Group{ return &Group{
ID: groupID.Int64(),
Name: string(name), Name: string(name),
Namespace: string(namespace), Namespace: string(namespace),
KV: kvs,
}, nil }, nil
} }
func parseGroupExtended(structArr []stackitem.Item) (*GroupExtended, error) { func parseGroupExtended(structArr []stackitem.Item) (*GroupExtended, error) {
if len(structArr) < 3 { if len(structArr) < 5 {
return nil, errors.New("invalid response group extended struct") return nil, errors.New("invalid response group extended struct")
} }
name, err := structArr[0].TryBytes() groupID, err := structArr[0].TryInteger()
if err != nil { if err != nil {
return nil, err return nil, err
} }
namespace, err := structArr[1].TryBytes() name, err := structArr[1].TryBytes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
subjectCount, err := structArr[2].TryInteger() namespace, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
kvs, err := parseMap(structArr[3])
if err != nil {
return nil, err
}
subjectCount, err := structArr[4].TryInteger()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &GroupExtended{ return &GroupExtended{
ID: groupID.Int64(),
Name: string(name), Name: string(name),
Namespace: string(namespace), Namespace: string(namespace),
KV: kvs,
SubjectsCount: subjectCount.Int64(), SubjectsCount: subjectCount.Int64(),
}, nil }, nil
} }

View file

@ -65,25 +65,51 @@ events:
type: String type: String
- name: group - name: group
type: String type: String
- name: SetGroupName
parameters:
- name: namespace
type: String
- name: groupID
type: Integer
- name: name
type: String
- name: SetGroupKV
parameters:
- name: namespace
type: String
- name: groupID
type: Integer
- name: key
type: String
- name: value
type: String
- name: DeleteGroupKV
parameters:
- name: namespace
type: String
- name: groupID
type: Integer
- name: key
type: String
- name: AddSubjectToGroup - name: AddSubjectToGroup
parameters: parameters:
- name: subjectAddress - name: subjectAddress
type: Hash160 type: Hash160
- name: namespace - name: namespace
type: String type: String
- name: group - name: groupID
type: String type: Integer
- name: RemoveSubjectFromGroup - name: RemoveSubjectFromGroup
parameters: parameters:
- name: subjectAddress - name: subjectAddress
type: Hash160 type: Hash160
- name: namespace - name: namespace
type: String type: String
- name: group - name: groupID
type: String type: Integer
- name: DeleteGroup - name: DeleteGroup
parameters: parameters:
- name: namespace - name: namespace
type: String type: String
- name: group - name: groupID
type: String type: Integer

View file

@ -4,6 +4,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
@ -41,13 +42,17 @@ type (
} }
Group struct { Group struct {
ID int
Name string Name string
Namespace string Namespace string
KV map[string]string
} }
GroupExtended struct { GroupExtended struct {
ID int
Name string Name string
Namespace string Namespace string
KV map[string]string
SubjectsCount int SubjectsCount int
} }
) )
@ -61,6 +66,8 @@ const (
namespaceSubjectsNamesPrefix = 'l' namespaceSubjectsNamesPrefix = 'l'
groupKeysPrefix = 'g' groupKeysPrefix = 'g'
groupSubjectsKeysPrefix = 'G' groupSubjectsKeysPrefix = 'G'
groupCounterKey = 'c'
namespaceGroupsNamesPrefix = 'm'
) )
func _deploy(data any, isUpdate bool) { func _deploy(data any, isUpdate bool) {
@ -77,6 +84,8 @@ func _deploy(data any, isUpdate bool) {
storage.Put(ctx, ownerKey(owner), []byte{1}) storage.Put(ctx, ownerKey(owner), []byte{1})
} }
storage.Put(ctx, groupCounterKey, 0)
runtime.Log("frostfsid contract initialized") runtime.Log("frostfsid contract initialized")
} }
@ -560,7 +569,7 @@ func ListNamespaceSubjects(ns string) iterator.Iterator {
return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix) return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix)
} }
func CreateGroup(ns, group string) { func CreateGroup(ns, group string) int {
ctx := storage.GetContext() ctx := storage.GetContext()
checkContractOwner(ctx) checkContractOwner(ctx)
@ -577,23 +586,29 @@ func CreateGroup(ns, group string) {
panic("namespace not found") panic("namespace not found")
} }
groupCountID := storage.Get(ctx, groupCounterKey).(int)
groupCountID++
storage.Put(ctx, groupCounterKey, groupCountID)
gr := Group{ gr := Group{
ID: groupCountID,
Name: group, Name: group,
Namespace: ns, Namespace: ns,
} }
gKey := groupKey(ns, group)
data = storage.Get(ctx, gKey).([]byte) setNamespaceGroupName(ctx, gr)
if data != nil {
panic("group already exists") gKey := groupKey(ns, groupCountID)
}
storage.Put(ctx, gKey, std.Serialize(gr)) storage.Put(ctx, gKey, std.Serialize(gr))
runtime.Notify("CreateGroup", ns, group) runtime.Notify("CreateGroup", ns, group)
return groupCountID
} }
func GetGroup(ns, group string) Group { func GetGroup(ns string, groupID int) Group {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
gKey := groupKey(ns, group) gKey := groupKey(ns, groupID)
data := storage.Get(ctx, gKey).([]byte) data := storage.Get(ctx, gKey).([]byte)
if data == nil { if data == nil {
panic("group not found") panic("group not found")
@ -602,9 +617,9 @@ func GetGroup(ns, group string) Group {
return std.Deserialize(data).(Group) return std.Deserialize(data).(Group)
} }
func GetGroupExtended(ns, group string) GroupExtended { func GetGroupExtended(ns string, groupID int) GroupExtended {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
gKey := groupKey(ns, group) gKey := groupKey(ns, groupID)
data := storage.Get(ctx, gKey).([]byte) data := storage.Get(ctx, gKey).([]byte)
if data == nil { if data == nil {
panic("group not found") panic("group not found")
@ -613,11 +628,13 @@ func GetGroupExtended(ns, group string) GroupExtended {
gr := std.Deserialize(data).(Group) gr := std.Deserialize(data).(Group)
grExtended := GroupExtended{ grExtended := GroupExtended{
ID: gr.ID,
Name: gr.Name, Name: gr.Name,
Namespace: gr.Namespace, Namespace: gr.Namespace,
KV: gr.KV,
} }
it := storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly) it := storage.Find(ctx, groupSubjectPrefix(ns, groupID), storage.KeysOnly)
for iterator.Next(it) { for iterator.Next(it) {
grExtended.SubjectsCount += 1 grExtended.SubjectsCount += 1
} }
@ -625,12 +642,85 @@ func GetGroupExtended(ns, group string) GroupExtended {
return grExtended return grExtended
} }
func GetGroupIDByName(ns, name string) int {
if ns == "" || name == "" {
panic("invalid namespace or name")
}
ctx := storage.GetReadOnlyContext()
nsGroupNameKey := namespaceGroupNameKey(ns, name)
groupIDRaw := storage.Get(ctx, nsGroupNameKey).([]byte)
if groupIDRaw == nil {
panic("group not found")
}
return std.Deserialize(groupIDRaw).(int)
}
func SetGroupName(ns string, groupID int, name string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
gKey := groupKey(ns, groupID)
data := storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
gr := std.Deserialize(data).(Group)
oldName := gr.Name
gr.Name = name
storage.Put(ctx, gKey, std.Serialize(gr))
updateNamespaceGroupName(ctx, gr, oldName)
runtime.Notify("SetGroupName", ns, groupID, name)
}
func SetGroupKV(ns string, groupID int, key, val string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
gKey := groupKey(ns, groupID)
data := storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
gr := std.Deserialize(data).(Group)
if gr.KV == nil {
gr.KV = map[string]string{}
}
gr.KV[key] = val
storage.Put(ctx, gKey, std.Serialize(gr))
runtime.Notify("SetGroupKV", ns, groupID, key, val)
}
func DeleteGroupKV(ns string, groupID int, key string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
gKey := groupKey(ns, groupID)
data := storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
gr := std.Deserialize(data).(Group)
delete(gr.KV, key)
storage.Put(ctx, gKey, std.Serialize(gr))
runtime.Notify("DeleteGroupKV", ns, groupID, key)
}
func ListGroups(ns string) iterator.Iterator { func ListGroups(ns string) iterator.Iterator {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, groupPrefix(ns), storage.ValuesOnly|storage.DeserializeValues) return storage.Find(ctx, groupPrefix(ns), storage.ValuesOnly|storage.DeserializeValues)
} }
func AddSubjectToGroup(addr interop.Hash160, group string) { func AddSubjectToGroup(addr interop.Hash160, groupID int) {
ctx := storage.GetContext() ctx := storage.GetContext()
checkContractOwner(ctx) checkContractOwner(ctx)
@ -652,19 +742,19 @@ func AddSubjectToGroup(addr interop.Hash160, group string) {
nSubjKey := namespaceSubjectKey(subject.Namespace, addr) nSubjKey := namespaceSubjectKey(subject.Namespace, addr)
storage.Put(ctx, nSubjKey, []byte{1}) storage.Put(ctx, nSubjKey, []byte{1})
gKey := groupKey(subject.Namespace, group) gKey := groupKey(subject.Namespace, groupID)
data = storage.Get(ctx, gKey).([]byte) data = storage.Get(ctx, gKey).([]byte)
if data == nil { if data == nil {
panic("group not found") panic("group not found")
} }
gsKey := groupSubjectKey(subject.Namespace, group, addr) gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
storage.Put(ctx, gsKey, []byte{1}) storage.Put(ctx, gsKey, []byte{1})
runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, group) runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID)
} }
func RemoveSubjectFromGroup(addr interop.Hash160, ns, group string) { func RemoveSubjectFromGroup(addr interop.Hash160, groupID int) {
ctx := storage.GetContext() ctx := storage.GetContext()
checkContractOwner(ctx) checkContractOwner(ctx)
@ -678,41 +768,50 @@ func RemoveSubjectFromGroup(addr interop.Hash160, ns, group string) {
panic("subject not found") panic("subject not found")
} }
gKey := groupKey(ns, group) subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject doesn't belong to any namespace")
}
gKey := groupKey(subject.Namespace, groupID)
data = storage.Get(ctx, gKey).([]byte) data = storage.Get(ctx, gKey).([]byte)
if data == nil { if data == nil {
panic("group not found") panic("group not found")
} }
gsKey := groupSubjectKey(ns, group, addr) gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
storage.Delete(ctx, gsKey) storage.Delete(ctx, gsKey)
runtime.Notify("RemoveSubjectFromGroup", addr, ns, group) runtime.Notify("RemoveSubjectFromGroup", addr, subject.Namespace, groupID)
} }
func ListGroupSubjects(ns, group string) iterator.Iterator { func ListGroupSubjects(ns string, groupID int) iterator.Iterator {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly|storage.RemovePrefix) return storage.Find(ctx, groupSubjectPrefix(ns, groupID), storage.KeysOnly|storage.RemovePrefix)
} }
func DeleteGroup(ns, group string) { func DeleteGroup(ns string, groupID int) {
ctx := storage.GetContext() ctx := storage.GetContext()
checkContractOwner(ctx) checkContractOwner(ctx)
gKey := groupKey(ns, group) gKey := groupKey(ns, groupID)
data := storage.Get(ctx, gKey).([]byte) data := storage.Get(ctx, gKey).([]byte)
if data == nil { if data == nil {
panic("group not found") panic("group not found")
} }
gr := std.Deserialize(data).(Group)
storage.Delete(ctx, gKey) storage.Delete(ctx, gKey)
it := storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly) it := storage.Find(ctx, groupSubjectPrefix(ns, groupID), storage.KeysOnly)
for iterator.Next(it) { for iterator.Next(it) {
gsKey := iterator.Value(it).([]byte) gsKey := iterator.Value(it).([]byte)
storage.Delete(ctx, gsKey) storage.Delete(ctx, gsKey)
} }
runtime.Notify("DeleteGroup", ns, group) deleteNamespaceGroupName(ctx, ns, gr.Name)
runtime.Notify("DeleteGroup", ns, groupID)
} }
func checkContractOwner(ctx storage.Context) { func checkContractOwner(ctx storage.Context) {
@ -770,6 +869,42 @@ func updateNamespaceSubjectName(ctx storage.Context, subj Subject, oldName strin
setNamespaceSubjectName(ctx, subj) setNamespaceSubjectName(ctx, subj)
} }
func updateNamespaceGroupName(ctx storage.Context, gr Group, oldName string) {
if gr.Name == oldName {
return
}
deleteNamespaceGroupName(ctx, gr.Namespace, oldName)
setNamespaceGroupName(ctx, gr)
}
func deleteNamespaceGroupName(ctx storage.Context, ns, grName string) {
if grName == "" {
return
}
nsGroupNameKey := namespaceGroupNameKey(ns, grName)
storage.Delete(ctx, nsGroupNameKey)
}
func setNamespaceGroupName(ctx storage.Context, gr Group) {
if gr.Name == "" {
return
}
nsGroupNameKey := namespaceGroupNameKey(gr.Namespace, gr.Name)
groupIDRaw := storage.Get(ctx, nsGroupNameKey).([]byte)
if groupIDRaw == nil {
storage.Put(ctx, nsGroupNameKey, std.Serialize(gr.ID))
return
}
groupID := std.Deserialize(groupIDRaw).(int)
if groupID != gr.ID {
panic("group name is not available in the current namespace")
}
}
func ownerKey(owner interop.Hash160) []byte { func ownerKey(owner interop.Hash160) []byte {
return append([]byte{ownerKeysPrefix}, owner...) return append([]byte{ownerKeysPrefix}, owner...)
} }
@ -799,14 +934,14 @@ func namespaceKeyFromHash(ns []byte) []byte {
return append([]byte{namespaceKeysPrefix}, ns...) return append([]byte{namespaceKeysPrefix}, ns...)
} }
func groupKey(ns, group string) []byte { func groupKey(ns string, groupID int) []byte {
prefix := groupPrefix(ns) prefix := groupPrefix(ns)
return append(prefix, ripemd160Hash(group)...) return append(prefix, idToBytes(groupID)...)
} }
func groupKeyFromHashes(nsHash, groupHash []byte) []byte { func groupKeyFromHashes(nsHash []byte, groupIDBytes []byte) []byte {
prefix := groupPrefixFromHash(nsHash) prefix := groupPrefixFromHash(nsHash)
return append(prefix, groupHash...) return append(prefix, groupIDBytes...)
} }
func groupPrefix(ns string) []byte { func groupPrefix(ns string) []byte {
@ -837,8 +972,14 @@ func namespaceSubjectNameKey(ns, subjName string) []byte {
return append([]byte{namespaceSubjectsNamesPrefix}, append(nsHash, nameHash...)...) return append([]byte{namespaceSubjectsNamesPrefix}, append(nsHash, nameHash...)...)
} }
func groupSubjectKey(ns, group string, addr interop.Hash160) []byte { func namespaceGroupNameKey(ns, groupName string) []byte {
prefix := groupSubjectPrefix(ns, group) nsHash := ripemd160Hash(ns)
nameHash := ripemd160Hash(groupName)
return append([]byte{namespaceGroupsNamesPrefix}, append(nsHash, nameHash...)...)
}
func groupSubjectKey(ns string, groupID int, addr interop.Hash160) []byte {
prefix := groupSubjectPrefix(ns, groupID)
return append(prefix, addr...) return append(prefix, addr...)
} }
@ -847,12 +988,22 @@ func groupSubjectKeyFromHashes(nsHash, groupHash []byte, addr interop.Hash160) [
return append(prefix, addr...) return append(prefix, addr...)
} }
func groupSubjectPrefix(ns, group string) []byte { func groupSubjectPrefix(ns string, groupID int) []byte {
nsHash := ripemd160Hash(ns) nsHash := ripemd160Hash(ns)
gHash := ripemd160Hash(group) return groupSubjectPrefixFromHashes(nsHash, idToBytes(groupID))
return groupSubjectPrefixFromHashes(nsHash, gHash)
} }
func groupSubjectPrefixFromHashes(nsHash, groupHash []byte) []byte { func groupSubjectPrefixFromHashes(nsHash, groupIDBytes []byte) []byte {
return append([]byte{groupSubjectsKeysPrefix}, append(nsHash, groupHash...)...) return append([]byte{groupSubjectsKeysPrefix}, append(nsHash, groupIDBytes...)...)
}
// idToBytes converts i64 value to BE bytes. Panics if value is bigger than i64.
func idToBytes(itemID int) []byte {
b := convert.ToBytes(itemID)
ln := len(b)
if ln > 8 {
panic("item ID exceeds 8 byte limit")
}
zeros := make([]byte, 8-ln)
return append(b, zeros...)
} }

View file

@ -81,24 +81,46 @@ type CreateGroupEvent struct {
Group string Group string
} }
// SetGroupNameEvent represents "SetGroupName" event emitted by the contract.
type SetGroupNameEvent struct {
Namespace string
GroupID *big.Int
Name string
}
// SetGroupKVEvent represents "SetGroupKV" event emitted by the contract.
type SetGroupKVEvent struct {
Namespace string
GroupID *big.Int
Key string
Value string
}
// DeleteGroupKVEvent represents "DeleteGroupKV" event emitted by the contract.
type DeleteGroupKVEvent struct {
Namespace string
GroupID *big.Int
Key string
}
// AddSubjectToGroupEvent represents "AddSubjectToGroup" event emitted by the contract. // AddSubjectToGroupEvent represents "AddSubjectToGroup" event emitted by the contract.
type AddSubjectToGroupEvent struct { type AddSubjectToGroupEvent struct {
SubjectAddress util.Uint160 SubjectAddress util.Uint160
Namespace string Namespace string
Group string GroupID *big.Int
} }
// RemoveSubjectFromGroupEvent represents "RemoveSubjectFromGroup" event emitted by the contract. // RemoveSubjectFromGroupEvent represents "RemoveSubjectFromGroup" event emitted by the contract.
type RemoveSubjectFromGroupEvent struct { type RemoveSubjectFromGroupEvent struct {
SubjectAddress util.Uint160 SubjectAddress util.Uint160
Namespace string Namespace string
Group string GroupID *big.Int
} }
// DeleteGroupEvent represents "DeleteGroup" event emitted by the contract. // DeleteGroupEvent represents "DeleteGroup" event emitted by the contract.
type DeleteGroupEvent struct { type DeleteGroupEvent struct {
Namespace string Namespace string
Group string GroupID *big.Int
} }
// Invoker is used by ContractReader to call various safe methods. // Invoker is used by ContractReader to call various safe methods.
@ -193,23 +215,23 @@ func (c *Contract) AddSubjectKeyUnsigned(addr util.Uint160, key *keys.PublicKey)
// AddSubjectToGroup creates a transaction invoking `addSubjectToGroup` method of the contract. // AddSubjectToGroup creates a transaction invoking `addSubjectToGroup` method of the contract.
// This transaction is signed and immediately sent to the network. // This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any. // The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) AddSubjectToGroup(addr util.Uint160, group string) (util.Uint256, uint32, error) { func (c *Contract) AddSubjectToGroup(addr util.Uint160, groupID *big.Int) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "addSubjectToGroup", addr, group) return c.actor.SendCall(c.hash, "addSubjectToGroup", addr, groupID)
} }
// AddSubjectToGroupTransaction creates a transaction invoking `addSubjectToGroup` method of the contract. // AddSubjectToGroupTransaction creates a transaction invoking `addSubjectToGroup` method of the contract.
// This transaction is signed, but not sent to the network, instead it's // This transaction is signed, but not sent to the network, instead it's
// returned to the caller. // returned to the caller.
func (c *Contract) AddSubjectToGroupTransaction(addr util.Uint160, group string) (*transaction.Transaction, error) { func (c *Contract) AddSubjectToGroupTransaction(addr util.Uint160, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "addSubjectToGroup", addr, group) return c.actor.MakeCall(c.hash, "addSubjectToGroup", addr, groupID)
} }
// AddSubjectToGroupUnsigned creates a transaction invoking `addSubjectToGroup` method of the contract. // AddSubjectToGroupUnsigned creates a transaction invoking `addSubjectToGroup` method of the contract.
// This transaction is not signed, it's simply returned to the caller. // This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. // Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) AddSubjectToGroupUnsigned(addr util.Uint160, group string) (*transaction.Transaction, error) { func (c *Contract) AddSubjectToGroupUnsigned(addr util.Uint160, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "addSubjectToGroup", nil, addr, group) return c.actor.MakeUnsignedCall(c.hash, "addSubjectToGroup", nil, addr, groupID)
} }
// AddSubjectToNamespace creates a transaction invoking `addSubjectToNamespace` method of the contract. // AddSubjectToNamespace creates a transaction invoking `addSubjectToNamespace` method of the contract.
@ -303,23 +325,45 @@ func (c *Contract) CreateSubjectUnsigned(key *keys.PublicKey) (*transaction.Tran
// DeleteGroup creates a transaction invoking `deleteGroup` method of the contract. // DeleteGroup creates a transaction invoking `deleteGroup` method of the contract.
// This transaction is signed and immediately sent to the network. // This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any. // The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) DeleteGroup(ns string, group string) (util.Uint256, uint32, error) { func (c *Contract) DeleteGroup(ns string, groupID *big.Int) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "deleteGroup", ns, group) return c.actor.SendCall(c.hash, "deleteGroup", ns, groupID)
} }
// DeleteGroupTransaction creates a transaction invoking `deleteGroup` method of the contract. // DeleteGroupTransaction creates a transaction invoking `deleteGroup` method of the contract.
// This transaction is signed, but not sent to the network, instead it's // This transaction is signed, but not sent to the network, instead it's
// returned to the caller. // returned to the caller.
func (c *Contract) DeleteGroupTransaction(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) DeleteGroupTransaction(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "deleteGroup", ns, group) return c.actor.MakeCall(c.hash, "deleteGroup", ns, groupID)
} }
// DeleteGroupUnsigned creates a transaction invoking `deleteGroup` method of the contract. // DeleteGroupUnsigned creates a transaction invoking `deleteGroup` method of the contract.
// This transaction is not signed, it's simply returned to the caller. // This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. // Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) DeleteGroupUnsigned(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) DeleteGroupUnsigned(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "deleteGroup", nil, ns, group) return c.actor.MakeUnsignedCall(c.hash, "deleteGroup", nil, ns, groupID)
}
// DeleteGroupKV creates a transaction invoking `deleteGroupKV` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) DeleteGroupKV(ns string, groupID *big.Int, key string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "deleteGroupKV", ns, groupID, key)
}
// DeleteGroupKVTransaction creates a transaction invoking `deleteGroupKV` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) DeleteGroupKVTransaction(ns string, groupID *big.Int, key string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "deleteGroupKV", ns, groupID, key)
}
// DeleteGroupKVUnsigned creates a transaction invoking `deleteGroupKV` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) DeleteGroupKVUnsigned(ns string, groupID *big.Int, key string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "deleteGroupKV", nil, ns, groupID, key)
} }
// DeleteOwner creates a transaction invoking `deleteOwner` method of the contract. // DeleteOwner creates a transaction invoking `deleteOwner` method of the contract.
@ -391,45 +435,67 @@ func (c *Contract) DeleteSubjectKVUnsigned(addr util.Uint160, key string) (*tran
// GetGroup creates a transaction invoking `getGroup` method of the contract. // GetGroup creates a transaction invoking `getGroup` method of the contract.
// This transaction is signed and immediately sent to the network. // This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any. // The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) GetGroup(ns string, group string) (util.Uint256, uint32, error) { func (c *Contract) GetGroup(ns string, groupID *big.Int) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "getGroup", ns, group) return c.actor.SendCall(c.hash, "getGroup", ns, groupID)
} }
// GetGroupTransaction creates a transaction invoking `getGroup` method of the contract. // GetGroupTransaction creates a transaction invoking `getGroup` method of the contract.
// This transaction is signed, but not sent to the network, instead it's // This transaction is signed, but not sent to the network, instead it's
// returned to the caller. // returned to the caller.
func (c *Contract) GetGroupTransaction(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) GetGroupTransaction(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "getGroup", ns, group) return c.actor.MakeCall(c.hash, "getGroup", ns, groupID)
} }
// GetGroupUnsigned creates a transaction invoking `getGroup` method of the contract. // GetGroupUnsigned creates a transaction invoking `getGroup` method of the contract.
// This transaction is not signed, it's simply returned to the caller. // This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. // Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) GetGroupUnsigned(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) GetGroupUnsigned(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "getGroup", nil, ns, group) return c.actor.MakeUnsignedCall(c.hash, "getGroup", nil, ns, groupID)
} }
// GetGroupExtended creates a transaction invoking `getGroupExtended` method of the contract. // GetGroupExtended creates a transaction invoking `getGroupExtended` method of the contract.
// This transaction is signed and immediately sent to the network. // This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any. // The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) GetGroupExtended(ns string, group string) (util.Uint256, uint32, error) { func (c *Contract) GetGroupExtended(ns string, groupID *big.Int) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "getGroupExtended", ns, group) return c.actor.SendCall(c.hash, "getGroupExtended", ns, groupID)
} }
// GetGroupExtendedTransaction creates a transaction invoking `getGroupExtended` method of the contract. // GetGroupExtendedTransaction creates a transaction invoking `getGroupExtended` method of the contract.
// This transaction is signed, but not sent to the network, instead it's // This transaction is signed, but not sent to the network, instead it's
// returned to the caller. // returned to the caller.
func (c *Contract) GetGroupExtendedTransaction(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) GetGroupExtendedTransaction(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "getGroupExtended", ns, group) return c.actor.MakeCall(c.hash, "getGroupExtended", ns, groupID)
} }
// GetGroupExtendedUnsigned creates a transaction invoking `getGroupExtended` method of the contract. // GetGroupExtendedUnsigned creates a transaction invoking `getGroupExtended` method of the contract.
// This transaction is not signed, it's simply returned to the caller. // This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. // Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) GetGroupExtendedUnsigned(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) GetGroupExtendedUnsigned(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "getGroupExtended", nil, ns, group) return c.actor.MakeUnsignedCall(c.hash, "getGroupExtended", nil, ns, groupID)
}
// GetGroupIDByName creates a transaction invoking `getGroupIDByName` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) GetGroupIDByName(ns string, name string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "getGroupIDByName", ns, name)
}
// GetGroupIDByNameTransaction creates a transaction invoking `getGroupIDByName` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) GetGroupIDByNameTransaction(ns string, name string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "getGroupIDByName", ns, name)
}
// GetGroupIDByNameUnsigned creates a transaction invoking `getGroupIDByName` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) GetGroupIDByNameUnsigned(ns string, name string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "getGroupIDByName", nil, ns, name)
} }
// GetNamespace creates a transaction invoking `getNamespace` method of the contract. // GetNamespace creates a transaction invoking `getNamespace` method of the contract.
@ -567,23 +633,23 @@ func (c *Contract) GetSubjectKeyByNameUnsigned(ns string, name string) (*transac
// ListGroupSubjects creates a transaction invoking `listGroupSubjects` method of the contract. // ListGroupSubjects creates a transaction invoking `listGroupSubjects` method of the contract.
// This transaction is signed and immediately sent to the network. // This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any. // The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) ListGroupSubjects(ns string, group string) (util.Uint256, uint32, error) { func (c *Contract) ListGroupSubjects(ns string, groupID *big.Int) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "listGroupSubjects", ns, group) return c.actor.SendCall(c.hash, "listGroupSubjects", ns, groupID)
} }
// ListGroupSubjectsTransaction creates a transaction invoking `listGroupSubjects` method of the contract. // ListGroupSubjectsTransaction creates a transaction invoking `listGroupSubjects` method of the contract.
// This transaction is signed, but not sent to the network, instead it's // This transaction is signed, but not sent to the network, instead it's
// returned to the caller. // returned to the caller.
func (c *Contract) ListGroupSubjectsTransaction(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) ListGroupSubjectsTransaction(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "listGroupSubjects", ns, group) return c.actor.MakeCall(c.hash, "listGroupSubjects", ns, groupID)
} }
// ListGroupSubjectsUnsigned creates a transaction invoking `listGroupSubjects` method of the contract. // ListGroupSubjectsUnsigned creates a transaction invoking `listGroupSubjects` method of the contract.
// This transaction is not signed, it's simply returned to the caller. // This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. // Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) ListGroupSubjectsUnsigned(ns string, group string) (*transaction.Transaction, error) { func (c *Contract) ListGroupSubjectsUnsigned(ns string, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "listGroupSubjects", nil, ns, group) return c.actor.MakeUnsignedCall(c.hash, "listGroupSubjects", nil, ns, groupID)
} }
// ListGroups creates a transaction invoking `listGroups` method of the contract. // ListGroups creates a transaction invoking `listGroups` method of the contract.
@ -699,23 +765,23 @@ func (c *Contract) ListSubjectsUnsigned() (*transaction.Transaction, error) {
// RemoveSubjectFromGroup creates a transaction invoking `removeSubjectFromGroup` method of the contract. // RemoveSubjectFromGroup creates a transaction invoking `removeSubjectFromGroup` method of the contract.
// This transaction is signed and immediately sent to the network. // This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any. // The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) RemoveSubjectFromGroup(addr util.Uint160, ns string, group string) (util.Uint256, uint32, error) { func (c *Contract) RemoveSubjectFromGroup(addr util.Uint160, groupID *big.Int) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "removeSubjectFromGroup", addr, ns, group) return c.actor.SendCall(c.hash, "removeSubjectFromGroup", addr, groupID)
} }
// RemoveSubjectFromGroupTransaction creates a transaction invoking `removeSubjectFromGroup` method of the contract. // RemoveSubjectFromGroupTransaction creates a transaction invoking `removeSubjectFromGroup` method of the contract.
// This transaction is signed, but not sent to the network, instead it's // This transaction is signed, but not sent to the network, instead it's
// returned to the caller. // returned to the caller.
func (c *Contract) RemoveSubjectFromGroupTransaction(addr util.Uint160, ns string, group string) (*transaction.Transaction, error) { func (c *Contract) RemoveSubjectFromGroupTransaction(addr util.Uint160, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "removeSubjectFromGroup", addr, ns, group) return c.actor.MakeCall(c.hash, "removeSubjectFromGroup", addr, groupID)
} }
// RemoveSubjectFromGroupUnsigned creates a transaction invoking `removeSubjectFromGroup` method of the contract. // RemoveSubjectFromGroupUnsigned creates a transaction invoking `removeSubjectFromGroup` method of the contract.
// This transaction is not signed, it's simply returned to the caller. // This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. // Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) RemoveSubjectFromGroupUnsigned(addr util.Uint160, ns string, group string) (*transaction.Transaction, error) { func (c *Contract) RemoveSubjectFromGroupUnsigned(addr util.Uint160, groupID *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromGroup", nil, addr, ns, group) return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromGroup", nil, addr, groupID)
} }
// RemoveSubjectFromNamespace creates a transaction invoking `removeSubjectFromNamespace` method of the contract. // RemoveSubjectFromNamespace creates a transaction invoking `removeSubjectFromNamespace` method of the contract.
@ -762,6 +828,50 @@ func (c *Contract) RemoveSubjectKeyUnsigned(addr util.Uint160, key *keys.PublicK
return c.actor.MakeUnsignedCall(c.hash, "removeSubjectKey", nil, addr, key) return c.actor.MakeUnsignedCall(c.hash, "removeSubjectKey", nil, addr, key)
} }
// SetGroupKV creates a transaction invoking `setGroupKV` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) SetGroupKV(ns string, groupID *big.Int, key string, val string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "setGroupKV", ns, groupID, key, val)
}
// SetGroupKVTransaction creates a transaction invoking `setGroupKV` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) SetGroupKVTransaction(ns string, groupID *big.Int, key string, val string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "setGroupKV", ns, groupID, key, val)
}
// SetGroupKVUnsigned creates a transaction invoking `setGroupKV` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) SetGroupKVUnsigned(ns string, groupID *big.Int, key string, val string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "setGroupKV", nil, ns, groupID, key, val)
}
// SetGroupName creates a transaction invoking `setGroupName` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) SetGroupName(ns string, groupID *big.Int, name string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "setGroupName", ns, groupID, name)
}
// SetGroupNameTransaction creates a transaction invoking `setGroupName` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) SetGroupNameTransaction(ns string, groupID *big.Int, name string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "setGroupName", ns, groupID, name)
}
// SetGroupNameUnsigned creates a transaction invoking `setGroupName` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) SetGroupNameUnsigned(ns string, groupID *big.Int, name string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "setGroupName", nil, ns, groupID, name)
}
// SetSubjectKV creates a transaction invoking `setSubjectKV` method of the contract. // SetSubjectKV creates a transaction invoking `setSubjectKV` method of the contract.
// This transaction is signed and immediately sent to the network. // This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any. // The values returned are its hash, ValidUntilBlock value and error if any.
@ -1645,6 +1755,267 @@ func (e *CreateGroupEvent) FromStackItem(item *stackitem.Array) error {
return nil return nil
} }
// SetGroupNameEventsFromApplicationLog retrieves a set of all emitted events
// with "SetGroupName" name from the provided [result.ApplicationLog].
func SetGroupNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetGroupNameEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*SetGroupNameEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "SetGroupName" {
continue
}
event := new(SetGroupNameEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize SetGroupNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to SetGroupNameEvent or
// returns an error if it's not possible to do to so.
func (e *SetGroupNameEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 3 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Namespace, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Namespace: %w", err)
}
index++
e.GroupID, err = arr[index].TryInteger()
if err != nil {
return fmt.Errorf("field GroupID: %w", err)
}
index++
e.Name, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Name: %w", err)
}
return nil
}
// SetGroupKVEventsFromApplicationLog retrieves a set of all emitted events
// with "SetGroupKV" name from the provided [result.ApplicationLog].
func SetGroupKVEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetGroupKVEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*SetGroupKVEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "SetGroupKV" {
continue
}
event := new(SetGroupKVEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize SetGroupKVEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to SetGroupKVEvent or
// returns an error if it's not possible to do to so.
func (e *SetGroupKVEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 4 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Namespace, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Namespace: %w", err)
}
index++
e.GroupID, err = arr[index].TryInteger()
if err != nil {
return fmt.Errorf("field GroupID: %w", err)
}
index++
e.Key, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Key: %w", err)
}
index++
e.Value, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Value: %w", err)
}
return nil
}
// DeleteGroupKVEventsFromApplicationLog retrieves a set of all emitted events
// with "DeleteGroupKV" name from the provided [result.ApplicationLog].
func DeleteGroupKVEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteGroupKVEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*DeleteGroupKVEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "DeleteGroupKV" {
continue
}
event := new(DeleteGroupKVEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize DeleteGroupKVEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to DeleteGroupKVEvent or
// returns an error if it's not possible to do to so.
func (e *DeleteGroupKVEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 3 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Namespace, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Namespace: %w", err)
}
index++
e.GroupID, err = arr[index].TryInteger()
if err != nil {
return fmt.Errorf("field GroupID: %w", err)
}
index++
e.Key, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Key: %w", err)
}
return nil
}
// AddSubjectToGroupEventsFromApplicationLog retrieves a set of all emitted events // AddSubjectToGroupEventsFromApplicationLog retrieves a set of all emitted events
// with "AddSubjectToGroup" name from the provided [result.ApplicationLog]. // with "AddSubjectToGroup" name from the provided [result.ApplicationLog].
func AddSubjectToGroupEventsFromApplicationLog(log *result.ApplicationLog) ([]*AddSubjectToGroupEvent, error) { func AddSubjectToGroupEventsFromApplicationLog(log *result.ApplicationLog) ([]*AddSubjectToGroupEvent, error) {
@ -1720,18 +2091,9 @@ func (e *AddSubjectToGroupEvent) FromStackItem(item *stackitem.Array) error {
} }
index++ index++
e.Group, err = func(item stackitem.Item) (string, error) { e.GroupID, err = arr[index].TryInteger()
b, err := item.TryBytes()
if err != nil { if err != nil {
return "", err return fmt.Errorf("field GroupID: %w", err)
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Group: %w", err)
} }
return nil return nil
@ -1812,18 +2174,9 @@ func (e *RemoveSubjectFromGroupEvent) FromStackItem(item *stackitem.Array) error
} }
index++ index++
e.Group, err = func(item stackitem.Item) (string, error) { e.GroupID, err = arr[index].TryInteger()
b, err := item.TryBytes()
if err != nil { if err != nil {
return "", err return fmt.Errorf("field GroupID: %w", err)
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Group: %w", err)
} }
return nil return nil
@ -1888,18 +2241,9 @@ func (e *DeleteGroupEvent) FromStackItem(item *stackitem.Array) error {
} }
index++ index++
e.Group, err = func(item stackitem.Item) (string, error) { e.GroupID, err = arr[index].TryInteger()
b, err := item.TryBytes()
if err != nil { if err != nil {
return "", err return fmt.Errorf("field GroupID: %w", err)
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Group: %w", err)
} }
return nil return nil

View file

@ -114,17 +114,17 @@ func TestFrostFSID_Client_SubjectManagement(t *testing.T) {
ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey())) ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey()))
ffsid.a.await(ffsid.cli.SetSubjectName(subjAddr, subjLogin)) ffsid.a.await(ffsid.cli.SetSubjectName(subjAddr, subjLogin))
ffsid.a.await(ffsid.cli.SetSubjectKV(subjAddr, client.SubjectIAMPathKey, iamPathKV)) ffsid.a.await(ffsid.cli.SetSubjectKV(subjAddr, client.IAMPathKey, iamPathKV))
ffsid.a.await(ffsid.cli.AddSubjectKey(subjAddr, extraKey.PublicKey())) ffsid.a.await(ffsid.cli.AddSubjectKey(subjAddr, extraKey.PublicKey()))
subj, err := ffsid.cli.GetSubject(subjAddr) subj, err := ffsid.cli.GetSubject(subjAddr)
require.NoError(t, err) require.NoError(t, err)
require.True(t, subjKey.PublicKey().Equal(subj.PrimaryKey)) require.True(t, subjKey.PublicKey().Equal(subj.PrimaryKey))
require.Equal(t, subj.Name, subjLogin) require.Equal(t, subj.Name, subjLogin)
require.Equal(t, map[string]string{client.SubjectIAMPathKey: iamPathKV}, subj.KV) require.Equal(t, map[string]string{client.IAMPathKey: iamPathKV}, subj.KV)
require.ElementsMatch(t, []*keys.PublicKey{extraKey.PublicKey()}, subj.AdditionalKeys) require.ElementsMatch(t, []*keys.PublicKey{extraKey.PublicKey()}, subj.AdditionalKeys)
ffsid.a.await(ffsid.cli.DeleteSubjectKV(subjAddr, client.SubjectIAMPathKey)) ffsid.a.await(ffsid.cli.DeleteSubjectKV(subjAddr, client.IAMPathKey))
subj, err = ffsid.cli.GetSubject(subjAddr) subj, err = ffsid.cli.GetSubject(subjAddr)
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, subj.KV) require.Empty(t, subj.KV)
@ -204,43 +204,59 @@ func TestFrostFSID_Client_GroupManagement(t *testing.T) {
ffsid.a.await(ffsid.cli.CreateNamespace(namespace)) ffsid.a.await(ffsid.cli.CreateNamespace(namespace))
groupName := "group" groupName := "group"
ffsid.a.await(ffsid.cli.CreateGroup(namespace, groupName)) groupID := int64(1)
_, _, err := ffsid.cli.CreateGroup(namespace, groupName) actGroupID, err := ffsid.cli.ParseGroupID(ffsid.cli.Wait(ffsid.cli.CreateGroup(namespace, groupName)))
require.ErrorContains(t, err, "already exists") require.NoError(t, err)
require.Equal(t, groupID, actGroupID)
group, err := ffsid.cli.GetGroup(namespace, groupName) _, _, err = ffsid.cli.CreateGroup(namespace, groupName)
require.ErrorContains(t, err, "is not available")
iamARN := "arn"
ffsid.a.await(ffsid.cli.SetGroupKV(namespace, groupID, client.IAMARNKey, iamARN))
group, err := ffsid.cli.GetGroup(namespace, groupID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, groupName, group.Name) require.Equal(t, groupName, group.Name)
require.Equal(t, map[string]string{client.IAMARNKey: iamARN}, group.KV)
groupExt, err := ffsid.cli.GetGroupExtended(namespace, groupName) ffsid.a.await(ffsid.cli.DeleteGroupKV(namespace, groupID, client.IAMARNKey))
groupExt, err := ffsid.cli.GetGroupExtended(namespace, groupID)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, groupExt.SubjectsCount) require.Zero(t, groupExt.SubjectsCount)
ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace)) ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace))
ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupName)) ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupID))
subjExt, err := ffsid.cli.GetSubjectExtended(subjAddr) subjExt, err := ffsid.cli.GetSubjectExtended(subjAddr)
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, []*client.Group{{Name: groupName, Namespace: namespace}}, subjExt.Groups) require.ElementsMatch(t, []*client.Group{{ID: groupID, Name: groupName, Namespace: namespace, KV: map[string]string{}}}, subjExt.Groups)
groupExt, err = ffsid.cli.GetGroupExtended(namespace, groupName) groupExt, err = ffsid.cli.GetGroupExtended(namespace, groupID)
require.NoError(t, err) require.NoError(t, err)
require.EqualValues(t, 1, groupExt.SubjectsCount) require.EqualValues(t, 1, groupExt.SubjectsCount)
subjects, err := ffsid.cli.ListGroupSubjects(namespace, groupName) subjects, err := ffsid.cli.ListGroupSubjects(namespace, groupID)
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects) require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects)
ffsid.a.await(ffsid.cli.RemoveSubjectFromGroup(subjAddr, groupID))
subjects, err = ffsid.cli.ListGroupSubjects(namespace, groupID)
require.NoError(t, err)
require.Empty(t, subjects)
subjects, err = ffsid.cli.ListNamespaceSubjects(namespace) subjects, err = ffsid.cli.ListNamespaceSubjects(namespace)
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects) require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects)
groups, err := ffsid.cli.ListGroups(namespace) groups, err := ffsid.cli.ListGroups(namespace)
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, []*client.Group{{Name: groupName, Namespace: namespace}}, groups) require.ElementsMatch(t, []*client.Group{{ID: groupID, Name: groupName, Namespace: namespace, KV: map[string]string{}}}, groups)
ffsid.a.await(ffsid.cli.DeleteGroup(namespace, groupName)) ffsid.a.await(ffsid.cli.DeleteGroup(namespace, groupID))
_, err = ffsid.cli.GetGroup(namespace, groupName) _, err = ffsid.cli.GetGroup(namespace, groupID)
require.ErrorContains(t, err, "not found") require.ErrorContains(t, err, "not found")
groups, err = ffsid.cli.ListGroups(namespace) groups, err = ffsid.cli.ListGroups(namespace)
@ -277,33 +293,47 @@ func TestFrostFSID_Client_Lists(t *testing.T) {
groups := []client.Group{ groups := []client.Group{
{ {
ID: 1,
Name: "empty-group0", Name: "empty-group0",
Namespace: namespaces[0], Namespace: namespaces[0],
}, },
{ {
ID: 2,
Name: "empty-group1", Name: "empty-group1",
Namespace: namespaces[1], Namespace: namespaces[1],
}, },
{ {
ID: 3,
Name: "group2", Name: "group2",
Namespace: namespaces[1], Namespace: namespaces[1],
}, },
{ {
ID: 4,
Name: "group3", Name: "group3",
Namespace: namespaces[2], Namespace: namespaces[2],
}, },
{ {
ID: 5,
Name: "group4", Name: "group4",
Namespace: namespaces[3], Namespace: namespaces[3],
}, },
{ {
ID: 6,
Name: "group5", Name: "group5",
Namespace: namespaces[3], Namespace: namespaces[3],
}, },
} }
tx = ffsid.cli.StartTx() tx = ffsid.cli.StartTx()
for _, group := range groups { for _, group := range groups[:3] {
err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
// we split into two tx because of gas limit exceeded error
tx = ffsid.cli.StartTx()
for _, group := range groups[3:] {
err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name)) err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name))
require.NoError(t, err) require.NoError(t, err)
} }
@ -366,12 +396,12 @@ func addSubjectsToGroups(t *testing.T, ffsid *testFrostFSIDClientInvoker, groups
cli := ffsid.cli cli := ffsid.cli
tx := cli.StartTx() tx := cli.StartTx()
err := tx.WrapCall(cli.AddSubjectToGroupCall(subjects[2].addr, groups[2].Name)) err := tx.WrapCall(cli.AddSubjectToGroupCall(subjects[2].addr, groups[2].ID))
require.NoError(t, err) require.NoError(t, err)
err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[3].addr, groups[3].Name)) err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[3].addr, groups[3].ID))
require.NoError(t, err) require.NoError(t, err)
err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[4].addr, groups[3].Name)) err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[4].addr, groups[3].ID))
require.NoError(t, err) require.NoError(t, err)
ffsid.a.await(cli.SendTx(tx)) ffsid.a.await(cli.SendTx(tx))
@ -379,13 +409,13 @@ func addSubjectsToGroups(t *testing.T, ffsid *testFrostFSIDClientInvoker, groups
// we have to start new tx because of insufficient gas / gas limit exceeded error // we have to start new tx because of insufficient gas / gas limit exceeded error
tx = cli.StartTx() tx = cli.StartTx()
err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[6].addr, groups[4].Name)) err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[6].addr, groups[4].ID))
require.NoError(t, err) require.NoError(t, err)
err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[7].addr, groups[4].Name)) err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[7].addr, groups[4].ID))
require.NoError(t, err) require.NoError(t, err)
err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[8].addr, groups[5].Name)) err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[8].addr, groups[5].ID))
require.NoError(t, err) require.NoError(t, err)
err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[9].addr, groups[5].Name)) err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[9].addr, groups[5].ID))
require.NoError(t, err) require.NoError(t, err)
ffsid.a.await(cli.SendTx(tx)) ffsid.a.await(cli.SendTx(tx))
@ -398,7 +428,7 @@ func checkNamespaceSubjects(t *testing.T, cli *client.Client, ns string, subject
} }
func checkGroupSubjects(t *testing.T, cli *client.Client, group client.Group, subjects []testSubject, start, end int) { func checkGroupSubjects(t *testing.T, cli *client.Client, group client.Group, subjects []testSubject, start, end int) {
groupSubjects, err := cli.ListGroupSubjects(group.Namespace, group.Name) groupSubjects, err := cli.ListGroupSubjects(group.Namespace, group.ID)
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, subjSlice(subjects, start, end), groupSubjects) require.ElementsMatch(t, subjSlice(subjects, start, end), groupSubjects)
} }
@ -448,6 +478,7 @@ func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) {
namespace := "namespace" namespace := "namespace"
group := "group" group := "group"
groupID := int64(1)
tx := ffsid.cli.StartTx() tx := ffsid.cli.StartTx()
err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace)) err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace))
@ -471,7 +502,7 @@ func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
if i > len(subjects)/2 { if i > len(subjects)/2 {
err = tx.WrapCall(ffsid.cli.AddSubjectToGroupCall(subjects[i].addr, group)) err = tx.WrapCall(ffsid.cli.AddSubjectToGroupCall(subjects[i].addr, groupID))
require.NoError(t, err) require.NoError(t, err)
} }
ffsid.a.await(ffsid.cli.SendTx(tx)) ffsid.a.await(ffsid.cli.SendTx(tx))

View file

@ -50,6 +50,10 @@ const (
createGroupMethod = "createGroup" createGroupMethod = "createGroup"
getGroupMethod = "getGroup" getGroupMethod = "getGroup"
getGroupExtendedMethod = "getGroupExtended" getGroupExtendedMethod = "getGroupExtended"
getGroupIDByNameMethod = "getGroupIDByName"
setGroupNameMethod = "setGroupName"
setGroupKVMethod = "setGroupKV"
deleteGroupKVMethod = "deleteGroupKV"
listGroupsMethod = "listGroups" listGroupsMethod = "listGroups"
addSubjectToGroupMethod = "addSubjectToGroup" addSubjectToGroupMethod = "addSubjectToGroup"
removeSubjectFromGroupMethod = "removeSubjectFromGroup" removeSubjectFromGroupMethod = "removeSubjectFromGroup"
@ -220,16 +224,16 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
t.Run("set subject KVs", func(t *testing.T) { t.Run("set subject KVs", func(t *testing.T) {
iamPath := "iam/path" iamPath := "iam/path"
anonInvoker.InvokeFail(t, notWitnessedError, setSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey, iamPath) anonInvoker.InvokeFail(t, notWitnessedError, setSubjectKVMethod, subjKeyAddr, client.IAMPathKey, iamPath)
invoker.Invoke(t, stackitem.Null{}, setSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey, iamPath) invoker.Invoke(t, stackitem.Null{}, setSubjectKVMethod, subjKeyAddr, client.IAMPathKey, iamPath)
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err) require.NoError(t, err)
subj = parseSubject(t, s) subj = parseSubject(t, s)
require.Equal(t, iamPath, subj.KV[client.SubjectIAMPathKey]) require.Equal(t, iamPath, subj.KV[client.IAMPathKey])
anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey) anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectKVMethod, subjKeyAddr, client.IAMPathKey)
invoker.Invoke(t, stackitem.Null{}, deleteSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey) invoker.Invoke(t, stackitem.Null{}, deleteSubjectKVMethod, subjKeyAddr, client.IAMPathKey)
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err) require.NoError(t, err)
@ -324,6 +328,55 @@ func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) {
checkPublicKeyResult(t, s, err, nil) checkPublicKeyResult(t, s, err, nil)
} }
func TestFrostFSIS_GroupNameRelatedInvariants(t *testing.T) {
f := newFrostFSIDInvoker(t)
groupName1, groupName2 := "group1", "group2"
groupID1, groupID2 := int64(1), int64(2)
invoker := f.OwnerInvoker()
ns1, ns2 := "ns1", "ns2"
// Create two group
// Create two namespace.
// Add these groups to ns1
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1)
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2)
invoker.Invoke(t, stackitem.Make(groupID1), createGroupMethod, ns1, groupName1)
invoker.Invoke(t, stackitem.Make(groupID2), createGroupMethod, ns1, groupName2)
// Check that we can find group id by name for group1 in ns1 and not in ns2
s, err := invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName1)
checkGroupIDResult(t, s, err, groupID1)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns2, groupName1)
checkGroupIDResult(t, s, err, -1)
// Check that we cannot set for second group name that the first subject has already taken
invoker.InvokeFail(t, "not available", setGroupNameMethod, ns1, groupID1, groupName2)
// Check that we cannot create group with the same name in namespace, but can in another
invoker.InvokeFail(t, "not available", createGroupMethod, ns1, groupName2)
invoker.Invoke(t, stackitem.Make(3), createGroupMethod, ns2, groupName2)
// Check that we cannot find group id by name for group that was removed
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, ns1, groupID2)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName2)
checkGroupIDResult(t, s, err, -1)
// Check that we can create group with the name that was freed after deleting
invoker.Invoke(t, stackitem.Make(4), createGroupMethod, ns1, groupName2)
// Check that after group renaming its id cannot be found by old name
newGroupName:= "new"
invoker.Invoke(t, stackitem.Null{}, setGroupNameMethod, ns1, groupID1, newGroupName)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName1)
checkGroupIDResult(t, s, err, -1)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, newGroupName)
checkGroupIDResult(t, s, err, groupID1)
}
func TestFrostFSID_NamespaceManagement(t *testing.T) { func TestFrostFSID_NamespaceManagement(t *testing.T) {
f := newFrostFSIDInvoker(t) f := newFrostFSIDInvoker(t)
@ -428,19 +481,21 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
nsName := "namespace" nsName := "namespace"
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, nsName) invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, nsName)
groupID := int64(1)
groupName := "group" groupName := "group"
anonInvoker.InvokeFail(t, notWitnessedError, createGroupMethod, nsName, groupName) anonInvoker.InvokeFail(t, notWitnessedError, createGroupMethod, nsName, groupName)
invoker.Invoke(t, stackitem.Null{}, createGroupMethod, nsName, groupName) invoker.Invoke(t, stackitem.Make(groupID), createGroupMethod, nsName, groupName)
s, err := anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupName) s, err := anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err) require.NoError(t, err)
group := parseGroup(t, s.Pop().Item()) group := parseGroup(t, s.Pop().Item())
require.Equal(t, groupID, group.ID)
require.Equal(t, groupName, group.Name) require.Equal(t, groupName, group.Name)
s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName) s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName)
require.NoError(t, err) require.NoError(t, err)
groups := parseGroups(t, readIteratorAll(s)) groups := parseGroups(t, readIteratorAll(s))
require.ElementsMatch(t, groups, []Group{{Name: groupName, Namespace: nsName}}) require.ElementsMatch(t, groups, []Group{{ID: groupID, Name: groupName, Namespace: nsName}})
t.Run("add subjects to group", func(t *testing.T) { t.Run("add subjects to group", func(t *testing.T) {
subjKey, err := keys.NewPrivateKey() subjKey, err := keys.NewPrivateKey()
@ -449,21 +504,21 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
subjAddress := subjKey.PublicKey().GetScriptHash() subjAddress := subjKey.PublicKey().GetScriptHash()
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, nsName) invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, nsName)
anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupName) anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupID)
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupName) invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupID)
t.Run("list group subjects", func(t *testing.T) { t.Run("list group subjects", func(t *testing.T) {
s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupName) s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupID)
require.NoError(t, err) require.NoError(t, err)
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil) addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, addresses, []util.Uint160{subjAddress}) require.ElementsMatch(t, addresses, []util.Uint160{subjAddress})
anonInvoker.InvokeFail(t, "not witnessed", removeSubjectFromGroupMethod, subjAddress, nsName, groupName) anonInvoker.InvokeFail(t, "not witnessed", removeSubjectFromGroupMethod, subjAddress, groupID)
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjAddress, nsName, groupName) invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjAddress, groupID)
s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupName) s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupID)
require.NoError(t, err) require.NoError(t, err)
addresses, err = unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil) addresses, err = unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
@ -477,23 +532,61 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKey.PublicKey().GetScriptHash(), nsName) invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKey.PublicKey().GetScriptHash(), nsName)
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupName) invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupID)
} }
s, err = anonInvoker.TestInvoke(t, getGroupExtendedMethod, nsName, groupName) s, err = anonInvoker.TestInvoke(t, getGroupExtendedMethod, nsName, groupID)
require.NoError(t, err) require.NoError(t, err)
groupExt := parseGroupExtended(t, s.Pop().Item()) groupExt := parseGroupExtended(t, s.Pop().Item())
require.Equal(t, groupID, groupExt.ID)
require.Equal(t, groupName, groupExt.Name) require.Equal(t, groupName, groupExt.Name)
require.Empty(t, groupExt.KV)
require.EqualValues(t, subjectsCount, groupExt.SubjectsCount) require.EqualValues(t, subjectsCount, groupExt.SubjectsCount)
}) })
}) })
}) })
t.Run("delete group", func(t *testing.T) { t.Run("set group name", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupMethod, nsName, groupName) newGroupName := "new-name"
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, nsName, groupName)
anonInvoker.InvokeFail(t, "group not found", getGroupMethod, nsName, groupName) anonInvoker.InvokeFail(t, notWitnessedError, setGroupNameMethod, nsName, groupID, newGroupName)
invoker.Invoke(t, stackitem.Null{}, setGroupNameMethod, nsName, groupID, newGroupName)
s, err = anonInvoker.TestInvoke(t, getGroupIDByNameMethod, nsName, newGroupName)
require.NoError(t, err)
require.Equal(t, groupID, s.Pop().BigInt().Int64())
s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err)
group = parseGroup(t, s.Pop().Item())
require.Equal(t, newGroupName, group.Name)
})
t.Run("set group KVs", func(t *testing.T) {
iamARN := "arn"
anonInvoker.InvokeFail(t, notWitnessedError, setGroupKVMethod, nsName, groupID, client.IAMARNKey, iamARN)
invoker.Invoke(t, stackitem.Null{}, setGroupKVMethod, nsName, groupID, client.IAMARNKey, iamARN)
s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err)
group = parseGroup(t, s.Pop().Item())
require.Equal(t, iamARN, group.KV[client.IAMARNKey])
anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupKVMethod, nsName, groupID, client.IAMARNKey)
invoker.Invoke(t, stackitem.Null{}, deleteGroupKVMethod, nsName, groupID, client.IAMARNKey)
s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err)
group = parseGroup(t, s.Pop().Item())
require.Empty(t, group.KV)
})
t.Run("delete group", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupMethod, nsName, groupID)
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, nsName, groupID)
anonInvoker.InvokeFail(t, "group not found", getGroupMethod, nsName, groupID)
s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName) s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName)
require.NoError(t, err) require.NoError(t, err)
@ -514,6 +607,18 @@ func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.Privat
require.Equal(t, key.PublicKey(), foundKey) require.Equal(t, key.PublicKey(), foundKey)
} }
func checkGroupIDResult(t *testing.T, s *vm.Stack, err error, groupID int64) {
if groupID == -1 {
require.ErrorContains(t, err, "not found")
return
}
require.NoError(t, err)
foundGroupID, err := unwrap.Int64(makeValidRes(s.Pop().Item()), nil)
require.NoError(t, err)
require.Equal(t, groupID, foundGroupID)
}
func readIteratorAll(s *vm.Stack) []stackitem.Item { func readIteratorAll(s *vm.Stack) []stackitem.Item {
iter := s.Pop().Value().(*storage.Iterator) iter := s.Pop().Value().(*storage.Iterator)
@ -649,13 +754,17 @@ func parseNamespaces(t *testing.T, items []stackitem.Item) []Namespace {
} }
type Group struct { type Group struct {
ID int64
Name string Name string
Namespace string Namespace string
KV map[string]string
} }
type GroupExtended struct { type GroupExtended struct {
ID int64
Name string Name string
Namespace string Namespace string
KV map[string]string
SubjectsCount int64 SubjectsCount int64
} }
@ -663,16 +772,23 @@ func parseGroup(t *testing.T, item stackitem.Item) Group {
var group Group var group Group
subjStruct := item.Value().([]stackitem.Item) subjStruct := item.Value().([]stackitem.Item)
require.Len(t, subjStruct, 2) require.Len(t, subjStruct, 4)
nameBytes, err := subjStruct[0].TryBytes() groupID, err := subjStruct[0].TryInteger()
require.NoError(t, err)
group.ID = groupID.Int64()
nameBytes, err := subjStruct[1].TryBytes()
require.NoError(t, err) require.NoError(t, err)
group.Name = string(nameBytes) group.Name = string(nameBytes)
namespaceBytes, err := subjStruct[1].TryBytes() namespaceBytes, err := subjStruct[2].TryBytes()
require.NoError(t, err) require.NoError(t, err)
group.Namespace = string(namespaceBytes) group.Namespace = string(namespaceBytes)
group.KV, err = parseMap(subjStruct[3])
require.NoError(t, err)
return group return group
} }
@ -680,17 +796,24 @@ func parseGroupExtended(t *testing.T, item stackitem.Item) GroupExtended {
var gr GroupExtended var gr GroupExtended
subjStruct := item.Value().([]stackitem.Item) subjStruct := item.Value().([]stackitem.Item)
require.Len(t, subjStruct, 3) require.Len(t, subjStruct, 5)
nameBytes, err := subjStruct[0].TryBytes() groupID, err := subjStruct[0].TryInteger()
require.NoError(t, err)
gr.ID = groupID.Int64()
nameBytes, err := subjStruct[1].TryBytes()
require.NoError(t, err) require.NoError(t, err)
gr.Name = string(nameBytes) gr.Name = string(nameBytes)
namespaceBytes, err := subjStruct[1].TryBytes() namespaceBytes, err := subjStruct[2].TryBytes()
require.NoError(t, err) require.NoError(t, err)
gr.Namespace = string(namespaceBytes) gr.Namespace = string(namespaceBytes)
subjectsCountInt, err := subjStruct[2].TryInteger() gr.KV, err = parseMap(subjStruct[3])
require.NoError(t, err)
subjectsCountInt, err := subjStruct[4].TryInteger()
require.NoError(t, err) require.NoError(t, err)
gr.SubjectsCount = subjectsCountInt.Int64() gr.SubjectsCount = subjectsCountInt.Int64()