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:
parent
83c12c5362
commit
9a57598c91
|
|
@ -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))
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue