1128 lines
34 KiB
Go
Executable File
1128 lines
34 KiB
Go
Executable File
package native
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"math/big"
|
|
|
|
"github.com/tutus-one/tutus-chain/pkg/config"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/dao"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/interop"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/state"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/storage"
|
|
"github.com/tutus-one/tutus-chain/pkg/crypto/hash"
|
|
"github.com/tutus-one/tutus-chain/pkg/smartcontract"
|
|
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
|
|
"github.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
|
|
"github.com/tutus-one/tutus-chain/pkg/util"
|
|
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
|
)
|
|
|
|
// Palam represents the programmed transparency native contract.
|
|
// Palam (Latin for "openly/publicly") provides role-based encrypted
|
|
// transaction flows with judicial declassification.
|
|
type Palam struct {
|
|
interop.ContractMD
|
|
Tutus ITutus
|
|
Vita IVita
|
|
RoleRegistry IRoleRegistry
|
|
Lex ILex
|
|
}
|
|
|
|
// PalamCache represents the cached state for Palam contract.
|
|
type PalamCache struct {
|
|
flowCount uint64
|
|
requestCount uint64
|
|
logCount uint64
|
|
attachmentCount uint64
|
|
}
|
|
|
|
// Storage key prefixes for Palam.
|
|
const (
|
|
palamPrefixFlow byte = 0x01 // flowID -> Flow
|
|
palamPrefixFlowByBucket byte = 0x02 // bucket + flowID -> exists
|
|
palamPrefixFlowByParticipant byte = 0x03 // participant + flowID -> exists
|
|
palamPrefixFlowByTag byte = 0x04 // tag + flowID -> exists
|
|
palamPrefixRequest byte = 0x10 // requestID -> DeclassifyRequest
|
|
palamPrefixRequestByFlow byte = 0x11 // flowID + requestID -> exists
|
|
palamPrefixRequestByRequester byte = 0x12 // requester + requestID -> exists
|
|
palamPrefixPendingRequest byte = 0x13 // flowID -> pending requestID
|
|
palamPrefixAccessLog byte = 0x20 // logID -> AccessLog
|
|
palamPrefixLogByFlow byte = 0x21 // flowID + logID -> exists
|
|
palamPrefixLogByAccessor byte = 0x22 // accessor + logID -> exists
|
|
palamPrefixAttachment byte = 0x30 // attachmentID -> FlowAttachment
|
|
palamPrefixAttachmentByFlow byte = 0x31 // flowID + attachmentID -> exists
|
|
palamPrefixFlowCounter byte = 0xF0 // -> uint64
|
|
palamPrefixRequestCounter byte = 0xF1 // -> next request ID
|
|
palamPrefixLogCounter byte = 0xF2 // -> next log ID
|
|
palamPrefixAttachmentCounter byte = 0xF3 // -> next attachment ID
|
|
palamPrefixConfig byte = 0xFF // -> PalamConfig
|
|
)
|
|
|
|
// Event names for Palam.
|
|
const (
|
|
FlowRecordedEvent = "FlowRecorded"
|
|
FlowAttachmentEvent = "FlowAttachment"
|
|
FlowAccessedEvent = "FlowAccessed"
|
|
DeclassifyRequestedEvent = "DeclassifyRequested"
|
|
DeclassifyApprovalEvent = "DeclassifyApproval"
|
|
DeclassifyGrantedEvent = "DeclassifyGranted"
|
|
DeclassifyDeniedEvent = "DeclassifyDenied"
|
|
DeclassifyExpiredEvent = "DeclassifyExpired"
|
|
)
|
|
|
|
// Role constants for Palam (from RoleRegistry).
|
|
const (
|
|
RolePalamAuditor uint64 = 25 // Can request declassification
|
|
RolePalamJudge uint64 = 26 // Can approve declassification
|
|
)
|
|
|
|
// Various errors for Palam.
|
|
var (
|
|
ErrPalamFlowNotFound = errors.New("flow not found")
|
|
ErrPalamFlowExists = errors.New("flow already exists")
|
|
ErrPalamRequestNotFound = errors.New("declassify request not found")
|
|
ErrPalamRequestExists = errors.New("declassify request already exists")
|
|
ErrPalamNotParticipant = errors.New("caller is not a participant")
|
|
ErrPalamNotAuditor = errors.New("caller is not an auditor")
|
|
ErrPalamNotJudge = errors.New("caller is not a judge")
|
|
ErrPalamAlreadyApproved = errors.New("already approved this request")
|
|
ErrPalamRequestExpired = errors.New("request has expired")
|
|
ErrPalamRequestNotPending = errors.New("request is not pending")
|
|
ErrPalamRequestDenied = errors.New("request was denied")
|
|
ErrPalamNoVita = errors.New("caller must have an active Vita")
|
|
ErrPalamInvalidTag = errors.New("invalid tag")
|
|
ErrPalamInvalidBucket = errors.New("invalid bucket")
|
|
ErrPalamInvalidCaseID = errors.New("invalid case ID")
|
|
ErrPalamInvalidReason = errors.New("invalid reason (minimum 50 characters)")
|
|
ErrPalamCannotSelfApprove = errors.New("cannot approve own request")
|
|
ErrPalamNoPermission = errors.New("no permission for this action")
|
|
ErrPalamInvalidAttachment = errors.New("invalid attachment type")
|
|
ErrPalamAttachmentNotFound = errors.New("attachment not found")
|
|
ErrPalamNotCommittee = errors.New("invalid committee signature")
|
|
)
|
|
|
|
// NewPalam creates a new Palam native contract.
|
|
func NewPalam() *Palam {
|
|
p := &Palam{}
|
|
|
|
p.ContractMD = *interop.NewContractMD(nativenames.Palam, nativeids.Palam)
|
|
defer p.BuildHFSpecificMD(p.ActiveIn())
|
|
|
|
desc := NewDescriptor("getConfig", smartcontract.ArrayType)
|
|
md := NewMethodAndPrice(p.getConfig, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// ===== Flow Recording =====
|
|
|
|
desc = NewDescriptor("recordFlow", smartcontract.Hash256Type,
|
|
manifest.NewParameter("tag", smartcontract.StringType),
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
|
manifest.NewParameter("bucket", smartcontract.StringType),
|
|
manifest.NewParameter("participants", smartcontract.ArrayType),
|
|
manifest.NewParameter("consumerData", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("merchantData", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("distributorData", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("producerData", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("ngoData", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("auditorData", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("previousFlowID", smartcontract.Hash256Type))
|
|
md = NewMethodAndPrice(p.recordFlow, 1<<17, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("attachToFlow", smartcontract.IntegerType,
|
|
manifest.NewParameter("flowID", smartcontract.Hash256Type),
|
|
manifest.NewParameter("attachmentType", smartcontract.StringType),
|
|
manifest.NewParameter("encryptedData", smartcontract.ByteArrayType))
|
|
md = NewMethodAndPrice(p.attachToFlow, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
// ===== Flow Queries =====
|
|
|
|
desc = NewDescriptor("getFlow", smartcontract.ArrayType,
|
|
manifest.NewParameter("flowID", smartcontract.Hash256Type))
|
|
md = NewMethodAndPrice(p.getFlow, 1<<15, callflag.ReadStates|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getTotalFlows", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getTotalFlows, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getAttachment", smartcontract.ArrayType,
|
|
manifest.NewParameter("attachmentID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getAttachment, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getTotalAttachments", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getTotalAttachments, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// ===== Declassification =====
|
|
|
|
desc = NewDescriptor("requestDeclassify", smartcontract.IntegerType,
|
|
manifest.NewParameter("flowID", smartcontract.Hash256Type),
|
|
manifest.NewParameter("caseID", smartcontract.StringType),
|
|
manifest.NewParameter("reason", smartcontract.StringType),
|
|
manifest.NewParameter("expirationBlocks", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.requestDeclassify, 1<<17, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("approveDeclassify", smartcontract.BoolType,
|
|
manifest.NewParameter("requestID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.approveDeclassify, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("denyDeclassify", smartcontract.BoolType,
|
|
manifest.NewParameter("requestID", smartcontract.IntegerType),
|
|
manifest.NewParameter("reason", smartcontract.StringType))
|
|
md = NewMethodAndPrice(p.denyDeclassify, 1<<16, callflag.States|callflag.AllowNotify)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getDeclassifyRequest", smartcontract.ArrayType,
|
|
manifest.NewParameter("requestID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getDeclassifyRequest, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getTotalRequests", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getTotalRequests, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("hasDeclassifyGrant", smartcontract.BoolType,
|
|
manifest.NewParameter("flowID", smartcontract.Hash256Type),
|
|
manifest.NewParameter("requester", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(p.hasDeclassifyGrant, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// ===== Access Logging =====
|
|
|
|
desc = NewDescriptor("getAccessLog", smartcontract.ArrayType,
|
|
manifest.NewParameter("logID", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getAccessLog, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getTotalLogs", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getTotalLogs, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
// ===== Role & Permissions =====
|
|
|
|
desc = NewDescriptor("getRolePermissions", smartcontract.ArrayType,
|
|
manifest.NewParameter("role", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getRolePermissions, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
return p
|
|
}
|
|
|
|
// Metadata returns the metadata of the contract.
|
|
func (p *Palam) Metadata() *interop.ContractMD {
|
|
return &p.ContractMD
|
|
}
|
|
|
|
// Initialize initializes the Palam contract.
|
|
func (p *Palam) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
|
|
if hf != p.ActiveIn() {
|
|
return nil
|
|
}
|
|
|
|
// Initialize counters
|
|
p.setFlowCounter(ic.DAO, 0)
|
|
p.setRequestCounter(ic.DAO, 0)
|
|
p.setLogCounter(ic.DAO, 0)
|
|
p.setAttachmentCounter(ic.DAO, 0)
|
|
|
|
// Set default config
|
|
cfg := state.DefaultPalamConfig()
|
|
cfgItem := cfg.ToStackItem()
|
|
cfgBytes, err := ic.DAO.GetItemCtx().Serialize(cfgItem, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ic.DAO.PutStorageItem(p.ID, []byte{palamPrefixConfig}, cfgBytes)
|
|
|
|
return nil
|
|
}
|
|
|
|
// InitializeCache initializes the contract cache.
|
|
func (p *Palam) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
|
|
return nil // No cache needed - use storage directly
|
|
}
|
|
|
|
// ActiveIn returns the hardfork at which the contract becomes active.
|
|
func (p *Palam) ActiveIn() *config.Hardfork {
|
|
return nil // Always active
|
|
}
|
|
|
|
// Address returns the contract's script hash.
|
|
func (p *Palam) Address() util.Uint160 {
|
|
return p.Hash
|
|
}
|
|
|
|
// GetFlowInternal returns a flow by ID (for cross-contract use).
|
|
func (p *Palam) GetFlowInternal(d *dao.Simple, flowID util.Uint256) *state.Flow {
|
|
return p.getFlowInternal(d, flowID)
|
|
}
|
|
|
|
// HasDeclassifyGrant checks if requester has declassify grant for a flow (for cross-contract use).
|
|
func (p *Palam) HasDeclassifyGrant(d *dao.Simple, flowID util.Uint256, requester util.Uint160) bool {
|
|
return p.hasGrantInternal(d, flowID, requester)
|
|
}
|
|
|
|
// OnPersist implements the Contract interface.
|
|
func (p *Palam) OnPersist(ic *interop.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// PostPersist implements the Contract interface.
|
|
func (p *Palam) PostPersist(ic *interop.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// ===== Configuration =====
|
|
|
|
func (p *Palam) getConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
return cfg.ToStackItem()
|
|
}
|
|
|
|
func (p *Palam) getConfigInternal(d *dao.Simple) *state.PalamConfig {
|
|
si := d.GetStorageItem(p.ID, []byte{palamPrefixConfig})
|
|
if si == nil {
|
|
return state.DefaultPalamConfig()
|
|
}
|
|
|
|
item, err := stackitem.Deserialize(si)
|
|
if err != nil {
|
|
return state.DefaultPalamConfig()
|
|
}
|
|
|
|
cfg := &state.PalamConfig{}
|
|
if err := cfg.FromStackItem(item); err != nil {
|
|
return state.DefaultPalamConfig()
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
// ===== Counter Helpers =====
|
|
|
|
func (p *Palam) getFlowCounter(d *dao.Simple) uint64 {
|
|
si := d.GetStorageItem(p.ID, []byte{palamPrefixFlowCounter})
|
|
if si == nil || len(si) == 0 {
|
|
return 0
|
|
}
|
|
return binary.LittleEndian.Uint64(si)
|
|
}
|
|
|
|
func (p *Palam) setFlowCounter(d *dao.Simple, count uint64) {
|
|
data := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(data, count)
|
|
d.PutStorageItem(p.ID, []byte{palamPrefixFlowCounter}, data)
|
|
}
|
|
|
|
func (p *Palam) getRequestCounter(d *dao.Simple) uint64 {
|
|
si := d.GetStorageItem(p.ID, []byte{palamPrefixRequestCounter})
|
|
if si == nil || len(si) == 0 {
|
|
return 0
|
|
}
|
|
return binary.LittleEndian.Uint64(si)
|
|
}
|
|
|
|
func (p *Palam) setRequestCounter(d *dao.Simple, count uint64) {
|
|
data := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(data, count)
|
|
d.PutStorageItem(p.ID, []byte{palamPrefixRequestCounter}, data)
|
|
}
|
|
|
|
func (p *Palam) getNextRequestID(d *dao.Simple) uint64 {
|
|
id := p.getRequestCounter(d) + 1
|
|
p.setRequestCounter(d, id)
|
|
return id
|
|
}
|
|
|
|
func (p *Palam) getLogCounter(d *dao.Simple) uint64 {
|
|
si := d.GetStorageItem(p.ID, []byte{palamPrefixLogCounter})
|
|
if si == nil || len(si) == 0 {
|
|
return 0
|
|
}
|
|
return binary.LittleEndian.Uint64(si)
|
|
}
|
|
|
|
func (p *Palam) setLogCounter(d *dao.Simple, count uint64) {
|
|
data := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(data, count)
|
|
d.PutStorageItem(p.ID, []byte{palamPrefixLogCounter}, data)
|
|
}
|
|
|
|
func (p *Palam) getNextLogID(d *dao.Simple) uint64 {
|
|
id := p.getLogCounter(d) + 1
|
|
p.setLogCounter(d, id)
|
|
return id
|
|
}
|
|
|
|
func (p *Palam) getAttachmentCounter(d *dao.Simple) uint64 {
|
|
si := d.GetStorageItem(p.ID, []byte{palamPrefixAttachmentCounter})
|
|
if si == nil || len(si) == 0 {
|
|
return 0
|
|
}
|
|
return binary.LittleEndian.Uint64(si)
|
|
}
|
|
|
|
func (p *Palam) setAttachmentCounter(d *dao.Simple, count uint64) {
|
|
data := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(data, count)
|
|
d.PutStorageItem(p.ID, []byte{palamPrefixAttachmentCounter}, data)
|
|
}
|
|
|
|
func (p *Palam) getNextAttachmentID(d *dao.Simple) uint64 {
|
|
id := p.getAttachmentCounter(d) + 1
|
|
p.setAttachmentCounter(d, id)
|
|
return id
|
|
}
|
|
|
|
// ===== Flow Recording =====
|
|
|
|
func (p *Palam) recordFlow(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
tag, err := stackitem.ToString(args[0])
|
|
if err != nil || len(tag) == 0 || len(tag) > 64 {
|
|
panic(ErrPalamInvalidTag)
|
|
}
|
|
|
|
amountBI, err := args[1].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
amount := amountBI.Uint64()
|
|
|
|
bucket, err := stackitem.ToString(args[2])
|
|
if err != nil || len(bucket) == 0 || len(bucket) > 32 {
|
|
panic(ErrPalamInvalidBucket)
|
|
}
|
|
|
|
participantsArr, ok := args[3].Value().([]stackitem.Item)
|
|
if !ok {
|
|
panic("invalid participants array")
|
|
}
|
|
participants := make([]util.Uint160, len(participantsArr))
|
|
for i, p := range participantsArr {
|
|
pBytes, err := p.TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
participants[i], err = util.Uint160DecodeBytesBE(pBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
consumerData, err := args[4].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
merchantData, err := args[5].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
distributorData, err := args[6].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
producerData, err := args[7].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ngoData, err := args[8].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
auditorData, err := args[9].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
prevFlowBytes, err := args[10].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var previousFlowID util.Uint256
|
|
if len(prevFlowBytes) == 32 {
|
|
previousFlowID, err = util.Uint256DecodeBytesBE(prevFlowBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
|
|
// Verify caller has Vita
|
|
if p.Vita != nil && !p.Vita.TokenExists(ic.DAO, caller) {
|
|
panic(ErrPalamNoVita)
|
|
}
|
|
|
|
// Generate flow ID from content hash
|
|
flowData := append([]byte(tag), []byte(bucket)...)
|
|
flowData = append(flowData, caller.BytesBE()...)
|
|
flowData = append(flowData, consumerData...)
|
|
flowData = append(flowData, merchantData...)
|
|
blockBytes := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(blockBytes, ic.Block.Index)
|
|
flowData = append(flowData, blockBytes...)
|
|
flowID := hash.Sha256(flowData)
|
|
|
|
// Check if flow already exists
|
|
if p.getFlowInternal(ic.DAO, flowID) != nil {
|
|
panic(ErrPalamFlowExists)
|
|
}
|
|
|
|
flow := &state.Flow{
|
|
FlowID: flowID,
|
|
Bucket: bucket,
|
|
Tag: tag,
|
|
Amount: amount,
|
|
Timestamp: ic.Block.Index,
|
|
Creator: caller,
|
|
ConsumerData: consumerData,
|
|
MerchantData: merchantData,
|
|
DistributorData: distributorData,
|
|
ProducerData: producerData,
|
|
NGOData: ngoData,
|
|
AuditorData: auditorData,
|
|
Participants: participants,
|
|
PreviousFlowID: previousFlowID,
|
|
Status: state.FlowStatusActive,
|
|
}
|
|
|
|
// Store flow
|
|
p.putFlow(ic.DAO, flow)
|
|
|
|
// Update counter
|
|
count := p.getFlowCounter(ic.DAO) + 1
|
|
p.setFlowCounter(ic.DAO, count)
|
|
|
|
// Emit event
|
|
ic.AddNotification(p.Hash, FlowRecordedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(flowID.BytesBE()),
|
|
stackitem.NewByteArray([]byte(tag)),
|
|
stackitem.NewByteArray([]byte(bucket)),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(ic.Block.Index))),
|
|
}))
|
|
|
|
return stackitem.NewByteArray(flowID.BytesBE())
|
|
}
|
|
|
|
func (p *Palam) putFlow(d *dao.Simple, flow *state.Flow) {
|
|
flowItem := flow.ToStackItem()
|
|
flowBytes, err := d.GetItemCtx().Serialize(flowItem, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Store main record
|
|
key := append([]byte{palamPrefixFlow}, flow.FlowID.BytesBE()...)
|
|
d.PutStorageItem(p.ID, key, flowBytes)
|
|
|
|
// Index by bucket
|
|
bucketKey := append([]byte{palamPrefixFlowByBucket}, []byte(flow.Bucket)...)
|
|
bucketKey = append(bucketKey, flow.FlowID.BytesBE()...)
|
|
d.PutStorageItem(p.ID, bucketKey, []byte{1})
|
|
|
|
// Index by tag
|
|
tagKey := append([]byte{palamPrefixFlowByTag}, []byte(flow.Tag)...)
|
|
tagKey = append(tagKey, flow.FlowID.BytesBE()...)
|
|
d.PutStorageItem(p.ID, tagKey, []byte{1})
|
|
|
|
// Index by participants
|
|
for _, participant := range flow.Participants {
|
|
partKey := append([]byte{palamPrefixFlowByParticipant}, participant.BytesBE()...)
|
|
partKey = append(partKey, flow.FlowID.BytesBE()...)
|
|
d.PutStorageItem(p.ID, partKey, []byte{1})
|
|
}
|
|
}
|
|
|
|
func (p *Palam) getFlowInternal(d *dao.Simple, flowID util.Uint256) *state.Flow {
|
|
key := append([]byte{palamPrefixFlow}, flowID.BytesBE()...)
|
|
si := d.GetStorageItem(p.ID, key)
|
|
if si == nil {
|
|
return nil
|
|
}
|
|
|
|
item, err := stackitem.Deserialize(si)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
flow := &state.Flow{}
|
|
if err := flow.FromStackItem(item); err != nil {
|
|
return nil
|
|
}
|
|
return flow
|
|
}
|
|
|
|
func (p *Palam) getFlow(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
flowIDBytes, err := args[0].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
flowID, err := util.Uint256DecodeBytesBE(flowIDBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
flow := p.getFlowInternal(ic.DAO, flowID)
|
|
if flow == nil {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
// Log access if configured
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
if cfg.LogAllAccess {
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
p.logAccess(ic, flowID, caller, state.AccessTypeView, "getFlow")
|
|
}
|
|
|
|
return flow.ToStackItem()
|
|
}
|
|
|
|
func (p *Palam) getTotalFlows(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(p.getFlowCounter(ic.DAO)))
|
|
}
|
|
|
|
// ===== Attachments =====
|
|
|
|
func (p *Palam) attachToFlow(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
flowIDBytes, err := args[0].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
flowID, err := util.Uint256DecodeBytesBE(flowIDBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
attachmentType, err := stackitem.ToString(args[1])
|
|
if err != nil || len(attachmentType) == 0 || len(attachmentType) > 64 {
|
|
panic(ErrPalamInvalidAttachment)
|
|
}
|
|
|
|
encryptedData, err := args[2].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
|
|
// Verify flow exists
|
|
flow := p.getFlowInternal(ic.DAO, flowID)
|
|
if flow == nil {
|
|
panic(ErrPalamFlowNotFound)
|
|
}
|
|
|
|
// Verify caller is a participant
|
|
isParticipant := false
|
|
for _, p := range flow.Participants {
|
|
if p.Equals(caller) {
|
|
isParticipant = true
|
|
break
|
|
}
|
|
}
|
|
if !isParticipant && !flow.Creator.Equals(caller) {
|
|
panic(ErrPalamNotParticipant)
|
|
}
|
|
|
|
attachmentID := p.getNextAttachmentID(ic.DAO)
|
|
|
|
attachment := &state.FlowAttachment{
|
|
AttachmentID: attachmentID,
|
|
FlowID: flowID,
|
|
AttachmentType: attachmentType,
|
|
EncryptedData: encryptedData,
|
|
Attacher: caller,
|
|
AttachedAt: ic.Block.Index,
|
|
}
|
|
|
|
p.putAttachment(ic.DAO, attachment)
|
|
|
|
// Emit event
|
|
ic.AddNotification(p.Hash, FlowAttachmentEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(flowID.BytesBE()),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(attachmentID)),
|
|
stackitem.NewByteArray([]byte(attachmentType)),
|
|
}))
|
|
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(attachmentID))
|
|
}
|
|
|
|
func (p *Palam) putAttachment(d *dao.Simple, att *state.FlowAttachment) {
|
|
attItem := att.ToStackItem()
|
|
attBytes, err := d.GetItemCtx().Serialize(attItem, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
idBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(idBytes, att.AttachmentID)
|
|
|
|
// Store main record
|
|
key := append([]byte{palamPrefixAttachment}, idBytes...)
|
|
d.PutStorageItem(p.ID, key, attBytes)
|
|
|
|
// Index by flow
|
|
flowKey := append([]byte{palamPrefixAttachmentByFlow}, att.FlowID.BytesBE()...)
|
|
flowKey = append(flowKey, idBytes...)
|
|
d.PutStorageItem(p.ID, flowKey, []byte{1})
|
|
}
|
|
|
|
func (p *Palam) getAttachmentInternal(d *dao.Simple, attachmentID uint64) *state.FlowAttachment {
|
|
idBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(idBytes, attachmentID)
|
|
|
|
key := append([]byte{palamPrefixAttachment}, idBytes...)
|
|
si := d.GetStorageItem(p.ID, key)
|
|
if si == nil {
|
|
return nil
|
|
}
|
|
|
|
item, err := stackitem.Deserialize(si)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
att := &state.FlowAttachment{}
|
|
if err := att.FromStackItem(item); err != nil {
|
|
return nil
|
|
}
|
|
return att
|
|
}
|
|
|
|
func (p *Palam) getAttachment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
attachmentID, err := args[0].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
att := p.getAttachmentInternal(ic.DAO, attachmentID.Uint64())
|
|
if att == nil {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
return att.ToStackItem()
|
|
}
|
|
|
|
func (p *Palam) getTotalAttachments(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(p.getAttachmentCounter(ic.DAO)))
|
|
}
|
|
|
|
// ===== Declassification =====
|
|
|
|
func (p *Palam) requestDeclassify(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
flowIDBytes, err := args[0].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
flowID, err := util.Uint256DecodeBytesBE(flowIDBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
caseID, err := stackitem.ToString(args[1])
|
|
if err != nil || len(caseID) == 0 || len(caseID) > 64 {
|
|
panic(ErrPalamInvalidCaseID)
|
|
}
|
|
|
|
reason, err := stackitem.ToString(args[2])
|
|
if err != nil || len(reason) < 50 || len(reason) > 1024 {
|
|
panic(ErrPalamInvalidReason)
|
|
}
|
|
|
|
expirationBlocks, err := args[3].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
expBlocks := uint32(expirationBlocks.Uint64())
|
|
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
|
|
// Verify flow exists
|
|
flow := p.getFlowInternal(ic.DAO, flowID)
|
|
if flow == nil {
|
|
panic(ErrPalamFlowNotFound)
|
|
}
|
|
|
|
// Verify caller is an auditor
|
|
if p.RoleRegistry != nil {
|
|
if !p.RoleRegistry.HasRoleInternal(ic.DAO, caller, RolePalamAuditor, ic.Block.Index) {
|
|
panic(ErrPalamNotAuditor)
|
|
}
|
|
}
|
|
|
|
// Get config for defaults
|
|
cfg := p.getConfigInternal(ic.DAO)
|
|
if expBlocks == 0 {
|
|
expBlocks = cfg.DefaultExpiration
|
|
}
|
|
|
|
requestID := p.getNextRequestID(ic.DAO)
|
|
|
|
request := &state.DeclassifyRequest{
|
|
RequestID: requestID,
|
|
FlowID: flowID,
|
|
CaseID: caseID,
|
|
Reason: reason,
|
|
Requester: caller,
|
|
RequesterRole: state.PalamRoleAuditor,
|
|
RequiredApprovals: cfg.MinApprovals,
|
|
Approvals: []util.Uint160{},
|
|
ApprovalTimes: []uint32{},
|
|
Status: state.DeclassifyPending,
|
|
CreatedAt: ic.Block.Index,
|
|
ExpiresAt: ic.Block.Index + expBlocks,
|
|
GrantedAt: 0,
|
|
}
|
|
|
|
p.putDeclassifyRequest(ic.DAO, request)
|
|
|
|
// Emit event
|
|
ic.AddNotification(p.Hash, DeclassifyRequestedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)),
|
|
stackitem.NewByteArray(flowID.BytesBE()),
|
|
stackitem.NewByteArray([]byte(caseID)),
|
|
stackitem.NewByteArray(caller.BytesBE()),
|
|
}))
|
|
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(requestID))
|
|
}
|
|
|
|
func (p *Palam) putDeclassifyRequest(d *dao.Simple, req *state.DeclassifyRequest) {
|
|
reqItem := req.ToStackItem()
|
|
reqBytes, err := d.GetItemCtx().Serialize(reqItem, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
idBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(idBytes, req.RequestID)
|
|
|
|
// Store main record
|
|
key := append([]byte{palamPrefixRequest}, idBytes...)
|
|
d.PutStorageItem(p.ID, key, reqBytes)
|
|
|
|
// Index by flow
|
|
flowKey := append([]byte{palamPrefixRequestByFlow}, req.FlowID.BytesBE()...)
|
|
flowKey = append(flowKey, idBytes...)
|
|
d.PutStorageItem(p.ID, flowKey, []byte{1})
|
|
|
|
// Index by requester
|
|
reqKey := append([]byte{palamPrefixRequestByRequester}, req.Requester.BytesBE()...)
|
|
reqKey = append(reqKey, idBytes...)
|
|
d.PutStorageItem(p.ID, reqKey, []byte{1})
|
|
|
|
// Track pending request for flow
|
|
if req.Status == state.DeclassifyPending {
|
|
pendingKey := append([]byte{palamPrefixPendingRequest}, req.FlowID.BytesBE()...)
|
|
d.PutStorageItem(p.ID, pendingKey, idBytes)
|
|
}
|
|
}
|
|
|
|
func (p *Palam) getRequestInternal(d *dao.Simple, requestID uint64) *state.DeclassifyRequest {
|
|
idBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(idBytes, requestID)
|
|
|
|
key := append([]byte{palamPrefixRequest}, idBytes...)
|
|
si := d.GetStorageItem(p.ID, key)
|
|
if si == nil {
|
|
return nil
|
|
}
|
|
|
|
item, err := stackitem.Deserialize(si)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
req := &state.DeclassifyRequest{}
|
|
if err := req.FromStackItem(item); err != nil {
|
|
return nil
|
|
}
|
|
return req
|
|
}
|
|
|
|
func (p *Palam) approveDeclassify(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
requestID, err := args[0].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
|
|
// Verify caller is a judge
|
|
if p.RoleRegistry != nil {
|
|
if !p.RoleRegistry.HasRoleInternal(ic.DAO, caller, RolePalamJudge, ic.Block.Index) {
|
|
panic(ErrPalamNotJudge)
|
|
}
|
|
}
|
|
|
|
req := p.getRequestInternal(ic.DAO, requestID.Uint64())
|
|
if req == nil {
|
|
panic(ErrPalamRequestNotFound)
|
|
}
|
|
|
|
// Check request is pending
|
|
if req.Status != state.DeclassifyPending {
|
|
panic(ErrPalamRequestNotPending)
|
|
}
|
|
|
|
// Check not expired
|
|
if ic.Block.Index > req.ExpiresAt {
|
|
req.Status = state.DeclassifyExpired
|
|
p.putDeclassifyRequest(ic.DAO, req)
|
|
ic.AddNotification(p.Hash, DeclassifyExpiredEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(req.RequestID)),
|
|
}))
|
|
panic(ErrPalamRequestExpired)
|
|
}
|
|
|
|
// Cannot approve own request
|
|
if caller.Equals(req.Requester) {
|
|
panic(ErrPalamCannotSelfApprove)
|
|
}
|
|
|
|
// Check not already approved by this caller
|
|
for _, approver := range req.Approvals {
|
|
if approver.Equals(caller) {
|
|
panic(ErrPalamAlreadyApproved)
|
|
}
|
|
}
|
|
|
|
// Add approval
|
|
req.Approvals = append(req.Approvals, caller)
|
|
req.ApprovalTimes = append(req.ApprovalTimes, ic.Block.Index)
|
|
|
|
// Check if enough approvals
|
|
if uint32(len(req.Approvals)) >= req.RequiredApprovals {
|
|
req.Status = state.DeclassifyApproved
|
|
req.GrantedAt = ic.Block.Index
|
|
|
|
// Emit granted event
|
|
ic.AddNotification(p.Hash, DeclassifyGrantedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(req.RequestID)),
|
|
stackitem.NewByteArray(req.FlowID.BytesBE()),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(req.GrantedAt))),
|
|
}))
|
|
}
|
|
|
|
p.putDeclassifyRequest(ic.DAO, req)
|
|
|
|
// Emit approval event
|
|
ic.AddNotification(p.Hash, DeclassifyApprovalEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(req.RequestID)),
|
|
stackitem.NewByteArray(caller.BytesBE()),
|
|
stackitem.NewBigInteger(new(big.Int).SetInt64(int64(len(req.Approvals)))),
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(req.RequiredApprovals))),
|
|
}))
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Palam) denyDeclassify(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
requestID, err := args[0].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
reason, err := stackitem.ToString(args[1])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
caller := ic.VM.GetCallingScriptHash()
|
|
|
|
// Verify caller is a judge
|
|
if p.RoleRegistry != nil {
|
|
if !p.RoleRegistry.HasRoleInternal(ic.DAO, caller, RolePalamJudge, ic.Block.Index) {
|
|
panic(ErrPalamNotJudge)
|
|
}
|
|
}
|
|
|
|
req := p.getRequestInternal(ic.DAO, requestID.Uint64())
|
|
if req == nil {
|
|
panic(ErrPalamRequestNotFound)
|
|
}
|
|
|
|
if req.Status != state.DeclassifyPending {
|
|
panic(ErrPalamRequestNotPending)
|
|
}
|
|
|
|
req.Status = state.DeclassifyDenied
|
|
p.putDeclassifyRequest(ic.DAO, req)
|
|
|
|
// Emit denied event
|
|
ic.AddNotification(p.Hash, DeclassifyDeniedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(new(big.Int).SetUint64(req.RequestID)),
|
|
stackitem.NewByteArray(caller.BytesBE()),
|
|
stackitem.NewByteArray([]byte(reason)),
|
|
}))
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Palam) getDeclassifyRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
requestID, err := args[0].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
req := p.getRequestInternal(ic.DAO, requestID.Uint64())
|
|
if req == nil {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
return req.ToStackItem()
|
|
}
|
|
|
|
func (p *Palam) getTotalRequests(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(p.getRequestCounter(ic.DAO)))
|
|
}
|
|
|
|
func (p *Palam) hasDeclassifyGrant(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
flowIDBytes, err := args[0].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
flowID, err := util.Uint256DecodeBytesBE(flowIDBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
requesterBytes, err := args[1].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
requester, err := util.Uint160DecodeBytesBE(requesterBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Search for approved request by this requester for this flow
|
|
return stackitem.NewBool(p.hasGrantInternal(ic.DAO, flowID, requester))
|
|
}
|
|
|
|
func (p *Palam) hasGrantInternal(d *dao.Simple, flowID util.Uint256, requester util.Uint160) bool {
|
|
// Iterate through requests for this flow
|
|
prefix := append([]byte{palamPrefixRequestByFlow}, flowID.BytesBE()...)
|
|
d.Seek(p.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
|
|
if len(k) < 8 {
|
|
return true
|
|
}
|
|
requestID := binary.LittleEndian.Uint64(k[len(k)-8:])
|
|
req := p.getRequestInternal(d, requestID)
|
|
if req != nil && req.Status == state.DeclassifyApproved && req.Requester.Equals(requester) {
|
|
return false // Found a grant
|
|
}
|
|
return true
|
|
})
|
|
// Note: This is a simplification - in production would track this more efficiently
|
|
return false
|
|
}
|
|
|
|
// ===== Access Logging =====
|
|
|
|
func (p *Palam) logAccess(ic *interop.Context, flowID util.Uint256, accessor util.Uint160, accessType state.AccessType, details string) {
|
|
logID := p.getNextLogID(ic.DAO)
|
|
|
|
log := &state.AccessLog{
|
|
LogID: logID,
|
|
FlowID: flowID,
|
|
Accessor: accessor,
|
|
AccessType: accessType,
|
|
Timestamp: ic.Block.Index,
|
|
Details: details,
|
|
}
|
|
|
|
p.putAccessLog(ic.DAO, log)
|
|
|
|
// Emit event
|
|
ic.AddNotification(p.Hash, FlowAccessedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(flowID.BytesBE()),
|
|
stackitem.NewByteArray(accessor.BytesBE()),
|
|
stackitem.NewByteArray([]byte(details)),
|
|
}))
|
|
}
|
|
|
|
func (p *Palam) putAccessLog(d *dao.Simple, log *state.AccessLog) {
|
|
logItem := log.ToStackItem()
|
|
logBytes, err := d.GetItemCtx().Serialize(logItem, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
idBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(idBytes, log.LogID)
|
|
|
|
// Store main record
|
|
key := append([]byte{palamPrefixAccessLog}, idBytes...)
|
|
d.PutStorageItem(p.ID, key, logBytes)
|
|
|
|
// Index by flow
|
|
flowKey := append([]byte{palamPrefixLogByFlow}, log.FlowID.BytesBE()...)
|
|
flowKey = append(flowKey, idBytes...)
|
|
d.PutStorageItem(p.ID, flowKey, []byte{1})
|
|
|
|
// Index by accessor
|
|
accKey := append([]byte{palamPrefixLogByAccessor}, log.Accessor.BytesBE()...)
|
|
accKey = append(accKey, idBytes...)
|
|
d.PutStorageItem(p.ID, accKey, []byte{1})
|
|
}
|
|
|
|
func (p *Palam) getAccessLogInternal(d *dao.Simple, logID uint64) *state.AccessLog {
|
|
idBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(idBytes, logID)
|
|
|
|
key := append([]byte{palamPrefixAccessLog}, idBytes...)
|
|
si := d.GetStorageItem(p.ID, key)
|
|
if si == nil {
|
|
return nil
|
|
}
|
|
|
|
item, err := stackitem.Deserialize(si)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
log := &state.AccessLog{}
|
|
if err := log.FromStackItem(item); err != nil {
|
|
return nil
|
|
}
|
|
return log
|
|
}
|
|
|
|
func (p *Palam) getAccessLog(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
logID, err := args[0].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
log := p.getAccessLogInternal(ic.DAO, logID.Uint64())
|
|
if log == nil {
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
return log.ToStackItem()
|
|
}
|
|
|
|
func (p *Palam) getTotalLogs(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(new(big.Int).SetUint64(p.getLogCounter(ic.DAO)))
|
|
}
|
|
|
|
// ===== Role Permissions =====
|
|
|
|
func (p *Palam) getRolePermissions(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
roleInt, err := args[0].TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
role := state.PalamRole(roleInt.Uint64())
|
|
|
|
permissions := state.DefaultRolePermissions(role)
|
|
return permissions.ToStackItem()
|
|
}
|