prop_requests.gno
6.72 Kb ยท 239 lines
1package impl
2
3import (
4 "chain/runtime"
5 "strings"
6
7 "gno.land/p/aeddi/panictoerr"
8 "gno.land/p/moul/md"
9 trs_pkg "gno.land/p/nt/treasury"
10 "gno.land/p/nt/ufmt"
11
12 "gno.land/r/gov/dao"
13 "gno.land/r/gov/dao/v3/memberstore"
14 "gno.land/r/gov/dao/v3/treasury"
15)
16
17func NewChangeLawRequest(_ realm, newLaw Law) dao.ProposalRequest {
18 member, _ := memberstore.Get().GetMember(runtime.OriginCaller())
19 if member == nil {
20 panic("proposer is not a member")
21 }
22
23 cb := func(_ realm) error {
24 law = &newLaw
25 return nil
26 }
27
28 e := dao.NewSimpleExecutor(cb, ufmt.Sprintf("A new Law is proposed:\n %v", newLaw))
29
30 return dao.NewProposalRequest("Change Law Proposal", "This proposal is looking to change the actual govDAO Law", e)
31}
32
33func NewUpgradeDaoImplRequest(newDao dao.DAO, realmPkg, reason string) dao.ProposalRequest {
34 member, _ := memberstore.Get().GetMember(runtime.OriginCaller())
35 if member == nil {
36 panic("proposer is not a member")
37 }
38
39 cb := func(_ realm) error {
40 // dao.UpdateImpl() must be cross-called from v3/impl but
41 // what calls this cb function is r/gov/dao.
42 // therefore we must cross back into v3/impl and then
43 // cross call dao.UpdateRequest().
44 dao.UpdateImpl(cross, dao.UpdateRequest{
45 DAO: newDao,
46 AllowedDAOs: []string{"gno.land/r/gov/dao/v3/impl", realmPkg}, // keeping previous realm just in case something went wrong
47 })
48 return nil
49 }
50
51 e := dao.NewSimpleExecutor(cb, "")
52
53 return dao.NewProposalRequest("Change DAO implementation", "This proposal is looking to change the actual govDAO implementation. Reason: "+reason, e)
54}
55
56func NewAddMemberRequest(_ realm, addr address, tier string, portfolio string) dao.ProposalRequest {
57 _, ok := memberstore.GetTier(tier)
58 if !ok {
59 panic("provided tier does not exists")
60 }
61
62 if tier != memberstore.T1 && tier != memberstore.T2 {
63 panic("Only T1 and T2 members can be added by proposal. To add a T3 member use AddMember function directly.")
64 }
65
66 if portfolio == "" {
67 panic("A portfolio for the proposed member is required")
68 }
69
70 member, _ := memberstore.Get().GetMember(runtime.OriginCaller())
71 if member == nil {
72 panic("proposer is not a member")
73 }
74
75 if member.InvitationPoints <= 0 {
76 panic("proposer does not have enough invitation points for inviting new people to the board")
77 }
78
79 cb := func(_ realm) error {
80 member.RemoveInvitationPoint()
81 err := memberstore.Get().SetMember(tier, addr, memberByTier(tier))
82
83 return err
84 }
85
86 e := dao.NewSimpleExecutor(cb, ufmt.Sprintf("A new member with address %v is proposed to be on tier %v. Provided Portfolio information:\n\n%v", addr, tier, portfolio))
87
88 name := tryResolveAddr(addr)
89 return dao.NewProposalRequestWithFilter(
90 ufmt.Sprintf("New %s Member Proposal", tier),
91 ufmt.Sprintf("This is a proposal to add `%s` to **%s**.\n#### `%s`'s Portfolio:\n\n%s\n", name, tier, name, portfolio),
92 e,
93 FilterByTier{Tier: tier},
94 )
95}
96
97func NewWithdrawMemberRequest(_ realm, addr address, reason string) dao.ProposalRequest {
98 member, tier := memberstore.Get().GetMember(addr)
99 if member == nil {
100 panic("user we want to remove not found")
101 }
102
103 reason = strings.TrimSpace(reason)
104 if tier == memberstore.T1 && reason == "" {
105 panic("T1 user removals must contains a reason.")
106 }
107
108 cb := func(_ realm) error {
109 memberstore.Get().RemoveMember(addr)
110 return nil
111 }
112
113 e := dao.NewSimpleExecutor(cb, ufmt.Sprintf("Member with address %v will be withdrawn.\n\n REASON: %v.", addr, reason))
114
115 return dao.NewProposalRequest(
116 "Member Withdrawal Proposal",
117 ufmt.Sprintf("This is a proposal to remove %s from the GovDAO", tryResolveAddr(addr)),
118 e,
119 )
120}
121
122func NewPromoteMemberRequest(addr address, fromTier string, toTier string) dao.ProposalRequest {
123 cb := func(_ realm) error {
124 prevTier := memberstore.Get().RemoveMember(addr)
125 if prevTier == "" {
126 panic("member not found, so cannot be promoted")
127 }
128
129 if prevTier != fromTier {
130 panic("previous tier changed from the one indicated in the proposal")
131 }
132
133 err := memberstore.Get().SetMember(toTier, addr, memberByTier(toTier))
134
135 return err
136 }
137
138 e := dao.NewSimpleExecutor(cb, ufmt.Sprintf("A new member with address %v will be promoted from tier %v to tier %v.", addr, fromTier, toTier))
139
140 return dao.NewProposalRequestWithFilter(
141 "Member Promotion Proposal",
142 ufmt.Sprintf("This is a proposal to promote %s from **%s** to **%s**.", tryResolveAddr(addr), fromTier, toTier),
143 e,
144 FilterByTier{Tier: toTier},
145 )
146}
147
148func NewTreasuryPaymentRequest(payment trs_pkg.Payment, reason string) dao.ProposalRequest {
149 if !treasury.HasBanker(payment.BankerID()) {
150 panic("banker not registered in treasury with ID: " + payment.BankerID())
151 }
152
153 reason = strings.TrimSpace(reason)
154 if reason == "" {
155 panic("treasury payment request requires a reason")
156 }
157
158 cb := func(_ realm) error {
159 return panictoerr.PanicToError(func() {
160 treasury.Send(cross, payment)
161 })
162 }
163
164 e := dao.NewSimpleExecutor(
165 cb,
166 ufmt.Sprintf(
167 "A payment will be sent by the GovDAO treasury.\n\nReason: %s\n\nPayment: %s.",
168 reason,
169 payment.String(),
170 ),
171 )
172
173 return dao.NewProposalRequest(
174 "Treasury Payment",
175 ufmt.Sprintf(
176 "This proposal is looking to send a payment using the treasury.\n\nReason: %s\n\nPayment: %s",
177 reason,
178 payment.String(),
179 ),
180 e,
181 )
182}
183
184// NewTreasuryGRC20TokensUpdate creates a proposal request to update the list of GRC20 tokens registry
185// keys used by the treasury. The new list, if voted and accepted, will overwrite the current one.
186func NewTreasuryGRC20TokensUpdate(newTokenKeys []string) dao.ProposalRequest {
187 if len(newTokenKeys) == 0 {
188 panic("the list of new tokens is empty")
189 }
190
191 cb := func(_ realm) error {
192 return panictoerr.PanicToError(func() {
193 // NOTE:: Consider checking if the newTokenKeys are already registered
194 // in the grc20reg before updating the treasury tokens keys.
195 treasury.SetTokenKeys(cross, newTokenKeys)
196 })
197 }
198
199 bulletList := md.BulletList(newTokenKeys)
200
201 e := dao.NewSimpleExecutor(
202 cb,
203 ufmt.Sprintf(
204 "The list of GRC20 tokens used by the treasury will be updated.\n\nNew Token Keys:\n%s.\n",
205 bulletList,
206 ),
207 )
208
209 return dao.NewProposalRequest(
210 "Treasury GRC20 Tokens Update",
211 ufmt.Sprintf(
212 "This proposal is looking to update the list of GRC20 tokens used by the treasury.\n\nNew Token Keys:\n%s",
213 bulletList,
214 ),
215 e,
216 )
217}
218
219func memberByTier(tier string) *memberstore.Member {
220 switch tier {
221 case memberstore.T1:
222 t, _ := memberstore.GetTier(memberstore.T1)
223 return &memberstore.Member{
224 InvitationPoints: t.InvitationPoints,
225 }
226 case memberstore.T2:
227 t, _ := memberstore.GetTier(memberstore.T2)
228 return &memberstore.Member{
229 InvitationPoints: t.InvitationPoints,
230 }
231 case memberstore.T3:
232 t, _ := memberstore.GetTier(memberstore.T3)
233 return &memberstore.Member{
234 InvitationPoints: t.InvitationPoints,
235 }
236 default:
237 panic("member not found by the specified tier")
238 }
239}