Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

valopers_test.gno

23.55 Kb · 729 lines
  1package valopers
  2
  3import (
  4	"chain"
  5	"strings"
  6	"testing"
  7
  8	"gno.land/p/nt/avl/v0"
  9	"gno.land/p/nt/bptree/v0"
 10	"gno.land/p/nt/ownable/exts/authorizable/v0"
 11	"gno.land/p/nt/testutils/v0"
 12	"gno.land/p/nt/uassert/v0"
 13	"gno.land/p/nt/ufmt/v0"
 14)
 15
 16// Test-local fee constant. Production reads register_fee from sysparams;
 17// these tests use this value as a reference Coin for OriginSend setup
 18// and (when needed) seed it via testing.SetSysParamUint64.
 19var minFee = chain.NewCoin("ugnot", 20*1_000_000)
 20
 21// cur is a zero-value realm used as a placeholder when forwarding to
 22// uassert dispatch helpers that gained an `rlm realm` param. These tests
 23// pass `func()` callbacks (no crossing inside the callback), so rlm is
 24// ignored — a nil realm here is safe.
 25var cur realm
 26
 27// resetState clears realm-level state and zeroes the valoper sys-params
 28// so subtests don't leak through valopers (operator slots),
 29// signingRegistry (signing-address uniqueness), or sys-param values.
 30func resetState() {
 31	valopers = avl.NewTree()
 32	signingRegistry = bptree.NewBPTree32()
 33	testing.SetSysParamUint64("node", "valoper", "register_fee", 0)
 34	testing.SetSysParamUint64("node", "valoper", "rotation_fee", 0)
 35	testing.SetSysParamInt64("node", "valoper", "rotation_period_blocks", 600)
 36}
 37
 38// enableRegisterFee seeds register_fee = minFee.Amount in sysparams
 39// so the Register fee path fires. Subtests that exercise fee
 40// rejection or sufficient-fee acceptance call this after resetState.
 41func enableRegisterFee() {
 42	testing.SetSysParamUint64("node", "valoper", "register_fee", uint64(minFee.Amount))
 43}
 44
 45func validValidatorInfo(t *testing.T) struct {
 46	Moniker     string
 47	Description string
 48	ServerType  string
 49	Address     address
 50	PubKey      string
 51} {
 52	t.Helper()
 53
 54	return struct {
 55		Moniker     string
 56		Description string
 57		ServerType  string
 58		Address     address
 59		PubKey      string
 60	}{
 61		Moniker:     "test-1",
 62		Description: "test-1's description",
 63		ServerType:  ServerTypeOnPrem,
 64		Address:     address("g1sp8v98h2gadm5jggtzz9w5ksexqn68ympsd68h"),
 65		PubKey:      "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqwpdwpd0f9fvqla089ndw5g9hcsufad77fml2vlu73fk8q8sh8v72cza5p",
 66	}
 67}
 68
 69func TestValopers_Register(cur realm, t *testing.T) {
 70	t.Run("already a valoper", func(cur realm, t *testing.T) {
 71		resetState()
 72
 73		info := validValidatorInfo(t)
 74		testing.SetRealm(testing.NewUserRealm(info.Address))
 75
 76		v := Valoper{
 77			Moniker:         info.Moniker,
 78			Description:     info.Description,
 79			ServerType:      info.ServerType,
 80			OperatorAddress: info.Address,
 81			SigningPubKey:   info.PubKey,
 82			KeepRunning:     true,
 83		}
 84
 85		// Add the valoper directly to the slot.
 86		valopers.Set(v.OperatorAddress.String(), v)
 87
 88		// Send coins.
 89		testing.SetOriginSend(chain.Coins{minFee})
 90
 91		uassert.AbortsWithMessage(t, cur, ErrValoperExists.Error(), func() {
 92			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
 93		})
 94	})
 95
 96	t.Run("no coins deposited", func(cur realm, t *testing.T) {
 97		resetState()
 98		enableRegisterFee()
 99
100		info := validValidatorInfo(t)
101		testing.SetRealm(testing.NewUserRealm(info.Address))
102
103		// Send no coins.
104		testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", 0)})
105
106		uassert.AbortsWithMessage(t, cur, ufmt.Sprintf("payment must not be less than %d%s", minFee.Amount, minFee.Denom), func() {
107			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
108		})
109	})
110
111	t.Run("insufficient coins amount deposited", func(cur realm, t *testing.T) {
112		resetState()
113		enableRegisterFee()
114
115		info := validValidatorInfo(t)
116		testing.SetRealm(testing.NewUserRealm(info.Address))
117
118		// Send invalid coins.
119		testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", minFee.Amount-1)})
120
121		uassert.AbortsWithMessage(t, cur, ufmt.Sprintf("payment must not be less than %d%s", minFee.Amount, minFee.Denom), func() {
122			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
123		})
124	})
125
126	t.Run("coin amount deposited is not ugnot", func(cur realm, t *testing.T) {
127		resetState()
128		enableRegisterFee()
129
130		info := validValidatorInfo(t)
131		testing.SetRealm(testing.NewUserRealm(info.Address))
132
133		// Send invalid coins.
134		testing.SetOriginSend(chain.Coins{chain.NewCoin("gnogno", minFee.Amount)})
135
136		uassert.AbortsWithMessage(t, cur, "incompatible coin denominations: gnogno, ugnot", func() {
137			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
138		})
139	})
140
141	// A fee above MaxInt64 wraps negative in Coin.Amount, which would make
142	// the IsLT amount check false for every payment: the realm would read
143	// the fee as configured and collect 1ugnot. uint64(int64(-1)) is what
144	// valopers/proposal.ProposeNewMinFeeProposalRequest stores for a
145	// negative newMinFee, so this is reachable from one governance typo.
146	t.Run("unrepresentable fee fails closed", func(cur realm, t *testing.T) {
147		resetState()
148		testing.SetSysParamUint64("node", "valoper", "register_fee", ^uint64(0))
149
150		info := validValidatorInfo(t)
151		testing.SetRealm(testing.NewUserRealm(info.Address))
152
153		// Nowhere near the configured fee, and previously accepted.
154		testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", 1)})
155
156		uassert.AbortsWithMessage(t, cur, ErrFeeParamOutOfRange.Error(), func() {
157			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
158		})
159		uassert.False(t, isValoper(info.Address), "must not register")
160	})
161
162	t.Run("squat guard rejects mismatched OriginCaller", func(cur realm, t *testing.T) {
163		resetState()
164
165		info := validValidatorInfo(t)
166		// Caller is NOT info.Address: post-genesis squat guard fires.
167		testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("attacker")))
168		testing.SetOriginSend(chain.Coins{minFee})
169
170		uassert.AbortsWithMessage(t, cur, ErrOperatorSquatGuard.Error(), func() {
171			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
172		})
173	})
174
175	t.Run("successful registration", func(cur realm, t *testing.T) {
176		resetState()
177
178		info := validValidatorInfo(t)
179		testing.SetRealm(testing.NewUserRealm(info.Address))
180
181		// Send coins.
182		testing.SetOriginSend(chain.Coins{minFee})
183
184		uassert.NotAborts(t, cur, func() {
185			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
186		})
187
188		uassert.NotPanics(t, cur, func() {
189			valoper := GetByAddr(info.Address)
190
191			uassert.Equal(t, info.Moniker, valoper.Moniker)
192			uassert.Equal(t, info.Description, valoper.Description)
193			uassert.Equal(t, info.ServerType, valoper.ServerType)
194			uassert.Equal(t, info.Address, valoper.OperatorAddress)
195			uassert.Equal(t, info.PubKey, valoper.SigningPubKey)
196			uassert.Equal(t, true, valoper.KeepRunning)
197
198			// SigningAddress is derived from the pubkey and present.
199			derived, err := chain.PubKeyAddress(info.PubKey)
200			uassert.NoError(t, err)
201			uassert.Equal(t, derived, valoper.SigningAddress)
202
203			// signingRegistry tracks the active entry.
204			uassert.True(t, signingRegistry.Has(derived.String()), "signingRegistry must contain the active entry")
205		})
206	})
207
208	t.Run("signing-key reuse rejected", func(cur realm, t *testing.T) {
209		resetState()
210
211		info := validValidatorInfo(t)
212		testing.SetRealm(testing.NewUserRealm(info.Address))
213		testing.SetOriginSend(chain.Coins{minFee})
214
215		// First registration succeeds.
216		uassert.NotAborts(t, cur, func() {
217			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
218		})
219
220		// Second attempt with a different operator addr but the same
221		// pubkey must fail signingRegistry uniqueness.
222		other := testutils.TestAddress("other-op")
223		testing.SetRealm(testing.NewUserRealm(other))
224		testing.SetOriginSend(chain.Coins{minFee})
225
226		uassert.AbortsWithMessage(t, cur, ErrSigningKeyTaken.Error(), func() {
227			Register(cross(cur), info.Moniker, info.Description, info.ServerType, other, info.PubKey)
228		})
229	})
230
231	t.Run("front-running guard rejects post-genesis if signing addr already validates", func(cur realm, t *testing.T) {
232		resetState()
233
234		info := validValidatorInfo(t)
235		// Seed v0's valset:current with the very signing address that
236		// `info.PubKey` derives to (g1sp8v98...). Any post-genesis
237		// Register attempting the same pubkey now trips
238		// `ChainHeight()>0 && validators.IsValidator(signingAddr)`.
239		testing.SetSysParamStrings("node", "valset", "current", []string{info.PubKey + ":1"})
240
241		testing.SetRealm(testing.NewUserRealm(info.Address))
242		testing.SetOriginSend(chain.Coins{minFee})
243
244		uassert.AbortsWithMessage(t, cur, ErrFrontrunValidator.Error(), func() {
245			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
246		})
247
248		// Cleanup: clear the seeded valset to avoid leaking into
249		// later subtests if the package state isn't reset between
250		// them in this test runner mode.
251		testing.SetSysParamStrings("node", "valset", "current", []string{})
252	})
253}
254
255func TestValopers_Register_AuthOwnerIsOperatorAddress(cur realm, t *testing.T) {
256	// Pin: the Authorizable owner is bound to the OperatorAddress
257	// (the addr arg), NOT to OriginCaller. This matters in the
258	// genesis-mode deployer pattern: one signer (e.g., the hardfork
259	// ceremony deployer) registers profiles for many operators.
260	// Each operator must end up on their own profile's auth list
261	// so they can manage it post-genesis without depending on the
262	// deployer.
263	t.Run("genesis deployer pattern: operator (not deployer) is owner", func(cur realm, t *testing.T) {
264		resetState()
265
266		info := validValidatorInfo(t)
267		deployer := testutils.TestAddress("deployer")
268
269		// Genesis mode: ChainHeight()==0 bypasses the squat guard so
270		// deployer (OriginCaller) can register a profile for a
271		// different operator addr.
272		testing.SetHeight(0)
273		testing.SetRealm(testing.NewUserRealm(deployer))
274		testing.SetOriginCaller(deployer)
275		testing.SetOriginSend(chain.Coins{minFee})
276
277		uassert.NotPanics(t, cur, func() {
278			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
279		})
280
281		// Auth owner must be the operator addr (info.Address), NOT
282		// the deployer.
283		//
284		// Asserted through the exported AuthOwner() accessor, not the
285		// private v.auth: AuthOwner is the API that replaced the removed
286		// Auth() capability leak, so it is what external readers see. A
287		// second assertion against the underlying Authorizable pins that
288		// the accessor actually reports the auth list's owner rather
289		// than echoing OperatorAddress, which is what a plausible
290		// "simplification" of AuthOwner would do.
291		v := GetByAddr(info.Address)
292		uassert.Equal(t, info.Address.String(), v.AuthOwner().String(),
293			"auth owner must equal OperatorAddress, not OriginCaller")
294		uassert.Equal(t, v.auth.Owner().String(), v.AuthOwner().String(),
295			"AuthOwner must report the Authorizable's owner")
296
297		// Operator can manage their own profile post-genesis.
298		testing.SetHeight(100)
299		testing.SetRealm(testing.NewUserRealm(info.Address))
300		testing.SetOriginCaller(info.Address)
301		uassert.NotPanics(t, cur, func() {
302			UpdateMoniker(cross(cur), info.Address, "operator-renamed")
303		})
304		uassert.Equal(t, "operator-renamed", GetByAddr(info.Address).Moniker)
305
306		// Deployer cannot manage the operator's profile (not on the
307		// auth list).
308		testing.SetRealm(testing.NewUserRealm(deployer))
309		testing.SetOriginCaller(deployer)
310		uassert.AbortsContains(t, cur, "caller is not in authorized list", func() {
311			UpdateMoniker(cross(cur), info.Address, "deployer-attempt")
312		})
313	})
314
315	t.Run("post-genesis self-Register: operator is owner", func(cur realm, t *testing.T) {
316		// At H>0 the squat guard forces OriginCaller==addr, so owner
317		// would be the same regardless. This subtest pins that the
318		// post-genesis behavior is unchanged.
319		resetState()
320
321		info := validValidatorInfo(t)
322		testing.SetHeight(100)
323		testing.SetRealm(testing.NewUserRealm(info.Address))
324		testing.SetOriginCaller(info.Address)
325		testing.SetOriginSend(chain.Coins{minFee})
326
327		uassert.NotPanics(t, cur, func() {
328			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
329		})
330
331		v := GetByAddr(info.Address)
332		uassert.Equal(t, info.Address.String(), v.AuthOwner().String())
333	})
334}
335
336func TestValopers_UpdateAuthMembers(cur realm, t *testing.T) {
337	test2Address := testutils.TestAddress("test2")
338
339	t.Run("unauthorized member adds member", func(cur realm, t *testing.T) {
340		resetState()
341
342		info := validValidatorInfo(t)
343		testing.SetRealm(testing.NewUserRealm(info.Address))
344		testing.SetOriginSend(chain.Coins{minFee})
345
346		// Add the valoper.
347		uassert.NotPanics(t, cur, func() {
348			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
349		})
350
351		// A different caller (not on the auth list) tries to add a member.
352		testing.SetRealm(testing.NewUserRealm(test2Address))
353
354		uassert.AbortsWithMessage(t, cur, authorizable.ErrNotSuperuser.Error(), func() {
355			AddToAuthList(cross(cur), info.Address, test2Address)
356		})
357	})
358
359	t.Run("unauthorized member deletes member", func(cur realm, t *testing.T) {
360		resetState()
361
362		info := validValidatorInfo(t)
363		testing.SetRealm(testing.NewUserRealm(info.Address))
364		testing.SetOriginSend(chain.Coins{minFee})
365
366		uassert.NotPanics(t, cur, func() {
367			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
368		})
369
370		uassert.NotPanics(t, cur, func() {
371			AddToAuthList(cross(cur), info.Address, test2Address)
372		})
373
374		// A different caller tries to delete a member.
375		testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("attacker")))
376
377		uassert.AbortsWithMessage(t, cur, authorizable.ErrNotSuperuser.Error(), func() {
378			DeleteFromAuthList(cross(cur), info.Address, test2Address)
379		})
380	})
381
382	t.Run("authorized member adds member", func(cur realm, t *testing.T) {
383		resetState()
384
385		info := validValidatorInfo(t)
386		testing.SetRealm(testing.NewUserRealm(info.Address))
387		testing.SetOriginSend(chain.Coins{minFee})
388
389		uassert.NotPanics(t, cur, func() {
390			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
391		})
392
393		uassert.NotPanics(t, cur, func() {
394			AddToAuthList(cross(cur), info.Address, test2Address)
395		})
396
397		testing.SetRealm(testing.NewUserRealm(test2Address))
398
399		newMoniker := "new moniker"
400		uassert.NotPanics(t, cur, func() {
401			UpdateMoniker(cross(cur), info.Address, newMoniker)
402		})
403
404		uassert.NotPanics(t, cur, func() {
405			valoper := GetByAddr(info.Address)
406			uassert.Equal(t, newMoniker, valoper.Moniker)
407		})
408	})
409}
410
411func TestValopers_UpdateMoniker(cur realm, t *testing.T) {
412	test2Address := testutils.TestAddress("test2")
413
414	t.Run("non-existing valoper", func(cur realm, t *testing.T) {
415		resetState()
416
417		info := validValidatorInfo(t)
418
419		uassert.AbortsWithMessage(t, cur, ErrValoperMissing.Error(), func() {
420			UpdateMoniker(cross(cur), info.Address, "new moniker")
421		})
422	})
423
424	t.Run("invalid caller", func(cur realm, t *testing.T) {
425		resetState()
426
427		info := validValidatorInfo(t)
428		testing.SetRealm(testing.NewUserRealm(info.Address))
429		testing.SetOriginSend(chain.Coins{minFee})
430
431		uassert.NotPanics(t, cur, func() {
432			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
433		})
434
435		// Change the caller to someone not on the auth list.
436		testing.SetRealm(testing.NewUserRealm(test2Address))
437
438		uassert.AbortsWithMessage(t, cur, authorizable.ErrNotInAuthList.Error(), func() {
439			UpdateMoniker(cross(cur), info.Address, "new moniker")
440		})
441	})
442
443	t.Run("invalid moniker", func(cur realm, t *testing.T) {
444		resetState()
445
446		info := validValidatorInfo(t)
447		testing.SetRealm(testing.NewUserRealm(info.Address))
448		testing.SetOriginSend(chain.Coins{minFee})
449
450		uassert.NotPanics(t, cur, func() {
451			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
452		})
453
454		invalidMonikers := []string{
455			"",     // Empty
456			"    ", // Whitespace
457			"a",    // Too short
458			"a very long moniker that is longer than 32 characters", // Too long
459			"!@#$%^&*()+{}|:<>?/.,;'",                               // Invalid characters
460			" space in front",
461			"space in back ",
462		}
463
464		for _, invalidMoniker := range invalidMonikers {
465			uassert.AbortsWithMessage(t, cur, ErrInvalidMoniker.Error(), func() {
466				UpdateMoniker(cross(cur), info.Address, invalidMoniker)
467			})
468		}
469	})
470
471	t.Run("too long moniker", func(cur realm, t *testing.T) {
472		resetState()
473
474		info := validValidatorInfo(t)
475		testing.SetRealm(testing.NewUserRealm(info.Address))
476		testing.SetOriginSend(chain.Coins{minFee})
477
478		uassert.NotPanics(t, cur, func() {
479			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
480		})
481
482		uassert.AbortsWithMessage(t, cur, ErrInvalidMoniker.Error(), func() {
483			UpdateMoniker(cross(cur), info.Address, strings.Repeat("a", MonikerMaxLength+1))
484		})
485	})
486
487	t.Run("successful update", func(cur realm, t *testing.T) {
488		resetState()
489
490		info := validValidatorInfo(t)
491		testing.SetRealm(testing.NewUserRealm(info.Address))
492		testing.SetOriginSend(chain.Coins{minFee})
493
494		uassert.NotPanics(t, cur, func() {
495			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
496		})
497
498		newMoniker := "new moniker"
499		uassert.NotPanics(t, cur, func() {
500			UpdateMoniker(cross(cur), info.Address, newMoniker)
501		})
502
503		uassert.NotPanics(t, cur, func() {
504			valoper := GetByAddr(info.Address)
505			uassert.Equal(t, newMoniker, valoper.Moniker)
506		})
507	})
508}
509
510func TestValopers_UpdateDescription(cur realm, t *testing.T) {
511	test2Address := testutils.TestAddress("test2")
512
513	t.Run("non-existing valoper", func(cur realm, t *testing.T) {
514		resetState()
515
516		uassert.AbortsWithMessage(t, cur, ErrValoperMissing.Error(), func() {
517			UpdateDescription(cross(cur), validValidatorInfo(t).Address, "new description")
518		})
519	})
520
521	t.Run("invalid caller", func(cur realm, t *testing.T) {
522		resetState()
523
524		info := validValidatorInfo(t)
525		testing.SetRealm(testing.NewUserRealm(info.Address))
526		testing.SetOriginSend(chain.Coins{minFee})
527
528		uassert.NotPanics(t, cur, func() {
529			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
530		})
531
532		testing.SetRealm(testing.NewUserRealm(test2Address))
533
534		uassert.AbortsWithMessage(t, cur, authorizable.ErrNotInAuthList.Error(), func() {
535			UpdateDescription(cross(cur), info.Address, "new description")
536		})
537	})
538
539	t.Run("empty description", func(cur realm, t *testing.T) {
540		resetState()
541
542		info := validValidatorInfo(t)
543		testing.SetRealm(testing.NewUserRealm(info.Address))
544		testing.SetOriginSend(chain.Coins{minFee})
545
546		uassert.NotPanics(t, cur, func() {
547			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
548		})
549
550		uassert.AbortsWithMessage(t, cur, ErrInvalidDescription.Error(), func() {
551			UpdateDescription(cross(cur), info.Address, "")
552		})
553	})
554
555	t.Run("too long description", func(cur realm, t *testing.T) {
556		resetState()
557
558		info := validValidatorInfo(t)
559		testing.SetRealm(testing.NewUserRealm(info.Address))
560		testing.SetOriginSend(chain.Coins{minFee})
561
562		uassert.NotPanics(t, cur, func() {
563			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
564		})
565
566		uassert.AbortsWithMessage(t, cur, ErrInvalidDescription.Error(), func() {
567			UpdateDescription(cross(cur), info.Address, strings.Repeat("a", DescriptionMaxLength+1))
568		})
569	})
570
571	t.Run("successful update", func(cur realm, t *testing.T) {
572		resetState()
573
574		info := validValidatorInfo(t)
575		testing.SetRealm(testing.NewUserRealm(info.Address))
576		testing.SetOriginSend(chain.Coins{minFee})
577
578		uassert.NotPanics(t, cur, func() {
579			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
580		})
581
582		newDescription := "new description"
583		uassert.NotPanics(t, cur, func() {
584			UpdateDescription(cross(cur), info.Address, newDescription)
585		})
586
587		uassert.NotPanics(t, cur, func() {
588			valoper := GetByAddr(info.Address)
589			uassert.Equal(t, newDescription, valoper.Description)
590		})
591	})
592}
593
594func TestValopers_UpdateKeepRunning(cur realm, t *testing.T) {
595	test2Address := testutils.TestAddress("test2")
596
597	t.Run("non-existing valoper", func(cur realm, t *testing.T) {
598		resetState()
599
600		uassert.AbortsWithMessage(t, cur, ErrValoperMissing.Error(), func() {
601			UpdateKeepRunning(cross(cur), validValidatorInfo(t).Address, false)
602		})
603	})
604
605	t.Run("invalid caller", func(cur realm, t *testing.T) {
606		resetState()
607
608		info := validValidatorInfo(t)
609		testing.SetRealm(testing.NewUserRealm(info.Address))
610		testing.SetOriginSend(chain.Coins{minFee})
611
612		uassert.NotPanics(t, cur, func() {
613			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
614		})
615
616		testing.SetRealm(testing.NewUserRealm(test2Address))
617
618		uassert.AbortsWithMessage(t, cur, authorizable.ErrNotInAuthList.Error(), func() {
619			UpdateKeepRunning(cross(cur), info.Address, false)
620		})
621	})
622
623	t.Run("successful update", func(cur realm, t *testing.T) {
624		resetState()
625
626		info := validValidatorInfo(t)
627		testing.SetRealm(testing.NewUserRealm(info.Address))
628		testing.SetOriginSend(chain.Coins{minFee})
629
630		uassert.NotPanics(t, cur, func() {
631			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
632		})
633
634		uassert.NotPanics(t, cur, func() {
635			UpdateKeepRunning(cross(cur), info.Address, false)
636		})
637
638		uassert.NotPanics(t, cur, func() {
639			valoper := GetByAddr(info.Address)
640			uassert.Equal(t, false, valoper.KeepRunning)
641		})
642	})
643}
644
645func TestValopers_UpdateServerType(cur realm, t *testing.T) {
646	test2Address := testutils.TestAddress("test2")
647
648	t.Run("non-existing valoper", func(cur realm, t *testing.T) {
649		resetState()
650
651		uassert.AbortsWithMessage(t, cur, ErrValoperMissing.Error(), func() {
652			UpdateServerType(cross(cur), validValidatorInfo(t).Address, ServerTypeCloud)
653		})
654	})
655
656	t.Run("invalid caller", func(cur realm, t *testing.T) {
657		resetState()
658
659		info := validValidatorInfo(t)
660		testing.SetRealm(testing.NewUserRealm(info.Address))
661		testing.SetOriginSend(chain.Coins{minFee})
662
663		uassert.NotPanics(t, cur, func() {
664			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
665		})
666
667		testing.SetRealm(testing.NewUserRealm(test2Address))
668
669		uassert.AbortsWithMessage(t, cur, authorizable.ErrNotInAuthList.Error(), func() {
670			UpdateServerType(cross(cur), info.Address, ServerTypeCloud)
671		})
672	})
673
674	t.Run("invalid server type", func(cur realm, t *testing.T) {
675		resetState()
676
677		info := validValidatorInfo(t)
678		testing.SetRealm(testing.NewUserRealm(info.Address))
679		testing.SetOriginSend(chain.Coins{minFee})
680
681		uassert.NotPanics(t, cur, func() {
682			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
683		})
684
685		invalidServerTypes := []string{
686			"",
687			"invalid",
688			"Cloud",      // case sensitive
689			"ON-PREM",    // case sensitive
690			"datacenter", // wrong format
691		}
692
693		for _, invalidType := range invalidServerTypes {
694			uassert.AbortsWithMessage(t, cur, ErrInvalidServerType.Error(), func() {
695				UpdateServerType(cross(cur), info.Address, invalidType)
696			})
697		}
698	})
699
700	t.Run("successful update", func(cur realm, t *testing.T) {
701		resetState()
702
703		info := validValidatorInfo(t)
704		testing.SetRealm(testing.NewUserRealm(info.Address))
705		testing.SetOriginSend(chain.Coins{minFee})
706
707		uassert.NotPanics(t, cur, func() {
708			Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
709		})
710
711		uassert.NotPanics(t, cur, func() {
712			UpdateServerType(cross(cur), info.Address, ServerTypeCloud)
713		})
714
715		uassert.NotPanics(t, cur, func() {
716			valoper := GetByAddr(info.Address)
717			uassert.Equal(t, ServerTypeCloud, valoper.ServerType)
718		})
719
720		uassert.NotPanics(t, cur, func() {
721			UpdateServerType(cross(cur), info.Address, ServerTypeDataCenter)
722		})
723
724		uassert.NotPanics(t, cur, func() {
725			valoper := GetByAddr(info.Address)
726			uassert.Equal(t, ServerTypeDataCenter, valoper.ServerType)
727		})
728	})
729}