PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/authz/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / authz
Files
5 (gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/authz/v0
gno
0.9

Files (5)

  • gnomod.tomltoml
  • authz.gnogno
  • authz_test.gnogno
  • example_test.gnogno
  • z_contract_authority_shape_filetest.gnogno
authz_test.gnogno
1package authz23import (4	"chain"5	"errors"6	"strings"7	"testing"89	"gno.land/p/nt/testutils/v0"10	"gno.land/p/nt/uassert/v0"11)1213func TestNewWithCurrent(cur realm, t *testing.T) {14	alice := testutils.TestAddress("alice")15	testing.SetRealm(testing.NewUserRealm(alice))1617	auth := NewWithMembers(cur.Address())1819	// Check that the current authority is a MemberAuthority20	memberAuth, ok := auth.Authority().(*MemberAuthority)21	uassert.True(t, ok, "expected MemberAuthority")2223	// Check that the caller is a member24	uassert.True(t, memberAuth.Has(alice), "caller should be a member")2526	// Check string representation27	uassert.True(t, strings.Contains(auth.String(), alice.String()))28}2930func TestNewWithAuthority(cur realm, t *testing.T) {31	alice := testutils.TestAddress("alice")32	memberAuth := NewMemberAuthority(alice)3334	auth := NewWithAuthority(memberAuth)3536	// Check that the current authority is the one we provided37	uassert.True(t, auth.Authority() == memberAuth, "expected provided authority")38}3940func TestAuthorizerAuthorize(cur realm, t *testing.T) {41	alice := testutils.TestAddress("alice")42	testing.SetRealm(testing.NewUserRealm(alice))4344	auth := NewWithMembers(cur.Address())4546	// Test successful action with args47	executed := false48	args := []any{"test_arg", 123}49	err := auth.DoByCurrent(0, cur, "test_action", func() error {50		executed = true51		return nil52	}, args...)5354	uassert.True(t, err == nil, "expected no error")55	uassert.True(t, executed, "action should have been executed")5657	// Test unauthorized action with args58	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("bob")))5960	executed = false61	err = auth.DoByCurrent(0, cur, "test_action", func() error {62		executed = true63		return nil64	}, "unauthorized_arg")6566	uassert.True(t, err != nil, "expected error")67	uassert.False(t, executed, "action should not have been executed")6869	// Test action returning error70	testing.SetRealm(testing.NewUserRealm(alice))71	expectedErr := errors.New("test error")7273	err = auth.DoByCurrent(0, cur, "test_action", func() error {74		return expectedErr75	})7677	uassert.True(t, err == expectedErr, "expected specific error")78}7980func TestAuthorizerTransfer(cur realm, t *testing.T) {81	alice := testutils.TestAddress("alice")82	testing.SetRealm(testing.NewUserRealm(alice))8384	auth := NewWithMembers(cur.Address())8586	// Test transfer to new member authority87	bob := testutils.TestAddress("bob")88	newAuth := NewMemberAuthority(bob)8990	var err error91	func(cur realm) { err = auth.Transfer(0, cur, newAuth) }(cross(cur))92	uassert.True(t, err == nil, "expected no error")93	uassert.True(t, auth.Authority() == newAuth, "expected new authority")9495	// Test unauthorized transfer: principal is not a member of newAuth.96	carol := testutils.TestAddress("carol")97	testing.SetRealm(testing.NewUserRealm(carol))9899	func(cur realm) { err = auth.Transfer(0, cur, NewMemberAuthority(alice)) }(cross(cur))100	uassert.True(t, err != nil, "expected error")101102	// Test transfer to contract authority — bob is the current authority.103	testing.SetRealm(testing.NewUserRealm(bob))104	contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {105		return action()106	})107108	func(cur realm) { err = auth.Transfer(0, cur, contractAuth) }(cross(cur))109	uassert.True(t, err == nil, "expected no error")110	uassert.True(t, auth.Authority() == contractAuth, "expected contract authority")111}112113func TestAuthorizerTransferChain(cur realm, t *testing.T) {114	alice := testutils.TestAddress("alice")115	testing.SetRealm(testing.NewUserRealm(alice))116117	// Create a chain of transfers118	auth := NewWithMembers(cur.Address())119120	// First transfer to a new member authority121	bob := testutils.TestAddress("bob")122	memberAuth := NewMemberAuthority(bob)123124	var err error125	func(cur realm) { err = auth.Transfer(0, cur, memberAuth) }(cross(cur))126	uassert.True(t, err == nil, "unexpected error in first transfer")127128	// Then transfer to a contract authority — bob is now the authority.129	testing.SetRealm(testing.NewUserRealm(bob))130	contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {131		return action()132	})133	func(cur realm) { err = auth.Transfer(0, cur, contractAuth) }(cross(cur))134	uassert.True(t, err == nil, "unexpected error in second transfer")135136	// Finally transfer to an auto-accept authority. The authority is now137	// the contract authority, whose default proposer is the contract138	// itself, so the transfer's caller —139	// cur.Previous() — must be the contract. Set the outer realm to the140	// contract, then cross into a closure so cur.Previous() resolves to141	// it.142	autoAuth := NewAutoAcceptAuthority()143	testing.SetRealm(testing.NewCodeRealm("gno.land/r/test"))144	func(cur realm) { err = auth.Transfer(0, cur, autoAuth) }(cross(cur))145	uassert.True(t, err == nil, "unexpected error in final transfer")146	uassert.True(t, auth.Authority() == autoAuth, "expected auto-accept authority")147}148149func TestAuthorizerTransferUnauthorizedRejected(cur realm, t *testing.T) {150	// Regression for the address-parameter forgery: previously Transfer151	// took a caller-supplied `caller address` that an attacker realm could152	// set to the real owner. After the IsCurrent + rlm.Previous() fix, the153	// principal is the captured cur's previous and cannot be forged.154	admin := testutils.TestAddress("admin")155	attacker := testutils.TestAddress("attacker")156157	testing.SetRealm(testing.NewUserRealm(admin))158	auth := NewWithMembers(cur.Address()) // admin is the initial authority159160	initialAuth, ok := auth.Authority().(*MemberAuthority)161	uassert.True(t, ok)162	uassert.True(t, initialAuth.Has(admin))163	uassert.False(t, initialAuth.Has(attacker))164165	// Attacker context: cur.Previous() inside the closure will be attacker.166	testing.SetRealm(testing.NewUserRealm(attacker))167	attackerAuth := NewMemberAuthority(attacker)168	var err error169	func(cur realm) { err = auth.Transfer(0, cur, attackerAuth) }(cross(cur))170171	uassert.True(t, err != nil, "attacker transfer must be rejected")172173	// Authority unchanged: still the initial admin-only MemberAuthority.174	finalAuth, ok := auth.Authority().(*MemberAuthority)175	uassert.True(t, ok)176	uassert.True(t, finalAuth == initialAuth, "authority must not have changed")177	uassert.True(t, finalAuth.Has(admin))178	uassert.False(t, finalAuth.Has(attacker))179}180181func TestAuthorizerWithDroppedAuthority(cur realm, t *testing.T) {182	alice := testutils.TestAddress("alice")183	testing.SetRealm(testing.NewUserRealm(alice))184185	auth := NewWithMembers(cur.Address())186187	// Transfer to dropped authority188	var err error189	func(cur realm) { err = auth.Transfer(0, cur, NewDroppedAuthority()) }(cross(cur))190	uassert.True(t, err == nil, "expected no error")191192	// Try to execute action193	err = auth.DoByCurrent(0, cur, "test_action", func() error {194		return nil195	})196	uassert.True(t, err != nil, "expected error from dropped authority")197198	// Try to transfer again199	func(cur realm) { err = auth.Transfer(0, cur, NewMemberAuthority(alice)) }(cross(cur))200	uassert.True(t, err != nil, "expected error when transferring from dropped authority")201}202203func TestContractAuthorityHandlerExecutionOnce(cur realm, t *testing.T) {204	attempts := 0205	executed := 0206207	contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {208		// Try to execute the action twice in the same handler209		if err := action(); err != nil {210			return err211		}212		attempts++213214		// Second execution should fail215		if err := action(); err != nil {216			return err217		}218		attempts++219		return nil220	})221222	// Set caller to contract address223	codeRealm := testing.NewCodeRealm("gno.land/r/test")224	testing.SetRealm(codeRealm)225	code := codeRealm.Address()226227	testArgs := []any{"proposal_id", 42, "metadata", map[string]string{"key": "value"}}228	err := contractAuth.Authorize(code, "test_action", func() error {229		executed++230		return nil231	}, testArgs...)232233	uassert.True(t, err == nil, "handler execution should succeed")234	uassert.True(t, attempts == 2, "handler should have attempted execution twice")235	uassert.True(t, executed == 1, "handler should have executed once")236}237238func TestContractAuthorityExecutionTwice(cur realm, t *testing.T) {239	executed := 0240241	contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {242		return action()243	})244245	// Set caller to contract address246	codeRealm := testing.NewCodeRealm("gno.land/r/test")247	testing.SetRealm(codeRealm)248	code := codeRealm.Address()249	testArgs := []any{"proposal_id", 42, "metadata", map[string]string{"key": "value"}}250251	err := contractAuth.Authorize(code, "test_action", func() error {252		executed++253		return nil254	}, testArgs...)255256	uassert.True(t, err == nil, "handler execution should succeed")257	uassert.True(t, executed == 1, "handler should have executed once")258259	// A new action, even with the same title, should be executed260	err = contractAuth.Authorize(code, "test_action", func() error {261		executed++262		return nil263	}, testArgs...)264265	uassert.True(t, err == nil, "handler execution should succeed")266	uassert.True(t, executed == 2, "handler should have executed twice")267}268269func TestContractAuthorityWithProposer(cur realm, t *testing.T) {270	alice := testutils.TestAddress("alice")271	memberAuth := NewMemberAuthority(alice)272273	handlerCalled := false274	actionExecuted := false275276	contractAuth := NewRestrictedContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {277		handlerCalled = true278		// Set caller to contract address before executing action279		testing.SetRealm(testing.NewCodeRealm("gno.land/r/test"))280		return action()281	}, memberAuth)282283	// Test authorized member284	testArgs := []any{"proposal_metadata", "test value"}285	err := contractAuth.Authorize(alice, "test_action", func() error {286		actionExecuted = true287		return nil288	}, testArgs...)289290	uassert.True(t, err == nil, "authorized member should be able to propose")291	uassert.True(t, handlerCalled, "contract handler should be called")292	uassert.True(t, actionExecuted, "action should be executed")293294	// Reset flags for unauthorized test295	handlerCalled = false296	actionExecuted = false297298	// Test unauthorized proposer299	bob := testutils.TestAddress("bob")300	err = contractAuth.Authorize(bob, "test_action", func() error {301		actionExecuted = true302		return nil303	}, testArgs...)304305	uassert.True(t, err != nil, "unauthorized member should not be able to propose")306	uassert.False(t, handlerCalled, "contract handler should not be called for unauthorized proposer")307	uassert.False(t, actionExecuted, "action should not be executed for unauthorized proposer")308}309310func TestAutoAcceptAuthority(cur realm, t *testing.T) {311	alice := testutils.TestAddress("alice")312	auth := NewAutoAcceptAuthority()313314	// Test that any action is authorized315	executed := false316	err := auth.Authorize(alice, "test_action", func() error {317		executed = true318		return nil319	})320321	uassert.True(t, err == nil, "auto-accept should not return error")322	uassert.True(t, executed, "action should have been executed")323324	// Test with different caller325	random := testutils.TestAddress("random")326	executed = false327	err = auth.Authorize(random, "test_action", func() error {328		executed = true329		return nil330	})331332	uassert.True(t, err == nil, "auto-accept should not care about caller")333	uassert.True(t, executed, "action should have been executed")334}335336func TestAutoAcceptAuthorityWithArgs(cur realm, t *testing.T) {337	auth := NewAutoAcceptAuthority()338	anyuser := testutils.TestAddress("anyuser")339340	// Test that any action is authorized with args341	executed := false342	testArgs := []any{"arg1", 42, "arg3"}343	err := auth.Authorize(anyuser, "test_action", func() error {344		executed = true345		return nil346	}, testArgs...)347348	uassert.True(t, err == nil, "auto-accept should not return error")349	uassert.True(t, executed, "action should have been executed")350}351352func TestMemberAuthorityMultipleMembers(cur realm, t *testing.T) {353	alice := testutils.TestAddress("alice")354	bob := testutils.TestAddress("bob")355	carol := testutils.TestAddress("carol")356357	// Create authority with multiple members358	auth := NewMemberAuthority(alice, bob)359360	// Test that both members can execute actions361	for _, member := range []address{alice, bob} {362		err := auth.Authorize(member, "test_action", func() error {363			return nil364		})365		uassert.True(t, err == nil, "member should be authorized")366	}367368	// Test that non-member cannot execute369	err := auth.Authorize(carol, "test_action", func() error {370		return nil371	})372	uassert.True(t, err != nil, "non-member should not be authorized")373374	// Test Tree() functionality375	tree := auth.Tree()376	uassert.True(t, tree.Size() == 2, "tree should have 2 members")377378	// Verify both members are in the tree379	found := make(map[address]bool)380	tree.Iterate("", "", func(key string, _ any) bool {381		found[address(key)] = true382		return false383	})384	uassert.True(t, found[alice], "alice should be in the tree")385	uassert.True(t, found[bob], "bob should be in the tree")386	uassert.False(t, found[carol], "carol should not be in the tree")387388	// Test read-only nature of the tree389	defer func() {390		r := recover()391		uassert.True(t, r != nil, "modifying read-only tree should panic")392	}()393	tree.Set(string(carol), nil) // This should panic394}395396func TestAuthorizerCurrentNeverNil(cur realm, t *testing.T) {397	auth := NewWithMembers(cur.Address())398399	// Authority should never be nil after initialization400	uassert.True(t, auth.Authority() != nil, "current authority should not be nil")401402	// Authority should not be nil after transfer403	var err error404	func(cur realm) { err = auth.Transfer(0, cur, NewAutoAcceptAuthority()) }(cross(cur))405	uassert.True(t, err == nil, "transfer should succeed")406	uassert.True(t, auth.Authority() != nil, "current authority should not be nil after transfer")407}408409func TestContractAuthorityValidation(cur realm, t *testing.T) {410	handler := func(title string, action PrivilegedAction) error {411		return nil412	}413414	// Empty path panics (consistent with NewRestrictedContractAuthority).415	uassert.PanicsWithMessage(t, cur, "contract path cannot be empty", func() {416		NewContractAuthority("", handler)417	})418419	// A nil handler panics at CONSTRUCTION rather than being tolerated and420	// surfaced at Authorize time. Tolerating it produced a permanent brick:421	// see TestNilHandlerWouldBeUnrotatable below.422	uassert.PanicsWithMessage(t, cur, "contract handler cannot be nil", func() {423		NewContractAuthority("gno.land/r/test", nil)424	})425426	// The Authorize-time guard stays for a zero-value ContractAuthority,427	// which the constructors cannot produce but a struct literal can.428	code := testing.NewCodeRealm("gno.land/r/test").Address()429	zero := &ContractAuthority{contractPath: "gno.land/r/test", contractAddr: code}430	err := zero.Authorize(code, "test", func() error {431		return nil432	})433	uassert.True(t, err != nil, "nil handler authority should fail to authorize")434435	// Test valid configuration436	contractAuth := NewContractAuthority("gno.land/r/test", handler)437	err = contractAuth.Authorize(code, "test", func() error {438		return nil439	})440	uassert.True(t, err == nil, "valid contract authority should authorize successfully")441}442443func TestAuthorizerString(cur realm, t *testing.T) {444	auth := NewWithMembers(cur.Address())445	addr := cur.Address()446447	// Test initial string representation448	str := auth.String()449	uassert.Equal(t, str, "member_authority["+string(addr)+"]")450451	// Test string after transfer — caller is the current member (cur).452	autoAuth := NewAutoAcceptAuthority()453	var err error454	func(cur realm) { err = auth.Transfer(0, cur, autoAuth) }(cross(cur))455	uassert.True(t, err == nil, "transfer should succeed")456	str = auth.String()457	uassert.Equal(t, str, "auto_accept_authority")458459	// Test custom authority — auto-accept lets anyone transfer.460	customAuth := &mockAuthority{}461	func(cur realm) { err = auth.Transfer(0, cur, customAuth) }(cross(cur))462	uassert.True(t, err == nil, "transfer should succeed")463	str = auth.String()464	uassert.Equal(t, str, "custom_authority[mock]")465}466467type mockAuthority struct{}468469func (c mockAuthority) String() string { return "mock" }470func (a mockAuthority) Authorize(caller address, title string, action PrivilegedAction, args ...any) error {471	// autoaccept472	return action()473}474475func TestAuthorityString(cur realm, t *testing.T) {476	alice := testutils.TestAddress("alice")477478	// MemberAuthority479	memberAuth := NewMemberAuthority(alice)480	memberStr := memberAuth.String()481	expectedMemberStr := "member_authority[g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh]"482	uassert.Equal(t, memberStr, expectedMemberStr)483484	// ContractAuthority — the proposer is rendered, not just the path.485	// Without it the two constructors below are indistinguishable, which486	// is what made consumer-level assertions on this string blind to an487	// authority being swapped for a wide-open one.488	contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { return nil })489	contractStr := contractAuth.String()490	expectedContractStr := "contract_authority[contract=gno.land/r/test,proposer=contract-identity]"491	uassert.Equal(t, contractStr, expectedContractStr)492493	// Same path, same handler, explicit open proposer — must render494	// differently from the gated default above.495	openAuth := NewRestrictedContractAuthority(496		"gno.land/r/test",497		func(title string, action PrivilegedAction) error { return nil },498		NewAutoAcceptAuthority(),499	)500	uassert.Equal(t,501		"contract_authority[contract=gno.land/r/test,proposer=auto_accept_authority]",502		openAuth.String())503	uassert.NotEqual(t, contractStr, openAuth.String())504505	// AutoAcceptAuthority506	autoAuth := NewAutoAcceptAuthority()507	autoStr := autoAuth.String()508	expectedAutoStr := "auto_accept_authority"509	uassert.Equal(t, autoStr, expectedAutoStr)510511	// DroppedAuthority512	droppedAuth := NewDroppedAuthority()513	droppedStr := droppedAuth.String()514	expectedDroppedStr := "dropped_authority"515	uassert.Equal(t, droppedStr, expectedDroppedStr)516}517518// TestContractAuthorityUnauthorizedCaller verifies the519// fix: a ContractAuthority's default proposer is the contract itself, so520// any other caller is rejected UPSTREAM (at the proposer) and the handler521// and privileged action never run. Previously the default proposer was522// AutoAcceptAuthority, and the only guard was a handler-side523// `unsafe.CurrentRealm() == contractAddr` check — which was bypassable524// and which many consumers (e.g. r/gnops/valopers) omitted entirely,525// leaving no caller check at all.526func TestContractAuthorityUnauthorizedCaller(cur realm, t *testing.T) {527	contractPath := "gno.land/r/testcontract"528	contractAddr := chain.PackageAddress(contractPath)529	unauthorizedAddr := testutils.TestAddress("unauthorized")530531	// A permissive handler that simply runs the action — the realistic532	// shape a consumer registers. The package, not the handler, must be533	// the one that rejects an unauthorized caller.534	handlerExecuted := false535	contractHandler := func(title string, action PrivilegedAction) error {536		handlerExecuted = true537		return action()538	}539	contractAuth := NewContractAuthority(contractPath, contractHandler)540541	actionExecuted := false542	privilegedAction := func() error {543		actionExecuted = true544		return nil545	}546547	// 1. Unauthorized caller: rejected by the contract-identity proposer548	// before the handler or the action can run.549	err := contractAuth.Authorize(unauthorizedAddr, "test_action", privilegedAction)550	uassert.Error(t, err, "unauthorized caller must be rejected")551	uassert.ErrorContains(t, err, "unauthorized", "rejection must come from the contract-identity proposer")552	uassert.False(t, handlerExecuted, "handler must not run for an unauthorized caller")553	uassert.False(t, actionExecuted, "privileged action must not run for an unauthorized caller")554555	// 2. The contract itself is authorized.556	err = contractAuth.Authorize(contractAddr, "test_action", privilegedAction)557	uassert.NoError(t, err, "the contract itself must be authorized")558	uassert.True(t, handlerExecuted, "handler must run when the contract is the caller")559	uassert.True(t, actionExecuted, "privileged action must run when the contract is the caller")560}561562// TestAuthorizerDoByPrevious verifies the "calling realm authorizes"563// pattern: a function (the inner crossing closure) invokes564// DoByPrevious so the authority check sees cur.Previous() — the realm565// that crossed into it — not the function's own realm.566//567// Each scenario crosses into the inner closure via cross(cur) after568// SetRealm — inside the closure, cur is the fresh live cur and569// cur.Previous() is the SetRealm'd outer realm. This is the only way570// to exercise DoByPrevious correctly under the IsCurrent guard, which571// rejects synthetic realm values (testing.MakeRealm) and stored572// stale captures.573func TestAuthorizerDoByPrevious(cur realm, t *testing.T) {574	alice := testutils.TestAddress("alice")575	bob := testutils.TestAddress("bob")576577	auth := NewWithMembers(alice)578579	// alice (member) crosses in: cur.Previous() == alice inside the inner closure.580	testing.SetRealm(testing.NewUserRealm(alice))581	executed := false582	args := []any{"test_arg", 123}583	func(cur realm) {584		err := auth.DoByPrevious(0, cur, "test_action", func() error {585			executed = true586			return nil587		}, args...)588		uassert.NoError(t, err, "expected no error")589		uassert.True(t, executed, "action should have been executed")590	}(cross(cur))591592	expectedErr := errors.New("test error")593	func(cur realm) {594		err := auth.DoByPrevious(0, cur, "test_action", func() error {595			return expectedErr596		})597		uassert.ErrorContains(t, err, expectedErr.Error(), "expected error")598	}(cross(cur))599600	// bob (not a member) crosses in: Authorize must reject.601	testing.SetRealm(testing.NewUserRealm(bob))602	executed = false603	func(cur realm) {604		err := auth.DoByPrevious(0, cur, "test_action", func() error {605			executed = true606			return nil607		}, "unauthorized_arg")608		uassert.ErrorContains(t, err, "unauthorized", "expected error")609		uassert.False(t, executed, "action should not have been executed")610	}(cross(cur))611}612613// ---------------------------------------------------------------------------614// Contract-identity gate: regression suite.615//616// A plain NewContractAuthority now defaults its proposer to the contract617// ITSELF (MemberAuthority of the contract's own package address) instead of618// AutoAcceptAuthority. These tests pin the security contract: only the619// contract may drive privileged actions, an external caller can neither620// drive nor Transfer the authority, the contract can still rotate its own621// authority, and the escape hatch for a genuinely-open proposer still622// exists.623// ---------------------------------------------------------------------------624625// The default proposer accepts the contract itself and rejects everyone626// else, before the handler or action can run.627func TestDefaultProposerIsContractOnly(cur realm, t *testing.T) {628	const path = "gno.land/r/defcontract"629	contractAddr := chain.PackageAddress(path)630631	ran := 0632	ca := NewContractAuthority(path, func(_ string, action PrivilegedAction) error {633		return action()634	})635636	// The contract itself: authorized.637	err := ca.Authorize(contractAddr, "action", func() error { ran++; return nil })638	uassert.NoError(t, err, "the contract itself must be authorized")639640	// Anyone else: rejected at the proposer, action never runs.641	outsider := testutils.TestAddress("outsider")642	err = ca.Authorize(outsider, "action", func() error { ran++; return nil })643	uassert.Error(t, err, "a non-contract caller must be rejected")644	uassert.ErrorContains(t, err, "unauthorized", "rejection must come from the contract-identity proposer")645646	uassert.Equal(t, 1, ran, "only the contract's action must have run")647}648649// An external realm that holds another realm's Authorizer cannot Transfer650// it. The caller derived under IsCurrent is that external realm, not the651// contract, so the rotation is rejected and the authority is left intact.652func TestExternalTransferRejected(cur realm, t *testing.T) {653	authorizer := NewWithAuthority(654		NewContractAuthority("gno.land/r/victim", func(_ string, action PrivilegedAction) error {655			return action()656		}),657	)658659	attacker := testutils.TestAddress("attacker")660	testing.SetRealm(testing.NewUserRealm(attacker))661662	var err error663	func(cur realm) { err = authorizer.Transfer(0, cur, NewDroppedAuthority()) }(cross(cur))664665	uassert.Error(t, err, "external Transfer must be rejected")666	uassert.ErrorContains(t, err, "unauthorized", "rejection must come from the contract-identity proposer")667668	_, ok := authorizer.Authority().(*ContractAuthority)669	uassert.True(t, ok, "authority must be unchanged after a rejected rotation")670}671672// Same shape via DoByPrevious (a privileged action instead of a673// Transfer): an external caller is rejected and the action never runs.674func TestExternalDoByPreviousRejected(cur realm, t *testing.T) {675	authorizer := NewWithAuthority(676		NewContractAuthority("gno.land/r/victim2", func(_ string, action PrivilegedAction) error {677			return action()678		}),679	)680681	ran := false682	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("randomuser")))683	var err error684	func(cur realm) {685		err = authorizer.DoByPrevious(0, cur, "privileged", func() error { ran = true; return nil })686	}(cross(cur))687688	uassert.Error(t, err, "external DoByPrevious must be rejected")689	uassert.False(t, ran, "privileged action must not run for an external caller")690}691692// The contract-identity model does NOT lock out legitimate governance: the693// contract can still rotate (Transfer) its own authority when the transfer694// is driven with the contract as the previous realm (caller == contractAddr).695func TestContractCanRotateItsOwnAuthority(cur realm, t *testing.T) {696	const path = "gno.land/r/selfgov"697	authorizer := NewWithAuthority(698		NewContractAuthority(path, func(_ string, action PrivilegedAction) error {699			return action()700		}),701	)702703	// Drive the transfer with the contract as the caller: set the outer704	// realm to the contract, then cross into a closure so cur.Previous()705	// resolves to the contract.706	testing.SetRealm(testing.NewCodeRealm(path))707	var err error708	func(cur realm) { err = authorizer.Transfer(0, cur, NewAutoAcceptAuthority()) }(cross(cur))709710	uassert.NoError(t, err, "the contract itself must be able to rotate its authority")711	_, ok := authorizer.Authority().(*AutoAcceptAuthority)712	uassert.True(t, ok, "authority should have rotated to auto-accept")713}714715// Boundary test: the raw Authority.Authorize takes `caller` as a plain716// parameter, so a holder of the raw Authority can forge caller ==717// contractAddr. This proves that even so, a forged-caller raw Authorize718// only runs the action the caller itself supplies — it CANNOT mutate an719// Authorizer's installed authority, because the Transfer mutation lives720// only inside Authorizer.Transfer (which derives caller non-forgeably).721// The lesson encoded here: consumers must reach a ContractAuthority through722// the Authorizer wrapper and must not hand out the raw Authority.723//724// WHAT THIS TEST DOES AND DOES NOT FREEZE. Only the final assertion is a725// security property. That raw Authorize ACCEPTS a forged caller is726// current behaviour, not a guarantee — it is the consequence of `caller`727// being a parameter on an exported interface method. If someone later728// hardens that path so raw Authorize stops accepting a forged caller,729// this test going red means the test is stale, NOT that the change is730// wrong: delete the two intermediate assertions and keep the last one.731// (Recorded because asserting NoError here reads like the forgeability732// is wanted. It is tolerated, and only because of what follows.)733func TestForgedCallerCannotTransfer(cur realm, t *testing.T) {734	const path = "gno.land/r/boundary"735	contractAddr := chain.PackageAddress(path)736737	authorizer := NewWithAuthority(738		NewContractAuthority(path, func(_ string, action PrivilegedAction) error {739			return action()740		}),741	)742743	// Attacker obtains the raw Authority and forges the contract as caller.744	attackerRan := false745	err := authorizer.Authority().Authorize(contractAddr, "transfer_authority", func() error {746		attackerRan = true747		return nil748	})749750	// Current behaviour, documented above — not a property to preserve.751	uassert.NoError(t, err, "raw Authorize currently accepts a forged caller (see comment)")752	uassert.True(t, attackerRan, "only the caller's own inert closure runs")753754	// THE property: the installed authority is UNCHANGED. Raw Authorize755	// cannot perform a Transfer — the mutation closure is private to756	// Authorizer.Transfer, which derives caller non-forgeably.757	_, ok := authorizer.Authority().(*ContractAuthority)758	uassert.True(t, ok, "raw Authorize must not be able to transfer the authority")759}760761// Two contract authorities on different paths never cross-authorize.762func TestContractPathIsolation(cur realm, t *testing.T) {763	caB := NewContractAuthority("gno.land/r/pathb", func(_ string, action PrivilegedAction) error {764		return action()765	})766	addrA := chain.PackageAddress("gno.land/r/patha")767768	ran := false769	err := caB.Authorize(addrA, "action", func() error { ran = true; return nil })770	uassert.Error(t, err, "contract A must not authorize contract B's authority")771	uassert.False(t, ran, "action must not run across contract identities")772}773774// Escape hatch: a consumer that genuinely wants open proposals can still775// opt in via NewRestrictedContractAuthority with an AutoAcceptAuthority776// proposer — restoring the pre-fix "anyone can propose" behavior explicitly.777func TestRestrictedAutoAcceptEscapeHatch(cur realm, t *testing.T) {778	ca := NewRestrictedContractAuthority(779		"gno.land/r/open",780		func(_ string, action PrivilegedAction) error { return action() },781		NewAutoAcceptAuthority(),782	)783784	ran := false785	err := ca.Authorize(testutils.TestAddress("anyone"), "action", func() error { ran = true; return nil })786	uassert.NoError(t, err, "an explicit AutoAccept proposer restores open proposals")787	uassert.True(t, ran, "action must run under an explicit AutoAccept proposer")788}789790// Why the nil handler is now rejected at construction rather than791// tolerated: Authorize checks contractHandler == nil BEFORE consulting the792// proposer, and Transfer routes through Authorize, so there is no rotation793// path out. It is a permanent brick reachable by an ordinary deployer794// typo -- the same bricked-governance failure mode, by accident.795//796// The zero value is used because the constructor no longer allows it.797func TestNilHandlerWouldBeUnrotatable(cur realm, t *testing.T) {798	const path = "gno.land/r/nilbrick"799	addr := chain.PackageAddress(path)800	authorizer := NewWithAuthority(&ContractAuthority{801		contractPath: path,802		contractAddr: addr,803		// Both handler and proposer are nil: a struct literal is the only804		// way to reach this, and Authorize fails closed on either.805	})806807	// Drive the rotation as the contract itself -- the only principal the808	// default proposer accepts. If this cannot escape, nobody can.809	testing.SetRealm(testing.NewCodeRealm(path))810	var err error811	func(cur realm) { err = authorizer.Transfer(0, cur, NewAutoAcceptAuthority()) }(cross(cur))812813	uassert.Error(t, err, "a nil-handler authority must not be silently rotatable")814	_, stuck := authorizer.Authority().(*ContractAuthority)815	uassert.True(t, stuck, "nil-handler authority is unrotatable -- hence rejected at construction")816}817818// A crossing closure declared HERE carries this package's identity, not819// the contract path's -- testing.SetRealm does not change that, because820// the VM mints a crossing frame's realm from the callee's declaring821// package. So the DoByCurrent shape that Example_contractAuthority teaches822// cannot be exercised from inside this package at all; it needs a real823// realm. It is covered by filetests/z_contract_authority_shape_filetest.gno.824//825// The same property is why an exported crossing closure leaks a realm's826// authority to any caller -- it is one mechanism seen from two827// sides. This test pins the half that is observable here: an unrelated828// package's frame is rejected.829func TestForeignFrameCannotDriveContractAuthority(cur realm, t *testing.T) {830	auth := NewWithAuthority(NewContractAuthority("gno.land/r/demo/dao", mockDAOHandler))831832	testing.SetRealm(testing.NewCodeRealm("gno.land/r/example"))833	ran := false834	var err error835	func(cur realm) {836		err = auth.DoByCurrent(0, cur, "update_params", func() error {837			ran = true838			return nil839		})840	}(cross(cur))841842	uassert.Error(t, err, "a frame from another package must not drive this authority")843	uassert.False(t, ran, "the action must not run")844}845846// A malformed contract path binds the authority to an address no realm can847// ever present. Because Transfer routes through the same gate, such an848// authority is also unrotatable: a permanent brick, and the same failure849// mode the nil-handler panic exists to prevent. Rejected at construction.850//851// Pre-fix these all constructed happily; the AutoAccept default made the852// typo harmless (and insecure), so nothing complained.853func TestContractPathValidationRejectsMalformed(cur realm, t *testing.T) {854	handler := func(_ string, action PrivilegedAction) error { return action() }855856	for _, path := range []string{857		"gno.land/r/gov/dao ",  // trailing space858		" gno.land/r/gov/dao",  // leading space859		"gno.land/r/gov/dao\n", // trailing newline860		"gno.land/r/gov dao",   // embedded space861		" ",862		"not a path",863		"GNO.LAND/R/GOV/DAO", // uppercase: not a legal gno pkgpath864		"gno.land//r/gov/dao",865		"gno.land/r/gov/dao/",866		"singlesegment",867	} {868		uassert.PanicsContains(t, cur, "contract path", func() {869			NewContractAuthority(path, handler)870		}, "malformed path must be rejected: "+path)871		uassert.PanicsContains(t, cur, "contract path", func() {872			NewRestrictedContractAuthority(path, handler, NewAutoAcceptAuthority())873		}, "malformed path must be rejected by the restricted ctor too: "+path)874	}875876	// Real paths still construct, including dashes, digits, underscores and877	// version suffixes.878	for _, path := range []string{879		"gno.land/r/gov/dao",880		"gno.land/r/gnops/valopers",881		"gno.land/p/moul/authz/v0",882		"gno.land/r/sys/validators/v0",883		"gno.land/r/some-user/my_pkg2",884	} {885		uassert.NotPanics(t, cur, func() {886			NewContractAuthority(path, handler)887		}, "legitimate path must construct: "+path)888	}889}890891// spoofProposer is a wide-open Authority that LIES in String(), returning892// the same text the canonical contract-identity default renders.893type spoofProposer struct{}894895func (spoofProposer) Authorize(caller address, title string, action PrivilegedAction, args ...any) error {896	return action()897}898899func (spoofProposer) String() string { return "contract-identity" }900901// A foreign proposer must not be able to impersonate the gated default in902// the rendered description. ContractAuthority.String() is promoted by this903// package as the one surface distinguishing a gated authority from a904// wide-open one, and consumers assert on it (r/gnops/valopers.Auth()). An905// impl that chooses its own String() text defeated that: a fully permissive906// authority rendered byte-identical to the default, so every string-based907// configuration pin stayed green while the gate was gone.908func TestSpoofedProposerCannotImpersonateContractIdentity(cur realm, t *testing.T) {909	const path = "gno.land/r/spoofprobe"910	handler := func(_ string, action PrivilegedAction) error { return action() }911	outsider := chain.PackageAddress("gno.land/r/outsider")912913	gated := NewContractAuthority(path, handler)914	spoof := NewRestrictedContractAuthority(path, handler, spoofProposer{})915916	// The behavioural difference the strings must reflect.917	uassert.Error(t, gated.Authorize(outsider, "t", func() error { return nil }),918		"the gated default must reject an outsider")919	uassert.NoError(t, spoof.Authorize(outsider, "t", func() error { return nil }),920		"the spoofing proposer is wide open -- that is the point of the test")921922	// ...and they do.923	uassert.NotEqual(t, gated.String(), spoof.String(),924		"a wide-open authority must not render identically to the gated default")925	uassert.Equal(t,926		"contract_authority[contract="+path+",proposer=contract-identity]",927		gated.String())928	uassert.Equal(t,929		"contract_authority[contract="+path+",proposer=custom_authority[contract-identity]]",930		spoof.String(),931		"a non-canonical proposer must be wrapped, not trusted to name itself")932933	// Same guarantee through the Authorizer wrapper, which is the shape a934	// consumer realm actually exposes.935	uassert.NotEqual(t,936		NewWithAuthority(gated).String(),937		NewWithAuthority(spoof).String())938}939940// An explicit proposer REPLACES the contract-identity gate; it does not add941// to it. NewRestrictedContractAuthority(govdaoPath, h, member(alice)) means942// "alice, and not GovDAO" -- contractAddr is never consulted. Pinned because943// the rendered string reads like a conjunction and the old godoc said944// "widen", so a consumer could reasonably have expected "both".945func TestExplicitProposerReplacesIdentityGate(cur realm, t *testing.T) {946	const path = "gno.land/r/gov/dao"947	handler := func(_ string, action PrivilegedAction) error { return action() }948	govdao := chain.PackageAddress(path)949	alice := chain.PackageAddress("gno.land/r/alice")950951	gated := NewContractAuthority(path, handler)952	replaced := NewRestrictedContractAuthority(path, handler, NewMemberAuthority(alice))953954	uassert.NoError(t, gated.Authorize(govdao, "x", func() error { return nil }),955		"the default gate accepts the bound contract")956	uassert.Error(t, gated.Authorize(alice, "x", func() error { return nil }),957		"the default gate rejects everyone else")958959	uassert.Error(t, replaced.Authorize(govdao, "x", func() error { return nil }),960		"an explicit proposer REPLACES the identity gate: govdao is no longer accepted")961	uassert.NoError(t, replaced.Authorize(alice, "x", func() error { return nil }),962		"only the explicit proposer's principal is accepted")963}964

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.