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 Tutus ITutus 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.Tutus.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 }