diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 3c07a3f..05bc027 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -224,6 +224,10 @@ type Blockchain struct { lex native.ILex eligere native.IEligere scire native.IScire + salus native.ISalus + sese native.ISese + tribute native.ITribute + opus native.IOpus extensible atomic.Value @@ -495,6 +499,22 @@ func NewBlockchain(s storage.Store, cfg config.Blockchain, log *zap.Logger, newN if err := validateNative(bc.scire, nativeids.Scire, nativenames.Scire, nativehashes.Scire); err != nil { return nil, err } + bc.salus = bc.contracts.Salus() + if err := validateNative(bc.salus, nativeids.Salus, nativenames.Salus, nativehashes.Salus); err != nil { + return nil, err + } + bc.sese = bc.contracts.Sese() + if err := validateNative(bc.sese, nativeids.Sese, nativenames.Sese, nativehashes.Sese); err != nil { + return nil, err + } + bc.tribute = bc.contracts.Tribute() + if err := validateNative(bc.tribute, nativeids.Tribute, nativenames.Tribute, nativehashes.Tribute); err != nil { + return nil, err + } + bc.opus = bc.contracts.Opus() + if err := validateNative(bc.opus, nativeids.Opus, nativenames.Opus, nativehashes.Opus); err != nil { + return nil, err + } bc.persistCond = sync.NewCond(&bc.lock) bc.gcBlockTimes, _ = lru.New[uint32, uint64](defaultBlockTimesCache) // Never errors for positive size diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 96fd871..9756093 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -238,6 +238,60 @@ type ( // Address returns the contract's script hash. Address() util.Uint160 } + + // ISalus is an interface required from native Salus contract for + // interaction with Blockchain and other native contracts. + // Salus provides universal healthcare infrastructure. + ISalus interface { + interop.Contract + // GetAccountByOwner returns a healthcare account by owner address. + GetAccountByOwner(d *dao.Simple, owner util.Uint160) (*state.HealthcareAccount, error) + // HasValidAuthorization checks if provider has valid authorization for patient. + HasValidAuthorization(d *dao.Simple, patient util.Uint160, provider util.Uint160, blockHeight uint32) bool + // Address returns the contract's script hash. + Address() util.Uint160 + } + + // ISese is an interface required from native Sese contract for + // interaction with Blockchain and other native contracts. + // Sese provides life planning infrastructure. + ISese interface { + interop.Contract + // GetAccountByOwner returns a life plan account by owner address. + GetAccountByOwner(d *dao.Simple, owner util.Uint160) (*state.LifePlanAccount, error) + // HasActiveCareer checks if owner has an active career. + HasActiveCareer(d *dao.Simple, owner util.Uint160) bool + // Address returns the contract's script hash. + Address() util.Uint160 + } + + // ITribute is an interface required from native Tribute contract for + // interaction with Blockchain and other native contracts. + // Tribute provides anti-hoarding economics with velocity tracking. + ITribute interface { + interop.Contract + // GetAccountByOwner returns a velocity account by owner address. + GetAccountByOwner(d *dao.Simple, owner util.Uint160) (*state.VelocityAccount, error) + // GetVelocity returns the current velocity score for an owner. + GetVelocity(d *dao.Simple, owner util.Uint160) uint64 + // IsHoarding returns true if owner is hoarding resources. + IsHoarding(d *dao.Simple, owner util.Uint160) bool + // Address returns the contract's script hash. + Address() util.Uint160 + } + + // IOpus provides AI workforce integration and management. + IOpus interface { + interop.Contract + // GetWorkerByID returns an AI worker by ID. + GetWorkerByID(d *dao.Simple, workerID uint64) (*state.AIWorker, error) + // GetOperatorByOwner returns an operator profile by owner address. + GetOperatorByOwner(d *dao.Simple, owner util.Uint160) (*state.OperatorProfile, error) + // IsWorkerActive returns true if the AI worker is active. + IsWorkerActive(d *dao.Simple, workerID uint64) bool + // Address returns the contract's script hash. + Address() util.Uint160 + } ) // Contracts is a convenient wrapper around an arbitrary set of native contracts @@ -401,6 +455,30 @@ func (cs *Contracts) Scire() IScire { return cs.ByName(nativenames.Scire).(IScire) } +// Salus returns native ISalus contract implementation. It panics if +// there's no contract with proper name in cs. +func (cs *Contracts) Salus() ISalus { + return cs.ByName(nativenames.Salus).(ISalus) +} + +// Sese returns native ISese contract implementation. It panics if +// there's no contract with proper name in cs. +func (cs *Contracts) Sese() ISese { + return cs.ByName(nativenames.Sese).(ISese) +} + +// Tribute returns native ITribute contract implementation. It panics if +// there's no contract with proper name in cs. +func (cs *Contracts) Tribute() ITribute { + return cs.ByName(nativenames.Tribute).(ITribute) +} + +// Opus returns native IOpus contract implementation. It panics if +// there's no contract with proper name in cs. +func (cs *Contracts) Opus() IOpus { + return cs.ByName(nativenames.Opus).(IOpus) +} + // NewDefaultContracts returns a new set of default native contracts. func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { mgmt := NewManagement() @@ -501,6 +579,37 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { scire.RoleRegistry = roleRegistry scire.Lex = lex + // Create Salus (Universal Healthcare) contract + salus := newSalus() + salus.NEO = neo + salus.Vita = vita + salus.RoleRegistry = roleRegistry + salus.Lex = lex + + // Create Sese (Life Planning) contract + sese := newSese() + sese.NEO = neo + sese.Vita = vita + sese.RoleRegistry = roleRegistry + sese.Lex = lex + + // Create Tribute (Anti-Hoarding Economics) contract + tribute := newTribute() + tribute.NEO = neo + tribute.Vita = vita + tribute.VTS = vts + tribute.RoleRegistry = roleRegistry + tribute.Lex = lex + + // Create Opus (AI Workforce Integration) contract + opus := newOpus() + opus.NEO = neo + opus.Vita = vita + opus.VTS = vts + opus.RoleRegistry = roleRegistry + opus.Lex = lex + opus.Treasury = treasury + return []interop.Contract{ mgmt, s, @@ -520,5 +629,9 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { lex, eligere, scire, + salus, + sese, + tribute, + opus, } } diff --git a/pkg/core/native/native_test/opus_test.go b/pkg/core/native/native_test/opus_test.go new file mode 100644 index 0000000..3ade31e --- /dev/null +++ b/pkg/core/native/native_test/opus_test.go @@ -0,0 +1,254 @@ +package native_test + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/stretchr/testify/require" + "github.com/tutus-one/tutus-chain/pkg/core/native/nativenames" + "github.com/tutus-one/tutus-chain/pkg/neotest" + "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" +) + +func newOpusClient(t *testing.T) *neotest.ContractInvoker { + return newNativeClient(t, nativenames.Opus) +} + +// TestOpus_GetConfig tests the getConfig method. +func TestOpus_GetConfig(t *testing.T) { + c := newOpusClient(t) + + // Get default config + c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + arr, ok := stack[0].Value().([]stackitem.Item) + require.True(t, ok, "expected array result") + require.GreaterOrEqual(t, len(arr), 7) // OpusConfig has 7 fields + }, "getConfig") +} + +// TestOpus_GetTotalOperators tests the getTotalOperators method. +func TestOpus_GetTotalOperators(t *testing.T) { + c := newOpusClient(t) + + // Initially should be 0 + c.Invoke(t, 0, "getTotalOperators") +} + +// TestOpus_GetTotalWorkers tests the getTotalWorkers method. +func TestOpus_GetTotalWorkers(t *testing.T) { + c := newOpusClient(t) + + // Initially should be 0 + c.Invoke(t, 0, "getTotalWorkers") +} + +// TestOpus_GetTotalTasks tests the getTotalTasks method. +func TestOpus_GetTotalTasks(t *testing.T) { + c := newOpusClient(t) + + // Initially should be 0 + c.Invoke(t, 0, "getTotalTasks") +} + +// TestOpus_GetTotalCapabilities tests the getTotalCapabilities method. +func TestOpus_GetTotalCapabilities(t *testing.T) { + c := newOpusClient(t) + + // Initially should be 0 + c.Invoke(t, 0, "getTotalCapabilities") +} + +// TestOpus_GetOperator_NonExistent tests getting a non-existent operator. +func TestOpus_GetOperator_NonExistent(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + + // Non-existent operator should return null + c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + require.Nil(t, stack[0].Value(), "expected null for non-existent operator") + }, "getOperator", acc.ScriptHash()) +} + +// TestOpus_GetWorker_NonExistent tests getting a non-existent worker. +func TestOpus_GetWorker_NonExistent(t *testing.T) { + c := newOpusClient(t) + + // Non-existent worker should return null + c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + require.Nil(t, stack[0].Value(), "expected null for non-existent worker") + }, "getWorker", int64(999)) +} + +// TestOpus_GetTask_NonExistent tests getting a non-existent task. +func TestOpus_GetTask_NonExistent(t *testing.T) { + c := newOpusClient(t) + + // Non-existent task should return null + c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + require.Nil(t, stack[0].Value(), "expected null for non-existent task") + }, "getTask", int64(999)) +} + +// TestOpus_GetCapability_NonExistent tests getting a non-existent capability. +func TestOpus_GetCapability_NonExistent(t *testing.T) { + c := newOpusClient(t) + + // Non-existent capability should return null + c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + require.Nil(t, stack[0].Value(), "expected null for non-existent capability") + }, "getCapability", int64(999)) +} + +// Note: isWorkerActive and isOperatorVerified methods are not exposed as separate methods. +// Worker/operator status can be checked via getWorker/getOperator. + +// TestOpus_GetWorkerCountByOperator_NonExistent tests getting worker count for non-existent operator. +func TestOpus_GetWorkerCountByOperator_NonExistent(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + + // Non-existent operator should return 0 + c.Invoke(t, 0, "getWorkerCountByOperator", acc.ScriptHash()) +} + +// TestOpus_RegisterOperator_NoVita tests that registering operator without Vita fails. +func TestOpus_RegisterOperator_NoVita(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + invoker := c.WithSigners(acc) + + // Should fail - no Vita registered + invoker.InvokeFail(t, "operator must have an active Vita", "registerOperator", + acc.ScriptHash()) +} + +// TestOpus_VerifyOperator_NotSupervisor tests that non-supervisor cannot verify operator. +func TestOpus_VerifyOperator_NotSupervisor(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + invoker := c.WithSigners(acc) + + // Should fail - not supervisor + invoker.InvokeFail(t, "caller is not an authorized supervisor", "verifyOperator", acc.ScriptHash()) +} + +// Note: suspendOperator method is not currently implemented. Operators can be managed +// through their linked Vita status. + +// TestOpus_RegisterWorker_NoOperator tests that registering worker without operator fails. +func TestOpus_RegisterWorker_NoOperator(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + invoker := c.WithSigners(acc) + + modelHash := hash.Sha256([]byte("model")).BytesBE() + + // Should fail - no operator registered (registerWorker takes: name, description, workerType, modelHash) + invoker.InvokeFail(t, "operator profile not found", "registerWorker", + "TestWorker", "Test AI Worker", int64(0), modelHash) +} + +// TestOpus_SuspendWorker_NotSupervisor tests that non-supervisor cannot suspend worker. +func TestOpus_SuspendWorker_NotSupervisor(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + invoker := c.WithSigners(acc) + + // Should fail - not supervisor (checked before worker existence) + invoker.InvokeFail(t, "caller is not an authorized supervisor", "suspendWorker", int64(999), "suspension reason") +} + +// TestOpus_AssignTask_WorkerNotFound tests that assigning task to non-existent worker fails. +func TestOpus_AssignTask_WorkerNotFound(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + invoker := c.WithSigners(acc) + + // Should fail - worker not found + invoker.InvokeFail(t, "worker not found", "assignTask", + int64(999), "task-123", "Test Task", int64(1000)) +} + +// TestOpus_CompleteTask_TaskNotFound tests that completing non-existent task fails. +func TestOpus_CompleteTask_TaskNotFound(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + invoker := c.WithSigners(acc) + + resultHash := hash.Sha256([]byte("result")).BytesBE() + + // Should fail - task not found + invoker.InvokeFail(t, "task not found", "completeTask", int64(999), resultHash, int64(500)) +} + +// TestOpus_AddCapability_WorkerNotFound tests that adding capability to non-existent worker fails. +func TestOpus_AddCapability_WorkerNotFound(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + acc := e.NewAccount(t) + invoker := c.WithSigners(acc) + + // Should fail - worker not found (before checking supervisor) + invoker.InvokeFail(t, "AI worker not found", "addCapability", + int64(999), "language_model", int64(3), "Test certification") +} + +// TestOpus_RegisterOperatorWithVita tests operator registration with a valid Vita. +func TestOpus_RegisterOperatorWithVita(t *testing.T) { + c := newOpusClient(t) + e := c.Executor + + // Register Vita first + vitaHash := e.NativeHash(t, nativenames.Vita) + acc := e.NewAccount(t) + vitaInvoker := e.NewInvoker(vitaHash, acc) + + owner := acc.ScriptHash() + personHash := hash.Sha256(owner.BytesBE()).BytesBE() + isEntity := false + recoveryHash := hash.Sha256([]byte("recovery")).BytesBE() + + // Register Vita token + vitaInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + _, ok := stack[0].Value().([]byte) + require.True(t, ok, "expected ByteArray result") + }, "register", owner.BytesBE(), personHash, isEntity, recoveryHash) + + // Now register operator (only takes owner as parameter) + opusInvoker := c.WithSigners(acc) + opusInvoker.Invoke(t, true, "registerOperator", owner.BytesBE()) + + // Verify operator exists + opusInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + arr, ok := stack[0].Value().([]stackitem.Item) + require.True(t, ok, "expected array result for existing operator") + require.GreaterOrEqual(t, len(arr), 9) // OperatorProfile has 9 fields + }, "getOperator", owner.BytesBE()) + + // Verify total operators increased + c.Invoke(t, 1, "getTotalOperators") +} diff --git a/pkg/core/native/nativehashes/hashes.go b/pkg/core/native/nativehashes/hashes.go index ca04716..863f336 100644 --- a/pkg/core/native/nativehashes/hashes.go +++ b/pkg/core/native/nativehashes/hashes.go @@ -45,4 +45,12 @@ var ( Eligere = util.Uint160{0x1, 0x94, 0x73, 0x8e, 0xab, 0x6b, 0xc5, 0xa0, 0xff, 0xab, 0xe0, 0x2a, 0xce, 0xea, 0xd7, 0xb3, 0xa8, 0xe5, 0x7, 0x40} // Scire is a hash of native Scire contract. Scire = util.Uint160{0x9f, 0x7, 0x16, 0xd4, 0xd6, 0xb8, 0xae, 0x2d, 0x58, 0x42, 0x94, 0xf8, 0x92, 0x62, 0x5d, 0x8e, 0x63, 0xa0, 0xde, 0x3} + // Salus is a hash of native Salus contract. + Salus = util.Uint160{0x91, 0x6e, 0x6a, 0xf6, 0x70, 0x7b, 0x91, 0x75, 0x9f, 0xbd, 0xff, 0xdb, 0x25, 0xe6, 0xee, 0xf9, 0x4f, 0x20, 0x88, 0xc8} + // Sese is a hash of native Sese contract. + Sese = util.Uint160{0x16, 0x99, 0x28, 0x39, 0x2, 0xfe, 0x12, 0x60, 0xf3, 0xe, 0x4b, 0x2b, 0xf7, 0x44, 0x19, 0xc4, 0x7d, 0x24, 0xfd, 0x48} + // Tribute is a hash of native Tribute contract. + Tribute = util.Uint160{0x35, 0x9b, 0xbf, 0x51, 0xb5, 0xe3, 0x73, 0x6d, 0x52, 0xad, 0x80, 0xb0, 0x28, 0x4f, 0x22, 0x8f, 0x9, 0xaf, 0x49, 0x1e} + // Opus is a hash of native Opus contract. + Opus = util.Uint160{0xf5, 0x22, 0x73, 0x17, 0x98, 0xab, 0xfc, 0x4d, 0x7c, 0x3a, 0x51, 0x8, 0x47, 0xc7, 0xe5, 0xe4, 0x6, 0x96, 0xb6, 0xfd} ) diff --git a/pkg/core/native/nativeids/ids.go b/pkg/core/native/nativeids/ids.go index 47bfce8..569c561 100644 --- a/pkg/core/native/nativeids/ids.go +++ b/pkg/core/native/nativeids/ids.go @@ -43,4 +43,12 @@ const ( Eligere int32 = -17 // Scire is an ID of native Scire contract. Scire int32 = -18 + // Salus is an ID of native Salus contract. + Salus int32 = -19 + // Sese is an ID of native Sese contract. + Sese int32 = -20 + // Tribute is an ID of native Tribute contract. + Tribute int32 = -21 + // Opus is an ID of native Opus contract. + Opus int32 = -22 ) diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go index bbfbac1..83e7bea 100644 --- a/pkg/core/native/nativenames/names.go +++ b/pkg/core/native/nativenames/names.go @@ -20,6 +20,10 @@ const ( Lex = "Lex" Eligere = "Eligere" Scire = "Scire" + Salus = "Salus" + Sese = "Sese" + Tribute = "Tribute" + Opus = "Opus" ) // All contains the list of all native contract names ordered by the contract ID. @@ -42,6 +46,10 @@ var All = []string{ Lex, Eligere, Scire, + Salus, + Sese, + Tribute, + Opus, } // IsValid checks if the name is a valid native contract's name. @@ -63,5 +71,9 @@ func IsValid(name string) bool { name == Federation || name == Lex || name == Eligere || - name == Scire + name == Scire || + name == Salus || + name == Sese || + name == Tribute || + name == Opus } diff --git a/pkg/core/native/opus.go b/pkg/core/native/opus.go new file mode 100644 index 0000000..dc0aa32 --- /dev/null +++ b/pkg/core/native/opus.go @@ -0,0 +1,1657 @@ +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 +} diff --git a/pkg/core/state/opus.go b/pkg/core/state/opus.go new file mode 100644 index 0000000..2f40cbf --- /dev/null +++ b/pkg/core/state/opus.go @@ -0,0 +1,623 @@ +package state + +import ( + "errors" + "fmt" + "math/big" + + "github.com/tutus-one/tutus-chain/pkg/util" + "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" +) + +// AIWorkerStatus represents the status of an AI worker. +type AIWorkerStatus uint8 + +const ( + // AIWorkerActive indicates an active AI worker. + AIWorkerActive AIWorkerStatus = 0 + // AIWorkerSuspended indicates a suspended AI worker. + AIWorkerSuspended AIWorkerStatus = 1 + // AIWorkerDecommissioned indicates a decommissioned AI worker. + AIWorkerDecommissioned AIWorkerStatus = 2 +) + +// TaskStatus represents the status of an AI task. +type TaskStatus uint8 + +const ( + // TaskAssigned indicates a task has been assigned. + TaskAssigned TaskStatus = 0 + // TaskInProgress indicates a task is being worked on. + TaskInProgress TaskStatus = 1 + // TaskCompleted indicates a task was completed successfully. + TaskCompleted TaskStatus = 2 + // TaskFailed indicates a task failed. + TaskFailed TaskStatus = 3 + // TaskCancelled indicates a task was cancelled. + TaskCancelled TaskStatus = 4 +) + +// CapabilityLevel represents the proficiency level of an AI capability. +type CapabilityLevel uint8 + +const ( + // CapabilityBasic represents basic proficiency. + CapabilityBasic CapabilityLevel = 0 + // CapabilityStandard represents standard proficiency. + CapabilityStandard CapabilityLevel = 1 + // CapabilityAdvanced represents advanced proficiency. + CapabilityAdvanced CapabilityLevel = 2 + // CapabilityExpert represents expert proficiency. + CapabilityExpert CapabilityLevel = 3 +) + +// WorkerType represents the type of AI worker. +type WorkerType uint8 + +const ( + // WorkerTypeGeneral represents a general-purpose AI. + WorkerTypeGeneral WorkerType = 0 + // WorkerTypeSpecialized represents a domain-specialized AI. + WorkerTypeSpecialized WorkerType = 1 + // WorkerTypeAutonomous represents a fully autonomous AI agent. + WorkerTypeAutonomous WorkerType = 2 + // WorkerTypeAssistive represents an AI that assists humans. + WorkerTypeAssistive WorkerType = 3 +) + +// AIWorker represents a registered AI worker in the Opus system. +type AIWorker struct { + ID uint64 // Unique AI worker ID + Name string // Human-readable name + Description string // Description of capabilities + OperatorVitaID uint64 // Vita ID of the human/org operator + Operator util.Uint160 // Operator's address + WorkerType WorkerType // Type of AI worker + ModelHash util.Uint256 // Hash of the AI model/system + TributeRate uint32 // Tribute rate in basis points (e.g., 5000 = 50%) + TotalTasksAssigned uint64 // Total tasks assigned + TotalTasksCompleted uint64 // Total tasks completed + TotalValueGenerated uint64 // Total value generated (in VTS) + TotalTributePaid uint64 // Total tribute paid to society + Status AIWorkerStatus // Worker status + RegisteredAt uint32 // Block height when registered + UpdatedAt uint32 // Block height of last update +} + +// ToStackItem implements stackitem.Convertible interface. +func (w *AIWorker) ToStackItem() (stackitem.Item, error) { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(w.ID))), + stackitem.NewByteArray([]byte(w.Name)), + stackitem.NewByteArray([]byte(w.Description)), + stackitem.NewBigInteger(big.NewInt(int64(w.OperatorVitaID))), + stackitem.NewByteArray(w.Operator.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(w.WorkerType))), + stackitem.NewByteArray(w.ModelHash.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(w.TributeRate))), + stackitem.NewBigInteger(big.NewInt(int64(w.TotalTasksAssigned))), + stackitem.NewBigInteger(big.NewInt(int64(w.TotalTasksCompleted))), + stackitem.NewBigInteger(big.NewInt(int64(w.TotalValueGenerated))), + stackitem.NewBigInteger(big.NewInt(int64(w.TotalTributePaid))), + stackitem.NewBigInteger(big.NewInt(int64(w.Status))), + stackitem.NewBigInteger(big.NewInt(int64(w.RegisteredAt))), + stackitem.NewBigInteger(big.NewInt(int64(w.UpdatedAt))), + }), nil +} + +// FromStackItem implements stackitem.Convertible interface. +func (w *AIWorker) FromStackItem(item stackitem.Item) error { + items, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not a struct") + } + if len(items) != 15 { + return fmt.Errorf("wrong number of elements: expected 15, got %d", len(items)) + } + + id, err := items[0].TryInteger() + if err != nil { + return fmt.Errorf("invalid id: %w", err) + } + w.ID = id.Uint64() + + name, err := items[1].TryBytes() + if err != nil { + return fmt.Errorf("invalid name: %w", err) + } + w.Name = string(name) + + description, err := items[2].TryBytes() + if err != nil { + return fmt.Errorf("invalid description: %w", err) + } + w.Description = string(description) + + operatorVitaID, err := items[3].TryInteger() + if err != nil { + return fmt.Errorf("invalid operatorVitaID: %w", err) + } + w.OperatorVitaID = operatorVitaID.Uint64() + + operator, err := items[4].TryBytes() + if err != nil { + return fmt.Errorf("invalid operator: %w", err) + } + w.Operator, err = util.Uint160DecodeBytesBE(operator) + if err != nil { + return fmt.Errorf("invalid operator address: %w", err) + } + + workerType, err := items[5].TryInteger() + if err != nil { + return fmt.Errorf("invalid workerType: %w", err) + } + w.WorkerType = WorkerType(workerType.Uint64()) + + modelHash, err := items[6].TryBytes() + if err != nil { + return fmt.Errorf("invalid modelHash: %w", err) + } + w.ModelHash, err = util.Uint256DecodeBytesBE(modelHash) + if err != nil { + return fmt.Errorf("invalid modelHash value: %w", err) + } + + tributeRate, err := items[7].TryInteger() + if err != nil { + return fmt.Errorf("invalid tributeRate: %w", err) + } + w.TributeRate = uint32(tributeRate.Uint64()) + + tasksAssigned, err := items[8].TryInteger() + if err != nil { + return fmt.Errorf("invalid totalTasksAssigned: %w", err) + } + w.TotalTasksAssigned = tasksAssigned.Uint64() + + tasksCompleted, err := items[9].TryInteger() + if err != nil { + return fmt.Errorf("invalid totalTasksCompleted: %w", err) + } + w.TotalTasksCompleted = tasksCompleted.Uint64() + + valueGenerated, err := items[10].TryInteger() + if err != nil { + return fmt.Errorf("invalid totalValueGenerated: %w", err) + } + w.TotalValueGenerated = valueGenerated.Uint64() + + tributePaid, err := items[11].TryInteger() + if err != nil { + return fmt.Errorf("invalid totalTributePaid: %w", err) + } + w.TotalTributePaid = tributePaid.Uint64() + + status, err := items[12].TryInteger() + if err != nil { + return fmt.Errorf("invalid status: %w", err) + } + w.Status = AIWorkerStatus(status.Uint64()) + + registeredAt, err := items[13].TryInteger() + if err != nil { + return fmt.Errorf("invalid registeredAt: %w", err) + } + w.RegisteredAt = uint32(registeredAt.Uint64()) + + updatedAt, err := items[14].TryInteger() + if err != nil { + return fmt.Errorf("invalid updatedAt: %w", err) + } + w.UpdatedAt = uint32(updatedAt.Uint64()) + + return nil +} + +// AITask represents a task assigned to an AI worker. +type AITask struct { + ID uint64 // Unique task ID + WorkerID uint64 // AI worker ID + RequesterVitaID uint64 // Vita ID of the requester (0 = system task) + Requester util.Uint160 // Requester's address + TaskType string // Type/category of task + Description string // Task description + ValueOffered uint64 // VTS value offered for task + ValueCompleted uint64 // Actual value upon completion + TributePaid uint64 // Tribute paid from this task + ResultHash util.Uint256 // Hash of task result/output + Status TaskStatus // Task status + AssignedAt uint32 // Block height when assigned + CompletedAt uint32 // Block height when completed (0 = incomplete) +} + +// ToStackItem implements stackitem.Convertible interface. +func (t *AITask) ToStackItem() (stackitem.Item, error) { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(t.ID))), + stackitem.NewBigInteger(big.NewInt(int64(t.WorkerID))), + stackitem.NewBigInteger(big.NewInt(int64(t.RequesterVitaID))), + stackitem.NewByteArray(t.Requester.BytesBE()), + stackitem.NewByteArray([]byte(t.TaskType)), + stackitem.NewByteArray([]byte(t.Description)), + stackitem.NewBigInteger(big.NewInt(int64(t.ValueOffered))), + stackitem.NewBigInteger(big.NewInt(int64(t.ValueCompleted))), + stackitem.NewBigInteger(big.NewInt(int64(t.TributePaid))), + stackitem.NewByteArray(t.ResultHash.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(t.Status))), + stackitem.NewBigInteger(big.NewInt(int64(t.AssignedAt))), + stackitem.NewBigInteger(big.NewInt(int64(t.CompletedAt))), + }), nil +} + +// FromStackItem implements stackitem.Convertible interface. +func (t *AITask) FromStackItem(item stackitem.Item) error { + items, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not a struct") + } + if len(items) != 13 { + return fmt.Errorf("wrong number of elements: expected 13, got %d", len(items)) + } + + id, err := items[0].TryInteger() + if err != nil { + return fmt.Errorf("invalid id: %w", err) + } + t.ID = id.Uint64() + + workerID, err := items[1].TryInteger() + if err != nil { + return fmt.Errorf("invalid workerID: %w", err) + } + t.WorkerID = workerID.Uint64() + + requesterVitaID, err := items[2].TryInteger() + if err != nil { + return fmt.Errorf("invalid requesterVitaID: %w", err) + } + t.RequesterVitaID = requesterVitaID.Uint64() + + requester, err := items[3].TryBytes() + if err != nil { + return fmt.Errorf("invalid requester: %w", err) + } + t.Requester, err = util.Uint160DecodeBytesBE(requester) + if err != nil { + return fmt.Errorf("invalid requester address: %w", err) + } + + taskType, err := items[4].TryBytes() + if err != nil { + return fmt.Errorf("invalid taskType: %w", err) + } + t.TaskType = string(taskType) + + description, err := items[5].TryBytes() + if err != nil { + return fmt.Errorf("invalid description: %w", err) + } + t.Description = string(description) + + valueOffered, err := items[6].TryInteger() + if err != nil { + return fmt.Errorf("invalid valueOffered: %w", err) + } + t.ValueOffered = valueOffered.Uint64() + + valueCompleted, err := items[7].TryInteger() + if err != nil { + return fmt.Errorf("invalid valueCompleted: %w", err) + } + t.ValueCompleted = valueCompleted.Uint64() + + tributePaid, err := items[8].TryInteger() + if err != nil { + return fmt.Errorf("invalid tributePaid: %w", err) + } + t.TributePaid = tributePaid.Uint64() + + resultHash, err := items[9].TryBytes() + if err != nil { + return fmt.Errorf("invalid resultHash: %w", err) + } + t.ResultHash, err = util.Uint256DecodeBytesBE(resultHash) + if err != nil { + return fmt.Errorf("invalid resultHash value: %w", err) + } + + status, err := items[10].TryInteger() + if err != nil { + return fmt.Errorf("invalid status: %w", err) + } + t.Status = TaskStatus(status.Uint64()) + + assignedAt, err := items[11].TryInteger() + if err != nil { + return fmt.Errorf("invalid assignedAt: %w", err) + } + t.AssignedAt = uint32(assignedAt.Uint64()) + + completedAt, err := items[12].TryInteger() + if err != nil { + return fmt.Errorf("invalid completedAt: %w", err) + } + t.CompletedAt = uint32(completedAt.Uint64()) + + return nil +} + +// AICapability represents a certified capability of an AI worker. +type AICapability struct { + ID uint64 // Unique capability ID + WorkerID uint64 // AI worker ID + Name string // Capability name + Category string // Capability category + Level CapabilityLevel // Proficiency level + CertifiedBy util.Uint160 // Certifier's address + CertifiedAt uint32 // Block height when certified + ExpiresAt uint32 // Expiration block (0 = never) + IsVerified bool // Whether capability is verified +} + +// ToStackItem implements stackitem.Convertible interface. +func (c *AICapability) ToStackItem() (stackitem.Item, error) { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(c.ID))), + stackitem.NewBigInteger(big.NewInt(int64(c.WorkerID))), + stackitem.NewByteArray([]byte(c.Name)), + stackitem.NewByteArray([]byte(c.Category)), + stackitem.NewBigInteger(big.NewInt(int64(c.Level))), + stackitem.NewByteArray(c.CertifiedBy.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(c.CertifiedAt))), + stackitem.NewBigInteger(big.NewInt(int64(c.ExpiresAt))), + stackitem.NewBool(c.IsVerified), + }), nil +} + +// FromStackItem implements stackitem.Convertible interface. +func (c *AICapability) FromStackItem(item stackitem.Item) error { + items, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not a struct") + } + if len(items) != 9 { + return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items)) + } + + id, err := items[0].TryInteger() + if err != nil { + return fmt.Errorf("invalid id: %w", err) + } + c.ID = id.Uint64() + + workerID, err := items[1].TryInteger() + if err != nil { + return fmt.Errorf("invalid workerID: %w", err) + } + c.WorkerID = workerID.Uint64() + + name, err := items[2].TryBytes() + if err != nil { + return fmt.Errorf("invalid name: %w", err) + } + c.Name = string(name) + + category, err := items[3].TryBytes() + if err != nil { + return fmt.Errorf("invalid category: %w", err) + } + c.Category = string(category) + + level, err := items[4].TryInteger() + if err != nil { + return fmt.Errorf("invalid level: %w", err) + } + c.Level = CapabilityLevel(level.Uint64()) + + certifiedBy, err := items[5].TryBytes() + if err != nil { + return fmt.Errorf("invalid certifiedBy: %w", err) + } + c.CertifiedBy, err = util.Uint160DecodeBytesBE(certifiedBy) + if err != nil { + return fmt.Errorf("invalid certifiedBy address: %w", err) + } + + certifiedAt, err := items[6].TryInteger() + if err != nil { + return fmt.Errorf("invalid certifiedAt: %w", err) + } + c.CertifiedAt = uint32(certifiedAt.Uint64()) + + expiresAt, err := items[7].TryInteger() + if err != nil { + return fmt.Errorf("invalid expiresAt: %w", err) + } + c.ExpiresAt = uint32(expiresAt.Uint64()) + + isVerified, err := items[8].TryBool() + if err != nil { + return fmt.Errorf("invalid isVerified: %w", err) + } + c.IsVerified = isVerified + + return nil +} + +// OperatorProfile represents an AI operator's profile (human/org that deploys AI). +type OperatorProfile struct { + VitaID uint64 // Operator's Vita ID + Owner util.Uint160 // Operator's address + TotalWorkersOwned uint64 // Number of AI workers owned + TotalTributePaid uint64 // Total tribute paid by all workers + TotalValueGenerated uint64 // Total value generated by all workers + ReputationScore uint32 // Reputation score (0-10000 basis points) + IsVerified bool // Whether operator is verified + CreatedAt uint32 // Block height when created + UpdatedAt uint32 // Block height of last update +} + +// ToStackItem implements stackitem.Convertible interface. +func (p *OperatorProfile) ToStackItem() (stackitem.Item, error) { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(p.VitaID))), + stackitem.NewByteArray(p.Owner.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(p.TotalWorkersOwned))), + stackitem.NewBigInteger(big.NewInt(int64(p.TotalTributePaid))), + stackitem.NewBigInteger(big.NewInt(int64(p.TotalValueGenerated))), + stackitem.NewBigInteger(big.NewInt(int64(p.ReputationScore))), + stackitem.NewBool(p.IsVerified), + stackitem.NewBigInteger(big.NewInt(int64(p.CreatedAt))), + stackitem.NewBigInteger(big.NewInt(int64(p.UpdatedAt))), + }), nil +} + +// FromStackItem implements stackitem.Convertible interface. +func (p *OperatorProfile) FromStackItem(item stackitem.Item) error { + items, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not a struct") + } + if len(items) != 9 { + return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items)) + } + + vitaID, err := items[0].TryInteger() + if err != nil { + return fmt.Errorf("invalid vitaID: %w", err) + } + p.VitaID = vitaID.Uint64() + + owner, err := items[1].TryBytes() + if err != nil { + return fmt.Errorf("invalid owner: %w", err) + } + p.Owner, err = util.Uint160DecodeBytesBE(owner) + if err != nil { + return fmt.Errorf("invalid owner address: %w", err) + } + + totalWorkers, err := items[2].TryInteger() + if err != nil { + return fmt.Errorf("invalid totalWorkersOwned: %w", err) + } + p.TotalWorkersOwned = totalWorkers.Uint64() + + tributePaid, err := items[3].TryInteger() + if err != nil { + return fmt.Errorf("invalid totalTributePaid: %w", err) + } + p.TotalTributePaid = tributePaid.Uint64() + + valueGenerated, err := items[4].TryInteger() + if err != nil { + return fmt.Errorf("invalid totalValueGenerated: %w", err) + } + p.TotalValueGenerated = valueGenerated.Uint64() + + reputationScore, err := items[5].TryInteger() + if err != nil { + return fmt.Errorf("invalid reputationScore: %w", err) + } + p.ReputationScore = uint32(reputationScore.Uint64()) + + isVerified, err := items[6].TryBool() + if err != nil { + return fmt.Errorf("invalid isVerified: %w", err) + } + p.IsVerified = isVerified + + createdAt, err := items[7].TryInteger() + if err != nil { + return fmt.Errorf("invalid createdAt: %w", err) + } + p.CreatedAt = uint32(createdAt.Uint64()) + + updatedAt, err := items[8].TryInteger() + if err != nil { + return fmt.Errorf("invalid updatedAt: %w", err) + } + p.UpdatedAt = uint32(updatedAt.Uint64()) + + return nil +} + +// OpusConfig represents configurable parameters for the Opus contract. +type OpusConfig struct { + MinTributeRate uint32 // Minimum tribute rate (basis points) + MaxTributeRate uint32 // Maximum tribute rate (basis points) + DefaultTributeRate uint32 // Default tribute rate for new workers + RegistrationFee uint64 // VTS fee to register an AI worker + TaskCompletionMinimum uint64 // Minimum value for task completion + VerificationRequired bool // Whether operator verification is required + MaxWorkersPerOperator uint64 // Maximum AI workers per operator (0 = unlimited) +} + +// ToStackItem implements stackitem.Convertible interface. +func (c *OpusConfig) ToStackItem() (stackitem.Item, error) { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(c.MinTributeRate))), + stackitem.NewBigInteger(big.NewInt(int64(c.MaxTributeRate))), + stackitem.NewBigInteger(big.NewInt(int64(c.DefaultTributeRate))), + stackitem.NewBigInteger(big.NewInt(int64(c.RegistrationFee))), + stackitem.NewBigInteger(big.NewInt(int64(c.TaskCompletionMinimum))), + stackitem.NewBool(c.VerificationRequired), + stackitem.NewBigInteger(big.NewInt(int64(c.MaxWorkersPerOperator))), + }), nil +} + +// FromStackItem implements stackitem.Convertible interface. +func (c *OpusConfig) FromStackItem(item stackitem.Item) error { + items, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not a struct") + } + if len(items) != 7 { + return fmt.Errorf("wrong number of elements: expected 7, got %d", len(items)) + } + + minRate, err := items[0].TryInteger() + if err != nil { + return fmt.Errorf("invalid minTributeRate: %w", err) + } + c.MinTributeRate = uint32(minRate.Uint64()) + + maxRate, err := items[1].TryInteger() + if err != nil { + return fmt.Errorf("invalid maxTributeRate: %w", err) + } + c.MaxTributeRate = uint32(maxRate.Uint64()) + + defaultRate, err := items[2].TryInteger() + if err != nil { + return fmt.Errorf("invalid defaultTributeRate: %w", err) + } + c.DefaultTributeRate = uint32(defaultRate.Uint64()) + + regFee, err := items[3].TryInteger() + if err != nil { + return fmt.Errorf("invalid registrationFee: %w", err) + } + c.RegistrationFee = regFee.Uint64() + + taskMin, err := items[4].TryInteger() + if err != nil { + return fmt.Errorf("invalid taskCompletionMinimum: %w", err) + } + c.TaskCompletionMinimum = taskMin.Uint64() + + verificationReq, err := items[5].TryBool() + if err != nil { + return fmt.Errorf("invalid verificationRequired: %w", err) + } + c.VerificationRequired = verificationReq + + maxWorkers, err := items[6].TryInteger() + if err != nil { + return fmt.Errorf("invalid maxWorkersPerOperator: %w", err) + } + c.MaxWorkersPerOperator = maxWorkers.Uint64() + + return nil +}