instance.gno
6.38 Kb · 207 lines
1package governance
2
3import (
4 "gno.land/p/gnoswap/utils/v1"
5 bptree "gno.land/p/nt/bptree/v0"
6 ufmt "gno.land/p/nt/ufmt/v0"
7 "gno.land/r/gnoswap/gov/governance"
8)
9
10type governanceV1 struct {
11 store governance.IGovernanceStore
12 stakerAccessor governance.GovStakerAccessor
13}
14
15// NewGovernanceV1 creates a governance v1 implementation backed by the supplied
16// state store and staker voting-weight accessor.
17//
18// Parameters:
19// - governanceStore: persistence adapter for configuration, proposals, counters, and voting information
20// - stakerAccessor: accessor that supplies delegation snapshots and total xGNS supply for governance decisions
21//
22// Returns:
23// - governance.IGovernance: governance manager and getter implementation using the supplied dependencies
24func NewGovernanceV1(
25 governanceStore governance.IGovernanceStore,
26 stakerAccessor governance.GovStakerAccessor,
27) governance.IGovernance {
28 return &governanceV1{
29 store: governanceStore,
30 stakerAccessor: stakerAccessor,
31 }
32}
33
34// Config version methods
35func (g *governanceV1) getCurrentConfigVersion() int64 {
36 return g.store.GetConfigCounter().Get()
37}
38
39// nextConfigVersion increments and persists the config counter. It mutates the
40// underlying KV store, so it requires a realm value that resolves to the
41// governance proxy realm (the only writer-permitted address).
42func (g *governanceV1) nextConfigVersion(_ int, rlm realm) int64 {
43 counter := g.store.GetConfigCounter()
44 next := counter.Next()
45 if err := g.store.SetConfigCounter(0, rlm, counter); err != nil {
46 panic(err)
47 }
48 return next
49}
50
51// Proposal ID methods
52func (g *governanceV1) getCurrentProposalID() int64 {
53 return g.store.GetProposalCounter().Get()
54}
55
56// nextProposalID increments and persists the proposal counter. Same realm
57// rules as nextConfigVersion.
58func (g *governanceV1) nextProposalID(_ int, rlm realm) int64 {
59 counter := g.store.GetProposalCounter()
60 next := counter.Next()
61 if err := g.store.SetProposalCounter(0, rlm, counter); err != nil {
62 panic(err)
63 }
64 return next
65}
66
67// Config methods
68func (g *governanceV1) getConfig(version int64) (governance.Config, bool) {
69 return g.store.GetConfig(version)
70}
71
72func (g *governanceV1) setConfig(_ int, rlm realm, version int64, config governance.Config) error {
73 return g.store.SetConfig(0, rlm, version, config)
74}
75
76func (g *governanceV1) getCurrentConfig() (governance.Config, bool) {
77 return g.getConfig(g.getCurrentConfigVersion())
78}
79
80// Proposal methods
81func (g *governanceV1) getProposal(id int64) (*governance.Proposal, bool) {
82 proposal, exists := g.store.GetProposal(id)
83 if !exists {
84 return nil, false
85 }
86
87 return proposal, true
88}
89
90func activeProposalSnapshotKey(proposal *governance.Proposal) string {
91 return utils.EncodeUint64(uint64(proposal.SnapshotTime())) + utils.EncodeUint64(uint64(proposal.ID()))
92}
93
94func (g *governanceV1) addProposal(_ int, rlm realm, proposal *governance.Proposal) bool {
95 // Set the proposal (ID already set in proposal)
96 err := g.store.SetProposal(0, rlm, proposal.ID(), proposal)
97 if err != nil {
98 return false
99 }
100
101 // Add to the active snapshot index.
102 snapshotIndex := g.store.GetActiveProposalsBySnapshot()
103 snapshotIndex.Set(activeProposalSnapshotKey(proposal), proposal.ID())
104 if err := g.store.SetActiveProposalsBySnapshot(0, rlm, snapshotIndex); err != nil {
105 return false
106 }
107
108 // Add to user proposals
109 err = g.store.AddUserProposal(0, rlm, proposal.Proposer().String(), proposal.ID())
110 if err != nil {
111 return false
112 }
113
114 return true
115}
116
117// removeActiveProposal removes a proposal from the active snapshot index.
118func (g *governanceV1) removeActiveProposal(_ int, rlm realm, proposal *governance.Proposal) error {
119 snapshotIndex := g.store.GetActiveProposalsBySnapshot()
120 key := activeProposalSnapshotKey(proposal)
121 if !snapshotIndex.Has(key) {
122 return makeErrorWithDetails(errDataNotFound, ufmt.Sprintf("active proposal index entry not found, proposalID: %d", proposal.ID()))
123 }
124 snapshotIndex.Remove(key)
125
126 return g.store.SetActiveProposalsBySnapshot(0, rlm, snapshotIndex)
127}
128
129// User proposals methods
130func (g *governanceV1) getUserProposals(user string) []*governance.Proposal {
131 proposalIDs, exists := g.store.GetUserProposalIDs(user)
132 if !exists {
133 return []*governance.Proposal{}
134 }
135
136 proposals := make([]*governance.Proposal, 0)
137
138 for _, id := range proposalIDs {
139 proposal, exists := g.store.GetProposal(id)
140 if !exists {
141 continue
142 }
143
144 proposals = append(proposals, proposal)
145 }
146
147 return proposals
148}
149
150// hasActiveProposal assumes removeInactiveUserProposals has already pruned stale proposals.
151func (g *governanceV1) hasActiveProposal(proposerAddress address) bool {
152 proposals := g.getUserProposals(proposerAddress.String())
153
154 return len(proposals) > 0
155}
156
157// Remove inactive user proposals
158// This function is used to remove inactive proposals from the user proposals list.
159// It is used to clean up user's active proposal list when creating a new proposal.
160func (g *governanceV1) removeInactiveUserProposals(_ int, rlm realm, proposerAddress address, current int64) error {
161 proposals := g.getUserProposals(proposerAddress.String())
162
163 for _, proposal := range proposals {
164 proposalResolver := NewProposalResolver(proposal)
165
166 if !proposalResolver.IsActive(current) {
167 err := g.store.RemoveUserProposal(0, rlm, proposerAddress.String(), proposal.ID())
168 if err != nil {
169 return err
170 }
171 }
172 }
173
174 return nil
175}
176
177// Proposal voting info methods
178func (g *governanceV1) getProposalUserVotingInfos(proposalID int64) (*bptree.BPTree, bool) {
179 return g.store.GetProposalVotingInfos(proposalID)
180}
181
182func (g *governanceV1) updateProposalUserVotes(_ int, rlm realm, proposal *governance.Proposal, userVotingInfos *bptree.BPTree) error {
183 return g.store.SetProposalVotingInfos(0, rlm, proposal.ID(), userVotingInfos)
184}
185
186// Helper methods for API
187func (g *governanceV1) mustGetProposal(id int64) *governance.Proposal {
188 proposal, exists := g.getProposal(id)
189 if !exists {
190 panic(makeErrorWithDetails(errProposalNotFound, ufmt.Sprintf("proposal(%d) not found", id)))
191 }
192 return proposal
193}
194
195func (g *governanceV1) getProposalUserVotingInfo(proposalID int64, addr address) (governance.VotingInfo, bool) {
196 votingInfosTree, exists := g.getProposalUserVotingInfos(proposalID)
197 if !exists {
198 return governance.DefaultVotingInfo(), false
199 }
200
201 votingInfoRaw := votingInfosTree.Get(addr.String())
202 if votingInfoRaw == nil {
203 return governance.DefaultVotingInfo(), false
204 }
205 votingInfo, ok := votingInfoRaw.(governance.VotingInfo)
206 return votingInfo, ok
207}