417 lines
13 KiB
Go
417 lines
13 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"
|
|
)
|
|
|
|
// EducationAccountStatus represents the status of an education account.
|
|
type EducationAccountStatus uint8
|
|
|
|
const (
|
|
// EducationAccountActive indicates an active account.
|
|
EducationAccountActive EducationAccountStatus = 0
|
|
// EducationAccountSuspended indicates a temporarily suspended account.
|
|
EducationAccountSuspended EducationAccountStatus = 1
|
|
// EducationAccountClosed indicates a permanently closed account.
|
|
EducationAccountClosed EducationAccountStatus = 2
|
|
)
|
|
|
|
// CertificationStatus represents the validity status of a certification.
|
|
type CertificationStatus uint8
|
|
|
|
const (
|
|
// CertificationActive indicates a valid active certification.
|
|
CertificationActive CertificationStatus = 0
|
|
// CertificationExpired indicates an expired certification.
|
|
CertificationExpired CertificationStatus = 1
|
|
// CertificationRevoked indicates a revoked certification.
|
|
CertificationRevoked CertificationStatus = 2
|
|
)
|
|
|
|
// EnrollmentStatus represents the status of a program enrollment.
|
|
type EnrollmentStatus uint8
|
|
|
|
const (
|
|
// EnrollmentActive indicates an active enrollment.
|
|
EnrollmentActive EnrollmentStatus = 0
|
|
// EnrollmentCompleted indicates successful completion.
|
|
EnrollmentCompleted EnrollmentStatus = 1
|
|
// EnrollmentWithdrawn indicates voluntary withdrawal.
|
|
EnrollmentWithdrawn EnrollmentStatus = 2
|
|
// EnrollmentTransferred indicates transfer to another institution.
|
|
EnrollmentTransferred EnrollmentStatus = 3
|
|
)
|
|
|
|
// EducationAccount represents a citizen's lifelong learning account.
|
|
type EducationAccount struct {
|
|
VitaID uint64 // Owner's Vita token ID
|
|
Owner util.Uint160 // Owner's address
|
|
TotalCredits uint64 // Lifetime credits received
|
|
UsedCredits uint64 // Credits spent on education
|
|
AvailableCredits uint64 // Current balance
|
|
Status EducationAccountStatus // Account status
|
|
CreatedAt uint32 // Block height when created
|
|
UpdatedAt uint32 // Block height of last modification
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (a *EducationAccount) 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.TotalCredits))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.UsedCredits))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(a.AvailableCredits))),
|
|
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 *EducationAccount) 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))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
totalCredits, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid totalCredits: %w", err)
|
|
}
|
|
a.TotalCredits = totalCredits.Uint64()
|
|
|
|
usedCredits, err := items[3].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid usedCredits: %w", err)
|
|
}
|
|
a.UsedCredits = usedCredits.Uint64()
|
|
|
|
availableCredits, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid availableCredits: %w", err)
|
|
}
|
|
a.AvailableCredits = availableCredits.Uint64()
|
|
|
|
status, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid status: %w", err)
|
|
}
|
|
a.Status = EducationAccountStatus(status.Uint64())
|
|
|
|
createdAt, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid createdAt: %w", err)
|
|
}
|
|
a.CreatedAt = uint32(createdAt.Uint64())
|
|
|
|
updatedAt, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid updatedAt: %w", err)
|
|
}
|
|
a.UpdatedAt = uint32(updatedAt.Uint64())
|
|
|
|
return nil
|
|
}
|
|
|
|
// Certification represents a verified skill or credential.
|
|
type Certification struct {
|
|
ID uint64 // Unique certification ID
|
|
VitaID uint64 // Owner's Vita ID
|
|
Owner util.Uint160 // Owner's address
|
|
CertType string // Type of certification
|
|
Name string // Certification name
|
|
Institution util.Uint160 // Issuing institution
|
|
ContentHash util.Uint256 // Off-chain content hash
|
|
IssuedAt uint32 // Block height when issued
|
|
ExpiresAt uint32 // 0 = never expires
|
|
Status CertificationStatus // Certification status
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (c *Certification) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.ID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.VitaID))),
|
|
stackitem.NewByteArray(c.Owner.BytesBE()),
|
|
stackitem.NewByteArray([]byte(c.CertType)),
|
|
stackitem.NewByteArray([]byte(c.Name)),
|
|
stackitem.NewByteArray(c.Institution.BytesBE()),
|
|
stackitem.NewByteArray(c.ContentHash.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.IssuedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.ExpiresAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.Status))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (c *Certification) 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))
|
|
}
|
|
|
|
id, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid id: %w", err)
|
|
}
|
|
c.ID = id.Uint64()
|
|
|
|
vitaID, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid vitaID: %w", err)
|
|
}
|
|
c.VitaID = vitaID.Uint64()
|
|
|
|
owner, err := items[2].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid owner: %w", err)
|
|
}
|
|
c.Owner, err = util.Uint160DecodeBytesBE(owner)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid owner address: %w", err)
|
|
}
|
|
|
|
certType, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid certType: %w", err)
|
|
}
|
|
c.CertType = string(certType)
|
|
|
|
name, err := items[4].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid name: %w", err)
|
|
}
|
|
c.Name = string(name)
|
|
|
|
institution, err := items[5].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid institution: %w", err)
|
|
}
|
|
c.Institution, err = util.Uint160DecodeBytesBE(institution)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid institution address: %w", err)
|
|
}
|
|
|
|
contentHash, err := items[6].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid contentHash: %w", err)
|
|
}
|
|
c.ContentHash, err = util.Uint256DecodeBytesBE(contentHash)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid contentHash value: %w", err)
|
|
}
|
|
|
|
issuedAt, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid issuedAt: %w", err)
|
|
}
|
|
c.IssuedAt = uint32(issuedAt.Uint64())
|
|
|
|
expiresAt, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid expiresAt: %w", err)
|
|
}
|
|
c.ExpiresAt = uint32(expiresAt.Uint64())
|
|
|
|
status, err := items[9].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid status: %w", err)
|
|
}
|
|
c.Status = CertificationStatus(status.Uint64())
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsExpired checks if the certification has expired.
|
|
func (c *Certification) IsExpired(currentBlock uint32) bool {
|
|
return c.ExpiresAt != 0 && c.ExpiresAt <= currentBlock
|
|
}
|
|
|
|
// IsValid checks if the certification is currently valid.
|
|
func (c *Certification) IsValid(currentBlock uint32) bool {
|
|
return c.Status == CertificationActive && !c.IsExpired(currentBlock)
|
|
}
|
|
|
|
// Enrollment represents a program enrollment record.
|
|
type Enrollment struct {
|
|
ID uint64 // Unique enrollment ID
|
|
VitaID uint64 // Student's Vita ID
|
|
Student util.Uint160 // Student's address
|
|
ProgramID string // Program identifier
|
|
Institution util.Uint160 // Educational institution
|
|
CreditsAllocated uint64 // Credits committed to program
|
|
StartedAt uint32 // Block height when started
|
|
CompletedAt uint32 // Block height when completed (0 = ongoing)
|
|
Status EnrollmentStatus // Enrollment status
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (e *Enrollment) 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.Student.BytesBE()),
|
|
stackitem.NewByteArray([]byte(e.ProgramID)),
|
|
stackitem.NewByteArray(e.Institution.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.CreditsAllocated))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.StartedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.CompletedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.Status))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (e *Enrollment) 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)
|
|
}
|
|
e.ID = id.Uint64()
|
|
|
|
vitaID, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid vitaID: %w", err)
|
|
}
|
|
e.VitaID = vitaID.Uint64()
|
|
|
|
student, err := items[2].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid student: %w", err)
|
|
}
|
|
e.Student, err = util.Uint160DecodeBytesBE(student)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid student address: %w", err)
|
|
}
|
|
|
|
programID, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid programID: %w", err)
|
|
}
|
|
e.ProgramID = string(programID)
|
|
|
|
institution, err := items[4].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid institution: %w", err)
|
|
}
|
|
e.Institution, err = util.Uint160DecodeBytesBE(institution)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid institution address: %w", err)
|
|
}
|
|
|
|
creditsAllocated, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid creditsAllocated: %w", err)
|
|
}
|
|
e.CreditsAllocated = creditsAllocated.Uint64()
|
|
|
|
startedAt, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid startedAt: %w", err)
|
|
}
|
|
e.StartedAt = uint32(startedAt.Uint64())
|
|
|
|
completedAt, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid completedAt: %w", err)
|
|
}
|
|
e.CompletedAt = uint32(completedAt.Uint64())
|
|
|
|
status, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid status: %w", err)
|
|
}
|
|
e.Status = EnrollmentStatus(status.Uint64())
|
|
|
|
return nil
|
|
}
|
|
|
|
// ScireConfig represents configurable parameters for the Scire contract.
|
|
type ScireConfig struct {
|
|
AnnualCreditAllocation uint64 // Default credits per year
|
|
MaxCreditsPerProgram uint64 // Maximum credits for single program
|
|
CertificationFee uint64 // VTS fee for issuing certifications (0 = free)
|
|
MinEnrollmentDuration uint32 // Minimum blocks for enrollment
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (c *ScireConfig) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.AnnualCreditAllocation))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MaxCreditsPerProgram))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.CertificationFee))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MinEnrollmentDuration))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (c *ScireConfig) 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))
|
|
}
|
|
|
|
annualCredits, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid annualCreditAllocation: %w", err)
|
|
}
|
|
c.AnnualCreditAllocation = annualCredits.Uint64()
|
|
|
|
maxCredits, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid maxCreditsPerProgram: %w", err)
|
|
}
|
|
c.MaxCreditsPerProgram = maxCredits.Uint64()
|
|
|
|
certFee, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid certificationFee: %w", err)
|
|
}
|
|
c.CertificationFee = certFee.Uint64()
|
|
|
|
minDuration, err := items[3].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minEnrollmentDuration: %w", err)
|
|
}
|
|
c.MinEnrollmentDuration = uint32(minDuration.Uint64())
|
|
|
|
return nil
|
|
}
|