proposal_test.gno
8.78 Kb · 268 lines
1package proposal
2
3import (
4 "chain"
5 "testing"
6
7 "gno.land/p/nt/testutils/v0"
8 "gno.land/p/nt/ufmt/v0"
9 "gno.land/p/nt/urequire/v0"
10 "gno.land/r/gnops/valopers"
11 "gno.land/r/gov/dao"
12 daoinit "gno.land/r/gov/dao/v3/init" // so that the govdao initializer is executed
13)
14
15// cur is a zero-value realm used as a placeholder when forwarding to
16// uassert/urequire dispatch helpers that gained an `rlm realm` param.
17// These tests pass `func()` callbacks (no crossing inside the callback),
18// so rlm is ignored — a nil realm here is safe.
19var cur realm
20var g1user = testutils.TestAddress("g1user")
21
22func init(cur realm) {
23 daoinit.InitWithUsers(cross(cur), g1user)
24}
25
26func TestValopers_ProposeNewValidator(cur realm, t *testing.T) {
27 const (
28 registerMinFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.
29 proposalMinFee int64 = 100 * 1_000_000
30
31 moniker string = "moniker"
32 description string = "description"
33 serverType string = valopers.ServerTypeOnPrem
34 pubKey = "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqwpdwpd0f9fvqla089ndw5g9hcsufad77fml2vlu73fk8q8sh8v72cza5p"
35 )
36
37 // signingAddr is derived from the pubkey and is what
38 // v3.IsValidator/GetValidator return after a proposal lands
39 // (parseEntry in r/sys/params canonicalizes from PubKey). Used
40 // for the "ErrSameValues" subtest's post-execute check.
41 signingAddr, err := chain.PubKeyAddress(pubKey)
42 urequire.NoError(t, err, "valid pubkey")
43
44 // The operator address is g1user (the GovDAO member); the
45 // signing address is derived separately from pubKey. Splitting
46 // them exercises the operator/signer separation.
47 opAddr := g1user
48
49 // Set origin caller for valoper operations: must equal the
50 // operator address (post-genesis squat guard in Register).
51 testing.SetRealm(testing.NewUserRealm(opAddr))
52
53 t.Run("remove an unexisting validator", func(t *testing.T) {
54 // Send coins to be able to register a valoper
55 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", registerMinFee)})
56
57 urequire.NotPanics(t, cur, func() {
58 valopers.Register(cross(cur), moniker, description, serverType, opAddr, pubKey)
59 valopers.UpdateKeepRunning(cross(cur), opAddr, false)
60 })
61
62 urequire.NotPanics(t, cur, func() {
63 valopers.GetByAddr(opAddr)
64 })
65
66 // Send coins to be able to make a proposal
67 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", proposalMinFee)})
68
69 urequire.AbortsWithMessage(t, cur, ErrValidatorMissing.Error(), func(cur realm) {
70 pr := NewValidatorProposalRequest(cur, opAddr)
71
72 dao.MustCreateProposal(cross(cur), pr)
73 })
74 })
75
76 t.Run("proposal successfully created", func(t *testing.T) {
77 // Send coins to be able to register a valoper
78 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", registerMinFee)})
79
80 urequire.NotPanics(t, cur, func() {
81 valopers.UpdateKeepRunning(cross(cur), opAddr, true)
82 })
83
84 var valoper valopers.Valoper
85
86 urequire.NotPanics(t, cur, func() {
87 valoper = valopers.GetByAddr(opAddr)
88 })
89
90 // Send coins to be able to make a proposal
91 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", proposalMinFee)})
92
93 var pid dao.ProposalID
94 urequire.NotPanics(t, cur, func(cur realm) {
95 testing.SetRealm(testing.NewUserRealm(g1user))
96 pr := NewValidatorProposalRequest(cur, opAddr)
97
98 pid = dao.MustCreateProposal(cross(cur), pr)
99 })
100
101 proposal, err := dao.GetProposal(pid) // index starts from 0
102 urequire.NoError(t, err, "proposal not found")
103
104 // The proposal description is now operator-keyed (matches the
105 // new v3.NewValidatorProposalRequest format): both the profile
106 // link and the validator-updates section reference the
107 // OPERATOR address with explicit power.
108 description := ufmt.Sprintf(
109 "Valoper profile: [%s](/r/gnops/valopers:%s)\n\n%s\n\n## Validator Updates\n- %s: add (power 1)\n",
110 valoper.Moniker,
111 valoper.OperatorAddress,
112 valoper.Render(),
113 valoper.OperatorAddress,
114 )
115
116 // Check that the proposal is correct
117 urequire.Equal(t, description, proposal.Description())
118 })
119
120 t.Run("try to update a validator with the same values", func(t *testing.T) {
121 // Send coins to be able to register a valoper
122 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", registerMinFee)})
123
124 urequire.NotPanics(t, cur, func() {
125 valopers.GetByAddr(opAddr)
126 })
127
128 urequire.NotPanics(t, cur, func() {
129 // Vote the proposal created in the previous test
130 dao.MustVoteOnProposal(cross(cur), dao.NewVoteRequest(dao.YesVote, dao.ProposalID(0)))
131
132 // Execute the proposal — callback writes valset:proposed +
133 // dirty=true. v3.IsValidator(signingAddr) below reads the
134 // effective view (proposed-when-dirty) and finds the
135 // just-added validator → enters the ErrSameValues branch.
136 dao.ExecuteProposal(cross(cur), dao.ProposalID(0))
137 })
138
139 // Verify the just-added entry exists at the SIGNING address.
140 _ = signingAddr
141
142 // Send coins to be able to make a proposal
143 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", proposalMinFee)})
144
145 urequire.AbortsWithMessage(t, cur, ErrSameValues.Error(), func() {
146 pr := NewValidatorProposalRequest(cross(cur), opAddr)
147 dao.MustCreateProposal(cross(cur), pr)
148 })
149 })
150}
151
152func TestValopers_ProposeNewInstructions(cur realm, t *testing.T) {
153 const proposalMinFee int64 = 100 * 1_000_000
154
155 newInstructions := "new instructions"
156 description := ufmt.Sprintf("Update the instructions to: \n\n%s", newInstructions)
157
158 // Set origin caller
159 testing.SetRealm(testing.NewUserRealm(g1user))
160
161 // Send coins to be able to make a proposal
162 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", proposalMinFee)})
163
164 var pid dao.ProposalID
165 urequire.NotPanics(t, cur, func() {
166 pr := ProposeNewInstructionsProposalRequest(cross(cur), newInstructions)
167
168 pid = dao.MustCreateProposal(cross(cur), pr)
169 })
170
171 proposal, err := dao.GetProposal(pid) // index starts from 0
172 urequire.NoError(t, err, "proposal not found")
173 if proposal == nil {
174 panic("PROPOSAL NOT FOUND")
175 }
176
177 // Check that the proposal is correct
178 urequire.Equal(t, description, proposal.Description())
179}
180
181func TestValopers_ProposeNewMinFee(cur realm, t *testing.T) {
182 const proposalMinFee int64 = 100 * 1_000_000
183 newMinFee := int64(10)
184 // ProposeNewMinFeeProposalRequest now delegates to the generic
185 // sys/params factory; description is the standard one from
186 // r/sys/params.newPropRequest, keyed on node:valoper:register_fee.
187 description := "This proposal wants to add a new key to sys/params: node:valoper:register_fee"
188
189 // Set origin caller
190 testing.SetRealm(testing.NewUserRealm(g1user))
191
192 // Send coins to be able to make a proposal
193 testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", proposalMinFee)})
194
195 var pid dao.ProposalID
196 urequire.NotPanics(t, cur, func() {
197 pr := ProposeNewMinFeeProposalRequest(cross(cur), newMinFee)
198
199 pid = dao.MustCreateProposal(cross(cur), pr)
200 })
201
202 proposal, err := dao.GetProposal(pid) // index starts from 0
203 urequire.NoError(t, err, "proposal not found")
204 // Check that the proposal is correct
205 urequire.Equal(t, description, proposal.Description())
206}
207
208/* TODO fix this @moul
209func TestValopers_ProposeNewValidator2(cur realm, t *testing.T) {
210 const (
211 registerMinFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.
212 proposalMinFee int64 = 100 * 1_000_000
213
214 moniker string = "moniker"
215 description string = "description"
216 pubKey = "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqwpdwpd0f9fvqla089ndw5g9hcsufad77fml2vlu73fk8q8sh8v72cza5p"
217 )
218
219 // Set origin caller
220 testing.SetRealm(std.NewUserRealm(g1user))
221
222 t.Run("create valid proposal", func(t *testing.T) {
223 // Validator exists, should not panic
224 urequire.NotPanics(t, cur, func() {
225 _ = valopers.MustGetValoper(g1user)
226 })
227
228 // Create the proposal
229 urequire.NotPanics(t, cur, func() {
230 cross(valopers.Register)(moniker, description, g1user, pubKey)
231 })
232
233 // Verify proposal details
234 urequire.NotPanics(t, cur, func() {
235 valoper := valopers.MustGetValoper(g1user)
236 urequire.Equal(t, moniker, valoper.Moniker)
237 urequire.Equal(t, description, valoper.Description)
238 })
239 // Execute proposal with admin rights
240 urequire.NotPanics(t, cur, func() {
241 std.TestSetOrigCaller(std.Admin)
242 cross(dao.ExecuteProposal)(dao.ProposalID(0))
243 })
244 // Check if valoper was updated
245 urequire.NotPanics(t, cur, func() {
246 valoper := valopers.MustGetValoper(g1user)
247 urequire.Equal(t, moniker, valoper.Moniker)
248 urequire.Equal(t, description, valoper.Description)
249 })
250
251 // Expect ExecuteProposal to pass
252 urequire.NotPanics(t, cur, func() {
253 cross(dao.ExecuteProposal)(dao.ProposalID(0))
254 })
255 // Check if valoper was updated
256 urequire.NotPanics(t, cur, func() {
257 valoper := valopers.MustGetValoper(g1user)
258 urequire.Equal(t, moniker, valoper.Moniker)
259 urequire.Equal(t, description, valoper.Description)
260 })
261 // Execute proposal with admin rights
262 urequire.NotPanics(t, cur, func() {
263 std.TestSetOrigCaller(std.Admin)
264 cross(dao.ExecuteProposal)(dao.ProposalID(0))
265 })
266 })
267}
268*/