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