definition.gno
3.58 Kb · 132 lines
1package definition
2
3import (
4 "errors"
5 "strings"
6 "time"
7
8 "gno.land/p/nt/commondao/v0"
9)
10
11// DefaultVotingPeriod defines the default voting period for proposals.
12const DefaultVotingPeriod = time.Hour * 24 * 7
13
14var (
15 ErrBodyIsRequired = errors.New("proposal body is required")
16 ErrTitleIsRequired = errors.New("proposal title is required")
17)
18
19var defaultVoteChoices = []commondao.VoteChoice{
20 commondao.ChoiceYes,
21 commondao.ChoiceNo,
22 commondao.ChoiceAbstain,
23}
24
25// TallyFunc defines a function to handle proposal votes tallying.
26type TallyFunc func(commondao.VotingContext) (passes bool, _ error)
27
28// New creates a new proposal definition.
29//
30// By default, new definitions have a voting period of 7 days, allowing YES, NO and
31// ABSTAIN votes, tallying those votes using an absolute majority of more than 50% of
32// member votes, considering that a proposal passes when the majority of the votes are YES.
33//
34// Default definition behavior can be configured by setting custom options.
35func New(title, body string, options ...Option) (Definition, error) {
36 title = strings.TrimSpace(title)
37 if title == "" {
38 return Definition{}, ErrTitleIsRequired
39 }
40
41 body = strings.TrimSpace(body)
42 if body == "" {
43 return Definition{}, ErrBodyIsRequired
44 }
45
46 def := Definition{
47 title: title,
48 body: body,
49 votingPeriod: DefaultVotingPeriod,
50 voteChoices: defaultVoteChoices,
51 tallyCb: TallyByAbsoluteMajority,
52 }
53 for _, apply := range options {
54 apply(&def)
55 }
56 return def, nil
57}
58
59// MustNew creates a new proposal definition or panics on error.
60func MustNew(title, body string, options ...Option) Definition {
61 def, err := New(title, body, options...)
62 if err != nil {
63 panic(err)
64 }
65 return def
66}
67
68// Definition defines CommonDAO proposal types.
69type Definition struct {
70 title string
71 body string
72 votingPeriod time.Duration
73 voteChoices []commondao.VoteChoice
74 tallyCb TallyFunc
75 validateCb func() error
76 executeCb commondao.ExecFunc
77}
78
79// Title returns the proposal title.
80func (d Definition) Title() string {
81 return d.title
82}
83
84// Body returns proposal's body.
85func (d Definition) Body() string {
86 return d.body
87}
88
89// VotingPeriod returns the period where votes are allowed after proposal creation.
90func (d Definition) VotingPeriod() time.Duration {
91 return d.votingPeriod
92}
93
94// CustomVoteChoices returns a list of valid voting choices.
95func (d Definition) CustomVoteChoices() []commondao.VoteChoice {
96 return d.voteChoices
97}
98
99// Tally counts the number of votes and verifies if proposal passes.
100func (d Definition) Tally(ctx commondao.VotingContext) (passes bool, _ error) {
101 return d.tallyCb(ctx)
102}
103
104// Validate validates that the proposal is valid for the current state.
105func (d Definition) Validate() error {
106 if d.validateCb != nil {
107 return d.validateCb()
108 }
109 return nil
110}
111
112// Executor returns a function to execute the proposal.
113func (d Definition) Executor() commondao.ExecFunc {
114 return d.executeCb
115}
116
117// TallyByAbsoluteMajority tallies votes by absolute majority.
118// A quorum of 51% of member votes is required to tally votes.
119// Tally considers that proposals passes when more than half of members votes YES.
120func TallyByAbsoluteMajority(ctx commondao.VotingContext) (bool, error) {
121 // Check if a quorum of 51% has been met
122 if !commondao.IsQuorumReached(commondao.QuorumMoreThanHalf, ctx.VotingRecord, ctx.Members) {
123 return false, commondao.ErrNoQuorum
124 }
125
126 // Tally votes by absolute majority, which requires 51% votes
127 c, success := commondao.SelectChoiceByAbsoluteMajority(ctx.VotingRecord, ctx.Members.Size())
128 if success {
129 return c == commondao.ChoiceYes, nil
130 }
131 return false, nil
132}