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

upgrade_registry_test.gno

19.94 Kb · 559 lines
  1package upgrade_registry
  2
  3import (
  4	"chain"
  5	"strconv"
  6	"strings"
  7	"testing"
  8
  9	"gno.land/p/nt/testutils/v0"
 10	"gno.land/p/nt/uassert/v0"
 11)
 12
 13// In production the registrant is usually a realm; user addresses give
 14// the same self-registration semantics in tests (Register binds the
 15// entry to the CALLER's address either way).
 16var (
 17	v1       = testutils.TestAddress("contract_v1")
 18	v2       = testutils.TestAddress("contract_v2")
 19	v3       = testutils.TestAddress("contract_v3")
 20	teamEOA  = testutils.TestAddress("team")
 21	attacker = testutils.TestAddress("attacker")
 22)
 23
 24// resetState resets all package-level state between tests.
 25func resetState() {
 26	entries = make(map[address]*ContractEntry)
 27	entryAddrs = []address{}
 28	ownerContracts = make(map[address][]address)
 29}
 30
 31// register self-registers addr under name; when owner is non-empty the
 32// nominee accepts, completing the two-step handover.
 33func register(cur realm, addr address, name string, owner address) {
 34	testing.SetRealm(testing.NewUserRealm(addr))
 35	Register(cross(cur), name, owner)
 36	if owner != "" && owner != addr {
 37		testing.SetRealm(testing.NewUserRealm(owner))
 38		AcceptOwnership(cross(cur), addr)
 39	}
 40}
 41
 42// ---------- Register (proof of control) ----------
 43
 44func TestRegister_BindsToCallerAddress(cur realm, t *testing.T) {
 45	resetState()
 46	register(cur, v1, "vault", teamEOA)
 47
 48	e := entries[v1]
 49	uassert.Equal(t, string(v1), string(e.Address))
 50	uassert.Equal(t, string(teamEOA), string(e.Owner))
 51	uassert.Equal(t, "vault", e.Name)
 52
 53	// the audit's P1: there is no way to register an address you do not
 54	// control — the entry address is ALWAYS the caller's own. An
 55	// attacker registering gets an entry for themselves, not the victim.
 56	testing.SetRealm(testing.NewUserRealm(attacker))
 57	Register(cross(cur), "fake_vault", "")
 58	uassert.Equal(t, string(attacker), string(entries[attacker].Address))
 59	_, hijacked := entries[v2]
 60	uassert.False(t, hijacked, "no entry may exist for an address that never registered itself")
 61}
 62
 63func TestRegister_OwnerDefaultsToContract(cur realm, t *testing.T) {
 64	resetState()
 65	register(cur, v1, "vault", "")
 66
 67	uassert.Equal(t, string(v1), string(entries[v1].Owner))
 68}
 69
 70func TestRegister_DuplicateRejected(cur realm, t *testing.T) {
 71	resetState()
 72	register(cur, v1, "vault", "")
 73
 74	testing.SetRealm(testing.NewUserRealm(v1))
 75	uassert.AbortsWithMessage(t, cur,
 76		"contract already registered: "+string(v1),
 77		func() { Register(cross(cur), "vault_again", "") },
 78	)
 79}
 80
 81func TestRegister_NameValidation(cur realm, t *testing.T) {
 82	resetState()
 83	testing.SetRealm(testing.NewUserRealm(v1))
 84
 85	for _, bad := range []string{"", "Vault", "has space", "tick`name", strings.Repeat("a", MaxNameLen+1)} {
 86		uassert.AbortsContains(t, cur,
 87			"name must be",
 88			func() { Register(cross(cur), bad, "") },
 89		)
 90	}
 91}
 92
 93func TestRegister_InvalidOwnerRejected(cur realm, t *testing.T) {
 94	resetState()
 95	testing.SetRealm(testing.NewUserRealm(v1))
 96
 97	uassert.AbortsWithMessage(t, cur,
 98		"invalid owner address: junk",
 99		func() { Register(cross(cur), "vault", address("junk")) },
100	)
101}
102
103// ---------- Deprecate ----------
104
105func TestDeprecate_OwnerOnly(cur realm, t *testing.T) {
106	resetState()
107	register(cur, v1, "vault_v1", teamEOA)
108	register(cur, v2, "vault_v2", teamEOA)
109
110	testing.SetRealm(testing.NewUserRealm(attacker))
111	uassert.AbortsWithMessage(t, cur,
112		"only the owner can deprecate this contract",
113		func() { Deprecate(cross(cur), v1, v2) },
114	)
115
116	testing.SetRealm(testing.NewUserRealm(teamEOA))
117	Deprecate(cross(cur), v1, v2)
118	uassert.Equal(t, string(v2), string(GetLatest(v1)))
119}
120
121func TestDeprecate_SuccessorMustBeRegistered(cur realm, t *testing.T) {
122	resetState()
123	register(cur, v1, "vault_v1", teamEOA)
124
125	// the audit's dangling-successor + front-running route: pointing at
126	// an unregistered address is refused outright, and a successor entry
127	// can only ever be created by the successor contract itself
128	testing.SetRealm(testing.NewUserRealm(teamEOA))
129	uassert.AbortsWithMessage(t, cur,
130		"successor is not a registered contract: "+string(v2),
131		func() { Deprecate(cross(cur), v1, v2) },
132	)
133}
134
135func TestDeprecate_SelfAndDoubleRejected(cur realm, t *testing.T) {
136	resetState()
137	register(cur, v1, "vault_v1", teamEOA)
138	register(cur, v2, "vault_v2", teamEOA)
139
140	testing.SetRealm(testing.NewUserRealm(teamEOA))
141	uassert.AbortsWithMessage(t, cur,
142		"successor cannot be the contract itself",
143		func() { Deprecate(cross(cur), v1, v1) },
144	)
145	Deprecate(cross(cur), v1, v2)
146	uassert.AbortsWithMessage(t, cur,
147		"contract is already deprecated",
148		func() { Deprecate(cross(cur), v1, v2) },
149	)
150}
151
152func TestDeprecate_CircularChainRejected(cur realm, t *testing.T) {
153	resetState()
154	register(cur, v1, "vault_v1", teamEOA)
155	register(cur, v2, "vault_v2", teamEOA)
156	register(cur, v3, "vault_v3", teamEOA)
157
158	testing.SetRealm(testing.NewUserRealm(teamEOA))
159	Deprecate(cross(cur), v1, v2)
160	Deprecate(cross(cur), v2, v3)
161
162	// closing the loop from the tail must abort
163	uassert.AbortsWithMessage(t, cur,
164		"deprecating "+string(v3)+" to "+string(v1)+" would create a circular migration chain",
165		func() { Deprecate(cross(cur), v3, v1) },
166	)
167}
168
169// ---------- chain queries ----------
170
171func TestGetLatest_WalksChain(cur realm, t *testing.T) {
172	resetState()
173	register(cur, v1, "vault_v1", teamEOA)
174	register(cur, v2, "vault_v2", teamEOA)
175	register(cur, v3, "vault_v3", teamEOA)
176
177	testing.SetRealm(testing.NewUserRealm(teamEOA))
178	Deprecate(cross(cur), v1, v2)
179	Deprecate(cross(cur), v2, v3)
180
181	uassert.Equal(t, string(v3), string(GetLatest(v1)))
182	uassert.Equal(t, string(v3), string(GetLatest(v2)))
183	uassert.Equal(t, string(v3), string(GetLatest(v3)))
184	// unknown addresses come back unchanged
185	uassert.Equal(t, string(attacker), string(GetLatest(attacker)))
186}
187
188func TestGetMigrationChain(cur realm, t *testing.T) {
189	resetState()
190	register(cur, v1, "vault_v1", teamEOA)
191	register(cur, v2, "vault_v2", teamEOA)
192
193	testing.SetRealm(testing.NewUserRealm(teamEOA))
194	Deprecate(cross(cur), v1, v2)
195
196	out := GetMigrationChain(v1)
197	uassert.True(t, strings.Contains(out, "vault_v1"), "chain must name v1")
198	uassert.True(t, strings.Contains(out, "[deprecated]"), "chain must tag deprecation")
199	uassert.True(t, strings.Contains(out, "vault_v2"), "chain must name v2")
200	uassert.Equal(t, "not found: "+string(attacker), GetMigrationChain(attacker))
201}
202
203// ---------- TransferOwnership ----------
204
205func TestTransferOwnership(cur realm, t *testing.T) {
206	resetState()
207	register(cur, v1, "vault", teamEOA)
208	newOwner := testutils.TestAddress("newowner")
209
210	testing.SetRealm(testing.NewUserRealm(attacker))
211	uassert.AbortsWithMessage(t, cur,
212		"only the owner can transfer ownership",
213		func() { TransferOwnership(cross(cur), v1, attacker) },
214	)
215
216	testing.SetRealm(testing.NewUserRealm(teamEOA))
217	uassert.AbortsWithMessage(t, cur,
218		"invalid new owner address: junk",
219		func() { TransferOwnership(cross(cur), v1, address("junk")) },
220	)
221	TransferOwnership(cross(cur), v1, newOwner)
222	// nomination alone changes nothing (two-step)
223	uassert.Equal(t, string(teamEOA), string(entries[v1].Owner))
224	uassert.Equal(t, string(v1), GetOwnerContracts(teamEOA))
225
226	testing.SetRealm(testing.NewUserRealm(newOwner))
227	AcceptOwnership(cross(cur), v1)
228	uassert.Equal(t, string(newOwner), string(entries[v1].Owner))
229	uassert.Equal(t, "none", GetOwnerContracts(teamEOA))
230	uassert.Equal(t, string(v1), GetOwnerContracts(newOwner))
231}
232
233// ---------- render ----------
234
235func TestRender_EmptyState(cur realm, t *testing.T) {
236	resetState()
237	out := Render("")
238	uassert.True(t, strings.HasPrefix(out, "# Upgrade Registry\n\n"), "heading first")
239	uassert.True(t, strings.HasSuffix(out, "No contracts registered.\n"),
240		"empty state must still say the registry is empty")
241	// round-4 audit: the scope disclosure is not conditional on there being
242	// entries — an empty page must not read as an endorsement-free zone.
243	uassert.True(t, strings.Contains(out, "**Names are not.**"),
244		"the name-scope disclosure must appear even in the empty state")
245}
246
247// TestRender_CarriesNameScopeDisclosure is the regression guard for the
248// round-4 audit finding that all three review passes raised: the rendered
249// page gives a self-asserted NAME the most visual weight while only the
250// ADDRESS is actually proven. Names are deliberately never made unique
251// (first-come uniqueness on an immutable permissionless registry would let
252// an attacker burn every valuable name forever), so disclosing the scope in
253// the output is the entire mitigation. It cannot be added after deploy.
254//
255// Each assertion below names a distinct claim, so deleting or softening any
256// one of them fails this test rather than silently degrading the warning.
257func TestRender_CarriesNameScopeDisclosure(cur realm, t *testing.T) {
258	resetState()
259	register(cur, v1, "vault_v1", teamEOA)
260
261	// The impersonation this disclosure exists for: a second, unrelated
262	// address registering the SAME name is permitted and renders equally.
263	register(cur, attacker, "vault_v1", "")
264
265	out := Render("")
266	for _, claim := range []string{
267		"> [!NOTE]",
268		"Addresses are self-registered and therefore verified",
269		"**Names are not.**",
270		"NOT unique",
271		"may impersonate another project",
272		"address, never by name.",
273	} {
274		uassert.True(t, strings.Contains(out, claim),
275			"rendered disclosure must carry the claim: "+claim)
276	}
277
278	// Both duplicate-named entries really are present — the disclosure is
279	// describing a live condition, not a hypothetical one.
280	uassert.Equal(t, 2, strings.Count(out, "**vault_v1**"),
281		"duplicate names are permitted and both must render")
282}
283
284func TestRender_Sections(cur realm, t *testing.T) {
285	resetState()
286	register(cur, v1, "vault_v1", teamEOA)
287	register(cur, v2, "vault_v2", teamEOA)
288
289	testing.SetRealm(testing.NewUserRealm(teamEOA))
290	Deprecate(cross(cur), v1, v2)
291
292	out := Render("")
293	uassert.True(t, strings.Contains(out, "## Active"), "active section")
294	uassert.True(t, strings.Contains(out, "## Deprecated"), "deprecated section")
295	uassert.True(t, strings.Contains(out, "DEPRECATED"), "deprecation flag")
296	uassert.True(t, strings.Contains(out, string(v2)), "successor shown")
297}
298
299// ---------- re-audit regressions (2026-09-02) ----------
300
301func TestOwnerNomination_RequiresConsent(cur realm, t *testing.T) {
302	resetState()
303
304	// re-audit P2: registering with someone else's address as owner must
305	// NOT attach them — no pollution of GetOwnerContracts, no GetInfo
306	// showing them as owner, until they accept
307	victim := teamEOA
308	testing.SetRealm(testing.NewUserRealm(attacker))
309	Register(cross(cur), "gnoswap_router", victim)
310
311	uassert.Equal(t, string(attacker), string(entries[attacker].Owner))
312	uassert.Equal(t, "none", GetOwnerContracts(victim))
313	uassert.True(t, strings.Contains(GetInfo(attacker), string(attacker)),
314		"info must show the contract itself as owner pre-acceptance")
315
316	// only the nominee can accept; a bystander cannot
317	testing.SetRealm(testing.NewUserRealm(newOwnerAddr()))
318	uassert.AbortsWithMessage(t, cur,
319		"caller is not the pending owner of "+string(attacker),
320		func() { AcceptOwnership(cross(cur), attacker) },
321	)
322}
323
324func newOwnerAddr() address {
325	return testutils.TestAddress("bystander")
326}
327
328func TestDeprecate_SuccessorConsentRequired(cur realm, t *testing.T) {
329	resetState()
330	register(cur, v1, "vault", teamEOA)
331
332	// re-audit P2: an attacker chains their entry INTO a legit contract,
333	// forging predecessor provenance — refused unless the caller owns
334	// the successor entry too
335	testing.SetRealm(testing.NewUserRealm(attacker))
336	Register(cross(cur), "vault_v1_old", "")
337	uassert.AbortsWithMessage(t, cur,
338		"successor entry is not owned by the caller: "+string(v1),
339		func() { Deprecate(cross(cur), attacker, v1) },
340	)
341}
342
343func TestMigrationChain_MarksTruncation(cur realm, t *testing.T) {
344	resetState()
345	// build a chain longer than MaxChainLen entirely under one owner
346	prev := address("")
347	addrs := make([]address, MaxChainLen+5)
348	for i := range addrs {
349		addrs[i] = testutils.TestAddress("link_" + strconv.Itoa(i))
350		testing.SetRealm(testing.NewUserRealm(addrs[i]))
351		Register(cross(cur), "link", teamEOA)
352		testing.SetRealm(testing.NewUserRealm(teamEOA))
353		AcceptOwnership(cross(cur), addrs[i])
354		if prev != "" {
355			Deprecate(cross(cur), prev, addrs[i])
356		}
357		prev = addrs[i]
358	}
359
360	out := GetMigrationChain(addrs[0])
361	uassert.True(t, strings.Contains(out, "truncated"),
362		"over-length chains must carry an explicit truncation marker")
363	// GetLatest still resolves the true endpoint
364	uassert.Equal(t, string(addrs[len(addrs)-1]), string(GetLatest(addrs[0])))
365}
366
367func TestTransferNomination_CanBeCleared(cur realm, t *testing.T) {
368	resetState()
369	register(cur, v1, "vault", teamEOA)
370
371	testing.SetRealm(testing.NewUserRealm(teamEOA))
372	TransferOwnership(cross(cur), v1, attacker)
373	TransferOwnership(cross(cur), v1, "")
374
375	// round-4 audit L2: clearing again is a pure no-op, and the emitted
376	// event is now suppressed for it. The return value and the state are
377	// unchanged either way, which is all a unit test can observe here --
378	// this toolchain's testing stdlib carries no events accessor. That the
379	// second clear emits NOTHING is asserted externally by
380	// tools/test_upgrade_registry_events.sh, which counts the events
381	// `gno test -print-events` prints for this very suite.
382	uassert.Equal(t, "pending ownership nomination cleared",
383		TransferOwnership(cross(cur), v1, ""))
384	uassert.Equal(t, "", string(mustGet(v1).PendingOwner))
385
386	testing.SetRealm(testing.NewUserRealm(attacker))
387	uassert.AbortsWithMessage(t, cur,
388		"caller is not the pending owner of "+string(v1),
389		func() { AcceptOwnership(cross(cur), v1) },
390	)
391}
392
393// ---------- round-3 audit regressions (2026-09-02) ----------
394
395func TestRenderLatest_ExactBudgetChainIsComplete(cur realm, t *testing.T) {
396	resetState()
397	// exactly RenderLatestHops deprecated hops + terminal endpoint:
398	// the walk must be reported COMPLETE (round-3 off-by-one)
399	n := RenderLatestHops + 1
400	addrs := make([]address, n)
401	for i := range addrs {
402		addrs[i] = testutils.TestAddress("hop_" + strconv.Itoa(i))
403		testing.SetRealm(testing.NewUserRealm(addrs[i]))
404		Register(cross(cur), "hop", teamEOA)
405		testing.SetRealm(testing.NewUserRealm(teamEOA))
406		AcceptOwnership(cross(cur), addrs[i])
407		if i > 0 {
408			Deprecate(cross(cur), addrs[i-1], addrs[i])
409		}
410	}
411	latest, complete := renderLatest(addrs[0])
412	uassert.True(t, complete, "an exactly-budget chain must be complete")
413	uassert.Equal(t, string(addrs[n-1]), string(latest))
414}
415
416// ---------- audit gap closures (2026-09-21, apps-pipeline round) ----------
417
418func TestStraySendRejectedEverywhere(cur realm, t *testing.T) {
419	resetState()
420	register(cur, v1, "vault", teamEOA)
421
422	// audit Y2: the realm holds no banker, so attached coins would
423	// strand forever — every crossing function must abort them
424	msg := "this realm does not accept coins"
425	testing.SetRealm(testing.NewUserRealm(teamEOA))
426	testing.SetOriginSend(chain.Coins{{Denom: "ugnot", Amount: 5}})
427	uassert.AbortsWithMessage(t, cur, msg, func() { Register(cross(cur), "paid", "") })
428	uassert.AbortsWithMessage(t, cur, msg, func() { AcceptOwnership(cross(cur), v1) })
429	uassert.AbortsWithMessage(t, cur, msg, func() { TransferOwnership(cross(cur), v1, attacker) })
430	uassert.AbortsWithMessage(t, cur, msg, func() { Deprecate(cross(cur), v1, v2) })
431	testing.SetOriginSend(nil)
432}
433
434func TestDeprecate_BoundedWalkRefusal(cur realm, t *testing.T) {
435	resetState()
436	// audit Y5.1: the round-3 bounded-walk panic had zero coverage.
437	// Build a chain longer than MaxChainLen, then point a fresh entry
438	// at its head — the verification walk must refuse, not walk it.
439	prev := address("")
440	addrs := make([]address, MaxChainLen+5)
441	for i := range addrs {
442		addrs[i] = testutils.TestAddress("bw_" + strconv.Itoa(i))
443		testing.SetRealm(testing.NewUserRealm(addrs[i]))
444		Register(cross(cur), "link", teamEOA)
445		testing.SetRealm(testing.NewUserRealm(teamEOA))
446		AcceptOwnership(cross(cur), addrs[i])
447		if prev != "" {
448			Deprecate(cross(cur), prev, addrs[i])
449		}
450		prev = addrs[i]
451	}
452	fresh := testutils.TestAddress("bw_fresh")
453	testing.SetRealm(testing.NewUserRealm(fresh))
454	Register(cross(cur), "fresh", teamEOA)
455	testing.SetRealm(testing.NewUserRealm(teamEOA))
456	AcceptOwnership(cross(cur), fresh)
457	uassert.AbortsWithMessage(t, cur,
458		"successor chain too long to verify; cannot deprecate",
459		func() { Deprecate(cross(cur), fresh, addrs[0]) },
460	)
461}
462
463func TestRenderLatest_LongChainMarked(cur realm, t *testing.T) {
464	resetState()
465	// audit Y5.2: the incomplete branch and Render's long-chain marker
466	// were untested. Budget-boundary node must be NON-terminal here.
467	n := RenderLatestHops + 3
468	addrs := make([]address, n)
469	for i := range addrs {
470		addrs[i] = testutils.TestAddress("lc_" + strconv.Itoa(i))
471		testing.SetRealm(testing.NewUserRealm(addrs[i]))
472		Register(cross(cur), "hop", teamEOA)
473		testing.SetRealm(testing.NewUserRealm(teamEOA))
474		AcceptOwnership(cross(cur), addrs[i])
475		if i > 0 {
476			Deprecate(cross(cur), addrs[i-1], addrs[i])
477		}
478	}
479	_, complete := renderLatest(addrs[0])
480	uassert.False(t, complete, "an over-budget chain must be reported incomplete")
481	uassert.True(t, strings.Contains(Render(""), "(long chain; use GetLatest)"),
482		"render must mark over-budget chains instead of mislabeling an endpoint")
483}
484
485func TestRender_Pagination(cur realm, t *testing.T) {
486	resetState()
487	// audit Y5.3: the MaxRenderEntries window was untested
488	for i := 0; i < MaxRenderEntries+5; i++ {
489		a := testutils.TestAddress("pg_" + strconv.Itoa(i))
490		testing.SetRealm(testing.NewUserRealm(a))
491		Register(cross(cur), "entry", "")
492	}
493	out := Render("")
494	uassert.True(t, strings.Contains(out,
495		"_Showing the most recent "+strconv.Itoa(MaxRenderEntries)+" entries of "+
496			strconv.Itoa(MaxRenderEntries+5)+"."),
497		"over-window registries must announce the window AND the true total")
498	// round-4 audit: announcing the window is not enough on an immutable
499	// realm — the page must also say where the unlisted entries went, since
500	// a flood of new entries evicts older ones from this view permanently.
501	uassert.True(t, strings.Contains(out, "remain queryable by address via GetInfo"),
502		"the eviction notice must point at the accessors that still serve them")
503}
504
505func TestRenomination_OverwritesPrior(cur realm, t *testing.T) {
506	resetState()
507	register(cur, v1, "vault", teamEOA)
508	a := testutils.TestAddress("nominee_a")
509	b := testutils.TestAddress("nominee_b")
510
511	// audit Y5.4: a newer nomination must revoke the older one
512	testing.SetRealm(testing.NewUserRealm(teamEOA))
513	TransferOwnership(cross(cur), v1, a)
514	TransferOwnership(cross(cur), v1, b)
515	testing.SetRealm(testing.NewUserRealm(a))
516	uassert.AbortsWithMessage(t, cur,
517		"caller is not the pending owner of "+string(v1),
518		func() { AcceptOwnership(cross(cur), v1) },
519	)
520	testing.SetRealm(testing.NewUserRealm(b))
521	AcceptOwnership(cross(cur), v1)
522	uassert.Equal(t, string(b), string(entries[v1].Owner))
523}
524
525func TestSelfNominationAcceptKeepsIndexClean(cur realm, t *testing.T) {
526	resetState()
527	register(cur, v1, "vault", teamEOA)
528
529	// audit Y5.5: owner nominating themselves and accepting must not
530	// duplicate or lose the ownerContracts index entry
531	testing.SetRealm(testing.NewUserRealm(teamEOA))
532	TransferOwnership(cross(cur), v1, teamEOA)
533	AcceptOwnership(cross(cur), v1)
534	uassert.Equal(t, string(teamEOA), string(entries[v1].Owner))
535	uassert.Equal(t, string(v1), GetOwnerContracts(teamEOA))
536}
537
538func TestOpsOnUnregisteredAbort(cur realm, t *testing.T) {
539	resetState()
540	// audit Y5.6: the not-found aborts were never asserted
541	ghost := testutils.TestAddress("ghost")
542	msg := "contract not found: " + string(ghost)
543	testing.SetRealm(testing.NewUserRealm(teamEOA))
544	uassert.AbortsWithMessage(t, cur, msg, func() { AcceptOwnership(cross(cur), ghost) })
545	uassert.AbortsWithMessage(t, cur, msg, func() { TransferOwnership(cross(cur), ghost, teamEOA) })
546	uassert.AbortsWithMessage(t, cur, msg, func() { Deprecate(cross(cur), ghost, v1) })
547	// GetInfo shares mustGet with the three crossing paths above; its
548	// same-realm panic is not catchable by the revive-based Aborts helpers
549}
550
551func TestRegister_OwnerEqualsSelfNoPending(cur realm, t *testing.T) {
552	resetState()
553	// audit Y5.7: registering with your own address as owner must leave
554	// no pending nomination (the explicit owner==contractAddr branch)
555	testing.SetRealm(testing.NewUserRealm(v1))
556	Register(cross(cur), "vault", v1)
557	uassert.Equal(t, string(v1), string(entries[v1].Owner))
558	uassert.Equal(t, "", string(entries[v1].PendingOwner))
559}