Search Apps Documentation Source Content File Folder Download Copy Actions Download

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*/