package state import ( "errors" "fmt" "math/big" "git.marketally.com/tutus-one/tutus-chain/pkg/util" "git.marketally.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 Vita holder. type RoleAssignment struct { TokenID uint64 // Vita 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 Vitas 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 }