1437 lines
47 KiB
Go
1437 lines
47 KiB
Go
package state
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/tutus-one/tutus-chain/pkg/io"
|
|
"github.com/tutus-one/tutus-chain/pkg/util"
|
|
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
|
)
|
|
|
|
// OpportunityType represents the type of investment opportunity.
|
|
type OpportunityType uint8
|
|
|
|
// Investment opportunity types.
|
|
const (
|
|
OpportunityPIO OpportunityType = 0 // Public Investment Opportunity - universal access
|
|
OpportunityEIO OpportunityType = 1 // Employee Investment Opportunity - workplace
|
|
OpportunityCIO OpportunityType = 2 // Contractor Investment Opportunity - gig economy
|
|
)
|
|
|
|
// OpportunityStatus represents the status of an investment opportunity.
|
|
type OpportunityStatus uint8
|
|
|
|
// Investment opportunity statuses.
|
|
const (
|
|
OpportunityDraft OpportunityStatus = 0 // Being created
|
|
OpportunityVoting OpportunityStatus = 1 // Under democratic voting
|
|
OpportunityActive OpportunityStatus = 2 // Open for investments
|
|
OpportunityClosed OpportunityStatus = 3 // No longer accepting investments
|
|
OpportunityCompleted OpportunityStatus = 4 // Returns distributed
|
|
OpportunityCancelled OpportunityStatus = 5 // Cancelled with refunds
|
|
OpportunityFailed OpportunityStatus = 6 // Did not meet minimum requirements
|
|
)
|
|
|
|
// InvestmentStatus represents the status of an individual investment.
|
|
type InvestmentStatus uint8
|
|
|
|
// Investment statuses.
|
|
const (
|
|
InvestmentActive InvestmentStatus = 0 // Investment is active
|
|
InvestmentWithdrawn InvestmentStatus = 1 // Investor withdrew
|
|
InvestmentCompleted InvestmentStatus = 2 // Returns distributed
|
|
InvestmentRefunded InvestmentStatus = 3 // Refunded due to cancellation
|
|
)
|
|
|
|
// EligibilityType represents eligibility flags.
|
|
type EligibilityType uint8
|
|
|
|
// Eligibility flags (can be combined).
|
|
const (
|
|
EligibilityNone EligibilityType = 0
|
|
EligibilityPIO EligibilityType = 1 << 0 // Public investment eligible
|
|
EligibilityEIO EligibilityType = 1 << 1 // Employee investment eligible
|
|
EligibilityCIO EligibilityType = 1 << 2 // Contractor investment eligible
|
|
EligibilityAll EligibilityType = EligibilityPIO | EligibilityEIO | EligibilityCIO
|
|
)
|
|
|
|
// InvestmentOpportunity represents an investment opportunity.
|
|
type InvestmentOpportunity struct {
|
|
ID uint64 // Unique opportunity identifier
|
|
Type OpportunityType // PIO, EIO, or CIO
|
|
Status OpportunityStatus // Current status
|
|
Creator util.Uint160 // Creator's address (must have Vita)
|
|
SponsorVitaID uint64 // Sponsor's Vita ID (for EIO: employer, CIO: platform)
|
|
Name string // Opportunity name
|
|
Description string // Investment description
|
|
TermsHash util.Uint256 // Hash of full terms (off-chain)
|
|
MinParticipants uint64 // Required minimum participants
|
|
MaxParticipants uint64 // Maximum allowed participants (0 = unlimited)
|
|
CurrentParticipants uint64 // Current number of participants
|
|
MinInvestment uint64 // Minimum VTS investment per person
|
|
MaxInvestment uint64 // Maximum VTS investment per person
|
|
TotalPool uint64 // Total VTS in opportunity
|
|
TargetPool uint64 // Target total pool size
|
|
ExpectedReturns uint64 // Projected returns (basis points, 10000 = 100%)
|
|
RiskLevel uint8 // Risk level 1-10 (AI-assessed)
|
|
VotingDeadline uint32 // Block height for voting deadline
|
|
InvestmentDeadline uint32 // Block height for investment deadline
|
|
MaturityDate uint32 // Block height when returns distributed
|
|
ProposalID uint64 // Associated Eligere proposal ID (0 = none)
|
|
CreatedAt uint32 // Block height when created
|
|
UpdatedAt uint32 // Block height when last updated
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (o *InvestmentOpportunity) DecodeBinary(br *io.BinReader) {
|
|
o.ID = br.ReadU64LE()
|
|
o.Type = OpportunityType(br.ReadB())
|
|
o.Status = OpportunityStatus(br.ReadB())
|
|
br.ReadBytes(o.Creator[:])
|
|
o.SponsorVitaID = br.ReadU64LE()
|
|
o.Name = br.ReadString()
|
|
o.Description = br.ReadString()
|
|
br.ReadBytes(o.TermsHash[:])
|
|
o.MinParticipants = br.ReadU64LE()
|
|
o.MaxParticipants = br.ReadU64LE()
|
|
o.CurrentParticipants = br.ReadU64LE()
|
|
o.MinInvestment = br.ReadU64LE()
|
|
o.MaxInvestment = br.ReadU64LE()
|
|
o.TotalPool = br.ReadU64LE()
|
|
o.TargetPool = br.ReadU64LE()
|
|
o.ExpectedReturns = br.ReadU64LE()
|
|
o.RiskLevel = br.ReadB()
|
|
o.VotingDeadline = br.ReadU32LE()
|
|
o.InvestmentDeadline = br.ReadU32LE()
|
|
o.MaturityDate = br.ReadU32LE()
|
|
o.ProposalID = br.ReadU64LE()
|
|
o.CreatedAt = br.ReadU32LE()
|
|
o.UpdatedAt = br.ReadU32LE()
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (o *InvestmentOpportunity) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(o.ID)
|
|
bw.WriteB(byte(o.Type))
|
|
bw.WriteB(byte(o.Status))
|
|
bw.WriteBytes(o.Creator[:])
|
|
bw.WriteU64LE(o.SponsorVitaID)
|
|
bw.WriteString(o.Name)
|
|
bw.WriteString(o.Description)
|
|
bw.WriteBytes(o.TermsHash[:])
|
|
bw.WriteU64LE(o.MinParticipants)
|
|
bw.WriteU64LE(o.MaxParticipants)
|
|
bw.WriteU64LE(o.CurrentParticipants)
|
|
bw.WriteU64LE(o.MinInvestment)
|
|
bw.WriteU64LE(o.MaxInvestment)
|
|
bw.WriteU64LE(o.TotalPool)
|
|
bw.WriteU64LE(o.TargetPool)
|
|
bw.WriteU64LE(o.ExpectedReturns)
|
|
bw.WriteB(o.RiskLevel)
|
|
bw.WriteU32LE(o.VotingDeadline)
|
|
bw.WriteU32LE(o.InvestmentDeadline)
|
|
bw.WriteU32LE(o.MaturityDate)
|
|
bw.WriteU64LE(o.ProposalID)
|
|
bw.WriteU32LE(o.CreatedAt)
|
|
bw.WriteU32LE(o.UpdatedAt)
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (o *InvestmentOpportunity) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.ID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.Type))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.Status))),
|
|
stackitem.NewByteArray(o.Creator.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.SponsorVitaID))),
|
|
stackitem.NewByteArray([]byte(o.Name)),
|
|
stackitem.NewByteArray([]byte(o.Description)),
|
|
stackitem.NewByteArray(o.TermsHash.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.MinParticipants))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.MaxParticipants))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.CurrentParticipants))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.MinInvestment))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.MaxInvestment))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.TotalPool))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.TargetPool))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.ExpectedReturns))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.RiskLevel))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.VotingDeadline))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.InvestmentDeadline))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.MaturityDate))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.ProposalID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.CreatedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(o.UpdatedAt))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (o *InvestmentOpportunity) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 23 {
|
|
return fmt.Errorf("wrong number of elements: expected 23, got %d", len(items))
|
|
}
|
|
|
|
id, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid id: %w", err)
|
|
}
|
|
o.ID = id.Uint64()
|
|
|
|
oppType, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid type: %w", err)
|
|
}
|
|
o.Type = OpportunityType(oppType.Uint64())
|
|
|
|
status, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid status: %w", err)
|
|
}
|
|
o.Status = OpportunityStatus(status.Uint64())
|
|
|
|
creator, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid creator: %w", err)
|
|
}
|
|
o.Creator, err = util.Uint160DecodeBytesBE(creator)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid creator address: %w", err)
|
|
}
|
|
|
|
sponsorVitaID, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid sponsorVitaID: %w", err)
|
|
}
|
|
o.SponsorVitaID = sponsorVitaID.Uint64()
|
|
|
|
name, err := items[5].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid name: %w", err)
|
|
}
|
|
o.Name = string(name)
|
|
|
|
description, err := items[6].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid description: %w", err)
|
|
}
|
|
o.Description = string(description)
|
|
|
|
termsHash, err := items[7].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid termsHash: %w", err)
|
|
}
|
|
o.TermsHash, err = util.Uint256DecodeBytesBE(termsHash)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid termsHash: %w", err)
|
|
}
|
|
|
|
minParticipants, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minParticipants: %w", err)
|
|
}
|
|
o.MinParticipants = minParticipants.Uint64()
|
|
|
|
maxParticipants, err := items[9].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid maxParticipants: %w", err)
|
|
}
|
|
o.MaxParticipants = maxParticipants.Uint64()
|
|
|
|
currentParticipants, err := items[10].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid currentParticipants: %w", err)
|
|
}
|
|
o.CurrentParticipants = currentParticipants.Uint64()
|
|
|
|
minInvestment, err := items[11].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minInvestment: %w", err)
|
|
}
|
|
o.MinInvestment = minInvestment.Uint64()
|
|
|
|
maxInvestment, err := items[12].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid maxInvestment: %w", err)
|
|
}
|
|
o.MaxInvestment = maxInvestment.Uint64()
|
|
|
|
totalPool, err := items[13].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid totalPool: %w", err)
|
|
}
|
|
o.TotalPool = totalPool.Uint64()
|
|
|
|
targetPool, err := items[14].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid targetPool: %w", err)
|
|
}
|
|
o.TargetPool = targetPool.Uint64()
|
|
|
|
expectedReturns, err := items[15].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid expectedReturns: %w", err)
|
|
}
|
|
o.ExpectedReturns = expectedReturns.Uint64()
|
|
|
|
riskLevel, err := items[16].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid riskLevel: %w", err)
|
|
}
|
|
o.RiskLevel = uint8(riskLevel.Uint64())
|
|
|
|
votingDeadline, err := items[17].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid votingDeadline: %w", err)
|
|
}
|
|
o.VotingDeadline = uint32(votingDeadline.Uint64())
|
|
|
|
investmentDeadline, err := items[18].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investmentDeadline: %w", err)
|
|
}
|
|
o.InvestmentDeadline = uint32(investmentDeadline.Uint64())
|
|
|
|
maturityDate, err := items[19].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid maturityDate: %w", err)
|
|
}
|
|
o.MaturityDate = uint32(maturityDate.Uint64())
|
|
|
|
proposalID, err := items[20].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid proposalID: %w", err)
|
|
}
|
|
o.ProposalID = proposalID.Uint64()
|
|
|
|
createdAt, err := items[21].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid createdAt: %w", err)
|
|
}
|
|
o.CreatedAt = uint32(createdAt.Uint64())
|
|
|
|
updatedAt, err := items[22].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid updatedAt: %w", err)
|
|
}
|
|
o.UpdatedAt = uint32(updatedAt.Uint64())
|
|
|
|
return nil
|
|
}
|
|
|
|
// Investment represents an individual's investment in an opportunity.
|
|
type Investment struct {
|
|
ID uint64 // Unique investment ID
|
|
OpportunityID uint64 // Opportunity invested in
|
|
VitaID uint64 // Investor's Vita ID
|
|
Investor util.Uint160 // Investor's address
|
|
Amount uint64 // VTS amount invested
|
|
Status InvestmentStatus // Current status
|
|
ReturnAmount uint64 // Returns received (when completed)
|
|
CreatedAt uint32 // Block height when invested
|
|
UpdatedAt uint32 // Block height when last updated
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (i *Investment) DecodeBinary(br *io.BinReader) {
|
|
i.ID = br.ReadU64LE()
|
|
i.OpportunityID = br.ReadU64LE()
|
|
i.VitaID = br.ReadU64LE()
|
|
br.ReadBytes(i.Investor[:])
|
|
i.Amount = br.ReadU64LE()
|
|
i.Status = InvestmentStatus(br.ReadB())
|
|
i.ReturnAmount = br.ReadU64LE()
|
|
i.CreatedAt = br.ReadU32LE()
|
|
i.UpdatedAt = br.ReadU32LE()
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (i *Investment) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(i.ID)
|
|
bw.WriteU64LE(i.OpportunityID)
|
|
bw.WriteU64LE(i.VitaID)
|
|
bw.WriteBytes(i.Investor[:])
|
|
bw.WriteU64LE(i.Amount)
|
|
bw.WriteB(byte(i.Status))
|
|
bw.WriteU64LE(i.ReturnAmount)
|
|
bw.WriteU32LE(i.CreatedAt)
|
|
bw.WriteU32LE(i.UpdatedAt)
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (i *Investment) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.ID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.OpportunityID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.VitaID))),
|
|
stackitem.NewByteArray(i.Investor.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.Amount))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.Status))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.ReturnAmount))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.CreatedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(i.UpdatedAt))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (i *Investment) 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)
|
|
}
|
|
i.ID = id.Uint64()
|
|
|
|
oppID, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid opportunityID: %w", err)
|
|
}
|
|
i.OpportunityID = oppID.Uint64()
|
|
|
|
vitaID, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid vitaID: %w", err)
|
|
}
|
|
i.VitaID = vitaID.Uint64()
|
|
|
|
investor, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investor: %w", err)
|
|
}
|
|
i.Investor, err = util.Uint160DecodeBytesBE(investor)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investor address: %w", err)
|
|
}
|
|
|
|
amount, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid amount: %w", err)
|
|
}
|
|
i.Amount = amount.Uint64()
|
|
|
|
status, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid status: %w", err)
|
|
}
|
|
i.Status = InvestmentStatus(status.Uint64())
|
|
|
|
returnAmount, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid returnAmount: %w", err)
|
|
}
|
|
i.ReturnAmount = returnAmount.Uint64()
|
|
|
|
createdAt, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid createdAt: %w", err)
|
|
}
|
|
i.CreatedAt = uint32(createdAt.Uint64())
|
|
|
|
updatedAt, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid updatedAt: %w", err)
|
|
}
|
|
i.UpdatedAt = uint32(updatedAt.Uint64())
|
|
|
|
return nil
|
|
}
|
|
|
|
// InvestorEligibility represents an investor's eligibility status.
|
|
type InvestorEligibility struct {
|
|
VitaID uint64 // Investor's Vita ID
|
|
Investor util.Uint160 // Investor's address
|
|
Eligibility EligibilityType // Eligibility flags
|
|
ScireCompleted bool // Investment education completed
|
|
RiskScore uint8 // AI-calculated risk tolerance (1-10)
|
|
TotalInvested uint64 // Total VTS currently invested
|
|
TotalReturns uint64 // Total returns received
|
|
ActiveInvestments uint64 // Number of active investments
|
|
CompletedInvestments uint64 // Number of completed investments
|
|
HasViolations bool // Any past investment abuse
|
|
ViolationCount uint8 // Number of violations
|
|
LastActivity uint32 // Block height of last activity
|
|
CreatedAt uint32 // Block height when created
|
|
UpdatedAt uint32 // Block height when updated
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (e *InvestorEligibility) DecodeBinary(br *io.BinReader) {
|
|
e.VitaID = br.ReadU64LE()
|
|
br.ReadBytes(e.Investor[:])
|
|
e.Eligibility = EligibilityType(br.ReadB())
|
|
e.ScireCompleted = br.ReadBool()
|
|
e.RiskScore = br.ReadB()
|
|
e.TotalInvested = br.ReadU64LE()
|
|
e.TotalReturns = br.ReadU64LE()
|
|
e.ActiveInvestments = br.ReadU64LE()
|
|
e.CompletedInvestments = br.ReadU64LE()
|
|
e.HasViolations = br.ReadBool()
|
|
e.ViolationCount = br.ReadB()
|
|
e.LastActivity = br.ReadU32LE()
|
|
e.CreatedAt = br.ReadU32LE()
|
|
e.UpdatedAt = br.ReadU32LE()
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (e *InvestorEligibility) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(e.VitaID)
|
|
bw.WriteBytes(e.Investor[:])
|
|
bw.WriteB(byte(e.Eligibility))
|
|
bw.WriteBool(e.ScireCompleted)
|
|
bw.WriteB(e.RiskScore)
|
|
bw.WriteU64LE(e.TotalInvested)
|
|
bw.WriteU64LE(e.TotalReturns)
|
|
bw.WriteU64LE(e.ActiveInvestments)
|
|
bw.WriteU64LE(e.CompletedInvestments)
|
|
bw.WriteBool(e.HasViolations)
|
|
bw.WriteB(e.ViolationCount)
|
|
bw.WriteU32LE(e.LastActivity)
|
|
bw.WriteU32LE(e.CreatedAt)
|
|
bw.WriteU32LE(e.UpdatedAt)
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (e *InvestorEligibility) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.VitaID))),
|
|
stackitem.NewByteArray(e.Investor.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.Eligibility))),
|
|
stackitem.NewBool(e.ScireCompleted),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.RiskScore))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.TotalInvested))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.TotalReturns))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.ActiveInvestments))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.CompletedInvestments))),
|
|
stackitem.NewBool(e.HasViolations),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.ViolationCount))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.LastActivity))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.CreatedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(e.UpdatedAt))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (e *InvestorEligibility) 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)
|
|
}
|
|
e.VitaID = vitaID.Uint64()
|
|
|
|
investor, err := items[1].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investor: %w", err)
|
|
}
|
|
e.Investor, err = util.Uint160DecodeBytesBE(investor)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investor address: %w", err)
|
|
}
|
|
|
|
eligibility, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid eligibility: %w", err)
|
|
}
|
|
e.Eligibility = EligibilityType(eligibility.Uint64())
|
|
|
|
scireCompleted, err := items[3].TryBool()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid scireCompleted: %w", err)
|
|
}
|
|
e.ScireCompleted = scireCompleted
|
|
|
|
riskScore, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid riskScore: %w", err)
|
|
}
|
|
e.RiskScore = uint8(riskScore.Uint64())
|
|
|
|
totalInvested, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid totalInvested: %w", err)
|
|
}
|
|
e.TotalInvested = totalInvested.Uint64()
|
|
|
|
totalReturns, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid totalReturns: %w", err)
|
|
}
|
|
e.TotalReturns = totalReturns.Uint64()
|
|
|
|
activeInvestments, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid activeInvestments: %w", err)
|
|
}
|
|
e.ActiveInvestments = activeInvestments.Uint64()
|
|
|
|
completedInvestments, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid completedInvestments: %w", err)
|
|
}
|
|
e.CompletedInvestments = completedInvestments.Uint64()
|
|
|
|
hasViolations, err := items[9].TryBool()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid hasViolations: %w", err)
|
|
}
|
|
e.HasViolations = hasViolations
|
|
|
|
violationCount, err := items[10].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid violationCount: %w", err)
|
|
}
|
|
e.ViolationCount = uint8(violationCount.Uint64())
|
|
|
|
lastActivity, err := items[11].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid lastActivity: %w", err)
|
|
}
|
|
e.LastActivity = uint32(lastActivity.Uint64())
|
|
|
|
createdAt, err := items[12].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid createdAt: %w", err)
|
|
}
|
|
e.CreatedAt = uint32(createdAt.Uint64())
|
|
|
|
updatedAt, err := items[13].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid updatedAt: %w", err)
|
|
}
|
|
e.UpdatedAt = uint32(updatedAt.Uint64())
|
|
|
|
return nil
|
|
}
|
|
|
|
// EmploymentVerification represents verified employment for EIO eligibility.
|
|
type EmploymentVerification struct {
|
|
VitaID uint64 // Employee's Vita ID
|
|
Employee util.Uint160 // Employee's address
|
|
EmployerVitaID uint64 // Employer's Vita ID (organization)
|
|
Employer util.Uint160 // Employer's address
|
|
Position string // Job position/role
|
|
StartDate uint32 // Block height when employment started
|
|
EndDate uint32 // Block height when employment ended (0 = current)
|
|
IsActive bool // Currently employed
|
|
VerifiedAt uint32 // Block height when verified
|
|
VerifiedBy util.Uint160 // Verifier's address (RoleInvestmentManager)
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (ev *EmploymentVerification) DecodeBinary(br *io.BinReader) {
|
|
ev.VitaID = br.ReadU64LE()
|
|
br.ReadBytes(ev.Employee[:])
|
|
ev.EmployerVitaID = br.ReadU64LE()
|
|
br.ReadBytes(ev.Employer[:])
|
|
ev.Position = br.ReadString()
|
|
ev.StartDate = br.ReadU32LE()
|
|
ev.EndDate = br.ReadU32LE()
|
|
ev.IsActive = br.ReadBool()
|
|
ev.VerifiedAt = br.ReadU32LE()
|
|
br.ReadBytes(ev.VerifiedBy[:])
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (ev *EmploymentVerification) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(ev.VitaID)
|
|
bw.WriteBytes(ev.Employee[:])
|
|
bw.WriteU64LE(ev.EmployerVitaID)
|
|
bw.WriteBytes(ev.Employer[:])
|
|
bw.WriteString(ev.Position)
|
|
bw.WriteU32LE(ev.StartDate)
|
|
bw.WriteU32LE(ev.EndDate)
|
|
bw.WriteBool(ev.IsActive)
|
|
bw.WriteU32LE(ev.VerifiedAt)
|
|
bw.WriteBytes(ev.VerifiedBy[:])
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (ev *EmploymentVerification) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(ev.VitaID))),
|
|
stackitem.NewByteArray(ev.Employee.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(ev.EmployerVitaID))),
|
|
stackitem.NewByteArray(ev.Employer.BytesBE()),
|
|
stackitem.NewByteArray([]byte(ev.Position)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(ev.StartDate))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(ev.EndDate))),
|
|
stackitem.NewBool(ev.IsActive),
|
|
stackitem.NewBigInteger(big.NewInt(int64(ev.VerifiedAt))),
|
|
stackitem.NewByteArray(ev.VerifiedBy.BytesBE()),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (ev *EmploymentVerification) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 10 {
|
|
return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
|
|
}
|
|
|
|
vitaID, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid vitaID: %w", err)
|
|
}
|
|
ev.VitaID = vitaID.Uint64()
|
|
|
|
employee, err := items[1].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid employee: %w", err)
|
|
}
|
|
ev.Employee, err = util.Uint160DecodeBytesBE(employee)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid employee address: %w", err)
|
|
}
|
|
|
|
employerVitaID, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid employerVitaID: %w", err)
|
|
}
|
|
ev.EmployerVitaID = employerVitaID.Uint64()
|
|
|
|
employer, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid employer: %w", err)
|
|
}
|
|
ev.Employer, err = util.Uint160DecodeBytesBE(employer)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid employer address: %w", err)
|
|
}
|
|
|
|
position, err := items[4].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid position: %w", err)
|
|
}
|
|
ev.Position = string(position)
|
|
|
|
startDate, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid startDate: %w", err)
|
|
}
|
|
ev.StartDate = uint32(startDate.Uint64())
|
|
|
|
endDate, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid endDate: %w", err)
|
|
}
|
|
ev.EndDate = uint32(endDate.Uint64())
|
|
|
|
isActive, err := items[7].TryBool()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid isActive: %w", err)
|
|
}
|
|
ev.IsActive = isActive
|
|
|
|
verifiedAt, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid verifiedAt: %w", err)
|
|
}
|
|
ev.VerifiedAt = uint32(verifiedAt.Uint64())
|
|
|
|
verifiedBy, err := items[9].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid verifiedBy: %w", err)
|
|
}
|
|
ev.VerifiedBy, err = util.Uint160DecodeBytesBE(verifiedBy)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid verifiedBy address: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ContractorVerification represents verified contractor status for CIO eligibility.
|
|
type ContractorVerification struct {
|
|
VitaID uint64 // Contractor's Vita ID
|
|
Contractor util.Uint160 // Contractor's address
|
|
PlatformID string // Platform identifier
|
|
Platform util.Uint160 // Platform's address
|
|
ContractorID string // Platform-specific contractor ID
|
|
StartDate uint32 // Block height when started
|
|
EndDate uint32 // Block height when ended (0 = current)
|
|
IsActive bool // Currently active
|
|
VerifiedAt uint32 // Block height when verified
|
|
VerifiedBy util.Uint160 // Verifier's address
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (cv *ContractorVerification) DecodeBinary(br *io.BinReader) {
|
|
cv.VitaID = br.ReadU64LE()
|
|
br.ReadBytes(cv.Contractor[:])
|
|
cv.PlatformID = br.ReadString()
|
|
br.ReadBytes(cv.Platform[:])
|
|
cv.ContractorID = br.ReadString()
|
|
cv.StartDate = br.ReadU32LE()
|
|
cv.EndDate = br.ReadU32LE()
|
|
cv.IsActive = br.ReadBool()
|
|
cv.VerifiedAt = br.ReadU32LE()
|
|
br.ReadBytes(cv.VerifiedBy[:])
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (cv *ContractorVerification) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(cv.VitaID)
|
|
bw.WriteBytes(cv.Contractor[:])
|
|
bw.WriteString(cv.PlatformID)
|
|
bw.WriteBytes(cv.Platform[:])
|
|
bw.WriteString(cv.ContractorID)
|
|
bw.WriteU32LE(cv.StartDate)
|
|
bw.WriteU32LE(cv.EndDate)
|
|
bw.WriteBool(cv.IsActive)
|
|
bw.WriteU32LE(cv.VerifiedAt)
|
|
bw.WriteBytes(cv.VerifiedBy[:])
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (cv *ContractorVerification) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(cv.VitaID))),
|
|
stackitem.NewByteArray(cv.Contractor.BytesBE()),
|
|
stackitem.NewByteArray([]byte(cv.PlatformID)),
|
|
stackitem.NewByteArray(cv.Platform.BytesBE()),
|
|
stackitem.NewByteArray([]byte(cv.ContractorID)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cv.StartDate))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cv.EndDate))),
|
|
stackitem.NewBool(cv.IsActive),
|
|
stackitem.NewBigInteger(big.NewInt(int64(cv.VerifiedAt))),
|
|
stackitem.NewByteArray(cv.VerifiedBy.BytesBE()),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (cv *ContractorVerification) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 10 {
|
|
return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
|
|
}
|
|
|
|
vitaID, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid vitaID: %w", err)
|
|
}
|
|
cv.VitaID = vitaID.Uint64()
|
|
|
|
contractor, err := items[1].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid contractor: %w", err)
|
|
}
|
|
cv.Contractor, err = util.Uint160DecodeBytesBE(contractor)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid contractor address: %w", err)
|
|
}
|
|
|
|
platformID, err := items[2].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid platformID: %w", err)
|
|
}
|
|
cv.PlatformID = string(platformID)
|
|
|
|
platform, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid platform: %w", err)
|
|
}
|
|
cv.Platform, err = util.Uint160DecodeBytesBE(platform)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid platform address: %w", err)
|
|
}
|
|
|
|
contractorID, err := items[4].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid contractorID: %w", err)
|
|
}
|
|
cv.ContractorID = string(contractorID)
|
|
|
|
startDate, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid startDate: %w", err)
|
|
}
|
|
cv.StartDate = uint32(startDate.Uint64())
|
|
|
|
endDate, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid endDate: %w", err)
|
|
}
|
|
cv.EndDate = uint32(endDate.Uint64())
|
|
|
|
isActive, err := items[7].TryBool()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid isActive: %w", err)
|
|
}
|
|
cv.IsActive = isActive
|
|
|
|
verifiedAt, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid verifiedAt: %w", err)
|
|
}
|
|
cv.VerifiedAt = uint32(verifiedAt.Uint64())
|
|
|
|
verifiedBy, err := items[9].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid verifiedBy: %w", err)
|
|
}
|
|
cv.VerifiedBy, err = util.Uint160DecodeBytesBE(verifiedBy)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid verifiedBy address: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// InvestmentViolation represents a recorded investment abuse violation.
|
|
type InvestmentViolation struct {
|
|
ID uint64 // Unique violation ID
|
|
VitaID uint64 // Violator's Vita ID
|
|
Violator util.Uint160 // Violator's address
|
|
OpportunityID uint64 // Related opportunity (0 = general)
|
|
ViolationType string // Type of violation
|
|
Description string // Violation description
|
|
EvidenceHash util.Uint256 // Hash of evidence (off-chain)
|
|
Penalty uint64 // VTS penalty applied
|
|
ReportedBy util.Uint160 // Reporter's address
|
|
ReportedAt uint32 // Block height when reported
|
|
ResolvedAt uint32 // Block height when resolved (0 = pending)
|
|
Resolution string // Resolution description
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (v *InvestmentViolation) DecodeBinary(br *io.BinReader) {
|
|
v.ID = br.ReadU64LE()
|
|
v.VitaID = br.ReadU64LE()
|
|
br.ReadBytes(v.Violator[:])
|
|
v.OpportunityID = br.ReadU64LE()
|
|
v.ViolationType = br.ReadString()
|
|
v.Description = br.ReadString()
|
|
br.ReadBytes(v.EvidenceHash[:])
|
|
v.Penalty = br.ReadU64LE()
|
|
br.ReadBytes(v.ReportedBy[:])
|
|
v.ReportedAt = br.ReadU32LE()
|
|
v.ResolvedAt = br.ReadU32LE()
|
|
v.Resolution = br.ReadString()
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (v *InvestmentViolation) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(v.ID)
|
|
bw.WriteU64LE(v.VitaID)
|
|
bw.WriteBytes(v.Violator[:])
|
|
bw.WriteU64LE(v.OpportunityID)
|
|
bw.WriteString(v.ViolationType)
|
|
bw.WriteString(v.Description)
|
|
bw.WriteBytes(v.EvidenceHash[:])
|
|
bw.WriteU64LE(v.Penalty)
|
|
bw.WriteBytes(v.ReportedBy[:])
|
|
bw.WriteU32LE(v.ReportedAt)
|
|
bw.WriteU32LE(v.ResolvedAt)
|
|
bw.WriteString(v.Resolution)
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (v *InvestmentViolation) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(v.ID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(v.VitaID))),
|
|
stackitem.NewByteArray(v.Violator.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(v.OpportunityID))),
|
|
stackitem.NewByteArray([]byte(v.ViolationType)),
|
|
stackitem.NewByteArray([]byte(v.Description)),
|
|
stackitem.NewByteArray(v.EvidenceHash.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(v.Penalty))),
|
|
stackitem.NewByteArray(v.ReportedBy.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(v.ReportedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(v.ResolvedAt))),
|
|
stackitem.NewByteArray([]byte(v.Resolution)),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (v *InvestmentViolation) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 12 {
|
|
return fmt.Errorf("wrong number of elements: expected 12, got %d", len(items))
|
|
}
|
|
|
|
id, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid id: %w", err)
|
|
}
|
|
v.ID = id.Uint64()
|
|
|
|
vitaID, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid vitaID: %w", err)
|
|
}
|
|
v.VitaID = vitaID.Uint64()
|
|
|
|
violator, err := items[2].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid violator: %w", err)
|
|
}
|
|
v.Violator, err = util.Uint160DecodeBytesBE(violator)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid violator address: %w", err)
|
|
}
|
|
|
|
oppID, err := items[3].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid opportunityID: %w", err)
|
|
}
|
|
v.OpportunityID = oppID.Uint64()
|
|
|
|
violationType, err := items[4].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid violationType: %w", err)
|
|
}
|
|
v.ViolationType = string(violationType)
|
|
|
|
description, err := items[5].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid description: %w", err)
|
|
}
|
|
v.Description = string(description)
|
|
|
|
evidenceHash, err := items[6].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid evidenceHash: %w", err)
|
|
}
|
|
v.EvidenceHash, err = util.Uint256DecodeBytesBE(evidenceHash)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid evidenceHash: %w", err)
|
|
}
|
|
|
|
penalty, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid penalty: %w", err)
|
|
}
|
|
v.Penalty = penalty.Uint64()
|
|
|
|
reportedBy, err := items[8].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid reportedBy: %w", err)
|
|
}
|
|
v.ReportedBy, err = util.Uint160DecodeBytesBE(reportedBy)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid reportedBy address: %w", err)
|
|
}
|
|
|
|
reportedAt, err := items[9].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid reportedAt: %w", err)
|
|
}
|
|
v.ReportedAt = uint32(reportedAt.Uint64())
|
|
|
|
resolvedAt, err := items[10].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid resolvedAt: %w", err)
|
|
}
|
|
v.ResolvedAt = uint32(resolvedAt.Uint64())
|
|
|
|
resolution, err := items[11].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid resolution: %w", err)
|
|
}
|
|
v.Resolution = string(resolution)
|
|
|
|
return nil
|
|
}
|
|
|
|
// CollocatioConfig represents configurable parameters for the investment contract.
|
|
type CollocatioConfig struct {
|
|
// Minimum requirements
|
|
MinPIOParticipants uint64 // Default minimum for PIO (default: 100)
|
|
MinEIOParticipants uint64 // Default minimum for EIO (default: 10)
|
|
MinCIOParticipants uint64 // Default minimum for CIO (default: 25)
|
|
|
|
// Investment limits
|
|
DefaultMinInvestment uint64 // Default minimum investment in VTS (default: 100)
|
|
MaxIndividualCap uint64 // Maximum any individual can invest (default: 1000000)
|
|
WealthConcentration uint64 // Max % of pool by single investor (basis points, default: 500 = 5%)
|
|
|
|
// Fees
|
|
CreationFee uint64 // VTS fee to create opportunity
|
|
InvestmentFee uint64 // Fee per investment (basis points, default: 50 = 0.5%)
|
|
WithdrawalPenalty uint64 // Early withdrawal penalty (basis points, default: 200 = 2%)
|
|
|
|
// Timing
|
|
MinVotingPeriod uint32 // Minimum blocks for voting (default: 10000)
|
|
MinInvestmentPeriod uint32 // Minimum blocks for investment window (default: 20000)
|
|
MinMaturityPeriod uint32 // Minimum blocks until maturity (default: 50000)
|
|
|
|
// Violation thresholds
|
|
MaxViolationsBeforeBan uint8 // Violations before permanent ban (default: 3)
|
|
ViolationCooldown uint32 // Blocks before violation expires (default: 1000000)
|
|
|
|
// Commit-reveal timing (anti-front-running)
|
|
CommitRevealDelay uint32 // Min blocks between commit and reveal (default: 10)
|
|
CommitRevealWindow uint32 // Max blocks to reveal after delay (default: 1000)
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (c *CollocatioConfig) DecodeBinary(br *io.BinReader) {
|
|
c.MinPIOParticipants = br.ReadU64LE()
|
|
c.MinEIOParticipants = br.ReadU64LE()
|
|
c.MinCIOParticipants = br.ReadU64LE()
|
|
c.DefaultMinInvestment = br.ReadU64LE()
|
|
c.MaxIndividualCap = br.ReadU64LE()
|
|
c.WealthConcentration = br.ReadU64LE()
|
|
c.CreationFee = br.ReadU64LE()
|
|
c.InvestmentFee = br.ReadU64LE()
|
|
c.WithdrawalPenalty = br.ReadU64LE()
|
|
c.MinVotingPeriod = br.ReadU32LE()
|
|
c.MinInvestmentPeriod = br.ReadU32LE()
|
|
c.MinMaturityPeriod = br.ReadU32LE()
|
|
c.MaxViolationsBeforeBan = br.ReadB()
|
|
c.ViolationCooldown = br.ReadU32LE()
|
|
c.CommitRevealDelay = br.ReadU32LE()
|
|
c.CommitRevealWindow = br.ReadU32LE()
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (c *CollocatioConfig) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(c.MinPIOParticipants)
|
|
bw.WriteU64LE(c.MinEIOParticipants)
|
|
bw.WriteU64LE(c.MinCIOParticipants)
|
|
bw.WriteU64LE(c.DefaultMinInvestment)
|
|
bw.WriteU64LE(c.MaxIndividualCap)
|
|
bw.WriteU64LE(c.WealthConcentration)
|
|
bw.WriteU64LE(c.CreationFee)
|
|
bw.WriteU64LE(c.InvestmentFee)
|
|
bw.WriteU64LE(c.WithdrawalPenalty)
|
|
bw.WriteU32LE(c.MinVotingPeriod)
|
|
bw.WriteU32LE(c.MinInvestmentPeriod)
|
|
bw.WriteU32LE(c.MinMaturityPeriod)
|
|
bw.WriteB(c.MaxViolationsBeforeBan)
|
|
bw.WriteU32LE(c.ViolationCooldown)
|
|
bw.WriteU32LE(c.CommitRevealDelay)
|
|
bw.WriteU32LE(c.CommitRevealWindow)
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (c *CollocatioConfig) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MinPIOParticipants))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MinEIOParticipants))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MinCIOParticipants))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultMinInvestment))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MaxIndividualCap))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.WealthConcentration))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.CreationFee))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.InvestmentFee))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.WithdrawalPenalty))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MinVotingPeriod))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MinInvestmentPeriod))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MinMaturityPeriod))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.MaxViolationsBeforeBan))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.ViolationCooldown))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.CommitRevealDelay))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.CommitRevealWindow))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (c *CollocatioConfig) FromStackItem(item stackitem.Item) error {
|
|
items, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return errors.New("not a struct")
|
|
}
|
|
if len(items) != 16 {
|
|
return fmt.Errorf("wrong number of elements: expected 16, got %d", len(items))
|
|
}
|
|
|
|
minPIO, err := items[0].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minPIOParticipants: %w", err)
|
|
}
|
|
c.MinPIOParticipants = minPIO.Uint64()
|
|
|
|
minEIO, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minEIOParticipants: %w", err)
|
|
}
|
|
c.MinEIOParticipants = minEIO.Uint64()
|
|
|
|
minCIO, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minCIOParticipants: %w", err)
|
|
}
|
|
c.MinCIOParticipants = minCIO.Uint64()
|
|
|
|
defaultMin, err := items[3].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid defaultMinInvestment: %w", err)
|
|
}
|
|
c.DefaultMinInvestment = defaultMin.Uint64()
|
|
|
|
maxCap, err := items[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid maxIndividualCap: %w", err)
|
|
}
|
|
c.MaxIndividualCap = maxCap.Uint64()
|
|
|
|
wealthConc, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid wealthConcentration: %w", err)
|
|
}
|
|
c.WealthConcentration = wealthConc.Uint64()
|
|
|
|
creationFee, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid creationFee: %w", err)
|
|
}
|
|
c.CreationFee = creationFee.Uint64()
|
|
|
|
investmentFee, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investmentFee: %w", err)
|
|
}
|
|
c.InvestmentFee = investmentFee.Uint64()
|
|
|
|
withdrawalPenalty, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid withdrawalPenalty: %w", err)
|
|
}
|
|
c.WithdrawalPenalty = withdrawalPenalty.Uint64()
|
|
|
|
minVoting, err := items[9].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minVotingPeriod: %w", err)
|
|
}
|
|
c.MinVotingPeriod = uint32(minVoting.Uint64())
|
|
|
|
minInvestment, err := items[10].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minInvestmentPeriod: %w", err)
|
|
}
|
|
c.MinInvestmentPeriod = uint32(minInvestment.Uint64())
|
|
|
|
minMaturity, err := items[11].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid minMaturityPeriod: %w", err)
|
|
}
|
|
c.MinMaturityPeriod = uint32(minMaturity.Uint64())
|
|
|
|
maxViolations, err := items[12].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid maxViolationsBeforeBan: %w", err)
|
|
}
|
|
c.MaxViolationsBeforeBan = uint8(maxViolations.Uint64())
|
|
|
|
cooldown, err := items[13].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid violationCooldown: %w", err)
|
|
}
|
|
c.ViolationCooldown = uint32(cooldown.Uint64())
|
|
|
|
commitDelay, err := items[14].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid commitRevealDelay: %w", err)
|
|
}
|
|
c.CommitRevealDelay = uint32(commitDelay.Uint64())
|
|
|
|
commitWindow, err := items[15].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid commitRevealWindow: %w", err)
|
|
}
|
|
c.CommitRevealWindow = uint32(commitWindow.Uint64())
|
|
|
|
return nil
|
|
}
|
|
|
|
// DefaultCollocatioConfig returns the default configuration.
|
|
func DefaultCollocatioConfig() CollocatioConfig {
|
|
return CollocatioConfig{
|
|
MinPIOParticipants: 100,
|
|
MinEIOParticipants: 10,
|
|
MinCIOParticipants: 25,
|
|
DefaultMinInvestment: 100_00000000, // 100 VTS
|
|
MaxIndividualCap: 1_000_000_00000000, // 1M VTS
|
|
WealthConcentration: 500, // 5%
|
|
CreationFee: 1000_00000000, // 1000 VTS
|
|
InvestmentFee: 50, // 0.5%
|
|
WithdrawalPenalty: 200, // 2%
|
|
MinVotingPeriod: 10000, // ~10000 blocks
|
|
MinInvestmentPeriod: 20000, // ~20000 blocks
|
|
MinMaturityPeriod: 50000, // ~50000 blocks
|
|
MaxViolationsBeforeBan: 3,
|
|
ViolationCooldown: 1000000, // ~1M blocks
|
|
CommitRevealDelay: 10, // ~10 blocks minimum between commit and reveal
|
|
CommitRevealWindow: 1000, // ~1000 blocks to reveal after delay
|
|
}
|
|
}
|
|
|
|
// CommitmentStatus represents the status of an investment commitment.
|
|
type CommitmentStatus uint8
|
|
|
|
// Commitment statuses.
|
|
const (
|
|
CommitmentPending CommitmentStatus = 0 // Awaiting reveal
|
|
CommitmentRevealed CommitmentStatus = 1 // Successfully revealed and invested
|
|
CommitmentExpired CommitmentStatus = 2 // Expired without reveal
|
|
CommitmentCanceled CommitmentStatus = 3 // Canceled by investor
|
|
)
|
|
|
|
// InvestmentCommitment represents a commit-reveal commitment for investment.
|
|
// This prevents front-running attacks by hiding the investment amount until reveal.
|
|
type InvestmentCommitment struct {
|
|
ID uint64 // Unique commitment ID
|
|
OpportunityID uint64 // Opportunity being invested in
|
|
VitaID uint64 // Investor's Vita ID
|
|
Investor util.Uint160 // Investor's address
|
|
Commitment util.Uint256 // hash(amount || nonce || investor)
|
|
Status CommitmentStatus // Current status
|
|
CommittedAt uint32 // Block height when committed
|
|
RevealDeadline uint32 // Block height by which reveal must occur
|
|
RevealedAmount uint64 // Amount revealed (0 until revealed)
|
|
InvestmentID uint64 // Resulting investment ID (0 until revealed)
|
|
}
|
|
|
|
// DecodeBinary implements the io.Serializable interface.
|
|
func (c *InvestmentCommitment) DecodeBinary(br *io.BinReader) {
|
|
c.ID = br.ReadU64LE()
|
|
c.OpportunityID = br.ReadU64LE()
|
|
c.VitaID = br.ReadU64LE()
|
|
br.ReadBytes(c.Investor[:])
|
|
br.ReadBytes(c.Commitment[:])
|
|
c.Status = CommitmentStatus(br.ReadB())
|
|
c.CommittedAt = br.ReadU32LE()
|
|
c.RevealDeadline = br.ReadU32LE()
|
|
c.RevealedAmount = br.ReadU64LE()
|
|
c.InvestmentID = br.ReadU64LE()
|
|
}
|
|
|
|
// EncodeBinary implements the io.Serializable interface.
|
|
func (c *InvestmentCommitment) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteU64LE(c.ID)
|
|
bw.WriteU64LE(c.OpportunityID)
|
|
bw.WriteU64LE(c.VitaID)
|
|
bw.WriteBytes(c.Investor[:])
|
|
bw.WriteBytes(c.Commitment[:])
|
|
bw.WriteB(byte(c.Status))
|
|
bw.WriteU32LE(c.CommittedAt)
|
|
bw.WriteU32LE(c.RevealDeadline)
|
|
bw.WriteU64LE(c.RevealedAmount)
|
|
bw.WriteU64LE(c.InvestmentID)
|
|
}
|
|
|
|
// ToStackItem implements stackitem.Convertible interface.
|
|
func (c *InvestmentCommitment) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.ID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.OpportunityID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.VitaID))),
|
|
stackitem.NewByteArray(c.Investor.BytesBE()),
|
|
stackitem.NewByteArray(c.Commitment.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.Status))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.CommittedAt))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.RevealDeadline))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.RevealedAmount))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(c.InvestmentID))),
|
|
}), nil
|
|
}
|
|
|
|
// FromStackItem implements stackitem.Convertible interface.
|
|
func (c *InvestmentCommitment) 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()
|
|
|
|
oppID, err := items[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid opportunityID: %w", err)
|
|
}
|
|
c.OpportunityID = oppID.Uint64()
|
|
|
|
vitaID, err := items[2].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid vitaID: %w", err)
|
|
}
|
|
c.VitaID = vitaID.Uint64()
|
|
|
|
investor, err := items[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investor: %w", err)
|
|
}
|
|
c.Investor, err = util.Uint160DecodeBytesBE(investor)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investor address: %w", err)
|
|
}
|
|
|
|
commitment, err := items[4].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid commitment: %w", err)
|
|
}
|
|
c.Commitment, err = util.Uint256DecodeBytesBE(commitment)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid commitment hash: %w", err)
|
|
}
|
|
|
|
status, err := items[5].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid status: %w", err)
|
|
}
|
|
c.Status = CommitmentStatus(status.Uint64())
|
|
|
|
committedAt, err := items[6].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid committedAt: %w", err)
|
|
}
|
|
c.CommittedAt = uint32(committedAt.Uint64())
|
|
|
|
revealDeadline, err := items[7].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid revealDeadline: %w", err)
|
|
}
|
|
c.RevealDeadline = uint32(revealDeadline.Uint64())
|
|
|
|
revealedAmount, err := items[8].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid revealedAmount: %w", err)
|
|
}
|
|
c.RevealedAmount = revealedAmount.Uint64()
|
|
|
|
investmentID, err := items[9].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("invalid investmentID: %w", err)
|
|
}
|
|
c.InvestmentID = investmentID.Uint64()
|
|
|
|
return nil
|
|
}
|