record.gno

5.46 Kb ยท 195 lines
  1package commondao
  2
  3import (
  4	"errors"
  5	"math"
  6	"std"
  7
  8	"gno.land/p/nt/avl"
  9)
 10
 11// ErrVoteExists indicates that a user already voted.
 12var ErrVoteExists = errors.New("user already voted")
 13
 14type (
 15	// VoteIterFn defines a callback to iterate votes.
 16	VoteIterFn func(Vote) (stop bool)
 17
 18	// VotesCountIterFn defines a callback to iterate voted choices.
 19	VotesCountIterFn func(_ VoteChoice, voteCount int) (stop bool)
 20
 21	// Vote defines a single vote.
 22	Vote struct {
 23		// Address is the address of the user that this vote belons to.
 24		Address std.Address
 25
 26		// Choice contains the voted choice.
 27		Choice VoteChoice
 28
 29		// Reason contains an optional reason for the vote.
 30		Reason string
 31
 32		// Context can store any custom voting values related to the vote.
 33		//
 34		// Warning: When using context be careful if references/pointers are
 35		// assigned to it because they could potentially be accessed anywhere,
 36		// which could lead to unwanted indirect modifications.
 37		Context any
 38	}
 39)
 40
 41// ReadonlyVotingRecord defines an read only voting record.
 42type ReadonlyVotingRecord struct {
 43	votes avl.Tree // string(address) -> Vote
 44	count avl.Tree // string(choice) -> int
 45}
 46
 47// Size returns the total number of votes that record contains.
 48func (r ReadonlyVotingRecord) Size() int {
 49	return r.votes.Size()
 50}
 51
 52// Iterate iterates voting record votes.
 53func (r ReadonlyVotingRecord) Iterate(offset, count int, reverse bool, fn VoteIterFn) bool {
 54	cb := func(_ string, v any) bool { return fn(v.(Vote)) }
 55	if reverse {
 56		return r.votes.ReverseIterateByOffset(offset, count, cb)
 57	}
 58	return r.votes.IterateByOffset(offset, count, cb)
 59}
 60
 61// IterateVotesCount iterates voted choices with the amount of votes submited for each.
 62func (r ReadonlyVotingRecord) IterateVotesCount(fn VotesCountIterFn) bool {
 63	return r.count.Iterate("", "", func(k string, v any) bool {
 64		return fn(VoteChoice(k), v.(int))
 65	})
 66}
 67
 68// VoteCount returns the number of votes for a single voting choice.
 69func (r ReadonlyVotingRecord) VoteCount(c VoteChoice) int {
 70	if v, found := r.count.Get(string(c)); found {
 71		return v.(int)
 72	}
 73	return 0
 74}
 75
 76// HasVoted checks if an account already voted.
 77func (r ReadonlyVotingRecord) HasVoted(user std.Address) bool {
 78	return r.votes.Has(user.String())
 79}
 80
 81// GetVote returns a vote.
 82func (r ReadonlyVotingRecord) GetVote(user std.Address) (_ Vote, found bool) {
 83	if v, found := r.votes.Get(user.String()); found {
 84		return v.(Vote), true
 85	}
 86	return Vote{}, false
 87}
 88
 89// VotingRecord stores accounts that voted and vote choices.
 90type VotingRecord struct {
 91	ReadonlyVotingRecord
 92}
 93
 94// Readonly returns a read only voting record.
 95func (r VotingRecord) Readonly() ReadonlyVotingRecord {
 96	return r.ReadonlyVotingRecord
 97}
 98
 99// AddVote adds a vote to the voting record.
100// If a vote for the same user already exists is overwritten.
101func (r *VotingRecord) AddVote(vote Vote) (updated bool) {
102	// Get previous member vote if it exists
103	v, _ := r.votes.Get(vote.Address.String())
104
105	// When a previous vote exists update counter for the previous choice
106	updated = r.votes.Set(vote.Address.String(), vote)
107	if updated {
108		prev := v.(Vote)
109		r.count.Set(string(prev.Choice), r.VoteCount(prev.Choice)-1)
110	}
111
112	r.count.Set(string(vote.Choice), r.VoteCount(vote.Choice)+1)
113	return
114}
115
116// FindMostVotedChoice returns the most voted choice.
117// ChoiceNone is returned when there is a tie between different
118// voting choices or when the voting record has are no votes.
119func FindMostVotedChoice(r ReadonlyVotingRecord) VoteChoice {
120	var (
121		choice                  VoteChoice
122		currentCount, prevCount int
123	)
124
125	r.IterateVotesCount(func(c VoteChoice, count int) bool {
126		if currentCount <= count {
127			choice = c
128			prevCount = currentCount
129			currentCount = count
130		}
131		return false
132	})
133
134	if prevCount < currentCount {
135		return choice
136	}
137	return ChoiceNone
138}
139
140// SelectChoiceByAbsoluteMajority select the vote choice by absolute majority.
141// Vote choice is a majority when chosen by more than half of the votes.
142// Absolute majority considers abstentions when counting votes.
143func SelectChoiceByAbsoluteMajority(r ReadonlyVotingRecord, membersCount int) (VoteChoice, bool) {
144	choice := FindMostVotedChoice(r)
145	if choice != ChoiceNone && r.VoteCount(choice) > int(membersCount/2) {
146		return choice, true
147	}
148	return ChoiceNone, false
149}
150
151// SelectChoiceBySuperMajority select the vote choice by super majority using a 2/3s threshold.
152// Abstentions are considered when calculating the super majority choice.
153func SelectChoiceBySuperMajority(r ReadonlyVotingRecord, membersCount int) (VoteChoice, bool) {
154	if membersCount < 3 {
155		return ChoiceNone, false
156	}
157
158	choice := FindMostVotedChoice(r)
159	if choice != ChoiceNone && r.VoteCount(choice) >= int(math.Ceil((2*float64(membersCount))/3)) {
160		return choice, true
161	}
162	return ChoiceNone, false
163}
164
165// SelectChoiceByPlurality selects the vote choice by plurality.
166// The choice will be considered a majority if it has votes and if there is no other
167// choice with the same number of votes. A tie won't be considered majority.
168func SelectChoiceByPlurality(r ReadonlyVotingRecord) (VoteChoice, bool) {
169	var (
170		choice       VoteChoice
171		currentCount int
172		isMajority   bool
173	)
174
175	r.IterateVotesCount(func(c VoteChoice, count int) bool {
176		// Don't consider explicit abstentions or invalid votes
177		if c == ChoiceAbstain || c == ChoiceNone {
178			return false
179		}
180
181		if currentCount < count {
182			choice = c
183			currentCount = count
184			isMajority = true
185		} else if currentCount == count {
186			isMajority = false
187		}
188		return false
189	})
190
191	if isMajority {
192		return choice, true
193	}
194	return ChoiceNone, false
195}