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

420 lines
12 KiB
Go
Executable File

package state
import (
"errors"
"math/big"
"git.marketally.com/tutus-one/tutus-chain/pkg/util"
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// ProposalCategory represents the type of proposal.
type ProposalCategory uint8
// Proposal categories.
const (
ProposalCategoryLawAmendment ProposalCategory = 1 // Lex integration
ProposalCategoryInvestment ProposalCategory = 2 // PIO/EIO/CIO (future)
ProposalCategoryGovernanceAction ProposalCategory = 3 // Committee/policy changes
ProposalCategoryConstitutional ProposalCategory = 4 // Supermajority required
ProposalCategoryReferendum ProposalCategory = 5 // Citizen-initiated
)
// ProposalStatus represents the lifecycle status of a proposal.
type ProposalStatus uint8
// Proposal statuses.
const (
ProposalStatusDraft ProposalStatus = 0 // Not yet open for voting
ProposalStatusActive ProposalStatus = 1 // Open for voting
ProposalStatusPassed ProposalStatus = 2 // Met threshold, awaiting execution
ProposalStatusRejected ProposalStatus = 3 // Failed to meet threshold
ProposalStatusExecuted ProposalStatus = 4 // Successfully executed
ProposalStatusCancelled ProposalStatus = 5 // Cancelled by proposer/committee
ProposalStatusExpired ProposalStatus = 6 // Voting period ended without quorum
)
// VoteChoice represents a voting decision.
type VoteChoice uint8
// Vote choices.
const (
VoteChoiceAbstain VoteChoice = 0
VoteChoiceYes VoteChoice = 1
VoteChoiceNo VoteChoice = 2
)
// Proposal represents a democratic proposal in the Eligere voting system.
type Proposal struct {
ID uint64 // Unique proposal ID
Title string // Short title (max 128 chars)
ContentHash util.Uint256 // Hash of full proposal content (stored off-chain)
Category ProposalCategory // Type of proposal
Proposer util.Uint160 // Who created the proposal
ProposerVitaID uint64 // Vita ID of proposer
// Timing (block heights)
CreatedAt uint32 // When created
VotingStartsAt uint32 // When voting opens
VotingEndsAt uint32 // elegireDeadline - when voting closes
ExecutionDelay uint32 // Blocks after passing before execution allowed
// Thresholds
QuorumPercent uint8 // Minimum participation percentage (e.g., 10%)
ThresholdPercent uint8 // Required yes votes percentage (50% or 67%)
// Results
Status ProposalStatus
TotalVotes uint64 // Total votes cast
YesVotes uint64 // Votes in favor
NoVotes uint64 // Votes against
AbstainVotes uint64 // Abstentions (count toward quorum)
// Execution target (for contract integrations like Lex)
TargetContract util.Uint160 // Contract to call on execution
TargetMethod string // Method to call
TargetParams []byte // Serialized parameters
// Execution tracking
ExecutedAt uint32 // When executed (0 if not)
ExecutedBy util.Uint160 // Who executed
}
// ToStackItem implements stackitem.Convertible interface.
func (p *Proposal) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(p.ID))),
stackitem.NewByteArray([]byte(p.Title)),
stackitem.NewByteArray(p.ContentHash[:]),
stackitem.NewBigInteger(big.NewInt(int64(p.Category))),
stackitem.NewByteArray(p.Proposer.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(p.ProposerVitaID))),
stackitem.NewBigInteger(big.NewInt(int64(p.CreatedAt))),
stackitem.NewBigInteger(big.NewInt(int64(p.VotingStartsAt))),
stackitem.NewBigInteger(big.NewInt(int64(p.VotingEndsAt))),
stackitem.NewBigInteger(big.NewInt(int64(p.ExecutionDelay))),
stackitem.NewBigInteger(big.NewInt(int64(p.QuorumPercent))),
stackitem.NewBigInteger(big.NewInt(int64(p.ThresholdPercent))),
stackitem.NewBigInteger(big.NewInt(int64(p.Status))),
stackitem.NewBigInteger(big.NewInt(int64(p.TotalVotes))),
stackitem.NewBigInteger(big.NewInt(int64(p.YesVotes))),
stackitem.NewBigInteger(big.NewInt(int64(p.NoVotes))),
stackitem.NewBigInteger(big.NewInt(int64(p.AbstainVotes))),
stackitem.NewByteArray(p.TargetContract.BytesBE()),
stackitem.NewByteArray([]byte(p.TargetMethod)),
stackitem.NewByteArray(p.TargetParams),
stackitem.NewBigInteger(big.NewInt(int64(p.ExecutedAt))),
stackitem.NewByteArray(p.ExecutedBy.BytesBE()),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (p *Proposal) FromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(arr) < 22 {
return errors.New("invalid proposal struct length")
}
id, err := arr[0].TryInteger()
if err != nil {
return err
}
p.ID = id.Uint64()
titleBytes, err := arr[1].TryBytes()
if err != nil {
return err
}
p.Title = string(titleBytes)
contentHashBytes, err := arr[2].TryBytes()
if err != nil {
return err
}
if len(contentHashBytes) == 32 {
copy(p.ContentHash[:], contentHashBytes)
}
category, err := arr[3].TryInteger()
if err != nil {
return err
}
p.Category = ProposalCategory(category.Uint64())
proposerBytes, err := arr[4].TryBytes()
if err != nil {
return err
}
p.Proposer, _ = util.Uint160DecodeBytesBE(proposerBytes)
proposerVitaID, err := arr[5].TryInteger()
if err != nil {
return err
}
p.ProposerVitaID = proposerVitaID.Uint64()
createdAt, err := arr[6].TryInteger()
if err != nil {
return err
}
p.CreatedAt = uint32(createdAt.Uint64())
votingStartsAt, err := arr[7].TryInteger()
if err != nil {
return err
}
p.VotingStartsAt = uint32(votingStartsAt.Uint64())
votingEndsAt, err := arr[8].TryInteger()
if err != nil {
return err
}
p.VotingEndsAt = uint32(votingEndsAt.Uint64())
executionDelay, err := arr[9].TryInteger()
if err != nil {
return err
}
p.ExecutionDelay = uint32(executionDelay.Uint64())
quorumPercent, err := arr[10].TryInteger()
if err != nil {
return err
}
p.QuorumPercent = uint8(quorumPercent.Uint64())
thresholdPercent, err := arr[11].TryInteger()
if err != nil {
return err
}
p.ThresholdPercent = uint8(thresholdPercent.Uint64())
status, err := arr[12].TryInteger()
if err != nil {
return err
}
p.Status = ProposalStatus(status.Uint64())
totalVotes, err := arr[13].TryInteger()
if err != nil {
return err
}
p.TotalVotes = totalVotes.Uint64()
yesVotes, err := arr[14].TryInteger()
if err != nil {
return err
}
p.YesVotes = yesVotes.Uint64()
noVotes, err := arr[15].TryInteger()
if err != nil {
return err
}
p.NoVotes = noVotes.Uint64()
abstainVotes, err := arr[16].TryInteger()
if err != nil {
return err
}
p.AbstainVotes = abstainVotes.Uint64()
targetContractBytes, err := arr[17].TryBytes()
if err != nil {
return err
}
p.TargetContract, _ = util.Uint160DecodeBytesBE(targetContractBytes)
targetMethodBytes, err := arr[18].TryBytes()
if err != nil {
return err
}
p.TargetMethod = string(targetMethodBytes)
targetParams, err := arr[19].TryBytes()
if err != nil {
return err
}
p.TargetParams = targetParams
executedAt, err := arr[20].TryInteger()
if err != nil {
return err
}
p.ExecutedAt = uint32(executedAt.Uint64())
executedByBytes, err := arr[21].TryBytes()
if err != nil {
return err
}
p.ExecutedBy, _ = util.Uint160DecodeBytesBE(executedByBytes)
return nil
}
// Vote represents a single vote record in the Eligere voting system.
type Vote struct {
ProposalID uint64 // Which proposal
VoterVitaID uint64 // Vita ID of voter (ensures one-person-one-vote)
Voter util.Uint160 // Voter's address
Choice VoteChoice // How they voted
VotedAt uint32 // Block height when voted
Weight uint64 // Vote weight (1 for equal voting)
}
// ToStackItem implements stackitem.Convertible interface.
func (v *Vote) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(v.ProposalID))),
stackitem.NewBigInteger(big.NewInt(int64(v.VoterVitaID))),
stackitem.NewByteArray(v.Voter.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(v.Choice))),
stackitem.NewBigInteger(big.NewInt(int64(v.VotedAt))),
stackitem.NewBigInteger(big.NewInt(int64(v.Weight))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (v *Vote) FromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(arr) < 6 {
return errors.New("invalid vote struct length")
}
proposalID, err := arr[0].TryInteger()
if err != nil {
return err
}
v.ProposalID = proposalID.Uint64()
voterVitaID, err := arr[1].TryInteger()
if err != nil {
return err
}
v.VoterVitaID = voterVitaID.Uint64()
voterBytes, err := arr[2].TryBytes()
if err != nil {
return err
}
v.Voter, _ = util.Uint160DecodeBytesBE(voterBytes)
choice, err := arr[3].TryInteger()
if err != nil {
return err
}
v.Choice = VoteChoice(choice.Uint64())
votedAt, err := arr[4].TryInteger()
if err != nil {
return err
}
v.VotedAt = uint32(votedAt.Uint64())
weight, err := arr[5].TryInteger()
if err != nil {
return err
}
v.Weight = weight.Uint64()
return nil
}
// EligereConfig represents configurable parameters for the Eligere voting system.
type EligereConfig struct {
DefaultVotingPeriod uint32 // Default blocks for voting (~7 days at 1s blocks = 604800)
MinVotingPeriod uint32 // Minimum blocks (~1 day = 86400)
MaxVotingPeriod uint32 // Maximum blocks (~30 days = 2592000)
DefaultExecutionDelay uint32 // Blocks between passing and execution (~1 day = 86400)
DefaultQuorum uint8 // Default quorum percentage (e.g., 10%)
ConstitutionalThreshold uint8 // Supermajority threshold (e.g., 67%)
StandardThreshold uint8 // Simple majority (e.g., 51%)
}
// ToStackItem implements stackitem.Convertible interface.
func (c *EligereConfig) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultVotingPeriod))),
stackitem.NewBigInteger(big.NewInt(int64(c.MinVotingPeriod))),
stackitem.NewBigInteger(big.NewInt(int64(c.MaxVotingPeriod))),
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultExecutionDelay))),
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultQuorum))),
stackitem.NewBigInteger(big.NewInt(int64(c.ConstitutionalThreshold))),
stackitem.NewBigInteger(big.NewInt(int64(c.StandardThreshold))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (c *EligereConfig) FromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(arr) < 7 {
return errors.New("invalid config struct length")
}
defaultVotingPeriod, err := arr[0].TryInteger()
if err != nil {
return err
}
c.DefaultVotingPeriod = uint32(defaultVotingPeriod.Uint64())
minVotingPeriod, err := arr[1].TryInteger()
if err != nil {
return err
}
c.MinVotingPeriod = uint32(minVotingPeriod.Uint64())
maxVotingPeriod, err := arr[2].TryInteger()
if err != nil {
return err
}
c.MaxVotingPeriod = uint32(maxVotingPeriod.Uint64())
defaultExecutionDelay, err := arr[3].TryInteger()
if err != nil {
return err
}
c.DefaultExecutionDelay = uint32(defaultExecutionDelay.Uint64())
defaultQuorum, err := arr[4].TryInteger()
if err != nil {
return err
}
c.DefaultQuorum = uint8(defaultQuorum.Uint64())
constitutionalThreshold, err := arr[5].TryInteger()
if err != nil {
return err
}
c.ConstitutionalThreshold = uint8(constitutionalThreshold.Uint64())
standardThreshold, err := arr[6].TryInteger()
if err != nil {
return err
}
c.StandardThreshold = uint8(standardThreshold.Uint64())
return nil
}
// DefaultEligereConfig returns the default configuration for Eligere.
func DefaultEligereConfig() *EligereConfig {
return &EligereConfig{
DefaultVotingPeriod: 604800, // ~7 days at 1 block/second
MinVotingPeriod: 86400, // ~1 day minimum
MaxVotingPeriod: 2592000, // ~30 days maximum
DefaultExecutionDelay: 86400, // ~1 day delay after passing
DefaultQuorum: 10, // 10% participation required
ConstitutionalThreshold: 67, // 67% supermajority for constitutional
StandardThreshold: 51, // 51% simple majority
}
}