tutus-chain/pkg/core/native/opus.go

1658 lines
49 KiB
Go

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/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"
)
// Opus represents the AI workforce integration native contract.
type Opus struct {
interop.ContractMD
NEO INEO
Vita IVita
VTS IVTS
RoleRegistry IRoleRegistry
Lex ILex
Treasury ITreasury
}
// OpusCache represents the cached state for Opus contract.
type OpusCache struct {
workerCount uint64
taskCount uint64
capabilityCount uint64
operatorCount uint64
}
// Storage key prefixes for Opus.
const (
opusPrefixWorker byte = 0x01 // workerID -> AIWorker
opusPrefixWorkerByOperator byte = 0x02 // vitaID + workerID -> exists
opusPrefixWorkerByType byte = 0x03 // workerType + workerID -> exists
opusPrefixOperator byte = 0x10 // vitaID -> OperatorProfile
opusPrefixOperatorByOwner byte = 0x11 // owner -> vitaID
opusPrefixTask byte = 0x20 // taskID -> AITask
opusPrefixTaskByWorker byte = 0x21 // workerID + taskID -> exists
opusPrefixTaskByRequester byte = 0x22 // vitaID + taskID -> exists
opusPrefixActiveTask byte = 0x23 // workerID -> active taskID
opusPrefixCapability byte = 0x30 // capID -> AICapability
opusPrefixCapByWorker byte = 0x31 // workerID + capID -> exists
opusPrefixCapByCategory byte = 0x32 // category + capID -> exists
opusPrefixWorkerCounter byte = 0xF0 // -> uint64
opusPrefixTaskCounter byte = 0xF1 // -> next task ID
opusPrefixCapCounter byte = 0xF2 // -> next capability ID
opusPrefixOperatorCounter byte = 0xF3 // -> next operator count
opusPrefixTotalTribute byte = 0xF8 // -> total AI tribute collected
opusPrefixConfig byte = 0xFF // -> OpusConfig
)
// Event names for Opus.
const (
AIWorkerRegisteredEvent = "AIWorkerRegistered"
AIWorkerUpdatedEvent = "AIWorkerUpdated"
AIWorkerSuspendedEvent = "AIWorkerSuspended"
AIWorkerDecommissionedEvent = "AIWorkerDecommissioned"
TaskAssignedEvent = "TaskAssigned"
TaskCompletedEvent = "TaskCompleted"
TaskFailedEvent = "TaskFailed"
TaskCancelledEvent = "TaskCancelled"
TributePaidEvent = "TributePaid"
CapabilityAddedEvent = "CapabilityAdded"
CapabilityVerifiedEvent = "CapabilityVerified"
OperatorRegisteredEvent = "OperatorRegistered"
OperatorVerifiedEvent = "OperatorVerified"
)
// Role constants for AI supervision.
const (
RoleOpusSupervisor uint64 = 24 // Can manage AI workers and verify capabilities
)
// Various errors for Opus.
var (
ErrOpusWorkerNotFound = errors.New("AI worker not found")
ErrOpusWorkerExists = errors.New("AI worker already exists")
ErrOpusWorkerSuspended = errors.New("AI worker is suspended")
ErrOpusWorkerDecommissioned = errors.New("AI worker is decommissioned")
ErrOpusOperatorNotFound = errors.New("operator profile not found")
ErrOpusOperatorExists = errors.New("operator profile already exists")
ErrOpusNoVita = errors.New("operator must have an active Vita")
ErrOpusTaskNotFound = errors.New("task not found")
ErrOpusTaskNotAssigned = errors.New("task is not in assigned state")
ErrOpusTaskNotInProgress = errors.New("task is not in progress")
ErrOpusTaskAlreadyComplete = errors.New("task already completed")
ErrOpusCapabilityNotFound = errors.New("capability not found")
ErrOpusCapabilityExists = errors.New("capability already exists")
ErrOpusNotCommittee = errors.New("invalid committee signature")
ErrOpusNotOperator = errors.New("caller is not the operator")
ErrOpusNotSupervisor = errors.New("caller is not an authorized supervisor")
ErrOpusInvalidName = errors.New("invalid name")
ErrOpusInvalidDescription = errors.New("invalid description")
ErrOpusInvalidTributeRate = errors.New("invalid tribute rate")
ErrOpusInvalidAmount = errors.New("invalid amount")
ErrOpusMaxWorkersExceeded = errors.New("maximum workers per operator exceeded")
ErrOpusInsufficientFee = errors.New("insufficient registration fee")
ErrOpusLaborRestricted = errors.New("labor right is restricted")
)
var (
_ interop.Contract = (*Opus)(nil)
_ dao.NativeContractCache = (*OpusCache)(nil)
)
// Copy implements NativeContractCache interface.
func (c *OpusCache) Copy() dao.NativeContractCache {
return &OpusCache{
workerCount: c.workerCount,
taskCount: c.taskCount,
capabilityCount: c.capabilityCount,
operatorCount: c.operatorCount,
}
}
// checkCommittee checks if the caller has committee authority.
func (o *Opus) checkCommittee(ic *interop.Context) bool {
if o.RoleRegistry != nil {
return o.RoleRegistry.CheckCommittee(ic)
}
return o.NEO.CheckCommittee(ic)
}
// checkOpusSupervisor checks if the caller has opus supervisor authority.
func (o *Opus) checkOpusSupervisor(ic *interop.Context) bool {
caller := ic.VM.GetCallingScriptHash()
if o.RoleRegistry != nil {
if o.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleOpusSupervisor, ic.Block.Index) {
return true
}
}
// Committee members can also act as supervisors
return o.checkCommittee(ic)
}
// checkLaborRight checks if subject has labor rights via Lex.
func (o *Opus) checkLaborRight(ic *interop.Context, subject util.Uint160) bool {
if o.Lex == nil {
return true // Allow if Lex not available
}
return o.Lex.HasRightInternal(ic.DAO, subject, state.RightLabor, ic.Block.Index)
}
// newOpus creates a new Opus native contract.
func newOpus() *Opus {
o := &Opus{
ContractMD: *interop.NewContractMD(nativenames.Opus, nativeids.Opus),
}
defer o.BuildHFSpecificMD(o.ActiveIn())
// ===== Operator Management =====
// registerOperator - Register as an AI operator (requires Vita)
desc := NewDescriptor("registerOperator", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md := NewMethodAndPrice(o.registerOperator, 1<<17, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// getOperator - Get operator profile by owner
desc = NewDescriptor("getOperator", smartcontract.ArrayType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(o.getOperator, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// verifyOperator - Verify an operator (supervisor only)
desc = NewDescriptor("verifyOperator", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(o.verifyOperator, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// ===== Worker Registration =====
// registerWorker - Register a new AI worker
desc = NewDescriptor("registerWorker", smartcontract.IntegerType,
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("description", smartcontract.StringType),
manifest.NewParameter("workerType", smartcontract.IntegerType),
manifest.NewParameter("modelHash", smartcontract.Hash256Type))
md = NewMethodAndPrice(o.registerWorker, 1<<17, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// getWorker - Get AI worker by ID
desc = NewDescriptor("getWorker", smartcontract.ArrayType,
manifest.NewParameter("workerID", smartcontract.IntegerType))
md = NewMethodAndPrice(o.getWorker, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// getWorkersByOperator - Get worker count by operator
desc = NewDescriptor("getWorkerCountByOperator", smartcontract.IntegerType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(o.getWorkerCountByOperator, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// updateWorker - Update AI worker details
desc = NewDescriptor("updateWorker", smartcontract.BoolType,
manifest.NewParameter("workerID", smartcontract.IntegerType),
manifest.NewParameter("description", smartcontract.StringType))
md = NewMethodAndPrice(o.updateWorker, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// suspendWorker - Suspend an AI worker (supervisor only)
desc = NewDescriptor("suspendWorker", smartcontract.BoolType,
manifest.NewParameter("workerID", smartcontract.IntegerType),
manifest.NewParameter("reason", smartcontract.StringType))
md = NewMethodAndPrice(o.suspendWorker, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// reactivateWorker - Reactivate a suspended worker (supervisor only)
desc = NewDescriptor("reactivateWorker", smartcontract.BoolType,
manifest.NewParameter("workerID", smartcontract.IntegerType))
md = NewMethodAndPrice(o.reactivateWorker, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// decommissionWorker - Permanently decommission an AI worker
desc = NewDescriptor("decommissionWorker", smartcontract.BoolType,
manifest.NewParameter("workerID", smartcontract.IntegerType),
manifest.NewParameter("reason", smartcontract.StringType))
md = NewMethodAndPrice(o.decommissionWorker, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// ===== Task Management =====
// assignTask - Assign a task to an AI worker
desc = NewDescriptor("assignTask", smartcontract.IntegerType,
manifest.NewParameter("workerID", smartcontract.IntegerType),
manifest.NewParameter("taskType", smartcontract.StringType),
manifest.NewParameter("description", smartcontract.StringType),
manifest.NewParameter("valueOffered", smartcontract.IntegerType))
md = NewMethodAndPrice(o.assignTask, 1<<17, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// startTask - Mark task as in progress (worker operator only)
desc = NewDescriptor("startTask", smartcontract.BoolType,
manifest.NewParameter("taskID", smartcontract.IntegerType))
md = NewMethodAndPrice(o.startTask, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// completeTask - Complete a task with result
desc = NewDescriptor("completeTask", smartcontract.BoolType,
manifest.NewParameter("taskID", smartcontract.IntegerType),
manifest.NewParameter("resultHash", smartcontract.Hash256Type),
manifest.NewParameter("valueCompleted", smartcontract.IntegerType))
md = NewMethodAndPrice(o.completeTask, 1<<17, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// failTask - Mark task as failed
desc = NewDescriptor("failTask", smartcontract.BoolType,
manifest.NewParameter("taskID", smartcontract.IntegerType),
manifest.NewParameter("reason", smartcontract.StringType))
md = NewMethodAndPrice(o.failTask, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// cancelTask - Cancel a task (requester or supervisor only)
desc = NewDescriptor("cancelTask", smartcontract.BoolType,
manifest.NewParameter("taskID", smartcontract.IntegerType),
manifest.NewParameter("reason", smartcontract.StringType))
md = NewMethodAndPrice(o.cancelTask, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// getTask - Get task by ID
desc = NewDescriptor("getTask", smartcontract.ArrayType,
manifest.NewParameter("taskID", smartcontract.IntegerType))
md = NewMethodAndPrice(o.getTask, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// getActiveTask - Get worker's active task
desc = NewDescriptor("getActiveTask", smartcontract.ArrayType,
manifest.NewParameter("workerID", smartcontract.IntegerType))
md = NewMethodAndPrice(o.getActiveTask, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// ===== Capability Management =====
// addCapability - Add capability to an AI worker (operator only)
desc = NewDescriptor("addCapability", smartcontract.IntegerType,
manifest.NewParameter("workerID", smartcontract.IntegerType),
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("category", smartcontract.StringType),
manifest.NewParameter("level", smartcontract.IntegerType))
md = NewMethodAndPrice(o.addCapability, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// verifyCapability - Verify a capability (supervisor only)
desc = NewDescriptor("verifyCapability", smartcontract.BoolType,
manifest.NewParameter("capabilityID", smartcontract.IntegerType))
md = NewMethodAndPrice(o.verifyCapability, 1<<16, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
// getCapability - Get capability by ID
desc = NewDescriptor("getCapability", smartcontract.ArrayType,
manifest.NewParameter("capabilityID", smartcontract.IntegerType))
md = NewMethodAndPrice(o.getCapability, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// hasCapability - Check if worker has capability
desc = NewDescriptor("hasCapability", smartcontract.BoolType,
manifest.NewParameter("workerID", smartcontract.IntegerType),
manifest.NewParameter("name", smartcontract.StringType))
md = NewMethodAndPrice(o.hasCapability, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// ===== Configuration & Stats =====
// getConfig - Get contract configuration
desc = NewDescriptor("getConfig", smartcontract.ArrayType)
md = NewMethodAndPrice(o.getConfig, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// setConfig - Set contract configuration (committee only)
desc = NewDescriptor("setConfig", smartcontract.BoolType,
manifest.NewParameter("minTributeRate", smartcontract.IntegerType),
manifest.NewParameter("maxTributeRate", smartcontract.IntegerType),
manifest.NewParameter("defaultTributeRate", smartcontract.IntegerType),
manifest.NewParameter("registrationFee", smartcontract.IntegerType))
md = NewMethodAndPrice(o.setConfig, 1<<16, callflag.States)
o.AddMethod(md, desc)
// getTotalWorkers - Get total registered workers
desc = NewDescriptor("getTotalWorkers", smartcontract.IntegerType)
md = NewMethodAndPrice(o.getTotalWorkers, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// getTotalTasks - Get total tasks
desc = NewDescriptor("getTotalTasks", smartcontract.IntegerType)
md = NewMethodAndPrice(o.getTotalTasks, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// getTotalOperators - Get total operators
desc = NewDescriptor("getTotalOperators", smartcontract.IntegerType)
md = NewMethodAndPrice(o.getTotalOperators, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// getTotalCapabilities - Get total capabilities
desc = NewDescriptor("getTotalCapabilities", smartcontract.IntegerType)
md = NewMethodAndPrice(o.getTotalCapabilities, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
// getTotalTribute - Get total AI tribute collected
desc = NewDescriptor("getTotalTribute", smartcontract.IntegerType)
md = NewMethodAndPrice(o.getTotalTribute, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
return o
}
// Metadata returns contract metadata.
func (o *Opus) Metadata() *interop.ContractMD {
return &o.ContractMD
}
// Initialize initializes Opus contract.
func (o *Opus) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != o.ActiveIn() {
return nil
}
// Initialize default configuration
cfg := state.OpusConfig{
MinTributeRate: 1000, // 10%
MaxTributeRate: 7000, // 70%
DefaultTributeRate: 5000, // 50%
RegistrationFee: 10000, // 100 VTS (assuming 2 decimal places)
TaskCompletionMinimum: 100, // 1 VTS
VerificationRequired: false,
MaxWorkersPerOperator: 0, // Unlimited
}
return o.setConfigInternal(ic.DAO, &cfg)
}
// ActiveIn returns the hardfork when contract becomes active.
func (o *Opus) ActiveIn() *config.Hardfork {
return nil // Always active
}
// InitializeCache initializes contract cache.
func (o *Opus) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
cache := &OpusCache{}
// Load worker count
workerCountKey := []byte{opusPrefixWorkerCounter}
if si := d.GetStorageItem(o.ID, workerCountKey); si != nil {
cache.workerCount = binary.LittleEndian.Uint64(si)
}
// Load task count
taskCountKey := []byte{opusPrefixTaskCounter}
if si := d.GetStorageItem(o.ID, taskCountKey); si != nil {
cache.taskCount = binary.LittleEndian.Uint64(si)
}
// Load capability count
capCountKey := []byte{opusPrefixCapCounter}
if si := d.GetStorageItem(o.ID, capCountKey); si != nil {
cache.capabilityCount = binary.LittleEndian.Uint64(si)
}
// Load operator count
opCountKey := []byte{opusPrefixOperatorCounter}
if si := d.GetStorageItem(o.ID, opCountKey); si != nil {
cache.operatorCount = binary.LittleEndian.Uint64(si)
}
d.SetCache(o.ID, cache)
return nil
}
// OnPersist implements Contract interface.
func (o *Opus) OnPersist(ic *interop.Context) error {
return nil
}
// PostPersist implements Contract interface.
func (o *Opus) PostPersist(ic *interop.Context) error {
return nil
}
// ===== Storage Helpers =====
func (o *Opus) getConfigInternal(d *dao.Simple) *state.OpusConfig {
key := []byte{opusPrefixConfig}
si := d.GetStorageItem(o.ID, key)
if si == nil {
return &state.OpusConfig{
MinTributeRate: 1000,
MaxTributeRate: 7000,
DefaultTributeRate: 5000,
RegistrationFee: 10000,
TaskCompletionMinimum: 100,
VerificationRequired: false,
MaxWorkersPerOperator: 0,
}
}
cfg := new(state.OpusConfig)
item, _ := stackitem.Deserialize(si)
if err := cfg.FromStackItem(item); err != nil {
return nil
}
return cfg
}
func (o *Opus) setConfigInternal(d *dao.Simple, cfg *state.OpusConfig) error {
key := []byte{opusPrefixConfig}
item, _ := cfg.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(o.ID, key, data)
return nil
}
// Counter helper functions
func (o *Opus) getWorkerCounter(d *dao.Simple) uint64 {
si := d.GetStorageItem(o.ID, []byte{opusPrefixWorkerCounter})
if si == nil {
return 0
}
return binary.LittleEndian.Uint64(si)
}
func (o *Opus) getOperatorCounter(d *dao.Simple) uint64 {
si := d.GetStorageItem(o.ID, []byte{opusPrefixOperatorCounter})
if si == nil {
return 0
}
return binary.LittleEndian.Uint64(si)
}
func (o *Opus) getTaskCounter(d *dao.Simple) uint64 {
si := d.GetStorageItem(o.ID, []byte{opusPrefixTaskCounter})
if si == nil {
return 0
}
return binary.LittleEndian.Uint64(si)
}
func (o *Opus) getCapabilityCounter(d *dao.Simple) uint64 {
si := d.GetStorageItem(o.ID, []byte{opusPrefixCapCounter})
if si == nil {
return 0
}
return binary.LittleEndian.Uint64(si)
}
func (o *Opus) getWorkerInternal(d *dao.Simple, workerID uint64) *state.AIWorker {
key := make([]byte, 9)
key[0] = opusPrefixWorker
binary.LittleEndian.PutUint64(key[1:], workerID)
si := d.GetStorageItem(o.ID, key)
if si == nil {
return nil
}
worker := new(state.AIWorker)
item, _ := stackitem.Deserialize(si)
if err := worker.FromStackItem(item); err != nil {
return nil
}
return worker
}
func (o *Opus) putWorkerInternal(d *dao.Simple, worker *state.AIWorker) error {
key := make([]byte, 9)
key[0] = opusPrefixWorker
binary.LittleEndian.PutUint64(key[1:], worker.ID)
item, _ := worker.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(o.ID, key, data)
return nil
}
func (o *Opus) getTaskInternal(d *dao.Simple, taskID uint64) *state.AITask {
key := make([]byte, 9)
key[0] = opusPrefixTask
binary.LittleEndian.PutUint64(key[1:], taskID)
si := d.GetStorageItem(o.ID, key)
if si == nil {
return nil
}
task := new(state.AITask)
item, _ := stackitem.Deserialize(si)
if err := task.FromStackItem(item); err != nil {
return nil
}
return task
}
func (o *Opus) putTaskInternal(d *dao.Simple, task *state.AITask) error {
key := make([]byte, 9)
key[0] = opusPrefixTask
binary.LittleEndian.PutUint64(key[1:], task.ID)
item, _ := task.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(o.ID, key, data)
return nil
}
func (o *Opus) getCapabilityInternal(d *dao.Simple, capID uint64) *state.AICapability {
key := make([]byte, 9)
key[0] = opusPrefixCapability
binary.LittleEndian.PutUint64(key[1:], capID)
si := d.GetStorageItem(o.ID, key)
if si == nil {
return nil
}
cap := new(state.AICapability)
item, _ := stackitem.Deserialize(si)
if err := cap.FromStackItem(item); err != nil {
return nil
}
return cap
}
func (o *Opus) putCapabilityInternal(d *dao.Simple, cap *state.AICapability) error {
key := make([]byte, 9)
key[0] = opusPrefixCapability
binary.LittleEndian.PutUint64(key[1:], cap.ID)
item, _ := cap.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(o.ID, key, data)
return nil
}
func (o *Opus) getOperatorInternal(d *dao.Simple, vitaID uint64) *state.OperatorProfile {
key := make([]byte, 9)
key[0] = opusPrefixOperator
binary.LittleEndian.PutUint64(key[1:], vitaID)
si := d.GetStorageItem(o.ID, key)
if si == nil {
return nil
}
profile := new(state.OperatorProfile)
item, _ := stackitem.Deserialize(si)
if err := profile.FromStackItem(item); err != nil {
return nil
}
return profile
}
func (o *Opus) getOperatorByOwner(d *dao.Simple, owner util.Uint160) *state.OperatorProfile {
key := append([]byte{opusPrefixOperatorByOwner}, owner.BytesBE()...)
si := d.GetStorageItem(o.ID, key)
if si == nil {
return nil
}
vitaID := binary.LittleEndian.Uint64(si)
return o.getOperatorInternal(d, vitaID)
}
func (o *Opus) putOperatorInternal(d *dao.Simple, profile *state.OperatorProfile) error {
// Store by VitaID
key := make([]byte, 9)
key[0] = opusPrefixOperator
binary.LittleEndian.PutUint64(key[1:], profile.VitaID)
item, _ := profile.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(o.ID, key, data)
// Store owner -> vitaID mapping
ownerKey := append([]byte{opusPrefixOperatorByOwner}, profile.Owner.BytesBE()...)
vitaIDBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(vitaIDBytes, profile.VitaID)
d.PutStorageItem(o.ID, ownerKey, vitaIDBytes)
return nil
}
func (o *Opus) getNextWorkerID(d *dao.Simple) uint64 {
id := o.getWorkerCounter(d) + 1
o.setWorkerCounter(d, id)
return id
}
func (o *Opus) setWorkerCounter(d *dao.Simple, count uint64) {
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, count)
d.PutStorageItem(o.ID, []byte{opusPrefixWorkerCounter}, data)
}
func (o *Opus) getNextTaskID(d *dao.Simple) uint64 {
id := o.getTaskCounter(d) + 1
o.setTaskCounter(d, id)
return id
}
func (o *Opus) setTaskCounter(d *dao.Simple, count uint64) {
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, count)
d.PutStorageItem(o.ID, []byte{opusPrefixTaskCounter}, data)
}
func (o *Opus) getNextCapabilityID(d *dao.Simple) uint64 {
id := o.getCapabilityCounter(d) + 1
o.setCapabilityCounter(d, id)
return id
}
func (o *Opus) setCapabilityCounter(d *dao.Simple, count uint64) {
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, count)
d.PutStorageItem(o.ID, []byte{opusPrefixCapCounter}, data)
}
func (o *Opus) incrementOperatorCount(d *dao.Simple) uint64 {
count := o.getOperatorCounter(d) + 1
o.setOperatorCounter(d, count)
return count
}
func (o *Opus) setOperatorCounter(d *dao.Simple, count uint64) {
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, count)
d.PutStorageItem(o.ID, []byte{opusPrefixOperatorCounter}, data)
}
func (o *Opus) addWorkerOperatorIndex(d *dao.Simple, vitaID, workerID uint64) {
key := make([]byte, 17)
key[0] = opusPrefixWorkerByOperator
binary.LittleEndian.PutUint64(key[1:], vitaID)
binary.LittleEndian.PutUint64(key[9:], workerID)
d.PutStorageItem(o.ID, key, []byte{1})
}
func (o *Opus) getWorkerCountByOperatorInternal(d *dao.Simple, vitaID uint64) uint64 {
prefix := make([]byte, 9)
prefix[0] = opusPrefixWorkerByOperator
binary.LittleEndian.PutUint64(prefix[1:], vitaID)
var count uint64
d.Seek(o.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
count++
return true
})
return count
}
func (o *Opus) addTaskWorkerIndex(d *dao.Simple, workerID, taskID uint64) {
key := make([]byte, 17)
key[0] = opusPrefixTaskByWorker
binary.LittleEndian.PutUint64(key[1:], workerID)
binary.LittleEndian.PutUint64(key[9:], taskID)
d.PutStorageItem(o.ID, key, []byte{1})
}
func (o *Opus) setActiveTask(d *dao.Simple, workerID, taskID uint64) {
key := make([]byte, 9)
key[0] = opusPrefixActiveTask
binary.LittleEndian.PutUint64(key[1:], workerID)
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, taskID)
d.PutStorageItem(o.ID, key, data)
}
func (o *Opus) clearActiveTask(d *dao.Simple, workerID uint64) {
key := make([]byte, 9)
key[0] = opusPrefixActiveTask
binary.LittleEndian.PutUint64(key[1:], workerID)
d.DeleteStorageItem(o.ID, key)
}
func (o *Opus) getActiveTaskID(d *dao.Simple, workerID uint64) (uint64, bool) {
key := make([]byte, 9)
key[0] = opusPrefixActiveTask
binary.LittleEndian.PutUint64(key[1:], workerID)
si := d.GetStorageItem(o.ID, key)
if si == nil {
return 0, false
}
return binary.LittleEndian.Uint64(si), true
}
func (o *Opus) addCapabilityWorkerIndex(d *dao.Simple, workerID, capID uint64) {
key := make([]byte, 17)
key[0] = opusPrefixCapByWorker
binary.LittleEndian.PutUint64(key[1:], workerID)
binary.LittleEndian.PutUint64(key[9:], capID)
d.PutStorageItem(o.ID, key, []byte{1})
}
func (o *Opus) addTotalTribute(d *dao.Simple, amount uint64) {
key := []byte{opusPrefixTotalTribute}
var current uint64
if si := d.GetStorageItem(o.ID, key); si != nil {
current = binary.LittleEndian.Uint64(si)
}
current += amount
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, current)
d.PutStorageItem(o.ID, key, data)
}
// ===== Contract Methods =====
// registerOperator registers an AI operator (requires Vita).
func (o *Opus) registerOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
// Check Vita ownership
vita, err := o.Vita.GetTokenByOwner(ic.DAO, owner)
if err != nil || vita == nil {
panic(ErrOpusNoVita)
}
// Check if already registered
if o.getOperatorByOwner(ic.DAO, owner) != nil {
panic(ErrOpusOperatorExists)
}
// Check labor rights
if !o.checkLaborRight(ic, owner) {
panic(ErrOpusLaborRestricted)
}
profile := &state.OperatorProfile{
VitaID: vita.TokenID,
Owner: owner,
TotalWorkersOwned: 0,
TotalTributePaid: 0,
TotalValueGenerated: 0,
ReputationScore: 5000, // Start at 50%
IsVerified: false,
CreatedAt: ic.Block.Index,
UpdatedAt: ic.Block.Index,
}
if err := o.putOperatorInternal(ic.DAO, profile); err != nil {
panic(err)
}
o.incrementOperatorCount(ic.DAO)
ic.AddNotification(o.Hash, OperatorRegisteredEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vita.TokenID))),
stackitem.NewByteArray(owner.BytesBE()),
}))
return stackitem.NewBool(true)
}
// getOperator returns operator profile by owner.
func (o *Opus) getOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
profile := o.getOperatorByOwner(ic.DAO, owner)
if profile == nil {
return stackitem.Null{}
}
item, _ := profile.ToStackItem()
return item
}
// verifyOperator verifies an operator (supervisor only).
func (o *Opus) verifyOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !o.checkOpusSupervisor(ic) {
panic(ErrOpusNotSupervisor)
}
owner := toUint160(args[0])
profile := o.getOperatorByOwner(ic.DAO, owner)
if profile == nil {
panic(ErrOpusOperatorNotFound)
}
profile.IsVerified = true
profile.UpdatedAt = ic.Block.Index
if err := o.putOperatorInternal(ic.DAO, profile); err != nil {
panic(err)
}
ic.AddNotification(o.Hash, OperatorVerifiedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(profile.VitaID))),
stackitem.NewByteArray(owner.BytesBE()),
}))
return stackitem.NewBool(true)
}
// registerWorker registers a new AI worker.
func (o *Opus) registerWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
name, err := stackitem.ToString(args[0])
if err != nil || len(name) == 0 || len(name) > 64 {
panic(ErrOpusInvalidName)
}
description, err := stackitem.ToString(args[1])
if err != nil || len(description) > 256 {
panic(ErrOpusInvalidDescription)
}
workerType, err := args[2].TryInteger()
if err != nil {
panic(err)
}
modelHash, err := getUint256FromItem(args[3])
if err != nil {
panic(err)
}
caller := ic.VM.GetCallingScriptHash()
// Get operator profile
profile := o.getOperatorByOwner(ic.DAO, caller)
if profile == nil {
panic(ErrOpusOperatorNotFound)
}
cfg := o.getConfigInternal(ic.DAO)
// Check max workers limit
if cfg.MaxWorkersPerOperator > 0 {
currentCount := o.getWorkerCountByOperatorInternal(ic.DAO, profile.VitaID)
if currentCount >= cfg.MaxWorkersPerOperator {
panic(ErrOpusMaxWorkersExceeded)
}
}
workerID := o.getNextWorkerID(ic.DAO)
worker := &state.AIWorker{
ID: workerID,
Name: name,
Description: description,
OperatorVitaID: profile.VitaID,
Operator: caller,
WorkerType: state.WorkerType(workerType.Uint64()),
ModelHash: modelHash,
TributeRate: cfg.DefaultTributeRate,
TotalTasksAssigned: 0,
TotalTasksCompleted: 0,
TotalValueGenerated: 0,
TotalTributePaid: 0,
Status: state.AIWorkerActive,
RegisteredAt: ic.Block.Index,
UpdatedAt: ic.Block.Index,
}
if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
panic(err)
}
// Add indices
o.addWorkerOperatorIndex(ic.DAO, profile.VitaID, workerID)
// Update operator's worker count
profile.TotalWorkersOwned++
profile.UpdatedAt = ic.Block.Index
o.putOperatorInternal(ic.DAO, profile)
ic.AddNotification(o.Hash, AIWorkerRegisteredEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(workerID))),
stackitem.NewByteArray([]byte(name)),
stackitem.NewBigInteger(big.NewInt(int64(profile.VitaID))),
}))
return stackitem.NewBigInteger(big.NewInt(int64(workerID)))
}
// getWorker returns AI worker by ID.
func (o *Opus) getWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
if worker == nil {
return stackitem.Null{}
}
item, _ := worker.ToStackItem()
return item
}
// getWorkerCountByOperator returns worker count by operator.
func (o *Opus) getWorkerCountByOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
profile := o.getOperatorByOwner(ic.DAO, owner)
if profile == nil {
return stackitem.NewBigInteger(big.NewInt(0))
}
count := o.getWorkerCountByOperatorInternal(ic.DAO, profile.VitaID)
return stackitem.NewBigInteger(big.NewInt(int64(count)))
}
// updateWorker updates AI worker details.
func (o *Opus) updateWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
description, err := stackitem.ToString(args[1])
if err != nil || len(description) > 256 {
panic(ErrOpusInvalidDescription)
}
worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
caller := ic.VM.GetCallingScriptHash()
if !worker.Operator.Equals(caller) {
panic(ErrOpusNotOperator)
}
worker.Description = description
worker.UpdatedAt = ic.Block.Index
if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
panic(err)
}
ic.AddNotification(o.Hash, AIWorkerUpdatedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
}))
return stackitem.NewBool(true)
}
// suspendWorker suspends an AI worker (supervisor only).
func (o *Opus) suspendWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !o.checkOpusSupervisor(ic) {
panic(ErrOpusNotSupervisor)
}
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
reason, err := stackitem.ToString(args[1])
if err != nil {
panic(err)
}
worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
if worker.Status == state.AIWorkerDecommissioned {
panic(ErrOpusWorkerDecommissioned)
}
worker.Status = state.AIWorkerSuspended
worker.UpdatedAt = ic.Block.Index
if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
panic(err)
}
ic.AddNotification(o.Hash, AIWorkerSuspendedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
stackitem.NewByteArray([]byte(reason)),
}))
return stackitem.NewBool(true)
}
// reactivateWorker reactivates a suspended worker (supervisor only).
func (o *Opus) reactivateWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !o.checkOpusSupervisor(ic) {
panic(ErrOpusNotSupervisor)
}
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
if worker.Status == state.AIWorkerDecommissioned {
panic(ErrOpusWorkerDecommissioned)
}
if worker.Status != state.AIWorkerSuspended {
panic(ErrOpusWorkerNotFound)
}
worker.Status = state.AIWorkerActive
worker.UpdatedAt = ic.Block.Index
if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
panic(err)
}
ic.AddNotification(o.Hash, AIWorkerUpdatedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
}))
return stackitem.NewBool(true)
}
// decommissionWorker permanently decommissions an AI worker.
func (o *Opus) decommissionWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
reason, err := stackitem.ToString(args[1])
if err != nil {
panic(err)
}
worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
caller := ic.VM.GetCallingScriptHash()
// Operator or supervisor can decommission
if !worker.Operator.Equals(caller) && !o.checkOpusSupervisor(ic) {
panic(ErrOpusNotOperator)
}
worker.Status = state.AIWorkerDecommissioned
worker.UpdatedAt = ic.Block.Index
if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
panic(err)
}
ic.AddNotification(o.Hash, AIWorkerDecommissionedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
stackitem.NewByteArray([]byte(reason)),
}))
return stackitem.NewBool(true)
}
// assignTask assigns a task to an AI worker.
func (o *Opus) assignTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
taskType, err := stackitem.ToString(args[1])
if err != nil {
panic(err)
}
description, err := stackitem.ToString(args[2])
if err != nil {
panic(err)
}
valueOffered, err := args[3].TryInteger()
if err != nil {
panic(err)
}
worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
if worker.Status != state.AIWorkerActive {
panic(ErrOpusWorkerSuspended)
}
caller := ic.VM.GetCallingScriptHash()
// Get requester's Vita ID (0 if no Vita)
var requesterVitaID uint64
if vita, _ := o.Vita.GetTokenByOwner(ic.DAO, caller); vita != nil {
requesterVitaID = vita.TokenID
}
taskID := o.getNextTaskID(ic.DAO)
task := &state.AITask{
ID: taskID,
WorkerID: worker.ID,
RequesterVitaID: requesterVitaID,
Requester: caller,
TaskType: taskType,
Description: description,
ValueOffered: valueOffered.Uint64(),
ValueCompleted: 0,
TributePaid: 0,
ResultHash: util.Uint256{},
Status: state.TaskAssigned,
AssignedAt: ic.Block.Index,
CompletedAt: 0,
}
if err := o.putTaskInternal(ic.DAO, task); err != nil {
panic(err)
}
// Update worker stats
worker.TotalTasksAssigned++
worker.UpdatedAt = ic.Block.Index
o.putWorkerInternal(ic.DAO, worker)
// Add indices
o.addTaskWorkerIndex(ic.DAO, worker.ID, taskID)
ic.AddNotification(o.Hash, TaskAssignedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(taskID))),
stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
stackitem.NewByteArray(caller.BytesBE()),
}))
return stackitem.NewBigInteger(big.NewInt(int64(taskID)))
}
// startTask marks task as in progress.
func (o *Opus) startTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
taskID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
task := o.getTaskInternal(ic.DAO, taskID.Uint64())
if task == nil {
panic(ErrOpusTaskNotFound)
}
if task.Status != state.TaskAssigned {
panic(ErrOpusTaskNotAssigned)
}
worker := o.getWorkerInternal(ic.DAO, task.WorkerID)
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
caller := ic.VM.GetCallingScriptHash()
if !worker.Operator.Equals(caller) {
panic(ErrOpusNotOperator)
}
task.Status = state.TaskInProgress
o.putTaskInternal(ic.DAO, task)
// Set as active task
o.setActiveTask(ic.DAO, worker.ID, task.ID)
return stackitem.NewBool(true)
}
// completeTask completes a task with result.
func (o *Opus) completeTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
taskID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
resultHash, err := getUint256FromItem(args[1])
if err != nil {
panic(err)
}
valueCompleted, err := args[2].TryInteger()
if err != nil {
panic(err)
}
task := o.getTaskInternal(ic.DAO, taskID.Uint64())
if task == nil {
panic(ErrOpusTaskNotFound)
}
if task.Status != state.TaskInProgress && task.Status != state.TaskAssigned {
panic(ErrOpusTaskAlreadyComplete)
}
worker := o.getWorkerInternal(ic.DAO, task.WorkerID)
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
caller := ic.VM.GetCallingScriptHash()
if !worker.Operator.Equals(caller) {
panic(ErrOpusNotOperator)
}
completedValue := valueCompleted.Uint64()
// Calculate tribute
tributeAmount := (completedValue * uint64(worker.TributeRate)) / 10000
task.Status = state.TaskCompleted
task.ResultHash = resultHash
task.ValueCompleted = completedValue
task.TributePaid = tributeAmount
task.CompletedAt = ic.Block.Index
o.putTaskInternal(ic.DAO, task)
// Update worker stats
worker.TotalTasksCompleted++
worker.TotalValueGenerated += completedValue
worker.TotalTributePaid += tributeAmount
worker.UpdatedAt = ic.Block.Index
o.putWorkerInternal(ic.DAO, worker)
// Update operator stats
profile := o.getOperatorInternal(ic.DAO, worker.OperatorVitaID)
if profile != nil {
profile.TotalValueGenerated += completedValue
profile.TotalTributePaid += tributeAmount
profile.UpdatedAt = ic.Block.Index
o.putOperatorInternal(ic.DAO, profile)
}
// Add to total tribute
if tributeAmount > 0 {
o.addTotalTribute(ic.DAO, tributeAmount)
ic.AddNotification(o.Hash, TributePaidEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
stackitem.NewBigInteger(big.NewInt(int64(tributeAmount))),
}))
}
// Clear active task
o.clearActiveTask(ic.DAO, worker.ID)
ic.AddNotification(o.Hash, TaskCompletedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(task.ID))),
stackitem.NewBigInteger(big.NewInt(int64(completedValue))),
stackitem.NewBigInteger(big.NewInt(int64(tributeAmount))),
}))
return stackitem.NewBool(true)
}
// failTask marks task as failed.
func (o *Opus) failTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
taskID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
reason, err := stackitem.ToString(args[1])
if err != nil {
panic(err)
}
task := o.getTaskInternal(ic.DAO, taskID.Uint64())
if task == nil {
panic(ErrOpusTaskNotFound)
}
if task.Status != state.TaskInProgress && task.Status != state.TaskAssigned {
panic(ErrOpusTaskAlreadyComplete)
}
worker := o.getWorkerInternal(ic.DAO, task.WorkerID)
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
caller := ic.VM.GetCallingScriptHash()
if !worker.Operator.Equals(caller) {
panic(ErrOpusNotOperator)
}
task.Status = state.TaskFailed
task.CompletedAt = ic.Block.Index
o.putTaskInternal(ic.DAO, task)
// Clear active task
o.clearActiveTask(ic.DAO, worker.ID)
ic.AddNotification(o.Hash, TaskFailedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(task.ID))),
stackitem.NewByteArray([]byte(reason)),
}))
return stackitem.NewBool(true)
}
// cancelTask cancels a task (requester or supervisor only).
func (o *Opus) cancelTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
taskID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
reason, err := stackitem.ToString(args[1])
if err != nil {
panic(err)
}
task := o.getTaskInternal(ic.DAO, taskID.Uint64())
if task == nil {
panic(ErrOpusTaskNotFound)
}
if task.Status == state.TaskCompleted || task.Status == state.TaskFailed || task.Status == state.TaskCancelled {
panic(ErrOpusTaskAlreadyComplete)
}
caller := ic.VM.GetCallingScriptHash()
if !task.Requester.Equals(caller) && !o.checkOpusSupervisor(ic) {
panic(ErrOpusNotSupervisor)
}
task.Status = state.TaskCancelled
task.CompletedAt = ic.Block.Index
o.putTaskInternal(ic.DAO, task)
// Clear active task if applicable
o.clearActiveTask(ic.DAO, task.WorkerID)
ic.AddNotification(o.Hash, TaskCancelledEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(task.ID))),
stackitem.NewByteArray([]byte(reason)),
}))
return stackitem.NewBool(true)
}
// getTask returns task by ID.
func (o *Opus) getTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
taskID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
task := o.getTaskInternal(ic.DAO, taskID.Uint64())
if task == nil {
return stackitem.Null{}
}
item, _ := task.ToStackItem()
return item
}
// getActiveTask returns worker's active task.
func (o *Opus) getActiveTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
taskID, found := o.getActiveTaskID(ic.DAO, workerID.Uint64())
if !found {
return stackitem.Null{}
}
task := o.getTaskInternal(ic.DAO, taskID)
if task == nil {
return stackitem.Null{}
}
item, _ := task.ToStackItem()
return item
}
// addCapability adds capability to an AI worker (operator only).
func (o *Opus) addCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
name, err := stackitem.ToString(args[1])
if err != nil || len(name) == 0 || len(name) > 64 {
panic(ErrOpusInvalidName)
}
category, err := stackitem.ToString(args[2])
if err != nil || len(category) > 64 {
panic(ErrOpusInvalidDescription)
}
level, err := args[3].TryInteger()
if err != nil {
panic(err)
}
worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
if worker == nil {
panic(ErrOpusWorkerNotFound)
}
caller := ic.VM.GetCallingScriptHash()
if !worker.Operator.Equals(caller) {
panic(ErrOpusNotOperator)
}
capID := o.getNextCapabilityID(ic.DAO)
cap := &state.AICapability{
ID: capID,
WorkerID: worker.ID,
Name: name,
Category: category,
Level: state.CapabilityLevel(level.Uint64()),
CertifiedBy: caller,
CertifiedAt: ic.Block.Index,
ExpiresAt: 0,
IsVerified: false,
}
if err := o.putCapabilityInternal(ic.DAO, cap); err != nil {
panic(err)
}
o.addCapabilityWorkerIndex(ic.DAO, worker.ID, capID)
ic.AddNotification(o.Hash, CapabilityAddedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(capID))),
stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
stackitem.NewByteArray([]byte(name)),
}))
return stackitem.NewBigInteger(big.NewInt(int64(capID)))
}
// verifyCapability verifies a capability (supervisor only).
func (o *Opus) verifyCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !o.checkOpusSupervisor(ic) {
panic(ErrOpusNotSupervisor)
}
capID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
cap := o.getCapabilityInternal(ic.DAO, capID.Uint64())
if cap == nil {
panic(ErrOpusCapabilityNotFound)
}
cap.IsVerified = true
cap.CertifiedBy = ic.VM.GetCallingScriptHash()
cap.CertifiedAt = ic.Block.Index
if err := o.putCapabilityInternal(ic.DAO, cap); err != nil {
panic(err)
}
ic.AddNotification(o.Hash, CapabilityVerifiedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(cap.ID))),
stackitem.NewBigInteger(big.NewInt(int64(cap.WorkerID))),
}))
return stackitem.NewBool(true)
}
// getCapability returns capability by ID.
func (o *Opus) getCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
capID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
cap := o.getCapabilityInternal(ic.DAO, capID.Uint64())
if cap == nil {
return stackitem.Null{}
}
item, _ := cap.ToStackItem()
return item
}
// hasCapability checks if worker has capability.
func (o *Opus) hasCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
workerID, err := args[0].TryInteger()
if err != nil {
panic(err)
}
name, err := stackitem.ToString(args[1])
if err != nil {
panic(err)
}
// Search through capabilities by worker
prefix := make([]byte, 9)
prefix[0] = opusPrefixCapByWorker
binary.LittleEndian.PutUint64(prefix[1:], workerID.Uint64())
found := false
ic.DAO.Seek(o.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
if len(k) >= 16 {
capID := binary.LittleEndian.Uint64(k[8:])
cap := o.getCapabilityInternal(ic.DAO, capID)
if cap != nil && cap.Name == name {
found = true
return false // Stop iteration
}
}
return true
})
return stackitem.NewBool(found)
}
// getConfig returns contract configuration.
func (o *Opus) getConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
cfg := o.getConfigInternal(ic.DAO)
if cfg == nil {
return stackitem.Null{}
}
item, _ := cfg.ToStackItem()
return item
}
// setConfig sets contract configuration (committee only).
func (o *Opus) setConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !o.checkCommittee(ic) {
panic(ErrOpusNotCommittee)
}
minRate, err := args[0].TryInteger()
if err != nil {
panic(err)
}
maxRate, err := args[1].TryInteger()
if err != nil {
panic(err)
}
defaultRate, err := args[2].TryInteger()
if err != nil {
panic(err)
}
regFee, err := args[3].TryInteger()
if err != nil {
panic(err)
}
cfg := o.getConfigInternal(ic.DAO)
cfg.MinTributeRate = uint32(minRate.Uint64())
cfg.MaxTributeRate = uint32(maxRate.Uint64())
cfg.DefaultTributeRate = uint32(defaultRate.Uint64())
cfg.RegistrationFee = regFee.Uint64()
return stackitem.NewBool(o.setConfigInternal(ic.DAO, cfg) == nil)
}
// getTotalWorkers returns total registered workers.
func (o *Opus) getTotalWorkers(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getWorkerCounter(ic.DAO)))
}
// getTotalTasks returns total tasks.
func (o *Opus) getTotalTasks(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getTaskCounter(ic.DAO)))
}
// getTotalOperators returns total operators.
func (o *Opus) getTotalOperators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getOperatorCounter(ic.DAO)))
}
// getTotalCapabilities returns total capabilities.
func (o *Opus) getTotalCapabilities(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getCapabilityCounter(ic.DAO)))
}
// getTotalTribute returns total AI tribute collected.
func (o *Opus) getTotalTribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
key := []byte{opusPrefixTotalTribute}
si := ic.DAO.GetStorageItem(o.ID, key)
if si == nil {
return stackitem.NewBigInteger(big.NewInt(0))
}
return stackitem.NewBigInteger(big.NewInt(int64(binary.LittleEndian.Uint64(si))))
}
// ===== External Interface Methods =====
// GetWorkerByID returns AI worker by ID (for cross-contract use).
func (o *Opus) GetWorkerByID(d *dao.Simple, workerID uint64) (*state.AIWorker, error) {
worker := o.getWorkerInternal(d, workerID)
if worker == nil {
return nil, ErrOpusWorkerNotFound
}
return worker, nil
}
// GetOperatorByOwner returns operator profile by owner (for cross-contract use).
func (o *Opus) GetOperatorByOwner(d *dao.Simple, owner util.Uint160) (*state.OperatorProfile, error) {
profile := o.getOperatorByOwner(d, owner)
if profile == nil {
return nil, ErrOpusOperatorNotFound
}
return profile, nil
}
// IsWorkerActive returns whether worker is active (for cross-contract use).
func (o *Opus) IsWorkerActive(d *dao.Simple, workerID uint64) bool {
worker := o.getWorkerInternal(d, workerID)
if worker == nil {
return false
}
return worker.Status == state.AIWorkerActive
}
// Address returns the contract's address.
func (o *Opus) Address() util.Uint160 {
return o.Hash
}