2024-05-12 22:47:24 +00:00
|
|
|
package voting
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"code.c-base.org/baccenfutter/govote/voting/quorum"
|
|
|
|
"code.c-base.org/baccenfutter/govote/voting/threshold"
|
|
|
|
"code.c-base.org/baccenfutter/govote/voting/vote"
|
|
|
|
"robpike.io/filter"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
Voting struct {
|
2024-05-13 08:45:38 +00:00
|
|
|
id string
|
2024-05-12 22:47:24 +00:00
|
|
|
referendum string
|
|
|
|
deadline time.Time
|
|
|
|
quorum quorum.Quorum
|
|
|
|
threshold threshold.Threshold
|
|
|
|
electors []string
|
2024-05-13 08:45:38 +00:00
|
|
|
annonymous bool
|
2024-05-12 22:47:24 +00:00
|
|
|
votes []vote.Vote
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewVoting(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool) *Voting {
|
|
|
|
return &Voting{
|
2024-05-13 08:45:38 +00:00
|
|
|
id: id,
|
2024-05-12 22:47:24 +00:00
|
|
|
referendum: r,
|
2024-05-13 08:45:38 +00:00
|
|
|
deadline: d,
|
|
|
|
quorum: q,
|
|
|
|
threshold: t,
|
|
|
|
electors: e,
|
|
|
|
annonymous: a,
|
|
|
|
votes: []vote.Vote{},
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewVotingWithVotes(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool, v []vote.Vote) *Voting {
|
|
|
|
voting := NewVoting(id, r, d, q, t, e, a)
|
|
|
|
for i := range v {
|
2024-05-13 08:45:38 +00:00
|
|
|
voting.votes = append(voting.votes, v[i])
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
return voting
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) String() string {
|
2024-05-13 08:45:38 +00:00
|
|
|
var (
|
|
|
|
possibleVotes int = len(v.electors)
|
|
|
|
totalVotes int = len(v.Votes())
|
|
|
|
yesVotes int = len(v.yesVotes())
|
|
|
|
noVotes int = len(v.noVotes())
|
|
|
|
deadlineStatus string = "🎭 ONGOING 🎭"
|
|
|
|
quorumStatus string = "❌ FAIL"
|
|
|
|
thresholdStatus string = "❌ FAIL"
|
|
|
|
out string = ""
|
|
|
|
)
|
|
|
|
|
|
|
|
quorumSatisfied := v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
|
|
|
thresholdSatisfied := v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
|
|
|
votingSatisfied := quorumSatisfied && thresholdSatisfied
|
|
|
|
|
|
|
|
if time.Now().UTC().After(v.deadline) {
|
|
|
|
if votingSatisfied {
|
|
|
|
deadlineStatus = "✅ APROVED ✅"
|
|
|
|
} else {
|
|
|
|
deadlineStatus = "❌ REJECTED ❌"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if v.quorum.IsSatisfied(possibleVotes, totalVotes) {
|
|
|
|
quorumStatus = "✅ PASS"
|
|
|
|
}
|
|
|
|
if v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes) {
|
|
|
|
thresholdStatus = "✅ PASS"
|
|
|
|
}
|
2024-05-12 22:47:24 +00:00
|
|
|
|
|
|
|
out += fmt.Sprintf("Referendum: %s\n", strings.ToUpper(v.referendum))
|
|
|
|
out += fmt.Sprintf("Deadline : %s UTC\n", v.deadline.Format(time.DateTime))
|
2024-05-13 08:45:38 +00:00
|
|
|
out += fmt.Sprintf("Quorum : %s (%d/%d) (required: %s)\n", quorumStatus, totalVotes, possibleVotes, v.quorum)
|
|
|
|
out += fmt.Sprintf("Threshold : %s (%d/%d) (required: %s)\n", thresholdStatus, yesVotes, totalVotes, v.threshold)
|
|
|
|
out += fmt.Sprintf("Electors : [ %d ] %s\n", len(v.electors), v.electors)
|
|
|
|
out += fmt.Sprintf(
|
|
|
|
"Votes : [ %d | %d | %d ] (❎|❌|❔)\n",
|
|
|
|
len(v.yesVotes()),
|
|
|
|
len(v.noVotes()),
|
|
|
|
len(v.abstainVotes()),
|
|
|
|
)
|
|
|
|
out += fmt.Sprintf("Status : %s\n", deadlineStatus)
|
|
|
|
|
|
|
|
if !v.annonymous {
|
|
|
|
out += "\n"
|
|
|
|
if v.votes != nil && len(v.votes) > 0 {
|
|
|
|
for _, _v := range v.votes {
|
|
|
|
out += fmt.Sprintf("💬 %s\n", _v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-05-12 22:47:24 +00:00
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) ID() string {
|
2024-05-13 08:45:38 +00:00
|
|
|
return v.id
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) Referendum() string {
|
|
|
|
return v.referendum
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) Deadline() time.Time {
|
2024-05-13 08:45:38 +00:00
|
|
|
return v.deadline
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) Quorum() string {
|
2024-05-13 08:45:38 +00:00
|
|
|
return v.quorum.String()
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) Threshold() string {
|
2024-05-13 08:45:38 +00:00
|
|
|
return v.threshold.String()
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) Electors() []string {
|
|
|
|
electors := make([]string, len(v.electors))
|
|
|
|
for i := range v.electors {
|
|
|
|
electors[i] = v.electors[i]
|
|
|
|
}
|
|
|
|
return electors
|
|
|
|
}
|
|
|
|
|
2024-05-13 10:46:13 +00:00
|
|
|
func (v Voting) Anonymous() bool {
|
2024-05-13 11:20:26 +00:00
|
|
|
return v.annonymous
|
2024-05-13 10:46:13 +00:00
|
|
|
}
|
|
|
|
|
2024-05-12 22:47:24 +00:00
|
|
|
func (v Voting) IsOpen() bool {
|
2024-05-13 08:45:38 +00:00
|
|
|
return v.deadline.After(time.Now().UTC())
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) Result() Result {
|
2024-05-13 08:45:38 +00:00
|
|
|
var (
|
|
|
|
possibleVotes = len(v.electors)
|
|
|
|
totalVotes = len(v.Votes())
|
|
|
|
yesVotes = len(v.yesVotes())
|
|
|
|
noVotes = len(v.noVotes())
|
|
|
|
votes []vote.Vote
|
|
|
|
|
|
|
|
quorumSatisfied = v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
|
|
|
thresholdSatisfied = v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
|
|
|
)
|
|
|
|
|
|
|
|
if !v.annonymous {
|
|
|
|
votes = v.Votes()
|
|
|
|
}
|
|
|
|
|
|
|
|
return Result{
|
|
|
|
Quorum: quorumSatisfied,
|
|
|
|
Threshold: thresholdSatisfied,
|
|
|
|
Yes: len(v.yesVotes()),
|
|
|
|
No: len(v.noVotes()),
|
|
|
|
Abstain: len(v.Votes()) - len(v.yesVotes()) - len(v.noVotes()),
|
|
|
|
Votes: votes,
|
|
|
|
}
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Voting) Vote(vote vote.Vote) error {
|
2024-05-13 08:45:38 +00:00
|
|
|
if time.Now().UTC().After(v.deadline) {
|
|
|
|
return fmt.Errorf("deadline has passed")
|
|
|
|
}
|
|
|
|
for _, elector := range v.electors {
|
|
|
|
if elector == vote.Elector {
|
|
|
|
v.votes = append(v.votes, vote)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("not eligable to vote: %s", vote.Elector)
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) Votes() []vote.Vote {
|
|
|
|
votes := []vote.Vote{}
|
|
|
|
nextVote:
|
2024-05-13 08:45:38 +00:00
|
|
|
for i := len(v.votes) - 1; i >= 0; i-- {
|
|
|
|
elector := v.votes[i].Elector
|
|
|
|
for _, e := range votes {
|
|
|
|
if e.Elector == elector {
|
|
|
|
continue nextVote
|
|
|
|
}
|
|
|
|
}
|
|
|
|
votes = append(votes, v.votes[i])
|
|
|
|
}
|
2024-05-12 22:47:24 +00:00
|
|
|
return votes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) yesVotes() []vote.Vote {
|
2024-05-13 08:45:38 +00:00
|
|
|
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Yes }
|
|
|
|
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) noVotes() []vote.Vote {
|
2024-05-13 08:45:38 +00:00
|
|
|
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.No }
|
|
|
|
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v Voting) abstainVotes() []vote.Vote {
|
2024-05-13 08:45:38 +00:00
|
|
|
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Abstain }
|
|
|
|
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
2024-05-12 22:47:24 +00:00
|
|
|
}
|