Add Sese native contract for life planning

Implement life planning and career evolution infrastructure:

- Life Plan Accounts: One per Vita holder
  - Contribution tracking for sabbatical fund
  - Life phase management (education, career, retirement)
  - Progress metrics and goal tracking

- Career Cycles: Work period management
  - Start/end tracking with employer records
  - Automatic contribution calculations
  - Transition support between careers

- Sabbaticals: Paid life breaks
  - Funded sabbatical periods for all citizens
  - Minimum contribution requirements
  - Re-entry support after breaks
  - Types: Learning, Family, Creative, Health, Community

- Life Milestones: Achievement tracking
  - Automatic and manual milestone recording
  - Types: Birth, Education, Career, Family, Retirement
  - Verification by authorized parties

- Life Goals: Personal objective management
  - Goal setting with progress tracking
  - Integration with other life planning features
  - Achievement recognition

- Cross-contract integration:
  - Vita: Identity and lifecycle management
  - Lex: Labor rights verification
  - RoleRegistry: RoleLifeCoach (ID 22)

Contract ID: -20

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Tutus Development 2025-12-20 08:54:28 +00:00
parent 83c12c5362
commit 9a57598c91
3 changed files with 2827 additions and 0 deletions

View File

@ -0,0 +1,359 @@
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 newSeseClient(t *testing.T) *neotest.ContractInvoker {
return newNativeClient(t, nativenames.Sese)
}
// TestSese_GetConfig tests the getConfig method.
func TestSese_GetConfig(t *testing.T) {
c := newSeseClient(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), 5) // SeseConfig has 5 fields
}, "getConfig")
}
// TestSese_GetTotalAccounts tests the getTotalAccounts method.
func TestSese_GetTotalAccounts(t *testing.T) {
c := newSeseClient(t)
// Initially should be 0
c.Invoke(t, 0, "getTotalAccounts")
}
// TestSese_GetTotalCareers tests the getTotalCareers method.
func TestSese_GetTotalCareers(t *testing.T) {
c := newSeseClient(t)
// Initially should be 0
c.Invoke(t, 0, "getTotalCareers")
}
// TestSese_GetTotalSabbaticals tests the getTotalSabbaticals method.
func TestSese_GetTotalSabbaticals(t *testing.T) {
c := newSeseClient(t)
// Initially should be 0
c.Invoke(t, 0, "getTotalSabbaticals")
}
// TestSese_GetTotalMilestones tests the getTotalMilestones method.
func TestSese_GetTotalMilestones(t *testing.T) {
c := newSeseClient(t)
// Initially should be 0
c.Invoke(t, 0, "getTotalMilestones")
}
// TestSese_GetTotalGoals tests the getTotalGoals method.
func TestSese_GetTotalGoals(t *testing.T) {
c := newSeseClient(t)
// Initially should be 0
c.Invoke(t, 0, "getTotalGoals")
}
// TestSese_GetAccount_NonExistent tests getting a non-existent account.
func TestSese_GetAccount_NonExistent(t *testing.T) {
c := newSeseClient(t)
e := c.Executor
acc := e.NewAccount(t)
// Non-existent account 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 account")
}, "getAccount", acc.ScriptHash())
}
// TestSese_GetBalance_NonExistent tests getting balance for non-existent account.
func TestSese_GetBalance_NonExistent(t *testing.T) {
c := newSeseClient(t)
e := c.Executor
acc := e.NewAccount(t)
// Non-existent account should return 0 balance
c.Invoke(t, 0, "getBalance", acc.ScriptHash())
}
// TestSese_GetSabbaticalCredits_NonExistent tests getting sabbatical credits for non-existent account.
func TestSese_GetSabbaticalCredits_NonExistent(t *testing.T) {
c := newSeseClient(t)
e := c.Executor
acc := e.NewAccount(t)
// Non-existent account should return 0 credits
c.Invoke(t, 0, "getSabbaticalCredits", acc.ScriptHash())
}
// TestSese_ActivateLifePlan_NoVita tests that activating life plan without Vita fails.
func TestSese_ActivateLifePlan_NoVita(t *testing.T) {
c := newSeseClient(t)
e := c.Executor
acc := e.NewAccount(t)
invoker := c.WithSigners(acc)
// Should fail - no Vita registered
invoker.InvokeFail(t, "owner must have an active Vita", "activateLifePlan", acc.ScriptHash())
}
// TestSese_AllocateSabbaticalCredits_NotCommittee tests that non-committee cannot allocate credits.
func TestSese_AllocateSabbaticalCredits_NotCommittee(t *testing.T) {
c := newSeseClient(t)
e := c.Executor
acc := e.NewAccount(t)
invoker := c.WithSigners(acc)
// Should fail - not committee
invoker.InvokeFail(t, "invalid committee signature", "allocateSabbaticalCredits",
acc.ScriptHash(), 100, "test allocation")
}
// TestSese_GetCareer_NonExistent tests getting a non-existent career.
func TestSese_GetCareer_NonExistent(t *testing.T) {
c := newSeseClient(t)
// Non-existent career 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 career")
}, "getCareer", int64(999))
}
// TestSese_GetSabbatical_NonExistent tests getting a non-existent sabbatical.
func TestSese_GetSabbatical_NonExistent(t *testing.T) {
c := newSeseClient(t)
// Non-existent sabbatical 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 sabbatical")
}, "getSabbatical", int64(999))
}
// TestSese_GetMilestone_NonExistent tests getting a non-existent milestone.
func TestSese_GetMilestone_NonExistent(t *testing.T) {
c := newSeseClient(t)
// Non-existent milestone 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 milestone")
}, "getMilestone", int64(999))
}
// TestSese_GetGoal_NonExistent tests getting a non-existent goal.
func TestSese_GetGoal_NonExistent(t *testing.T) {
c := newSeseClient(t)
// Non-existent goal 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 goal")
}, "getGoal", int64(999))
}
// TestSese_GetActiveCareer_NoAccount tests getting active career for non-existent account.
func TestSese_GetActiveCareer_NoAccount(t *testing.T) {
c := newSeseClient(t)
e := c.Executor
acc := e.NewAccount(t)
// No account = 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 career")
}, "getActiveCareer", acc.ScriptHash())
}
// TestSese_GetActiveSabbatical_NoAccount tests getting active sabbatical for non-existent account.
func TestSese_GetActiveSabbatical_NoAccount(t *testing.T) {
c := newSeseClient(t)
e := c.Executor
acc := e.NewAccount(t)
// No account = 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 sabbatical")
}, "getActiveSabbatical", acc.ScriptHash())
}
// TestSese_ActivateLifePlanWithVita tests life plan activation with a valid Vita.
func TestSese_ActivateLifePlanWithVita(t *testing.T) {
c := newSeseClient(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 activate Sese account - need to pass owner as BytesBE for Hash160 type
seseInvoker := c.WithSigners(acc)
seseInvoker.Invoke(t, true, "activateLifePlan", owner.BytesBE())
// Verify account exists - also pass as BytesBE
seseInvoker.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 account")
require.GreaterOrEqual(t, len(arr), 14) // LifePlanAccount has 14 fields
}, "getAccount", owner.BytesBE())
// Verify total accounts increased
c.Invoke(t, 1, "getTotalAccounts")
// Verify default sabbatical credits (5000)
c.Invoke(t, 5000, "getSabbaticalCredits", owner.BytesBE())
}
// TestSese_Contribute tests contribution to life plan.
func TestSese_Contribute(t *testing.T) {
c := newSeseClient(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)
// Activate Sese account
seseInvoker := c.WithSigners(acc)
seseInvoker.Invoke(t, true, "activateLifePlan", owner.BytesBE())
// Contribute 1000
seseInvoker.Invoke(t, true, "contribute", owner.BytesBE(), int64(1000), false)
// Balance should be 1000 + 500 (50% gov match) = 1500
c.Invoke(t, 1500, "getBalance", owner.BytesBE())
}
// TestSese_StartCareer tests starting a career.
func TestSese_StartCareer(t *testing.T) {
c := newSeseClient(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))
}, "register", owner.BytesBE(), personHash, isEntity, recoveryHash)
// Activate Sese account
seseInvoker := c.WithSigners(acc)
seseInvoker.Invoke(t, true, "activateLifePlan", owner.BytesBE())
// Start career
seseInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack))
// Career ID should be returned (0 for first career)
}, "startCareer", owner.BytesBE(), "Software Engineering")
// Total careers should be 1
c.Invoke(t, 1, "getTotalCareers")
// Get active career
seseInvoker.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 career")
require.GreaterOrEqual(t, len(arr), 9) // CareerCycle has 9 fields
}, "getActiveCareer", owner.BytesBE())
}
// TestSese_CreateGoal tests creating a life goal.
func TestSese_CreateGoal(t *testing.T) {
c := newSeseClient(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))
}, "register", owner.BytesBE(), personHash, isEntity, recoveryHash)
// Activate Sese account
seseInvoker := c.WithSigners(acc)
seseInvoker.Invoke(t, true, "activateLifePlan", owner.BytesBE())
// Create goal
seseInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack))
// Goal ID should be returned (0 for first goal)
}, "createGoal", owner.BytesBE(), "Learn Blockchain", "Master blockchain development", int64(1000000))
// Total goals should be 1
c.Invoke(t, 1, "getTotalGoals")
// Get goal
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 for goal")
require.GreaterOrEqual(t, len(arr), 10) // LifeGoal has 10 fields
}, "getGoal", int64(0))
}

1748
pkg/core/native/sese.go Normal file

File diff suppressed because it is too large Load Diff

720
pkg/core/state/sese.go Normal file
View File

@ -0,0 +1,720 @@
package state
import (
"errors"
"fmt"
"math/big"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// LifePlanAccountStatus represents the status of a life planning account.
type LifePlanAccountStatus uint8
const (
// LifePlanAccountActive indicates an active account.
LifePlanAccountActive LifePlanAccountStatus = 0
// LifePlanAccountSuspended indicates a suspended account.
LifePlanAccountSuspended LifePlanAccountStatus = 1
// LifePlanAccountClosed indicates a closed account.
LifePlanAccountClosed LifePlanAccountStatus = 2
)
// CareerCycleStatus represents the status of a career cycle.
type CareerCycleStatus uint8
const (
// CareerCycleActive indicates an active career.
CareerCycleActive CareerCycleStatus = 0
// CareerCycleCompleted indicates a completed career.
CareerCycleCompleted CareerCycleStatus = 1
// CareerCycleTransitioning indicates transitioning to new career.
CareerCycleTransitioning CareerCycleStatus = 2
)
// SabbaticalStatus represents the status of a sabbatical.
type SabbaticalStatus uint8
const (
// SabbaticalPlanned indicates a planned sabbatical.
SabbaticalPlanned SabbaticalStatus = 0
// SabbaticalActive indicates an active sabbatical.
SabbaticalActive SabbaticalStatus = 1
// SabbaticalCompleted indicates a completed sabbatical.
SabbaticalCompleted SabbaticalStatus = 2
// SabbaticalCancelled indicates a cancelled sabbatical.
SabbaticalCancelled SabbaticalStatus = 3
)
// SabbaticalPurpose represents the purpose of a sabbatical.
type SabbaticalPurpose uint8
const (
// SabbaticalPurposeEducation for education and learning.
SabbaticalPurposeEducation SabbaticalPurpose = 0
// SabbaticalPurposeHealth for health and recovery.
SabbaticalPurposeHealth SabbaticalPurpose = 1
// SabbaticalPurposeFamily for family care.
SabbaticalPurposeFamily SabbaticalPurpose = 2
// SabbaticalPurposeExploration for personal exploration.
SabbaticalPurposeExploration SabbaticalPurpose = 3
// SabbaticalPurposeTransition for career transition.
SabbaticalPurposeTransition SabbaticalPurpose = 4
// SabbaticalPurposeVolunteer for volunteering.
SabbaticalPurposeVolunteer SabbaticalPurpose = 5
)
// MilestoneType represents the type of life milestone.
type MilestoneType uint8
const (
// MilestoneCareerStart for starting a career.
MilestoneCareerStart MilestoneType = 0
// MilestoneCareerEnd for ending a career.
MilestoneCareerEnd MilestoneType = 1
// MilestoneEducation for educational achievements.
MilestoneEducation MilestoneType = 2
// MilestoneFamily for family events.
MilestoneFamily MilestoneType = 3
// MilestoneHealth for health milestones.
MilestoneHealth MilestoneType = 4
// MilestoneFinancial for financial goals.
MilestoneFinancial MilestoneType = 5
// MilestonePersonal for personal achievements.
MilestonePersonal MilestoneType = 6
)
// GoalStatus represents the status of a life goal.
type GoalStatus uint8
const (
// GoalStatusPlanned indicates a planned goal.
GoalStatusPlanned GoalStatus = 0
// GoalStatusInProgress indicates an in-progress goal.
GoalStatusInProgress GoalStatus = 1
// GoalStatusCompleted indicates a completed goal.
GoalStatusCompleted GoalStatus = 2
// GoalStatusAbandoned indicates an abandoned goal.
GoalStatusAbandoned GoalStatus = 3
)
// LifePlanAccount represents a citizen's life planning account.
type LifePlanAccount struct {
VitaID uint64 // Owner's Vita token ID
Owner util.Uint160 // Owner's address
TotalContributions uint64 // Lifetime contributions
EmployerContributions uint64 // Employer contributions
GovernmentContributions uint64 // Government matching
CurrentBalance uint64 // Current account balance
SabbaticalCredits uint64 // Available sabbatical credits
LongevityMultiplier uint32 // Age-based benefit multiplier (basis points)
CurrentCareerCycleID uint64 // Active career cycle ID
TotalCareerCycles uint32 // Number of career cycles
TotalSabbaticals uint32 // Number of sabbaticals taken
Status LifePlanAccountStatus // Account status
CreatedAt uint32 // Block height when created
UpdatedAt uint32 // Block height of last update
}
// ToStackItem implements stackitem.Convertible interface.
func (a *LifePlanAccount) 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.TotalContributions))),
stackitem.NewBigInteger(big.NewInt(int64(a.EmployerContributions))),
stackitem.NewBigInteger(big.NewInt(int64(a.GovernmentContributions))),
stackitem.NewBigInteger(big.NewInt(int64(a.CurrentBalance))),
stackitem.NewBigInteger(big.NewInt(int64(a.SabbaticalCredits))),
stackitem.NewBigInteger(big.NewInt(int64(a.LongevityMultiplier))),
stackitem.NewBigInteger(big.NewInt(int64(a.CurrentCareerCycleID))),
stackitem.NewBigInteger(big.NewInt(int64(a.TotalCareerCycles))),
stackitem.NewBigInteger(big.NewInt(int64(a.TotalSabbaticals))),
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 *LifePlanAccount) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 14 {
return fmt.Errorf("wrong number of elements: expected 14, 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)
}
totalContributions, err := items[2].TryInteger()
if err != nil {
return fmt.Errorf("invalid totalContributions: %w", err)
}
a.TotalContributions = totalContributions.Uint64()
employerContributions, err := items[3].TryInteger()
if err != nil {
return fmt.Errorf("invalid employerContributions: %w", err)
}
a.EmployerContributions = employerContributions.Uint64()
governmentContributions, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid governmentContributions: %w", err)
}
a.GovernmentContributions = governmentContributions.Uint64()
currentBalance, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid currentBalance: %w", err)
}
a.CurrentBalance = currentBalance.Uint64()
sabbaticalCredits, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid sabbaticalCredits: %w", err)
}
a.SabbaticalCredits = sabbaticalCredits.Uint64()
longevityMultiplier, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid longevityMultiplier: %w", err)
}
a.LongevityMultiplier = uint32(longevityMultiplier.Uint64())
currentCareerCycleID, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid currentCareerCycleID: %w", err)
}
a.CurrentCareerCycleID = currentCareerCycleID.Uint64()
totalCareerCycles, err := items[9].TryInteger()
if err != nil {
return fmt.Errorf("invalid totalCareerCycles: %w", err)
}
a.TotalCareerCycles = uint32(totalCareerCycles.Uint64())
totalSabbaticals, err := items[10].TryInteger()
if err != nil {
return fmt.Errorf("invalid totalSabbaticals: %w", err)
}
a.TotalSabbaticals = uint32(totalSabbaticals.Uint64())
status, err := items[11].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
a.Status = LifePlanAccountStatus(status.Uint64())
createdAt, err := items[12].TryInteger()
if err != nil {
return fmt.Errorf("invalid createdAt: %w", err)
}
a.CreatedAt = uint32(createdAt.Uint64())
updatedAt, err := items[13].TryInteger()
if err != nil {
return fmt.Errorf("invalid updatedAt: %w", err)
}
a.UpdatedAt = uint32(updatedAt.Uint64())
return nil
}
// CareerCycle represents a career phase in a person's life.
type CareerCycle struct {
ID uint64 // Unique career cycle ID
VitaID uint64 // Owner's Vita ID
Owner util.Uint160 // Owner's address
CareerField string // Industry/profession
StartedAt uint32 // Block height when started
EndedAt uint32 // Block height when ended (0 = active)
ContributionTotal uint64 // Total contributions in this cycle
TransitionReason string // Why career ended
Status CareerCycleStatus // Career status
}
// ToStackItem implements stackitem.Convertible interface.
func (c *CareerCycle) 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.CareerField)),
stackitem.NewBigInteger(big.NewInt(int64(c.StartedAt))),
stackitem.NewBigInteger(big.NewInt(int64(c.EndedAt))),
stackitem.NewBigInteger(big.NewInt(int64(c.ContributionTotal))),
stackitem.NewByteArray([]byte(c.TransitionReason)),
stackitem.NewBigInteger(big.NewInt(int64(c.Status))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (c *CareerCycle) 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()
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)
}
careerField, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid careerField: %w", err)
}
c.CareerField = string(careerField)
startedAt, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid startedAt: %w", err)
}
c.StartedAt = uint32(startedAt.Uint64())
endedAt, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid endedAt: %w", err)
}
c.EndedAt = uint32(endedAt.Uint64())
contributionTotal, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid contributionTotal: %w", err)
}
c.ContributionTotal = contributionTotal.Uint64()
transitionReason, err := items[7].TryBytes()
if err != nil {
return fmt.Errorf("invalid transitionReason: %w", err)
}
c.TransitionReason = string(transitionReason)
status, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
c.Status = CareerCycleStatus(status.Uint64())
return nil
}
// Sabbatical represents a career break.
type Sabbatical struct {
ID uint64 // Unique sabbatical ID
VitaID uint64 // Owner's Vita ID
Owner util.Uint160 // Owner's address
Purpose SabbaticalPurpose // Purpose of sabbatical
Description string // Description of plans
StartedAt uint32 // Block height when started
Duration uint32 // Planned duration in blocks
EndedAt uint32 // Block height when ended (0 = ongoing)
FundingUsed uint64 // Credits spent during sabbatical
Outcome string // Sabbatical results
Status SabbaticalStatus // Sabbatical status
}
// ToStackItem implements stackitem.Convertible interface.
func (s *Sabbatical) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(s.ID))),
stackitem.NewBigInteger(big.NewInt(int64(s.VitaID))),
stackitem.NewByteArray(s.Owner.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(s.Purpose))),
stackitem.NewByteArray([]byte(s.Description)),
stackitem.NewBigInteger(big.NewInt(int64(s.StartedAt))),
stackitem.NewBigInteger(big.NewInt(int64(s.Duration))),
stackitem.NewBigInteger(big.NewInt(int64(s.EndedAt))),
stackitem.NewBigInteger(big.NewInt(int64(s.FundingUsed))),
stackitem.NewByteArray([]byte(s.Outcome)),
stackitem.NewBigInteger(big.NewInt(int64(s.Status))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (s *Sabbatical) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 11 {
return fmt.Errorf("wrong number of elements: expected 11, got %d", len(items))
}
id, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid id: %w", err)
}
s.ID = id.Uint64()
vitaID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
s.VitaID = vitaID.Uint64()
owner, err := items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid owner: %w", err)
}
s.Owner, err = util.Uint160DecodeBytesBE(owner)
if err != nil {
return fmt.Errorf("invalid owner address: %w", err)
}
purpose, err := items[3].TryInteger()
if err != nil {
return fmt.Errorf("invalid purpose: %w", err)
}
s.Purpose = SabbaticalPurpose(purpose.Uint64())
description, err := items[4].TryBytes()
if err != nil {
return fmt.Errorf("invalid description: %w", err)
}
s.Description = string(description)
startedAt, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid startedAt: %w", err)
}
s.StartedAt = uint32(startedAt.Uint64())
duration, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid duration: %w", err)
}
s.Duration = uint32(duration.Uint64())
endedAt, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid endedAt: %w", err)
}
s.EndedAt = uint32(endedAt.Uint64())
fundingUsed, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid fundingUsed: %w", err)
}
s.FundingUsed = fundingUsed.Uint64()
outcome, err := items[9].TryBytes()
if err != nil {
return fmt.Errorf("invalid outcome: %w", err)
}
s.Outcome = string(outcome)
status, err := items[10].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
s.Status = SabbaticalStatus(status.Uint64())
return nil
}
// LifeMilestone represents a significant life event.
type LifeMilestone struct {
ID uint64 // Unique milestone ID
VitaID uint64 // Owner's Vita ID
Owner util.Uint160 // Owner's address
MilestoneType MilestoneType // Type of milestone
Title string // Milestone title
Description string // Milestone description
ContentHash util.Uint256 // Hash of supporting documentation
AchievedAt uint32 // Block height when achieved
IsVerified bool // Whether milestone is verified
}
// ToStackItem implements stackitem.Convertible interface.
func (m *LifeMilestone) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(m.ID))),
stackitem.NewBigInteger(big.NewInt(int64(m.VitaID))),
stackitem.NewByteArray(m.Owner.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(m.MilestoneType))),
stackitem.NewByteArray([]byte(m.Title)),
stackitem.NewByteArray([]byte(m.Description)),
stackitem.NewByteArray(m.ContentHash.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(m.AchievedAt))),
stackitem.NewBool(m.IsVerified),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (m *LifeMilestone) 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)
}
m.ID = id.Uint64()
vitaID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
m.VitaID = vitaID.Uint64()
owner, err := items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid owner: %w", err)
}
m.Owner, err = util.Uint160DecodeBytesBE(owner)
if err != nil {
return fmt.Errorf("invalid owner address: %w", err)
}
milestoneType, err := items[3].TryInteger()
if err != nil {
return fmt.Errorf("invalid milestoneType: %w", err)
}
m.MilestoneType = MilestoneType(milestoneType.Uint64())
title, err := items[4].TryBytes()
if err != nil {
return fmt.Errorf("invalid title: %w", err)
}
m.Title = string(title)
description, err := items[5].TryBytes()
if err != nil {
return fmt.Errorf("invalid description: %w", err)
}
m.Description = string(description)
contentHash, err := items[6].TryBytes()
if err != nil {
return fmt.Errorf("invalid contentHash: %w", err)
}
m.ContentHash, err = util.Uint256DecodeBytesBE(contentHash)
if err != nil {
return fmt.Errorf("invalid contentHash value: %w", err)
}
achievedAt, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid achievedAt: %w", err)
}
m.AchievedAt = uint32(achievedAt.Uint64())
isVerified, err := items[8].TryBool()
if err != nil {
return fmt.Errorf("invalid isVerified: %w", err)
}
m.IsVerified = isVerified
return nil
}
// LifeGoal represents a future life goal.
type LifeGoal struct {
ID uint64 // Unique goal ID
VitaID uint64 // Owner's Vita ID
Owner util.Uint160 // Owner's address
Title string // Goal title
Description string // Goal description
TargetBlock uint32 // Target block height for completion
Progress uint32 // Progress percentage (0-10000 basis points)
Status GoalStatus // Goal status
CreatedAt uint32 // Block height when created
CompletedAt uint32 // Block height when completed (0 = incomplete)
}
// ToStackItem implements stackitem.Convertible interface.
func (g *LifeGoal) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(g.ID))),
stackitem.NewBigInteger(big.NewInt(int64(g.VitaID))),
stackitem.NewByteArray(g.Owner.BytesBE()),
stackitem.NewByteArray([]byte(g.Title)),
stackitem.NewByteArray([]byte(g.Description)),
stackitem.NewBigInteger(big.NewInt(int64(g.TargetBlock))),
stackitem.NewBigInteger(big.NewInt(int64(g.Progress))),
stackitem.NewBigInteger(big.NewInt(int64(g.Status))),
stackitem.NewBigInteger(big.NewInt(int64(g.CreatedAt))),
stackitem.NewBigInteger(big.NewInt(int64(g.CompletedAt))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (g *LifeGoal) 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)
}
g.ID = id.Uint64()
vitaID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid vitaID: %w", err)
}
g.VitaID = vitaID.Uint64()
owner, err := items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid owner: %w", err)
}
g.Owner, err = util.Uint160DecodeBytesBE(owner)
if err != nil {
return fmt.Errorf("invalid owner address: %w", err)
}
title, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid title: %w", err)
}
g.Title = string(title)
description, err := items[4].TryBytes()
if err != nil {
return fmt.Errorf("invalid description: %w", err)
}
g.Description = string(description)
targetBlock, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid targetBlock: %w", err)
}
g.TargetBlock = uint32(targetBlock.Uint64())
progress, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid progress: %w", err)
}
g.Progress = uint32(progress.Uint64())
status, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
g.Status = GoalStatus(status.Uint64())
createdAt, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid createdAt: %w", err)
}
g.CreatedAt = uint32(createdAt.Uint64())
completedAt, err := items[9].TryInteger()
if err != nil {
return fmt.Errorf("invalid completedAt: %w", err)
}
g.CompletedAt = uint32(completedAt.Uint64())
return nil
}
// SeseConfig represents configurable parameters for the Sese contract.
type SeseConfig struct {
DefaultSabbaticalCredits uint64 // Default sabbatical credits per year
MinSabbaticalDuration uint32 // Minimum sabbatical duration in blocks
MaxSabbaticalDuration uint32 // Maximum sabbatical duration in blocks
GovernmentMatchPercent uint32 // Government matching percentage (basis points)
LongevityBonusPercent uint32 // Longevity bonus percentage (basis points per year)
}
// ToStackItem implements stackitem.Convertible interface.
func (c *SeseConfig) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultSabbaticalCredits))),
stackitem.NewBigInteger(big.NewInt(int64(c.MinSabbaticalDuration))),
stackitem.NewBigInteger(big.NewInt(int64(c.MaxSabbaticalDuration))),
stackitem.NewBigInteger(big.NewInt(int64(c.GovernmentMatchPercent))),
stackitem.NewBigInteger(big.NewInt(int64(c.LongevityBonusPercent))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (c *SeseConfig) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 5 {
return fmt.Errorf("wrong number of elements: expected 5, got %d", len(items))
}
defaultCredits, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid defaultSabbaticalCredits: %w", err)
}
c.DefaultSabbaticalCredits = defaultCredits.Uint64()
minDuration, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid minSabbaticalDuration: %w", err)
}
c.MinSabbaticalDuration = uint32(minDuration.Uint64())
maxDuration, err := items[2].TryInteger()
if err != nil {
return fmt.Errorf("invalid maxSabbaticalDuration: %w", err)
}
c.MaxSabbaticalDuration = uint32(maxDuration.Uint64())
govMatch, err := items[3].TryInteger()
if err != nil {
return fmt.Errorf("invalid governmentMatchPercent: %w", err)
}
c.GovernmentMatchPercent = uint32(govMatch.Uint64())
longevityBonus, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid longevityBonusPercent: %w", err)
}
c.LongevityBonusPercent = uint32(longevityBonus.Uint64())
return nil
}