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/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" ) // Salus represents the universal healthcare native contract. type Salus struct { interop.ContractMD NEO INEO Vita IVita RoleRegistry IRoleRegistry Lex ILex } // SalusCache represents the cached state for Salus contract. type SalusCache struct { accountCount uint64 recordCount uint64 providerCount uint64 authorizationCount uint64 emergencyCount uint64 } // Storage key prefixes for Salus. const ( salusPrefixAccount byte = 0x01 // vitaID -> HealthcareAccount salusPrefixAccountByOwner byte = 0x02 // owner -> vitaID salusPrefixRecord byte = 0x10 // recordID -> MedicalRecord salusPrefixRecordByPatient byte = 0x11 // vitaID + recordID -> exists salusPrefixRecordByProvider byte = 0x12 // provider + recordID -> exists salusPrefixProvider byte = 0x20 // providerID -> HealthcareProvider salusPrefixProviderByAddress byte = 0x21 // provider address -> providerID salusPrefixAuthorization byte = 0x30 // authID -> ProviderAuthorization salusPrefixAuthByPatient byte = 0x31 // vitaID + authID -> exists salusPrefixAuthByProvider byte = 0x32 // provider + authID -> exists salusPrefixActiveAuth byte = 0x33 // vitaID + provider -> authID salusPrefixEmergencyAccess byte = 0x40 // emergencyID -> EmergencyAccess salusPrefixEmergencyByPatient byte = 0x41 // vitaID + emergencyID -> exists salusPrefixAccountCounter byte = 0xF0 // -> uint64 salusPrefixRecordCounter byte = 0xF1 // -> next record ID salusPrefixProviderCounter byte = 0xF2 // -> next provider ID salusPrefixAuthCounter byte = 0xF3 // -> next authorization ID salusPrefixEmergencyCounter byte = 0xF4 // -> next emergency access ID salusPrefixConfig byte = 0xFF // -> SalusConfig ) // Event names for Salus. const ( HealthcareActivatedEvent = "HealthcareActivated" CreditsAllocatedEventSalus = "CreditsAllocated" MedicalRecordCreatedEvent = "MedicalRecordCreated" ProviderRegisteredEvent = "ProviderRegistered" ProviderSuspendedEvent = "ProviderSuspended" AuthorizationGrantedEvent = "AuthorizationGranted" AuthorizationRevokedEvent = "AuthorizationRevoked" EmergencyAccessGrantedEvent = "EmergencyAccessGranted" EmergencyAccessReviewedEvent = "EmergencyAccessReviewed" ) // Role constants for healthcare providers. const ( RoleHealthcare uint64 = 21 // Can record medical events and access authorized records ) // Various errors for Salus. var ( ErrSalusAccountNotFound = errors.New("healthcare account not found") ErrSalusAccountExists = errors.New("healthcare account already exists") ErrSalusAccountSuspended = errors.New("healthcare account is suspended") ErrSalusAccountClosed = errors.New("healthcare account is closed") ErrSalusNoVita = errors.New("owner must have an active Vita") ErrSalusInsufficientCredits = errors.New("insufficient healthcare credits") ErrSalusInvalidCredits = errors.New("invalid credit amount") ErrSalusRecordNotFound = errors.New("medical record not found") ErrSalusProviderNotFound = errors.New("healthcare provider not found") ErrSalusProviderExists = errors.New("healthcare provider already registered") ErrSalusProviderSuspended = errors.New("healthcare provider is suspended") ErrSalusProviderRevoked = errors.New("healthcare provider is revoked") ErrSalusNotProvider = errors.New("caller is not an authorized healthcare provider") ErrSalusNotCommittee = errors.New("invalid committee signature") ErrSalusInvalidOwner = errors.New("invalid owner address") ErrSalusInvalidProvider = errors.New("invalid provider address") ErrSalusAuthorizationNotFound = errors.New("authorization not found") ErrSalusAuthorizationExpired = errors.New("authorization has expired") ErrSalusAuthorizationExists = errors.New("authorization already exists") ErrSalusNotPatient = errors.New("caller is not the patient") ErrSalusHealthcareRestricted = errors.New("healthcare right is restricted") ErrSalusEmergencyNotFound = errors.New("emergency access not found") ErrSalusInvalidReason = errors.New("invalid reason") ErrSalusInvalidName = errors.New("invalid name") ErrSalusInvalidSpecialty = errors.New("invalid specialty") ErrSalusNoAccess = errors.New("no access to patient records") ErrSalusExceedsMaxDuration = errors.New("exceeds maximum authorization duration") ) var ( _ interop.Contract = (*Salus)(nil) _ dao.NativeContractCache = (*SalusCache)(nil) ) // Copy implements NativeContractCache interface. func (c *SalusCache) Copy() dao.NativeContractCache { return &SalusCache{ accountCount: c.accountCount, recordCount: c.recordCount, providerCount: c.providerCount, authorizationCount: c.authorizationCount, emergencyCount: c.emergencyCount, } } // checkCommittee checks if the caller has committee authority. func (s *Salus) checkCommittee(ic *interop.Context) bool { if s.RoleRegistry != nil { return s.RoleRegistry.CheckCommittee(ic) } return s.NEO.CheckCommittee(ic) } // checkHealthcareProvider checks if the caller has healthcare provider authority. func (s *Salus) checkHealthcareProvider(ic *interop.Context) bool { caller := ic.VM.GetCallingScriptHash() if s.RoleRegistry != nil { if s.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleHealthcare, ic.Block.Index) { return true } } // Committee members can also act as healthcare providers return s.checkCommittee(ic) } // checkHealthcareRight checks if subject has healthcare rights via Lex. func (s *Salus) checkHealthcareRight(ic *interop.Context, subject util.Uint160) bool { if s.Lex == nil { return true // Allow if Lex not available } return s.Lex.HasRightInternal(ic.DAO, subject, state.RightHealthcare, ic.Block.Index) } // newSalus creates a new Salus native contract. func newSalus() *Salus { s := &Salus{ ContractMD: *interop.NewContractMD(nativenames.Salus, nativeids.Salus), } defer s.BuildHFSpecificMD(s.ActiveIn()) // ===== Account Management ===== // activateHealthcare - Activate healthcare account for a Vita holder desc := NewDescriptor("activateHealthcare", smartcontract.BoolType, manifest.NewParameter("owner", smartcontract.Hash160Type)) md := NewMethodAndPrice(s.activateHealthcare, 1<<17, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // getAccount - Get healthcare account by owner desc = NewDescriptor("getAccount", smartcontract.ArrayType, manifest.NewParameter("owner", smartcontract.Hash160Type)) md = NewMethodAndPrice(s.getAccount, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // getAccountByVitaID - Get account by Vita ID desc = NewDescriptor("getAccountByVitaID", smartcontract.ArrayType, manifest.NewParameter("vitaID", smartcontract.IntegerType)) md = NewMethodAndPrice(s.getAccountByVitaID, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // allocateCredits - Allocate healthcare credits (committee only) desc = NewDescriptor("allocateCredits", smartcontract.BoolType, manifest.NewParameter("owner", smartcontract.Hash160Type), manifest.NewParameter("amount", smartcontract.IntegerType), manifest.NewParameter("reason", smartcontract.StringType)) md = NewMethodAndPrice(s.allocateCredits, 1<<16, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // getCredits - Get available credits desc = NewDescriptor("getCredits", smartcontract.IntegerType, manifest.NewParameter("owner", smartcontract.Hash160Type)) md = NewMethodAndPrice(s.getCredits, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // ===== Medical Records ===== // recordMedicalEvent - Record a medical event (provider only) desc = NewDescriptor("recordMedicalEvent", smartcontract.IntegerType, manifest.NewParameter("patient", smartcontract.Hash160Type), manifest.NewParameter("recordType", smartcontract.IntegerType), manifest.NewParameter("contentHash", smartcontract.Hash256Type), manifest.NewParameter("credits", smartcontract.IntegerType)) md = NewMethodAndPrice(s.recordMedicalEvent, 1<<17, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // getMedicalRecord - Get medical record by ID desc = NewDescriptor("getMedicalRecord", smartcontract.ArrayType, manifest.NewParameter("recordID", smartcontract.IntegerType)) md = NewMethodAndPrice(s.getMedicalRecord, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // ===== Provider Management ===== // registerProvider - Register a healthcare provider (committee only) desc = NewDescriptor("registerProvider", smartcontract.IntegerType, manifest.NewParameter("address", smartcontract.Hash160Type), manifest.NewParameter("name", smartcontract.StringType), manifest.NewParameter("specialty", smartcontract.StringType), manifest.NewParameter("licenseHash", smartcontract.Hash256Type)) md = NewMethodAndPrice(s.registerProvider, 1<<17, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // suspendProvider - Suspend a healthcare provider (committee only) desc = NewDescriptor("suspendProvider", smartcontract.BoolType, manifest.NewParameter("providerID", smartcontract.IntegerType), manifest.NewParameter("reason", smartcontract.StringType)) md = NewMethodAndPrice(s.suspendProvider, 1<<16, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // getProvider - Get provider details desc = NewDescriptor("getProvider", smartcontract.ArrayType, manifest.NewParameter("providerID", smartcontract.IntegerType)) md = NewMethodAndPrice(s.getProvider, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // getProviderByAddress - Get provider by address desc = NewDescriptor("getProviderByAddress", smartcontract.ArrayType, manifest.NewParameter("address", smartcontract.Hash160Type)) md = NewMethodAndPrice(s.getProviderByAddress, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // ===== Authorization Management ===== // authorizeAccess - Grant provider access to patient records desc = NewDescriptor("authorizeAccess", smartcontract.IntegerType, manifest.NewParameter("patient", smartcontract.Hash160Type), manifest.NewParameter("provider", smartcontract.Hash160Type), manifest.NewParameter("accessLevel", smartcontract.IntegerType), manifest.NewParameter("duration", smartcontract.IntegerType)) md = NewMethodAndPrice(s.authorizeAccess, 1<<17, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // revokeAccess - Revoke provider access desc = NewDescriptor("revokeAccess", smartcontract.BoolType, manifest.NewParameter("authID", smartcontract.IntegerType)) md = NewMethodAndPrice(s.revokeAccess, 1<<16, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // getAuthorization - Get authorization details desc = NewDescriptor("getAuthorization", smartcontract.ArrayType, manifest.NewParameter("authID", smartcontract.IntegerType)) md = NewMethodAndPrice(s.getAuthorization, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // hasAccess - Check if provider has access to patient desc = NewDescriptor("hasAccess", smartcontract.BoolType, manifest.NewParameter("patient", smartcontract.Hash160Type), manifest.NewParameter("provider", smartcontract.Hash160Type)) md = NewMethodAndPrice(s.hasAccess, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // ===== Emergency Access ===== // emergencyAccess - Request emergency access (provider only) desc = NewDescriptor("emergencyAccess", smartcontract.IntegerType, manifest.NewParameter("patient", smartcontract.Hash160Type), manifest.NewParameter("reason", smartcontract.StringType)) md = NewMethodAndPrice(s.emergencyAccess, 1<<17, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // reviewEmergencyAccess - Review emergency access (committee only) desc = NewDescriptor("reviewEmergencyAccess", smartcontract.BoolType, manifest.NewParameter("emergencyID", smartcontract.IntegerType)) md = NewMethodAndPrice(s.reviewEmergencyAccess, 1<<16, callflag.States|callflag.AllowNotify) s.AddMethod(md, desc) // getEmergencyAccess - Get emergency access details desc = NewDescriptor("getEmergencyAccess", smartcontract.ArrayType, manifest.NewParameter("emergencyID", smartcontract.IntegerType)) md = NewMethodAndPrice(s.getEmergencyAccess, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // ===== Query Methods ===== // getConfig - Get Salus configuration desc = NewDescriptor("getConfig", smartcontract.ArrayType) md = NewMethodAndPrice(s.getConfig, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // getTotalAccounts - Get total healthcare accounts desc = NewDescriptor("getTotalAccounts", smartcontract.IntegerType) md = NewMethodAndPrice(s.getTotalAccounts, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // getTotalRecords - Get total medical records desc = NewDescriptor("getTotalRecords", smartcontract.IntegerType) md = NewMethodAndPrice(s.getTotalRecords, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // getTotalProviders - Get total healthcare providers desc = NewDescriptor("getTotalProviders", smartcontract.IntegerType) md = NewMethodAndPrice(s.getTotalProviders, 1<<15, callflag.ReadStates) s.AddMethod(md, desc) // ===== Events ===== // HealthcareActivated event eDesc := NewEventDescriptor(HealthcareActivatedEvent, manifest.NewParameter("vitaID", smartcontract.IntegerType), manifest.NewParameter("owner", smartcontract.Hash160Type)) s.AddEvent(NewEvent(eDesc)) // CreditsAllocated event eDesc = NewEventDescriptor(CreditsAllocatedEventSalus, manifest.NewParameter("vitaID", smartcontract.IntegerType), manifest.NewParameter("amount", smartcontract.IntegerType), manifest.NewParameter("total", smartcontract.IntegerType)) s.AddEvent(NewEvent(eDesc)) // MedicalRecordCreated event eDesc = NewEventDescriptor(MedicalRecordCreatedEvent, manifest.NewParameter("recordID", smartcontract.IntegerType), manifest.NewParameter("vitaID", smartcontract.IntegerType), manifest.NewParameter("recordType", smartcontract.IntegerType)) s.AddEvent(NewEvent(eDesc)) // ProviderRegistered event eDesc = NewEventDescriptor(ProviderRegisteredEvent, manifest.NewParameter("providerID", smartcontract.IntegerType), manifest.NewParameter("address", smartcontract.Hash160Type), manifest.NewParameter("specialty", smartcontract.StringType)) s.AddEvent(NewEvent(eDesc)) // ProviderSuspended event eDesc = NewEventDescriptor(ProviderSuspendedEvent, manifest.NewParameter("providerID", smartcontract.IntegerType), manifest.NewParameter("reason", smartcontract.StringType)) s.AddEvent(NewEvent(eDesc)) // AuthorizationGranted event eDesc = NewEventDescriptor(AuthorizationGrantedEvent, manifest.NewParameter("authID", smartcontract.IntegerType), manifest.NewParameter("vitaID", smartcontract.IntegerType), manifest.NewParameter("provider", smartcontract.Hash160Type)) s.AddEvent(NewEvent(eDesc)) // AuthorizationRevoked event eDesc = NewEventDescriptor(AuthorizationRevokedEvent, manifest.NewParameter("authID", smartcontract.IntegerType)) s.AddEvent(NewEvent(eDesc)) // EmergencyAccessGranted event eDesc = NewEventDescriptor(EmergencyAccessGrantedEvent, manifest.NewParameter("emergencyID", smartcontract.IntegerType), manifest.NewParameter("vitaID", smartcontract.IntegerType), manifest.NewParameter("provider", smartcontract.Hash160Type)) s.AddEvent(NewEvent(eDesc)) // EmergencyAccessReviewed event eDesc = NewEventDescriptor(EmergencyAccessReviewedEvent, manifest.NewParameter("emergencyID", smartcontract.IntegerType)) s.AddEvent(NewEvent(eDesc)) return s } // Metadata returns contract metadata. func (s *Salus) Metadata() *interop.ContractMD { return &s.ContractMD } // Initialize initializes the Salus contract. func (s *Salus) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { if hf != s.ActiveIn() { return nil } // Initialize counters s.setAccountCounter(ic.DAO, 0) s.setRecordCounter(ic.DAO, 0) s.setProviderCounter(ic.DAO, 0) s.setAuthCounter(ic.DAO, 0) s.setEmergencyCounter(ic.DAO, 0) // Initialize config with defaults cfg := &state.SalusConfig{ DefaultAnnualCredits: 10000, // 10000 healthcare credits per year EmergencyAccessDuration: 86400, // ~24 hours (1-second blocks) PreventiveCareBonus: 500, // Bonus for preventive care visits MaxAuthorizationDuration: 2592000, // ~30 days (1-second blocks) } s.setConfig(ic.DAO, cfg) // Initialize cache cache := &SalusCache{ accountCount: 0, recordCount: 0, providerCount: 0, authorizationCount: 0, emergencyCount: 0, } ic.DAO.SetCache(s.ID, cache) return nil } // InitializeCache initializes the cache from storage. func (s *Salus) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error { cache := &SalusCache{ accountCount: s.getAccountCounter(d), recordCount: s.getRecordCounter(d), providerCount: s.getProviderCounter(d), authorizationCount: s.getAuthCounter(d), emergencyCount: s.getEmergencyCounter(d), } d.SetCache(s.ID, cache) return nil } // OnPersist is called before block is committed. func (s *Salus) OnPersist(ic *interop.Context) error { return nil } // PostPersist is called after block is committed. func (s *Salus) PostPersist(ic *interop.Context) error { return nil } // ActiveIn returns the hardfork at which this contract is activated. func (s *Salus) ActiveIn() *config.Hardfork { return nil // Always active } // ===== Storage Helpers ===== func (s *Salus) makeAccountKey(vitaID uint64) []byte { key := make([]byte, 9) key[0] = salusPrefixAccount binary.BigEndian.PutUint64(key[1:], vitaID) return key } func (s *Salus) makeAccountByOwnerKey(owner util.Uint160) []byte { key := make([]byte, 21) key[0] = salusPrefixAccountByOwner copy(key[1:], owner.BytesBE()) return key } func (s *Salus) makeRecordKey(recordID uint64) []byte { key := make([]byte, 9) key[0] = salusPrefixRecord binary.BigEndian.PutUint64(key[1:], recordID) return key } func (s *Salus) makeRecordByPatientKey(vitaID, recordID uint64) []byte { key := make([]byte, 17) key[0] = salusPrefixRecordByPatient binary.BigEndian.PutUint64(key[1:9], vitaID) binary.BigEndian.PutUint64(key[9:], recordID) return key } func (s *Salus) makeProviderKey(providerID uint64) []byte { key := make([]byte, 9) key[0] = salusPrefixProvider binary.BigEndian.PutUint64(key[1:], providerID) return key } func (s *Salus) makeProviderByAddressKey(address util.Uint160) []byte { key := make([]byte, 21) key[0] = salusPrefixProviderByAddress copy(key[1:], address.BytesBE()) return key } func (s *Salus) makeAuthorizationKey(authID uint64) []byte { key := make([]byte, 9) key[0] = salusPrefixAuthorization binary.BigEndian.PutUint64(key[1:], authID) return key } func (s *Salus) makeActiveAuthKey(vitaID uint64, provider util.Uint160) []byte { key := make([]byte, 29) key[0] = salusPrefixActiveAuth binary.BigEndian.PutUint64(key[1:9], vitaID) copy(key[9:], provider.BytesBE()) return key } func (s *Salus) makeEmergencyKey(emergencyID uint64) []byte { key := make([]byte, 9) key[0] = salusPrefixEmergencyAccess binary.BigEndian.PutUint64(key[1:], emergencyID) return key } // Counter getters/setters func (s *Salus) getAccountCounter(d *dao.Simple) uint64 { si := d.GetStorageItem(s.ID, []byte{salusPrefixAccountCounter}) if si == nil { return 0 } return binary.BigEndian.Uint64(si) } func (s *Salus) setAccountCounter(d *dao.Simple, count uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, count) d.PutStorageItem(s.ID, []byte{salusPrefixAccountCounter}, buf) } func (s *Salus) getRecordCounter(d *dao.Simple) uint64 { si := d.GetStorageItem(s.ID, []byte{salusPrefixRecordCounter}) if si == nil { return 0 } return binary.BigEndian.Uint64(si) } func (s *Salus) setRecordCounter(d *dao.Simple, count uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, count) d.PutStorageItem(s.ID, []byte{salusPrefixRecordCounter}, buf) } func (s *Salus) getProviderCounter(d *dao.Simple) uint64 { si := d.GetStorageItem(s.ID, []byte{salusPrefixProviderCounter}) if si == nil { return 0 } return binary.BigEndian.Uint64(si) } func (s *Salus) setProviderCounter(d *dao.Simple, count uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, count) d.PutStorageItem(s.ID, []byte{salusPrefixProviderCounter}, buf) } func (s *Salus) getAuthCounter(d *dao.Simple) uint64 { si := d.GetStorageItem(s.ID, []byte{salusPrefixAuthCounter}) if si == nil { return 0 } return binary.BigEndian.Uint64(si) } func (s *Salus) setAuthCounter(d *dao.Simple, count uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, count) d.PutStorageItem(s.ID, []byte{salusPrefixAuthCounter}, buf) } func (s *Salus) getEmergencyCounter(d *dao.Simple) uint64 { si := d.GetStorageItem(s.ID, []byte{salusPrefixEmergencyCounter}) if si == nil { return 0 } return binary.BigEndian.Uint64(si) } func (s *Salus) setEmergencyCounter(d *dao.Simple, count uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, count) d.PutStorageItem(s.ID, []byte{salusPrefixEmergencyCounter}, buf) } // Config getter/setter func (s *Salus) getConfigInternal(d *dao.Simple) *state.SalusConfig { si := d.GetStorageItem(s.ID, []byte{salusPrefixConfig}) if si == nil { return &state.SalusConfig{ DefaultAnnualCredits: 10000, EmergencyAccessDuration: 86400, PreventiveCareBonus: 500, MaxAuthorizationDuration: 2592000, } } cfg := new(state.SalusConfig) item, _ := stackitem.Deserialize(si) cfg.FromStackItem(item) return cfg } func (s *Salus) setConfig(d *dao.Simple, cfg *state.SalusConfig) { item, _ := cfg.ToStackItem() data, _ := stackitem.Serialize(item) d.PutStorageItem(s.ID, []byte{salusPrefixConfig}, data) } // Account storage func (s *Salus) getAccountInternal(d *dao.Simple, vitaID uint64) *state.HealthcareAccount { si := d.GetStorageItem(s.ID, s.makeAccountKey(vitaID)) if si == nil { return nil } acc := new(state.HealthcareAccount) item, _ := stackitem.Deserialize(si) acc.FromStackItem(item) return acc } func (s *Salus) putAccount(d *dao.Simple, acc *state.HealthcareAccount) { item, _ := acc.ToStackItem() data, _ := stackitem.Serialize(item) d.PutStorageItem(s.ID, s.makeAccountKey(acc.VitaID), data) } func (s *Salus) getVitaIDByOwner(d *dao.Simple, owner util.Uint160) (uint64, bool) { si := d.GetStorageItem(s.ID, s.makeAccountByOwnerKey(owner)) if si == nil { return 0, false } return binary.BigEndian.Uint64(si), true } func (s *Salus) setOwnerToVitaID(d *dao.Simple, owner util.Uint160, vitaID uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, vitaID) d.PutStorageItem(s.ID, s.makeAccountByOwnerKey(owner), buf) } // Record storage func (s *Salus) getRecordInternal(d *dao.Simple, recordID uint64) *state.MedicalRecord { si := d.GetStorageItem(s.ID, s.makeRecordKey(recordID)) if si == nil { return nil } record := new(state.MedicalRecord) item, _ := stackitem.Deserialize(si) record.FromStackItem(item) return record } func (s *Salus) putRecord(d *dao.Simple, record *state.MedicalRecord) { item, _ := record.ToStackItem() data, _ := stackitem.Serialize(item) d.PutStorageItem(s.ID, s.makeRecordKey(record.ID), data) } func (s *Salus) setRecordByPatient(d *dao.Simple, vitaID, recordID uint64) { d.PutStorageItem(s.ID, s.makeRecordByPatientKey(vitaID, recordID), []byte{1}) } // Provider storage func (s *Salus) getProviderInternal(d *dao.Simple, providerID uint64) *state.HealthcareProvider { si := d.GetStorageItem(s.ID, s.makeProviderKey(providerID)) if si == nil { return nil } provider := new(state.HealthcareProvider) item, _ := stackitem.Deserialize(si) provider.FromStackItem(item) return provider } func (s *Salus) putProvider(d *dao.Simple, provider *state.HealthcareProvider) { item, _ := provider.ToStackItem() data, _ := stackitem.Serialize(item) d.PutStorageItem(s.ID, s.makeProviderKey(provider.ProviderID), data) } func (s *Salus) getProviderIDByAddress(d *dao.Simple, address util.Uint160) (uint64, bool) { si := d.GetStorageItem(s.ID, s.makeProviderByAddressKey(address)) if si == nil { return 0, false } return binary.BigEndian.Uint64(si), true } func (s *Salus) setProviderByAddress(d *dao.Simple, address util.Uint160, providerID uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, providerID) d.PutStorageItem(s.ID, s.makeProviderByAddressKey(address), buf) } // Authorization storage func (s *Salus) getAuthInternal(d *dao.Simple, authID uint64) *state.ProviderAuthorization { si := d.GetStorageItem(s.ID, s.makeAuthorizationKey(authID)) if si == nil { return nil } auth := new(state.ProviderAuthorization) item, _ := stackitem.Deserialize(si) auth.FromStackItem(item) return auth } func (s *Salus) putAuth(d *dao.Simple, auth *state.ProviderAuthorization) { item, _ := auth.ToStackItem() data, _ := stackitem.Serialize(item) d.PutStorageItem(s.ID, s.makeAuthorizationKey(auth.ID), data) } func (s *Salus) getActiveAuthID(d *dao.Simple, vitaID uint64, provider util.Uint160) (uint64, bool) { si := d.GetStorageItem(s.ID, s.makeActiveAuthKey(vitaID, provider)) if si == nil { return 0, false } return binary.BigEndian.Uint64(si), true } func (s *Salus) setActiveAuthID(d *dao.Simple, vitaID uint64, provider util.Uint160, authID uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, authID) d.PutStorageItem(s.ID, s.makeActiveAuthKey(vitaID, provider), buf) } func (s *Salus) clearActiveAuth(d *dao.Simple, vitaID uint64, provider util.Uint160) { d.DeleteStorageItem(s.ID, s.makeActiveAuthKey(vitaID, provider)) } // Emergency access storage func (s *Salus) getEmergencyInternal(d *dao.Simple, emergencyID uint64) *state.EmergencyAccess { si := d.GetStorageItem(s.ID, s.makeEmergencyKey(emergencyID)) if si == nil { return nil } emergency := new(state.EmergencyAccess) item, _ := stackitem.Deserialize(si) emergency.FromStackItem(item) return emergency } func (s *Salus) putEmergency(d *dao.Simple, emergency *state.EmergencyAccess) { item, _ := emergency.ToStackItem() data, _ := stackitem.Serialize(item) d.PutStorageItem(s.ID, s.makeEmergencyKey(emergency.ID), data) } // ===== Contract Methods ===== // activateHealthcare activates healthcare account for a Vita holder. func (s *Salus) activateHealthcare(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) // Check owner has active Vita if s.Vita == nil { panic(ErrSalusNoVita) } vita, err := s.Vita.GetTokenByOwner(ic.DAO, owner) if err != nil || vita == nil { panic(ErrSalusNoVita) } if vita.Status != state.TokenStatusActive { panic(ErrSalusNoVita) } // Check if account already exists existing := s.getAccountInternal(ic.DAO, vita.TokenID) if existing != nil { panic(ErrSalusAccountExists) } // Check healthcare rights if !s.checkHealthcareRight(ic, owner) { // Log but allow (EnforcementLogging) } // Get cache and increment counter cache := ic.DAO.GetRWCache(s.ID).(*SalusCache) cache.accountCount++ s.setAccountCounter(ic.DAO, cache.accountCount) // Get default credits from config cfg := s.getConfigInternal(ic.DAO) // Create account acc := &state.HealthcareAccount{ VitaID: vita.TokenID, Owner: owner, AnnualAllocation: cfg.DefaultAnnualCredits, CreditsUsed: 0, CreditsAvailable: cfg.DefaultAnnualCredits, BiologicalAge: 0, LastCheckup: 0, Status: state.HealthcareAccountActive, CreatedAt: ic.Block.Index, UpdatedAt: ic.Block.Index, } // Store account s.putAccount(ic.DAO, acc) s.setOwnerToVitaID(ic.DAO, owner, vita.TokenID) // Emit event ic.AddNotification(s.Hash, HealthcareActivatedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(vita.TokenID))), stackitem.NewByteArray(owner.BytesBE()), })) return stackitem.NewBool(true) } // getAccount returns healthcare account by owner. func (s *Salus) getAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) vitaID, found := s.getVitaIDByOwner(ic.DAO, owner) if !found { return stackitem.Null{} } acc := s.getAccountInternal(ic.DAO, vitaID) if acc == nil { return stackitem.Null{} } item, _ := acc.ToStackItem() return item } // getAccountByVitaID returns healthcare account by Vita ID. func (s *Salus) getAccountByVitaID(ic *interop.Context, args []stackitem.Item) stackitem.Item { vitaID := toUint64(args[0]) acc := s.getAccountInternal(ic.DAO, vitaID) if acc == nil { return stackitem.Null{} } item, _ := acc.ToStackItem() return item } // allocateCredits allocates healthcare credits to an account (committee only). func (s *Salus) allocateCredits(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) amount := toUint64(args[1]) // reason := toString(args[2]) // for logging // Committee only if !s.checkCommittee(ic) { panic(ErrSalusNotCommittee) } if amount == 0 { panic(ErrSalusInvalidCredits) } // Get or create account vitaID, found := s.getVitaIDByOwner(ic.DAO, owner) if !found { // Auto-create account if Vita exists if s.Vita == nil { panic(ErrSalusNoVita) } vita, err := s.Vita.GetTokenByOwner(ic.DAO, owner) if err != nil || vita == nil || vita.Status != state.TokenStatusActive { panic(ErrSalusNoVita) } vitaID = vita.TokenID cfg := s.getConfigInternal(ic.DAO) // Create account cache := ic.DAO.GetRWCache(s.ID).(*SalusCache) cache.accountCount++ s.setAccountCounter(ic.DAO, cache.accountCount) acc := &state.HealthcareAccount{ VitaID: vitaID, Owner: owner, AnnualAllocation: cfg.DefaultAnnualCredits, CreditsUsed: 0, CreditsAvailable: amount, BiologicalAge: 0, LastCheckup: 0, Status: state.HealthcareAccountActive, CreatedAt: ic.Block.Index, UpdatedAt: ic.Block.Index, } s.putAccount(ic.DAO, acc) s.setOwnerToVitaID(ic.DAO, owner, vitaID) // Emit events ic.AddNotification(s.Hash, HealthcareActivatedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(vitaID))), stackitem.NewByteArray(owner.BytesBE()), })) ic.AddNotification(s.Hash, CreditsAllocatedEventSalus, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(vitaID))), stackitem.NewBigInteger(big.NewInt(int64(amount))), stackitem.NewBigInteger(big.NewInt(int64(amount))), })) return stackitem.NewBool(true) } acc := s.getAccountInternal(ic.DAO, vitaID) if acc == nil { panic(ErrSalusAccountNotFound) } if acc.Status != state.HealthcareAccountActive { panic(ErrSalusAccountSuspended) } // Add credits acc.CreditsAvailable += amount acc.UpdatedAt = ic.Block.Index s.putAccount(ic.DAO, acc) // Emit event ic.AddNotification(s.Hash, CreditsAllocatedEventSalus, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(vitaID))), stackitem.NewBigInteger(big.NewInt(int64(amount))), stackitem.NewBigInteger(big.NewInt(int64(acc.CreditsAvailable))), })) return stackitem.NewBool(true) } // getCredits returns available credits for an owner. func (s *Salus) getCredits(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) vitaID, found := s.getVitaIDByOwner(ic.DAO, owner) if !found { return stackitem.NewBigInteger(big.NewInt(0)) } acc := s.getAccountInternal(ic.DAO, vitaID) if acc == nil { return stackitem.NewBigInteger(big.NewInt(0)) } return stackitem.NewBigInteger(big.NewInt(int64(acc.CreditsAvailable))) } // recordMedicalEvent records a medical event (provider only). func (s *Salus) recordMedicalEvent(ic *interop.Context, args []stackitem.Item) stackitem.Item { patient := toUint160(args[0]) recordType := state.MedicalRecordType(toUint64(args[1])) contentHashBytes := toBytes(args[2]) credits := toUint64(args[3]) // Convert bytes to Uint256 var contentHash util.Uint256 if len(contentHashBytes) == 32 { copy(contentHash[:], contentHashBytes) } // Check provider authority if !s.checkHealthcareProvider(ic) { panic(ErrSalusNotProvider) } // Get provider address provider := ic.VM.GetCallingScriptHash() // Get patient's Vita if s.Vita == nil { panic(ErrSalusNoVita) } vita, err := s.Vita.GetTokenByOwner(ic.DAO, patient) if err != nil || vita == nil || vita.Status != state.TokenStatusActive { panic(ErrSalusNoVita) } // Check provider has access (unless emergency or authorized) vitaID, found := s.getVitaIDByOwner(ic.DAO, patient) if found { authID, hasAuth := s.getActiveAuthID(ic.DAO, vitaID, provider) if hasAuth { auth := s.getAuthInternal(ic.DAO, authID) if auth == nil || !auth.IsValid(ic.Block.Index) { panic(ErrSalusNoAccess) } } // If no auth, still allow (emergency can be logged separately) } // Get or auto-create account if !found { // Auto-create cfg := s.getConfigInternal(ic.DAO) cache := ic.DAO.GetRWCache(s.ID).(*SalusCache) cache.accountCount++ s.setAccountCounter(ic.DAO, cache.accountCount) acc := &state.HealthcareAccount{ VitaID: vita.TokenID, Owner: patient, AnnualAllocation: cfg.DefaultAnnualCredits, CreditsUsed: 0, CreditsAvailable: cfg.DefaultAnnualCredits, BiologicalAge: 0, LastCheckup: 0, Status: state.HealthcareAccountActive, CreatedAt: ic.Block.Index, UpdatedAt: ic.Block.Index, } s.putAccount(ic.DAO, acc) s.setOwnerToVitaID(ic.DAO, patient, vita.TokenID) vitaID = vita.TokenID } acc := s.getAccountInternal(ic.DAO, vitaID) if acc == nil { panic(ErrSalusAccountNotFound) } if acc.Status != state.HealthcareAccountActive { panic(ErrSalusAccountSuspended) } // Check sufficient credits if acc.CreditsAvailable < credits { panic(ErrSalusInsufficientCredits) } // Get next record ID cache := ic.DAO.GetRWCache(s.ID).(*SalusCache) recordID := cache.recordCount cache.recordCount++ s.setRecordCounter(ic.DAO, cache.recordCount) // Deduct credits acc.CreditsUsed += credits acc.CreditsAvailable -= credits acc.UpdatedAt = ic.Block.Index // Update last checkup if appropriate if recordType == state.RecordTypeCheckup || recordType == state.RecordTypePreventive { acc.LastCheckup = ic.Block.Index } s.putAccount(ic.DAO, acc) // Create record record := &state.MedicalRecord{ ID: recordID, VitaID: vitaID, Patient: patient, Provider: provider, RecordType: recordType, ContentHash: contentHash, CreditsUsed: credits, CreatedAt: ic.Block.Index, IsActive: true, } s.putRecord(ic.DAO, record) s.setRecordByPatient(ic.DAO, vitaID, recordID) // Emit event ic.AddNotification(s.Hash, MedicalRecordCreatedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(recordID))), stackitem.NewBigInteger(big.NewInt(int64(vitaID))), stackitem.NewBigInteger(big.NewInt(int64(recordType))), })) return stackitem.NewBigInteger(big.NewInt(int64(recordID))) } // getMedicalRecord returns medical record by ID. func (s *Salus) getMedicalRecord(ic *interop.Context, args []stackitem.Item) stackitem.Item { recordID := toUint64(args[0]) record := s.getRecordInternal(ic.DAO, recordID) if record == nil { return stackitem.Null{} } item, _ := record.ToStackItem() return item } // registerProvider registers a healthcare provider (committee only). func (s *Salus) registerProvider(ic *interop.Context, args []stackitem.Item) stackitem.Item { address := toUint160(args[0]) name := toString(args[1]) specialty := toString(args[2]) licenseHashBytes := toBytes(args[3]) // Convert bytes to Uint256 var licenseHash util.Uint256 if len(licenseHashBytes) == 32 { copy(licenseHash[:], licenseHashBytes) } // Committee only if !s.checkCommittee(ic) { panic(ErrSalusNotCommittee) } // Validate inputs if len(name) == 0 || len(name) > 128 { panic(ErrSalusInvalidName) } if len(specialty) == 0 || len(specialty) > 64 { panic(ErrSalusInvalidSpecialty) } // Check if provider already exists _, exists := s.getProviderIDByAddress(ic.DAO, address) if exists { panic(ErrSalusProviderExists) } // Get next provider ID cache := ic.DAO.GetRWCache(s.ID).(*SalusCache) providerID := cache.providerCount cache.providerCount++ s.setProviderCounter(ic.DAO, cache.providerCount) // Create provider provider := &state.HealthcareProvider{ Address: address, Name: name, ProviderID: providerID, Specialty: specialty, LicenseHash: licenseHash, Status: state.ProviderStatusActive, RegisteredAt: ic.Block.Index, UpdatedAt: ic.Block.Index, } s.putProvider(ic.DAO, provider) s.setProviderByAddress(ic.DAO, address, providerID) // Emit event ic.AddNotification(s.Hash, ProviderRegisteredEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(providerID))), stackitem.NewByteArray(address.BytesBE()), stackitem.NewByteArray([]byte(specialty)), })) return stackitem.NewBigInteger(big.NewInt(int64(providerID))) } // suspendProvider suspends a healthcare provider (committee only). func (s *Salus) suspendProvider(ic *interop.Context, args []stackitem.Item) stackitem.Item { providerID := toUint64(args[0]) reason := toString(args[1]) // Committee only if !s.checkCommittee(ic) { panic(ErrSalusNotCommittee) } provider := s.getProviderInternal(ic.DAO, providerID) if provider == nil { panic(ErrSalusProviderNotFound) } if provider.Status == state.ProviderStatusRevoked { panic(ErrSalusProviderRevoked) } // Suspend provider.Status = state.ProviderStatusSuspended provider.UpdatedAt = ic.Block.Index s.putProvider(ic.DAO, provider) // Emit event ic.AddNotification(s.Hash, ProviderSuspendedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(providerID))), stackitem.NewByteArray([]byte(reason)), })) return stackitem.NewBool(true) } // getProvider returns provider details. func (s *Salus) getProvider(ic *interop.Context, args []stackitem.Item) stackitem.Item { providerID := toUint64(args[0]) provider := s.getProviderInternal(ic.DAO, providerID) if provider == nil { return stackitem.Null{} } item, _ := provider.ToStackItem() return item } // getProviderByAddress returns provider by address. func (s *Salus) getProviderByAddress(ic *interop.Context, args []stackitem.Item) stackitem.Item { address := toUint160(args[0]) providerID, found := s.getProviderIDByAddress(ic.DAO, address) if !found { return stackitem.Null{} } provider := s.getProviderInternal(ic.DAO, providerID) if provider == nil { return stackitem.Null{} } item, _ := provider.ToStackItem() return item } // authorizeAccess grants provider access to patient records. func (s *Salus) authorizeAccess(ic *interop.Context, args []stackitem.Item) stackitem.Item { patient := toUint160(args[0]) provider := toUint160(args[1]) accessLevel := state.AccessLevel(toUint64(args[2])) duration := toUint32(args[3]) // Get patient's Vita and account vitaID, found := s.getVitaIDByOwner(ic.DAO, patient) if !found { panic(ErrSalusAccountNotFound) } acc := s.getAccountInternal(ic.DAO, vitaID) if acc == nil { panic(ErrSalusAccountNotFound) } if acc.Status != state.HealthcareAccountActive { panic(ErrSalusAccountSuspended) } // Check caller is patient (self-authorization) caller := ic.VM.GetCallingScriptHash() if caller != patient && !s.checkCommittee(ic) { panic(ErrSalusNotPatient) } // Check max duration cfg := s.getConfigInternal(ic.DAO) if duration > cfg.MaxAuthorizationDuration { panic(ErrSalusExceedsMaxDuration) } // Check if authorization already exists existingAuthID, exists := s.getActiveAuthID(ic.DAO, vitaID, provider) if exists { existingAuth := s.getAuthInternal(ic.DAO, existingAuthID) if existingAuth != nil && existingAuth.IsValid(ic.Block.Index) { panic(ErrSalusAuthorizationExists) } } // Get next auth ID cache := ic.DAO.GetRWCache(s.ID).(*SalusCache) authID := cache.authorizationCount cache.authorizationCount++ s.setAuthCounter(ic.DAO, cache.authorizationCount) // Calculate expiry expiresAt := uint32(0) if duration > 0 { expiresAt = ic.Block.Index + duration } // Create authorization auth := &state.ProviderAuthorization{ ID: authID, VitaID: vitaID, Patient: patient, Provider: provider, AccessLevel: accessLevel, StartsAt: ic.Block.Index, ExpiresAt: expiresAt, IsActive: true, GrantedAt: ic.Block.Index, } s.putAuth(ic.DAO, auth) s.setActiveAuthID(ic.DAO, vitaID, provider, authID) // Emit event ic.AddNotification(s.Hash, AuthorizationGrantedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(authID))), stackitem.NewBigInteger(big.NewInt(int64(vitaID))), stackitem.NewByteArray(provider.BytesBE()), })) return stackitem.NewBigInteger(big.NewInt(int64(authID))) } // revokeAccess revokes provider access. func (s *Salus) revokeAccess(ic *interop.Context, args []stackitem.Item) stackitem.Item { authID := toUint64(args[0]) auth := s.getAuthInternal(ic.DAO, authID) if auth == nil { panic(ErrSalusAuthorizationNotFound) } // Check caller is patient or committee caller := ic.VM.GetCallingScriptHash() if caller != auth.Patient && !s.checkCommittee(ic) { panic(ErrSalusNotPatient) } // Revoke auth.IsActive = false s.putAuth(ic.DAO, auth) s.clearActiveAuth(ic.DAO, auth.VitaID, auth.Provider) // Emit event ic.AddNotification(s.Hash, AuthorizationRevokedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(authID))), })) return stackitem.NewBool(true) } // getAuthorization returns authorization details. func (s *Salus) getAuthorization(ic *interop.Context, args []stackitem.Item) stackitem.Item { authID := toUint64(args[0]) auth := s.getAuthInternal(ic.DAO, authID) if auth == nil { return stackitem.Null{} } item, _ := auth.ToStackItem() return item } // hasAccess checks if provider has access to patient. func (s *Salus) hasAccess(ic *interop.Context, args []stackitem.Item) stackitem.Item { patient := toUint160(args[0]) provider := toUint160(args[1]) vitaID, found := s.getVitaIDByOwner(ic.DAO, patient) if !found { return stackitem.NewBool(false) } authID, exists := s.getActiveAuthID(ic.DAO, vitaID, provider) if !exists { return stackitem.NewBool(false) } auth := s.getAuthInternal(ic.DAO, authID) if auth == nil { return stackitem.NewBool(false) } return stackitem.NewBool(auth.IsValid(ic.Block.Index)) } // emergencyAccess requests emergency access to patient records. func (s *Salus) emergencyAccess(ic *interop.Context, args []stackitem.Item) stackitem.Item { patient := toUint160(args[0]) reason := toString(args[1]) // Check provider authority if !s.checkHealthcareProvider(ic) { panic(ErrSalusNotProvider) } if len(reason) == 0 || len(reason) > 256 { panic(ErrSalusInvalidReason) } // Get provider address provider := ic.VM.GetCallingScriptHash() // Get patient's Vita if s.Vita == nil { panic(ErrSalusNoVita) } vita, err := s.Vita.GetTokenByOwner(ic.DAO, patient) if err != nil || vita == nil || vita.Status != state.TokenStatusActive { panic(ErrSalusNoVita) } // Get config for emergency duration cfg := s.getConfigInternal(ic.DAO) // Get next emergency ID cache := ic.DAO.GetRWCache(s.ID).(*SalusCache) emergencyID := cache.emergencyCount cache.emergencyCount++ s.setEmergencyCounter(ic.DAO, cache.emergencyCount) // Create emergency access emergency := &state.EmergencyAccess{ ID: emergencyID, VitaID: vita.TokenID, Patient: patient, Provider: provider, Reason: reason, GrantedAt: ic.Block.Index, ExpiresAt: ic.Block.Index + cfg.EmergencyAccessDuration, WasReviewed: false, } s.putEmergency(ic.DAO, emergency) // Emit event ic.AddNotification(s.Hash, EmergencyAccessGrantedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(emergencyID))), stackitem.NewBigInteger(big.NewInt(int64(vita.TokenID))), stackitem.NewByteArray(provider.BytesBE()), })) return stackitem.NewBigInteger(big.NewInt(int64(emergencyID))) } // reviewEmergencyAccess marks emergency access as reviewed (committee only). func (s *Salus) reviewEmergencyAccess(ic *interop.Context, args []stackitem.Item) stackitem.Item { emergencyID := toUint64(args[0]) // Committee only if !s.checkCommittee(ic) { panic(ErrSalusNotCommittee) } emergency := s.getEmergencyInternal(ic.DAO, emergencyID) if emergency == nil { panic(ErrSalusEmergencyNotFound) } // Mark as reviewed emergency.WasReviewed = true s.putEmergency(ic.DAO, emergency) // Emit event ic.AddNotification(s.Hash, EmergencyAccessReviewedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(emergencyID))), })) return stackitem.NewBool(true) } // getEmergencyAccess returns emergency access details. func (s *Salus) getEmergencyAccess(ic *interop.Context, args []stackitem.Item) stackitem.Item { emergencyID := toUint64(args[0]) emergency := s.getEmergencyInternal(ic.DAO, emergencyID) if emergency == nil { return stackitem.Null{} } item, _ := emergency.ToStackItem() return item } // getConfig returns the Salus configuration. func (s *Salus) getConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item { cfg := s.getConfigInternal(ic.DAO) item, _ := cfg.ToStackItem() return item } // getTotalAccounts returns the total number of healthcare accounts. func (s *Salus) getTotalAccounts(ic *interop.Context, args []stackitem.Item) stackitem.Item { cache := ic.DAO.GetROCache(s.ID).(*SalusCache) return stackitem.NewBigInteger(big.NewInt(int64(cache.accountCount))) } // getTotalRecords returns the total number of medical records. func (s *Salus) getTotalRecords(ic *interop.Context, args []stackitem.Item) stackitem.Item { cache := ic.DAO.GetROCache(s.ID).(*SalusCache) return stackitem.NewBigInteger(big.NewInt(int64(cache.recordCount))) } // getTotalProviders returns the total number of healthcare providers. func (s *Salus) getTotalProviders(ic *interop.Context, args []stackitem.Item) stackitem.Item { cache := ic.DAO.GetROCache(s.ID).(*SalusCache) return stackitem.NewBigInteger(big.NewInt(int64(cache.providerCount))) } // ===== Public Interface Methods for Cross-Contract Access ===== // GetAccountByOwner returns a healthcare account by owner address. func (s *Salus) GetAccountByOwner(d *dao.Simple, owner util.Uint160) (*state.HealthcareAccount, error) { vitaID, found := s.getVitaIDByOwner(d, owner) if !found { return nil, ErrSalusAccountNotFound } acc := s.getAccountInternal(d, vitaID) if acc == nil { return nil, ErrSalusAccountNotFound } return acc, nil } // HasValidAuthorization checks if provider has valid authorization for patient. func (s *Salus) HasValidAuthorization(d *dao.Simple, patient util.Uint160, provider util.Uint160, blockHeight uint32) bool { vitaID, found := s.getVitaIDByOwner(d, patient) if !found { return false } authID, exists := s.getActiveAuthID(d, vitaID, provider) if !exists { return false } auth := s.getAuthInternal(d, authID) if auth == nil { return false } return auth.IsValid(blockHeight) } // Address returns the contract's script hash. func (s *Salus) Address() util.Uint160 { return s.Hash }