tutus-chain/pkg/core/state/salus.go

633 lines
20 KiB
Go

package state
import (
"errors"
"fmt"
"math/big"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// HealthcareAccountStatus represents the status of a healthcare account.
type HealthcareAccountStatus uint8
const (
// HealthcareAccountActive indicates an active account.
HealthcareAccountActive HealthcareAccountStatus = 0
// HealthcareAccountSuspended indicates a temporarily suspended account.
HealthcareAccountSuspended HealthcareAccountStatus = 1
// HealthcareAccountClosed indicates a permanently closed account.
HealthcareAccountClosed HealthcareAccountStatus = 2
)
// MedicalRecordType represents the type of medical record.
type MedicalRecordType uint8
const (
// RecordTypeCheckup indicates a routine checkup.
RecordTypeCheckup MedicalRecordType = 0
// RecordTypeTreatment indicates a treatment.
RecordTypeTreatment MedicalRecordType = 1
// RecordTypeEmergency indicates an emergency visit.
RecordTypeEmergency MedicalRecordType = 2
// RecordTypePrescription indicates a prescription.
RecordTypePrescription MedicalRecordType = 3
// RecordTypeLabResult indicates lab results.
RecordTypeLabResult MedicalRecordType = 4
// RecordTypeVaccination indicates a vaccination.
RecordTypeVaccination MedicalRecordType = 5
// RecordTypeMentalHealth indicates mental health services.
RecordTypeMentalHealth MedicalRecordType = 6
// RecordTypePreventive indicates preventive care.
RecordTypePreventive MedicalRecordType = 7
)
// AccessLevel represents the level of access granted to a provider.
type AccessLevel uint8
const (
// AccessLevelNone indicates no access.
AccessLevelNone AccessLevel = 0
// AccessLevelEmergency indicates emergency-only access.
AccessLevelEmergency AccessLevel = 1
// AccessLevelLimited indicates limited access (specific record types).
AccessLevelLimited AccessLevel = 2
// AccessLevelFull indicates full access to all records.
AccessLevelFull AccessLevel = 3
)
// ProviderStatus represents the status of a healthcare provider.
type ProviderStatus uint8
const (
// ProviderStatusActive indicates an active provider.
ProviderStatusActive ProviderStatus = 0
// ProviderStatusSuspended indicates a suspended provider.
ProviderStatusSuspended ProviderStatus = 1
// ProviderStatusRevoked indicates a revoked provider.
ProviderStatusRevoked ProviderStatus = 2
)
// HealthcareAccount represents a citizen's healthcare account.
type HealthcareAccount struct {
VitaID uint64 // Owner's Vita token ID
Owner util.Uint160 // Owner's address
AnnualAllocation uint64 // Annual healthcare credits
CreditsUsed uint64 // Credits used this year
CreditsAvailable uint64 // Available healthcare credits
BiologicalAge uint32 // Biological age (Salus-adjusted)
LastCheckup uint32 // Block height of last checkup
Status HealthcareAccountStatus // Account status
CreatedAt uint32 // Block height when created
UpdatedAt uint32 // Block height of last update
}
// ToStackItem implements stackitem.Convertible interface.
func (a *HealthcareAccount) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(a.VitaID))),
stackitem.NewByteArray(a.Owner.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(a.AnnualAllocation))),
stackitem.NewBigInteger(big.NewInt(int64(a.CreditsUsed))),
stackitem.NewBigInteger(big.NewInt(int64(a.CreditsAvailable))),
stackitem.NewBigInteger(big.NewInt(int64(a.BiologicalAge))),
stackitem.NewBigInteger(big.NewInt(int64(a.LastCheckup))),
stackitem.NewBigInteger(big.NewInt(int64(a.Status))),
stackitem.NewBigInteger(big.NewInt(int64(a.CreatedAt))),
stackitem.NewBigInteger(big.NewInt(int64(a.UpdatedAt))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (a *HealthcareAccount) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 10 {
return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
}
vitaID, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
a.VitaID = vitaID.Uint64()
owner, err := items[1].TryBytes()
if err != nil {
return fmt.Errorf("invalid owner: %w", err)
}
a.Owner, err = util.Uint160DecodeBytesBE(owner)
if err != nil {
return fmt.Errorf("invalid owner address: %w", err)
}
annualAllocation, err := items[2].TryInteger()
if err != nil {
return fmt.Errorf("invalid annualAllocation: %w", err)
}
a.AnnualAllocation = annualAllocation.Uint64()
creditsUsed, err := items[3].TryInteger()
if err != nil {
return fmt.Errorf("invalid creditsUsed: %w", err)
}
a.CreditsUsed = creditsUsed.Uint64()
creditsAvailable, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid creditsAvailable: %w", err)
}
a.CreditsAvailable = creditsAvailable.Uint64()
biologicalAge, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid biologicalAge: %w", err)
}
a.BiologicalAge = uint32(biologicalAge.Uint64())
lastCheckup, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid lastCheckup: %w", err)
}
a.LastCheckup = uint32(lastCheckup.Uint64())
status, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
a.Status = HealthcareAccountStatus(status.Uint64())
createdAt, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid createdAt: %w", err)
}
a.CreatedAt = uint32(createdAt.Uint64())
updatedAt, err := items[9].TryInteger()
if err != nil {
return fmt.Errorf("invalid updatedAt: %w", err)
}
a.UpdatedAt = uint32(updatedAt.Uint64())
return nil
}
// MedicalRecord represents a medical record reference (data stored off-chain).
type MedicalRecord struct {
ID uint64 // Unique record ID
VitaID uint64 // Patient's Vita ID
Patient util.Uint160 // Patient's address
Provider util.Uint160 // Healthcare provider's address
RecordType MedicalRecordType // Type of medical record
ContentHash util.Uint256 // Hash of encrypted off-chain data
CreditsUsed uint64 // Healthcare credits used
CreatedAt uint32 // Block height when created
IsActive bool // Whether record is valid
}
// ToStackItem implements stackitem.Convertible interface.
func (r *MedicalRecord) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(r.ID))),
stackitem.NewBigInteger(big.NewInt(int64(r.VitaID))),
stackitem.NewByteArray(r.Patient.BytesBE()),
stackitem.NewByteArray(r.Provider.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(r.RecordType))),
stackitem.NewByteArray(r.ContentHash.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(r.CreditsUsed))),
stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))),
stackitem.NewBool(r.IsActive),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (r *MedicalRecord) 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)
}
r.ID = id.Uint64()
vitaID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
r.VitaID = vitaID.Uint64()
patient, err := items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid patient: %w", err)
}
r.Patient, err = util.Uint160DecodeBytesBE(patient)
if err != nil {
return fmt.Errorf("invalid patient address: %w", err)
}
provider, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid provider: %w", err)
}
r.Provider, err = util.Uint160DecodeBytesBE(provider)
if err != nil {
return fmt.Errorf("invalid provider address: %w", err)
}
recordType, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid recordType: %w", err)
}
r.RecordType = MedicalRecordType(recordType.Uint64())
contentHash, err := items[5].TryBytes()
if err != nil {
return fmt.Errorf("invalid contentHash: %w", err)
}
r.ContentHash, err = util.Uint256DecodeBytesBE(contentHash)
if err != nil {
return fmt.Errorf("invalid contentHash value: %w", err)
}
creditsUsed, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid creditsUsed: %w", err)
}
r.CreditsUsed = creditsUsed.Uint64()
createdAt, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid createdAt: %w", err)
}
r.CreatedAt = uint32(createdAt.Uint64())
isActive, err := items[8].TryBool()
if err != nil {
return fmt.Errorf("invalid isActive: %w", err)
}
r.IsActive = isActive
return nil
}
// ProviderAuthorization represents a healthcare provider's access authorization.
type ProviderAuthorization struct {
ID uint64 // Authorization ID
VitaID uint64 // Patient's Vita ID
Patient util.Uint160 // Patient's address
Provider util.Uint160 // Healthcare provider's address
AccessLevel AccessLevel // Level of access granted
StartsAt uint32 // Block height when access starts
ExpiresAt uint32 // Block height when access expires (0 = no expiry)
IsActive bool // Whether authorization is currently active
GrantedAt uint32 // Block height when granted
}
// ToStackItem implements stackitem.Convertible interface.
func (p *ProviderAuthorization) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(p.ID))),
stackitem.NewBigInteger(big.NewInt(int64(p.VitaID))),
stackitem.NewByteArray(p.Patient.BytesBE()),
stackitem.NewByteArray(p.Provider.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(p.AccessLevel))),
stackitem.NewBigInteger(big.NewInt(int64(p.StartsAt))),
stackitem.NewBigInteger(big.NewInt(int64(p.ExpiresAt))),
stackitem.NewBool(p.IsActive),
stackitem.NewBigInteger(big.NewInt(int64(p.GrantedAt))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (p *ProviderAuthorization) 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)
}
p.ID = id.Uint64()
vitaID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
p.VitaID = vitaID.Uint64()
patient, err := items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid patient: %w", err)
}
p.Patient, err = util.Uint160DecodeBytesBE(patient)
if err != nil {
return fmt.Errorf("invalid patient address: %w", err)
}
provider, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid provider: %w", err)
}
p.Provider, err = util.Uint160DecodeBytesBE(provider)
if err != nil {
return fmt.Errorf("invalid provider address: %w", err)
}
accessLevel, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid accessLevel: %w", err)
}
p.AccessLevel = AccessLevel(accessLevel.Uint64())
startsAt, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid startsAt: %w", err)
}
p.StartsAt = uint32(startsAt.Uint64())
expiresAt, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid expiresAt: %w", err)
}
p.ExpiresAt = uint32(expiresAt.Uint64())
isActive, err := items[7].TryBool()
if err != nil {
return fmt.Errorf("invalid isActive: %w", err)
}
p.IsActive = isActive
grantedAt, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid grantedAt: %w", err)
}
p.GrantedAt = uint32(grantedAt.Uint64())
return nil
}
// IsExpired checks if the authorization has expired.
func (p *ProviderAuthorization) IsExpired(currentBlock uint32) bool {
return p.ExpiresAt != 0 && p.ExpiresAt <= currentBlock
}
// IsValid checks if the authorization is currently valid.
func (p *ProviderAuthorization) IsValid(currentBlock uint32) bool {
return p.IsActive && currentBlock >= p.StartsAt && !p.IsExpired(currentBlock)
}
// HealthcareProvider represents a registered healthcare provider.
type HealthcareProvider struct {
Address util.Uint160 // Provider's address
Name string // Provider name
ProviderID uint64 // Unique provider ID
Specialty string // Medical specialty
LicenseHash util.Uint256 // Hash of license documentation
Status ProviderStatus // Provider status
RegisteredAt uint32 // Block height when registered
UpdatedAt uint32 // Block height of last update
}
// ToStackItem implements stackitem.Convertible interface.
func (p *HealthcareProvider) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray(p.Address.BytesBE()),
stackitem.NewByteArray([]byte(p.Name)),
stackitem.NewBigInteger(big.NewInt(int64(p.ProviderID))),
stackitem.NewByteArray([]byte(p.Specialty)),
stackitem.NewByteArray(p.LicenseHash.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(p.Status))),
stackitem.NewBigInteger(big.NewInt(int64(p.RegisteredAt))),
stackitem.NewBigInteger(big.NewInt(int64(p.UpdatedAt))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (p *HealthcareProvider) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 8 {
return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items))
}
address, err := items[0].TryBytes()
if err != nil {
return fmt.Errorf("invalid address: %w", err)
}
p.Address, err = util.Uint160DecodeBytesBE(address)
if err != nil {
return fmt.Errorf("invalid provider address: %w", err)
}
name, err := items[1].TryBytes()
if err != nil {
return fmt.Errorf("invalid name: %w", err)
}
p.Name = string(name)
providerID, err := items[2].TryInteger()
if err != nil {
return fmt.Errorf("invalid providerID: %w", err)
}
p.ProviderID = providerID.Uint64()
specialty, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid specialty: %w", err)
}
p.Specialty = string(specialty)
licenseHash, err := items[4].TryBytes()
if err != nil {
return fmt.Errorf("invalid licenseHash: %w", err)
}
p.LicenseHash, err = util.Uint256DecodeBytesBE(licenseHash)
if err != nil {
return fmt.Errorf("invalid licenseHash value: %w", err)
}
status, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
p.Status = ProviderStatus(status.Uint64())
registeredAt, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid registeredAt: %w", err)
}
p.RegisteredAt = uint32(registeredAt.Uint64())
updatedAt, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid updatedAt: %w", err)
}
p.UpdatedAt = uint32(updatedAt.Uint64())
return nil
}
// EmergencyAccess represents an emergency access grant.
type EmergencyAccess struct {
ID uint64 // Emergency access ID
VitaID uint64 // Patient's Vita ID
Patient util.Uint160 // Patient's address
Provider util.Uint160 // Provider who accessed
Reason string // Emergency reason
GrantedAt uint32 // Block height when granted
ExpiresAt uint32 // Block height when expires
WasReviewed bool // Whether access was reviewed
}
// ToStackItem implements stackitem.Convertible interface.
func (e *EmergencyAccess) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(e.ID))),
stackitem.NewBigInteger(big.NewInt(int64(e.VitaID))),
stackitem.NewByteArray(e.Patient.BytesBE()),
stackitem.NewByteArray(e.Provider.BytesBE()),
stackitem.NewByteArray([]byte(e.Reason)),
stackitem.NewBigInteger(big.NewInt(int64(e.GrantedAt))),
stackitem.NewBigInteger(big.NewInt(int64(e.ExpiresAt))),
stackitem.NewBool(e.WasReviewed),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (e *EmergencyAccess) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 8 {
return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items))
}
id, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid id: %w", err)
}
e.ID = id.Uint64()
vitaID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
e.VitaID = vitaID.Uint64()
patient, err := items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid patient: %w", err)
}
e.Patient, err = util.Uint160DecodeBytesBE(patient)
if err != nil {
return fmt.Errorf("invalid patient address: %w", err)
}
provider, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid provider: %w", err)
}
e.Provider, err = util.Uint160DecodeBytesBE(provider)
if err != nil {
return fmt.Errorf("invalid provider address: %w", err)
}
reason, err := items[4].TryBytes()
if err != nil {
return fmt.Errorf("invalid reason: %w", err)
}
e.Reason = string(reason)
grantedAt, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid grantedAt: %w", err)
}
e.GrantedAt = uint32(grantedAt.Uint64())
expiresAt, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid expiresAt: %w", err)
}
e.ExpiresAt = uint32(expiresAt.Uint64())
wasReviewed, err := items[7].TryBool()
if err != nil {
return fmt.Errorf("invalid wasReviewed: %w", err)
}
e.WasReviewed = wasReviewed
return nil
}
// SalusConfig represents configurable parameters for the Salus contract.
type SalusConfig struct {
DefaultAnnualCredits uint64 // Default annual healthcare credits
EmergencyAccessDuration uint32 // Blocks for emergency access (default ~24 hours)
PreventiveCareBonus uint64 // Bonus credits for preventive care
MaxAuthorizationDuration uint32 // Maximum authorization duration in blocks
}
// ToStackItem implements stackitem.Convertible interface.
func (c *SalusConfig) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultAnnualCredits))),
stackitem.NewBigInteger(big.NewInt(int64(c.EmergencyAccessDuration))),
stackitem.NewBigInteger(big.NewInt(int64(c.PreventiveCareBonus))),
stackitem.NewBigInteger(big.NewInt(int64(c.MaxAuthorizationDuration))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (c *SalusConfig) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 4 {
return fmt.Errorf("wrong number of elements: expected 4, got %d", len(items))
}
defaultCredits, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid defaultAnnualCredits: %w", err)
}
c.DefaultAnnualCredits = defaultCredits.Uint64()
emergencyDuration, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid emergencyAccessDuration: %w", err)
}
c.EmergencyAccessDuration = uint32(emergencyDuration.Uint64())
preventiveBonus, err := items[2].TryInteger()
if err != nil {
return fmt.Errorf("invalid preventiveCareBonus: %w", err)
}
c.PreventiveCareBonus = preventiveBonus.Uint64()
maxAuthDuration, err := items[3].TryInteger()
if err != nil {
return fmt.Errorf("invalid maxAuthorizationDuration: %w", err)
}
c.MaxAuthorizationDuration = uint32(maxAuthDuration.Uint64())
return nil
}