328 lines
9.8 KiB
Go
328 lines
9.8 KiB
Go
package state
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/tutus-one/tutus-chain/pkg/util"
|
|
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
|
)
|
|
|
|
// Scope represents permission scope level.
|
|
type Scope uint8
|
|
|
|
const (
|
|
// ScopeGlobal applies the permission globally.
|
|
ScopeGlobal Scope = 0
|
|
// ScopePersonal applies only to the owner's own resources.
|
|
ScopePersonal Scope = 1
|
|
// ScopeDelegated is delegated by another token holder.
|
|
ScopeDelegated Scope = 2
|
|
)
|
|
|
|
// Role represents a custom role definition in the RoleRegistry.
|
|
type Role struct {
|
|
ID uint64 // Unique sequential identifier
|
|
Name string // Human-readable name (max 64 chars)
|
|
Description string // Description (max 256 chars)
|
|
ParentID uint64 // Parent role ID (0 = no parent, enables hierarchy)
|
|
CreatedAt uint32 // Block height when created
|
|
CreatedBy util.Uint160 // Creator's script hash
|
|
Active bool // Whether role is active
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (r *Role) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(r.ID))),
|
|
stackitem.NewByteArray([]byte(r.Name)),
|
|
stackitem.NewByteArray([]byte(r.Description)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(r.ParentID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))),
|
|
stackitem.NewByteArray(r.CreatedBy.BytesBE()),
|
|
stackitem.NewBool(r.Active),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (r *Role) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 7 {
|
|
return fmt.Errorf("wrong number of elements: expected 7, got %d", len(items))
|
|
}
|
|
|
|
id, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid id: %w", err)
|
|
}
|
|
r.ID = id.Uint64()
|
|
|
|
nameBytes, err := items[1].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid name: %w", err)
|
|
}
|
|
r.Name = string(nameBytes)
|
|
|
|
descBytes, err := items[2].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid description: %w", err)
|
|
}
|
|
r.Description = string(descBytes)
|
|
|
|
parentID, err := items[3].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid parentID: %w", err)
|
|
}
|
|
r.ParentID = parentID.Uint64()
|
|
|
|
createdAt, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid createdAt: %w", err)
|
|
}
|
|
r.CreatedAt = uint32(createdAt.Int64())
|
|
|
|
createdByBytes, err := items[5].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid createdBy: %w", err)
|
|
}
|
|
r.CreatedBy, err = util.Uint160DecodeBytesBE(createdByBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid createdBy hash: %w", err)
|
|
}
|
|
|
|
r.Active, err = items[6].TryBool()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid active: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RoleAssignment represents a role granted to a PersonToken holder.
|
|
type RoleAssignment struct {
|
|
TokenID uint64 // PersonToken ID (or script hash as uint64 for address-based)
|
|
RoleID uint64 // Role ID
|
|
GrantedAt uint32 // Block height when granted
|
|
GrantedBy util.Uint160 // Granter's script hash
|
|
ExpiresAt uint32 // Expiration block (0 = never)
|
|
Active bool // Whether assignment is active
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (a *RoleAssignment) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.TokenID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.RoleID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.GrantedAt))),
|
|
stackitem.NewByteArray(a.GrantedBy.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.ExpiresAt))),
|
|
stackitem.NewBool(a.Active),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (a *RoleAssignment) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 6 {
|
|
return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items))
|
|
}
|
|
|
|
tokenID, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid tokenID: %w", err)
|
|
}
|
|
a.TokenID = tokenID.Uint64()
|
|
|
|
roleID, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid roleID: %w", err)
|
|
}
|
|
a.RoleID = roleID.Uint64()
|
|
|
|
grantedAt, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedAt: %w", err)
|
|
}
|
|
a.GrantedAt = uint32(grantedAt.Int64())
|
|
|
|
grantedByBytes, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedBy: %w", err)
|
|
}
|
|
a.GrantedBy, err = util.Uint160DecodeBytesBE(grantedByBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedBy hash: %w", err)
|
|
}
|
|
|
|
expiresAt, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid expiresAt: %w", err)
|
|
}
|
|
a.ExpiresAt = uint32(expiresAt.Int64())
|
|
|
|
a.Active, err = items[5].TryBool()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid active: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PermissionGrant represents a permission assigned to a role.
|
|
type PermissionGrant struct {
|
|
RoleID uint64 // Role ID
|
|
Resource string // Resource identifier (e.g., "documents")
|
|
Action string // Action identifier (e.g., "read", "write")
|
|
Scope Scope // Scope level
|
|
GrantedAt uint32 // Block height when granted
|
|
GrantedBy util.Uint160 // Granter's script hash
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (p *PermissionGrant) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(p.RoleID))),
|
|
stackitem.NewByteArray([]byte(p.Resource)),
|
|
stackitem.NewByteArray([]byte(p.Action)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(p.Scope))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(p.GrantedAt))),
|
|
stackitem.NewByteArray(p.GrantedBy.BytesBE()),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (p *PermissionGrant) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 6 {
|
|
return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items))
|
|
}
|
|
|
|
roleID, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid roleID: %w", err)
|
|
}
|
|
p.RoleID = roleID.Uint64()
|
|
|
|
resourceBytes, err := items[1].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid resource: %w", err)
|
|
}
|
|
p.Resource = string(resourceBytes)
|
|
|
|
actionBytes, err := items[2].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid action: %w", err)
|
|
}
|
|
p.Action = string(actionBytes)
|
|
|
|
scope, err := items[3].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid scope: %w", err)
|
|
}
|
|
p.Scope = Scope(scope.Int64())
|
|
|
|
grantedAt, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedAt: %w", err)
|
|
}
|
|
p.GrantedAt = uint32(grantedAt.Int64())
|
|
|
|
grantedByBytes, err := items[5].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedBy: %w", err)
|
|
}
|
|
p.GrantedBy, err = util.Uint160DecodeBytesBE(grantedByBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedBy hash: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddressRoleAssignment represents a role granted directly to an address (script hash).
|
|
// This is used for bootstrapping when PersonTokens may not exist yet.
|
|
type AddressRoleAssignment struct {
|
|
Address util.Uint160 // Script hash of the address
|
|
RoleID uint64 // Role ID
|
|
GrantedAt uint32 // Block height when granted
|
|
GrantedBy util.Uint160 // Granter's script hash
|
|
ExpiresAt uint32 // Expiration block (0 = never)
|
|
Active bool // Whether assignment is active
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (a *AddressRoleAssignment) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewByteArray(a.Address.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.RoleID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.GrantedAt))),
|
|
stackitem.NewByteArray(a.GrantedBy.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.ExpiresAt))),
|
|
stackitem.NewBool(a.Active),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (a *AddressRoleAssignment) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 6 {
|
|
return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items))
|
|
}
|
|
|
|
addressBytes, err := items[0].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid address: %w", err)
|
|
}
|
|
a.Address, err = util.Uint160DecodeBytesBE(addressBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid address hash: %w", err)
|
|
}
|
|
|
|
roleID, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid roleID: %w", err)
|
|
}
|
|
a.RoleID = roleID.Uint64()
|
|
|
|
grantedAt, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedAt: %w", err)
|
|
}
|
|
a.GrantedAt = uint32(grantedAt.Int64())
|
|
|
|
grantedByBytes, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedBy: %w", err)
|
|
}
|
|
a.GrantedBy, err = util.Uint160DecodeBytesBE(grantedByBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid grantedBy hash: %w", err)
|
|
}
|
|
|
|
expiresAt, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid expiresAt: %w", err)
|
|
}
|
|
a.ExpiresAt = uint32(expiresAt.Int64())
|
|
|
|
a.Active, err = items[5].TryBool()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid active: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|