Add RoleRegistry native contract for hierarchical RBAC
Implements RoleRegistry as a native contract for role-based access control that integrates with PersonToken for democratic governance. Key features: - Built-in roles: COMMITTEE, REGISTRAR, ATTESTOR, OPERATOR - Hierarchical roles with parent inheritance - Permission system (resource/action/scope tuples) - CheckCommittee() method for admin authorization - TutusCommittee config for initial committee members PersonToken integration: - Added RoleRegistry field for cross-contract calls - checkCommittee helper delegates to RoleRegistry with NEO fallback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
99ba041a85
commit
de34f66286
|
|
@ -56,6 +56,11 @@ type (
|
||||||
|
|
||||||
SeedList []string `yaml:"SeedList"`
|
SeedList []string `yaml:"SeedList"`
|
||||||
StandbyCommittee []string `yaml:"StandbyCommittee"`
|
StandbyCommittee []string `yaml:"StandbyCommittee"`
|
||||||
|
// TutusCommittee is the list of initial Tutus committee member addresses.
|
||||||
|
// These are granted the COMMITTEE role in RoleRegistry at initialization.
|
||||||
|
// Unlike StandbyCommittee (for consensus), TutusCommittee represents
|
||||||
|
// designated officials for democratic governance (1 person = 1 vote).
|
||||||
|
TutusCommittee []string `yaml:"TutusCommittee"`
|
||||||
// StateRootInHeader enables storing state root in block header.
|
// StateRootInHeader enables storing state root in block header.
|
||||||
StateRootInHeader bool `yaml:"StateRootInHeader"`
|
StateRootInHeader bool `yaml:"StateRootInHeader"`
|
||||||
// StateSyncInterval is the number of blocks between state heights available for MPT state data synchronization.
|
// StateSyncInterval is the number of blocks between state heights available for MPT state data synchronization.
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,20 @@ type (
|
||||||
TokenExists(d *dao.Simple, owner util.Uint160) bool
|
TokenExists(d *dao.Simple, owner util.Uint160) bool
|
||||||
GetAttribute(d *dao.Simple, tokenID uint64, key string) (*state.Attribute, error)
|
GetAttribute(d *dao.Simple, tokenID uint64, key string) (*state.Attribute, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IRoleRegistry is an interface required from native RoleRegistry contract
|
||||||
|
// for interaction with Blockchain and other native contracts.
|
||||||
|
// RoleRegistry provides democratic governance for Tutus, replacing NEO.CheckCommittee().
|
||||||
|
IRoleRegistry interface {
|
||||||
|
interop.Contract
|
||||||
|
// CheckCommittee returns true if caller has COMMITTEE role.
|
||||||
|
// This replaces NEO.CheckCommittee() for Tutus democratic governance.
|
||||||
|
CheckCommittee(ic *interop.Context) bool
|
||||||
|
// HasRoleInternal checks if address has role (includes hierarchy).
|
||||||
|
HasRoleInternal(d *dao.Simple, address util.Uint160, roleID uint64, blockHeight uint32) bool
|
||||||
|
// HasPermissionInternal checks if address has permission via roles.
|
||||||
|
HasPermissionInternal(d *dao.Simple, address util.Uint160, resource, action string, scope state.Scope, blockHeight uint32) bool
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Contracts is a convenient wrapper around an arbitrary set of native contracts
|
// Contracts is a convenient wrapper around an arbitrary set of native contracts
|
||||||
|
|
@ -241,6 +255,12 @@ func (cs *Contracts) PersonToken() IPersonToken {
|
||||||
return cs.ByName(nativenames.PersonToken).(IPersonToken)
|
return cs.ByName(nativenames.PersonToken).(IPersonToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoleRegistry returns native IRoleRegistry contract implementation. It panics if
|
||||||
|
// there's no contract with proper name in cs.
|
||||||
|
func (cs *Contracts) RoleRegistry() IRoleRegistry {
|
||||||
|
return cs.ByName(nativenames.RoleRegistry).(IRoleRegistry)
|
||||||
|
}
|
||||||
|
|
||||||
// NewDefaultContracts returns a new set of default native contracts.
|
// NewDefaultContracts returns a new set of default native contracts.
|
||||||
func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
|
func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
|
||||||
mgmt := NewManagement()
|
mgmt := NewManagement()
|
||||||
|
|
@ -280,6 +300,25 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
|
||||||
personToken := newPersonToken()
|
personToken := newPersonToken()
|
||||||
personToken.NEO = neo
|
personToken.NEO = neo
|
||||||
|
|
||||||
|
// Parse TutusCommittee addresses from config
|
||||||
|
var tutusCommittee []util.Uint160
|
||||||
|
for _, addrStr := range cfg.TutusCommittee {
|
||||||
|
addr, err := util.Uint160DecodeStringLE(addrStr)
|
||||||
|
if err != nil {
|
||||||
|
// Try parsing as hex (BE format)
|
||||||
|
addr, err = util.Uint160DecodeStringBE(addrStr)
|
||||||
|
if err != nil {
|
||||||
|
continue // Skip invalid addresses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tutusCommittee = append(tutusCommittee, addr)
|
||||||
|
}
|
||||||
|
roleRegistry := newRoleRegistry(tutusCommittee)
|
||||||
|
roleRegistry.NEO = neo
|
||||||
|
|
||||||
|
// Set RoleRegistry on PersonToken for cross-contract integration
|
||||||
|
personToken.RoleRegistry = roleRegistry
|
||||||
|
|
||||||
return []interop.Contract{
|
return []interop.Contract{
|
||||||
mgmt,
|
mgmt,
|
||||||
s,
|
s,
|
||||||
|
|
@ -293,5 +332,6 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract {
|
||||||
notary,
|
notary,
|
||||||
treasury,
|
treasury,
|
||||||
personToken,
|
personToken,
|
||||||
|
roleRegistry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,296 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/neotest"
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRoleRegistryClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
return newNativeClient(t, nativenames.RoleRegistry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_BuiltinRoles tests that built-in roles are created at initialization.
|
||||||
|
func TestRoleRegistry_BuiltinRoles(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
|
||||||
|
// Check totalRoles returns at least 4 (the built-in roles)
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
count, err := stack[0].TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.GreaterOrEqual(t, count.Int64(), int64(4))
|
||||||
|
}, "totalRoles")
|
||||||
|
|
||||||
|
// Check COMMITTEE role exists (ID=1)
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
// Should be an array (role struct)
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array for role")
|
||||||
|
require.Equal(t, 7, len(arr)) // Role has 7 fields
|
||||||
|
|
||||||
|
// Check role ID is 1
|
||||||
|
id, err := arr[0].TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(1), id.Int64())
|
||||||
|
|
||||||
|
// Check name is COMMITTEE
|
||||||
|
name, err := arr[1].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "COMMITTEE", string(name))
|
||||||
|
|
||||||
|
// Check role is active
|
||||||
|
active, err := arr[6].TryBool()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, active)
|
||||||
|
}, "getRole", 1)
|
||||||
|
|
||||||
|
// Check REGISTRAR role exists (ID=2)
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array for role")
|
||||||
|
name, err := arr[1].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "REGISTRAR", string(name))
|
||||||
|
}, "getRole", 2)
|
||||||
|
|
||||||
|
// Check ATTESTOR role exists (ID=3)
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array for role")
|
||||||
|
name, err := arr[1].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "ATTESTOR", string(name))
|
||||||
|
}, "getRole", 3)
|
||||||
|
|
||||||
|
// Check OPERATOR role exists (ID=4)
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array for role")
|
||||||
|
name, err := arr[1].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "OPERATOR", string(name))
|
||||||
|
}, "getRole", 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_GetRoleByName tests looking up roles by name.
|
||||||
|
func TestRoleRegistry_GetRoleByName(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
|
||||||
|
// Look up COMMITTEE by name
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array for role")
|
||||||
|
id, err := arr[0].TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(1), id.Int64())
|
||||||
|
}, "getRoleByName", "COMMITTEE")
|
||||||
|
|
||||||
|
// Look up non-existent role by name
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
require.Nil(t, stack[0].Value())
|
||||||
|
}, "getRoleByName", "NONEXISTENT")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_HasRole tests checking if an address has a role.
|
||||||
|
func TestRoleRegistry_HasRole(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
|
||||||
|
// Account should not have COMMITTEE role initially
|
||||||
|
c.Invoke(t, false, "hasRole", acc.ScriptHash(), 1)
|
||||||
|
|
||||||
|
// Grant COMMITTEE role (committee only)
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
committeeInvoker.Invoke(t, true, "grantRole", acc.ScriptHash(), 1, 0)
|
||||||
|
|
||||||
|
// Now account should have COMMITTEE role
|
||||||
|
c.Invoke(t, true, "hasRole", acc.ScriptHash(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_GrantRevokeRole tests granting and revoking roles.
|
||||||
|
func TestRoleRegistry_GrantRevokeRole(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
// Non-committee cannot grant roles
|
||||||
|
userInvoker := c.WithSigners(acc)
|
||||||
|
userInvoker.InvokeFail(t, "caller is not a committee member", "grantRole", acc.ScriptHash(), 2, 0)
|
||||||
|
|
||||||
|
// Committee can grant role
|
||||||
|
committeeInvoker.Invoke(t, true, "grantRole", acc.ScriptHash(), 2, 0)
|
||||||
|
|
||||||
|
// Check role is granted
|
||||||
|
c.Invoke(t, true, "hasRole", acc.ScriptHash(), 2)
|
||||||
|
|
||||||
|
// Get roles for address
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array")
|
||||||
|
require.GreaterOrEqual(t, len(arr), 1)
|
||||||
|
}, "getRolesForAddress", acc.ScriptHash())
|
||||||
|
|
||||||
|
// Non-committee cannot revoke roles
|
||||||
|
userInvoker.InvokeFail(t, "caller is not a committee member", "revokeRole", acc.ScriptHash(), 2)
|
||||||
|
|
||||||
|
// Committee can revoke role
|
||||||
|
committeeInvoker.Invoke(t, true, "revokeRole", acc.ScriptHash(), 2)
|
||||||
|
|
||||||
|
// Check role is revoked
|
||||||
|
c.Invoke(t, false, "hasRole", acc.ScriptHash(), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_CreateRole tests creating custom roles.
|
||||||
|
func TestRoleRegistry_CreateRole(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
// Non-committee cannot create roles
|
||||||
|
userInvoker := c.WithSigners(acc)
|
||||||
|
userInvoker.InvokeFail(t, "caller is not a committee member", "createRole", "CUSTOM_ROLE", "A custom role", 0)
|
||||||
|
|
||||||
|
// Committee can create role
|
||||||
|
committeeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
roleID, err := stack[0].TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.GreaterOrEqual(t, roleID.Int64(), int64(5)) // Custom roles start at 5
|
||||||
|
}, "createRole", "CUSTOM_ROLE", "A custom role", 0)
|
||||||
|
|
||||||
|
// Verify role exists
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array for role")
|
||||||
|
name, err := arr[1].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "CUSTOM_ROLE", string(name))
|
||||||
|
}, "getRoleByName", "CUSTOM_ROLE")
|
||||||
|
|
||||||
|
// Cannot create duplicate role name
|
||||||
|
committeeInvoker.InvokeFail(t, "role name already exists", "createRole", "CUSTOM_ROLE", "Duplicate", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_DeleteRole tests deleting (deactivating) roles.
|
||||||
|
func TestRoleRegistry_DeleteRole(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
// Create a custom role first
|
||||||
|
var customRoleID int64
|
||||||
|
committeeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
roleID, _ := stack[0].TryInteger()
|
||||||
|
customRoleID = roleID.Int64()
|
||||||
|
}, "createRole", "DELETE_TEST", "Role to delete", 0)
|
||||||
|
|
||||||
|
// Cannot delete built-in roles
|
||||||
|
committeeInvoker.InvokeFail(t, "cannot modify built-in role", "deleteRole", 1)
|
||||||
|
committeeInvoker.InvokeFail(t, "cannot modify built-in role", "deleteRole", 2)
|
||||||
|
committeeInvoker.InvokeFail(t, "cannot modify built-in role", "deleteRole", 3)
|
||||||
|
committeeInvoker.InvokeFail(t, "cannot modify built-in role", "deleteRole", 4)
|
||||||
|
|
||||||
|
// Can delete custom role
|
||||||
|
committeeInvoker.Invoke(t, true, "deleteRole", customRoleID)
|
||||||
|
|
||||||
|
// Verify role is deactivated (still exists but not active)
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array for role")
|
||||||
|
active, err := arr[6].TryBool()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, active)
|
||||||
|
}, "getRole", customRoleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_Permissions tests assigning and checking permissions.
|
||||||
|
func TestRoleRegistry_Permissions(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
// Create a custom role
|
||||||
|
var customRoleID int64
|
||||||
|
committeeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
roleID, _ := stack[0].TryInteger()
|
||||||
|
customRoleID = roleID.Int64()
|
||||||
|
}, "createRole", "PERM_TEST", "Permission test role", 0)
|
||||||
|
|
||||||
|
// Assign permission to role
|
||||||
|
committeeInvoker.Invoke(t, true, "assignPermission", customRoleID, "documents", "read", 0)
|
||||||
|
|
||||||
|
// Grant role to account
|
||||||
|
committeeInvoker.Invoke(t, true, "grantRole", acc.ScriptHash(), customRoleID, 0)
|
||||||
|
|
||||||
|
// Check account has permission
|
||||||
|
c.Invoke(t, true, "hasPermission", acc.ScriptHash(), "documents", "read", 0)
|
||||||
|
|
||||||
|
// Check account does NOT have other permission
|
||||||
|
c.Invoke(t, false, "hasPermission", acc.ScriptHash(), "documents", "write", 0)
|
||||||
|
|
||||||
|
// Get permissions for role
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array")
|
||||||
|
require.Equal(t, 1, len(arr))
|
||||||
|
}, "getPermissions", customRoleID)
|
||||||
|
|
||||||
|
// Remove permission
|
||||||
|
committeeInvoker.Invoke(t, true, "removePermission", customRoleID, "documents", "read")
|
||||||
|
|
||||||
|
// Check permission is gone
|
||||||
|
c.Invoke(t, false, "hasPermission", acc.ScriptHash(), "documents", "read", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRoleRegistry_RoleHierarchy tests role hierarchy (parent roles).
|
||||||
|
func TestRoleRegistry_RoleHierarchy(t *testing.T) {
|
||||||
|
c := newRoleRegistryClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
// Create parent role
|
||||||
|
var parentRoleID int64
|
||||||
|
committeeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
roleID, _ := stack[0].TryInteger()
|
||||||
|
parentRoleID = roleID.Int64()
|
||||||
|
}, "createRole", "PARENT_ROLE", "Parent role", 0)
|
||||||
|
|
||||||
|
// Create child role with parent
|
||||||
|
var childRoleID int64
|
||||||
|
committeeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
roleID, _ := stack[0].TryInteger()
|
||||||
|
childRoleID = roleID.Int64()
|
||||||
|
}, "createRole", "CHILD_ROLE", "Child role", parentRoleID)
|
||||||
|
|
||||||
|
// Grant child role to account
|
||||||
|
committeeInvoker.Invoke(t, true, "grantRole", acc.ScriptHash(), childRoleID, 0)
|
||||||
|
|
||||||
|
// Check account has child role
|
||||||
|
c.Invoke(t, true, "hasRole", acc.ScriptHash(), childRoleID)
|
||||||
|
|
||||||
|
// Check account ALSO has parent role through hierarchy
|
||||||
|
c.Invoke(t, true, "hasRole", acc.ScriptHash(), parentRoleID)
|
||||||
|
}
|
||||||
|
|
@ -31,4 +31,6 @@ const (
|
||||||
Treasury int32 = -11
|
Treasury int32 = -11
|
||||||
// PersonToken is an ID of native PersonToken contract.
|
// PersonToken is an ID of native PersonToken contract.
|
||||||
PersonToken int32 = -12
|
PersonToken int32 = -12
|
||||||
|
// RoleRegistry is an ID of native RoleRegistry contract.
|
||||||
|
RoleRegistry int32 = -13
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ const (
|
||||||
Notary = "Notary"
|
Notary = "Notary"
|
||||||
CryptoLib = "CryptoLib"
|
CryptoLib = "CryptoLib"
|
||||||
StdLib = "StdLib"
|
StdLib = "StdLib"
|
||||||
Treasury = "Treasury"
|
Treasury = "Treasury"
|
||||||
PersonToken = "PersonToken"
|
PersonToken = "PersonToken"
|
||||||
|
RoleRegistry = "RoleRegistry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// All contains the list of all native contract names ordered by the contract ID.
|
// All contains the list of all native contract names ordered by the contract ID.
|
||||||
|
|
@ -30,6 +31,7 @@ var All = []string{
|
||||||
Notary,
|
Notary,
|
||||||
Treasury,
|
Treasury,
|
||||||
PersonToken,
|
PersonToken,
|
||||||
|
RoleRegistry,
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid checks if the name is a valid native contract's name.
|
// IsValid checks if the name is a valid native contract's name.
|
||||||
|
|
@ -45,5 +47,6 @@ func IsValid(name string) bool {
|
||||||
name == CryptoLib ||
|
name == CryptoLib ||
|
||||||
name == StdLib ||
|
name == StdLib ||
|
||||||
name == Treasury ||
|
name == Treasury ||
|
||||||
name == PersonToken
|
name == PersonToken ||
|
||||||
|
name == RoleRegistry
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ import (
|
||||||
// PersonToken represents a soul-bound identity native contract.
|
// PersonToken represents a soul-bound identity native contract.
|
||||||
type PersonToken struct {
|
type PersonToken struct {
|
||||||
interop.ContractMD
|
interop.ContractMD
|
||||||
NEO INEO
|
NEO INEO
|
||||||
|
RoleRegistry IRoleRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersonTokenCache represents the cached state for PersonToken contract.
|
// PersonTokenCache represents the cached state for PersonToken contract.
|
||||||
|
|
@ -129,6 +130,16 @@ func (c *PersonTokenCache) Copy() dao.NativeContractCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkCommittee checks if the caller has committee authority.
|
||||||
|
// Uses RoleRegistry if available, falls back to NEO.CheckCommittee().
|
||||||
|
func (p *PersonToken) checkCommittee(ic *interop.Context) bool {
|
||||||
|
if p.RoleRegistry != nil {
|
||||||
|
return p.RoleRegistry.CheckCommittee(ic)
|
||||||
|
}
|
||||||
|
// Fallback to NEO for backwards compatibility
|
||||||
|
return p.NEO.CheckCommittee(ic)
|
||||||
|
}
|
||||||
|
|
||||||
// newPersonToken creates a new PersonToken native contract.
|
// newPersonToken creates a new PersonToken native contract.
|
||||||
func newPersonToken() *PersonToken {
|
func newPersonToken() *PersonToken {
|
||||||
p := &PersonToken{
|
p := &PersonToken{
|
||||||
|
|
@ -709,7 +720,7 @@ func (p *PersonToken) suspend(ic *interop.Context, args []stackitem.Item) stacki
|
||||||
reason := toString(args[1])
|
reason := toString(args[1])
|
||||||
|
|
||||||
// Check committee
|
// Check committee
|
||||||
if !p.NEO.CheckCommittee(ic) {
|
if !p.checkCommittee(ic) {
|
||||||
panic(ErrNotCommittee)
|
panic(ErrNotCommittee)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -762,7 +773,7 @@ func (p *PersonToken) reinstate(ic *interop.Context, args []stackitem.Item) stac
|
||||||
owner := toUint160(args[0])
|
owner := toUint160(args[0])
|
||||||
|
|
||||||
// Check committee
|
// Check committee
|
||||||
if !p.NEO.CheckCommittee(ic) {
|
if !p.checkCommittee(ic) {
|
||||||
panic(ErrNotCommittee)
|
panic(ErrNotCommittee)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -815,7 +826,7 @@ func (p *PersonToken) revoke(ic *interop.Context, args []stackitem.Item) stackit
|
||||||
reason := toString(args[1])
|
reason := toString(args[1])
|
||||||
|
|
||||||
// Check committee
|
// Check committee
|
||||||
if !p.NEO.CheckCommittee(ic) {
|
if !p.checkCommittee(ic) {
|
||||||
panic(ErrNotCommittee)
|
panic(ErrNotCommittee)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1044,7 +1055,7 @@ func (p *PersonToken) revokeAttribute(ic *interop.Context, args []stackitem.Item
|
||||||
caller := ic.VM.GetCallingScriptHash()
|
caller := ic.VM.GetCallingScriptHash()
|
||||||
isOwner := caller.Equals(token.Owner)
|
isOwner := caller.Equals(token.Owner)
|
||||||
isAttestor := caller.Equals(attr.Attestor)
|
isAttestor := caller.Equals(attr.Attestor)
|
||||||
isCommittee := p.NEO.CheckCommittee(ic)
|
isCommittee := p.checkCommittee(ic)
|
||||||
|
|
||||||
if isOwner {
|
if isOwner {
|
||||||
ok, err := runtime.CheckHashedWitness(ic, token.Owner)
|
ok, err := runtime.CheckHashedWitness(ic, token.Owner)
|
||||||
|
|
@ -1532,7 +1543,7 @@ func (p *PersonToken) approveRecovery(ic *interop.Context, args []stackitem.Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check committee
|
// Check committee
|
||||||
if !p.NEO.CheckCommittee(ic) {
|
if !p.checkCommittee(ic) {
|
||||||
panic(ErrNotCommittee)
|
panic(ErrNotCommittee)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1714,7 +1725,7 @@ func (p *PersonToken) cancelRecovery(ic *interop.Context, args []stackitem.Item)
|
||||||
caller := ic.VM.GetCallingScriptHash()
|
caller := ic.VM.GetCallingScriptHash()
|
||||||
isOwner := caller.Equals(token.Owner)
|
isOwner := caller.Equals(token.Owner)
|
||||||
isRequester := caller.Equals(req.Requester)
|
isRequester := caller.Equals(req.Requester)
|
||||||
isCommittee := p.NEO.CheckCommittee(ic)
|
isCommittee := p.checkCommittee(ic)
|
||||||
|
|
||||||
if isOwner {
|
if isOwner {
|
||||||
ok, err := runtime.CheckHashedWitness(ic, token.Owner)
|
ok, err := runtime.CheckHashedWitness(ic, token.Owner)
|
||||||
|
|
@ -1845,7 +1856,7 @@ func (p *PersonToken) getCoreRoles(ic *interop.Context, token *state.PersonToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is a committee member
|
// Check if user is a committee member
|
||||||
if p.NEO.CheckCommittee(ic) {
|
if p.checkCommittee(ic) {
|
||||||
roles |= 1 << uint64(CoreRoleCommittee)
|
roles |= 1 << uint64(CoreRoleCommittee)
|
||||||
roles |= 1 << uint64(CoreRoleAttestor) // Committee members can attest
|
roles |= 1 << uint64(CoreRoleAttestor) // Committee members can attest
|
||||||
roles |= 1 << uint64(CoreRoleRecovery) // Committee members participate in recovery
|
roles |= 1 << uint64(CoreRoleRecovery) // Committee members participate in recovery
|
||||||
|
|
@ -1980,7 +1991,7 @@ func (p *PersonToken) requirePermission(ic *interop.Context, args []stackitem.It
|
||||||
|
|
||||||
// Stub: In future, this will check against RoleRegistry RBAC
|
// Stub: In future, this will check against RoleRegistry RBAC
|
||||||
// For now, committee members have all permissions
|
// For now, committee members have all permissions
|
||||||
if p.NEO.CheckCommittee(ic) {
|
if p.checkCommittee(ic) {
|
||||||
return stackitem.NewBigInteger(big.NewInt(int64(token.TokenID)))
|
return stackitem.NewBigInteger(big.NewInt(int64(token.TokenID)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,327 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue