Transaction

A4FAF77A070111…EFFEDFD51F94

Block 77,246 · index 0 · indexed

Summary

Hash
A4FAF77A070111C0316FDBBCB5E10E72120CFB97AE57F779B119EFFEDFD51F94
Block
77,246
Size
18180 bytes
Gas used
22,419,809 / 26,903,735
Fee
26904ugnot
Status
success

Messages

#1AddPackagegno.land/r/gnoswap/rbac/v121 arguments

Arguments · 21

  1. #1rbac
  2. #2README.md
  3. #3# RBAC Role-based access control management realm. ## Overview RBAC realm manages role addresses and permissions for the GnoSwap protocol, integrating with the access package. ## Configuration - **Admin/Governance Control**: Role management by admin or governance - **Dynamic Roles**: Add/remove at runtime - **Access Integration**: Syncs with access package - **Owner-Managed Admin Role**: `admin` role is bound to RBAC owner and cannot be updated via `UpdateRoleAddress` ## Key Functions ### `RegisterRole(cur realm, roleName string, roleAddress address)` Registers new role in system. Only callable by admin or governance. ### `RemoveRole(cur realm, roleName string)` Removes existing role. Only callable by admin or governance. System roles cannot be removed. ### `UpdateRoleAddress(cur realm, roleName string, addr address)` Updates address for role. Only callable by admin or governance. The `admin` role is not updatable via this function and is managed through ownership transfer. ### `GetRoleAddress(roleName string) (address, error)` Returns address for role. ### `IsOwner(addr address) bool` Returns true if addr is the current owner. ### `IsPendingOwner(addr address) bool` Returns true if addr is the pending owner. ### `GetOwner() address` Returns the current owner address. ### `GetPendingOwner() address` Returns the pending owner address. ### `TransferOwnership(cur realm, newOwner address)` Initiates two-step ownership transfer. Only callable by current owner. ### `AcceptOwnership(cur realm)` Accepts pending ownership transfer. Only callable by pending owner. Also updates the `admin` role address and syncs it to the access package. ## Gnoweb `Render("")` shows the owner, pending owner (`None` when absent), and the fixed system-role assignments. Unassigned roles are explicit; custom roles are not enumerated. Unsupported paths return `404`. ## Usage ```go // Register new role (requires admin or governance) RegisterRole(cross(cur), "new_role", roleAddress) // Update role address UpdateRoleAddress(cross(cur), "staker", newAddress) // Admin role is updated via ownership transfer TransferOwnership(cross(cur), newAdmin) AcceptOwnership(cross(cur)) // Get role address addr, err := GetRoleAddress("router") // Transfer ownership (two-step) TransferOwnership(cross(cur), newAdmin) // Step 1: Initiate AcceptOwnership(cross(cur)) // Step 2: Accept (by newAdmin) ``` ## Contract Upgrade RBAC supports contract upgrades by changing role addresses. Versioned implementations live under component-specific realm paths (for example, `gno.land/r/gnoswap/pool/v1`), while the stable realm resolves calls through the current role address. ### Upgrade Process 1. **Deploy a new component version** under its versioned realm path. 2. **Update the relevant role address** to point to the new implementation. 3. **Verify distribution and call flows** use the new role address. ### Versioned Components This checkout contains versioned implementations for: - `pool` - `position` - `router` - `staker` - `gov/governance` - `gov/staker` - `launchpad` - `protocol_fee` The `community_pool` role is a distribution and treasury destination, not a versioned component. Updating that role redirects distributions to the selected address. ### Example: GNS Distribution Upgrade ```go func changeDistributionTarget(cur realm) { // Update role addresses through the RBAC realm. rbac.UpdateRoleAddress(cross(cur), "staker", newStakerAddr) rbac.UpdateRoleAddress(cross(cur), "gov_staker", newGovStakerAddr) rbac.UpdateRoleAddress(cross(cur), "devops", newDevOpsAddr) // community_pool is a distribution target, not a versioned implementation. rbac.UpdateRoleAddress(cross(cur), "community_pool", newCommunityPoolAddr) } ``` ### Test Example The upgrade mechanism is demonstrated in the [upgrade scenario test](../../../scenario/upgrade/change_gns_distribution_target_filetest.gno). ```go // The scenario initializes distribution targets, updates role addresses, // and verifies that subsequent GNS distributions use the new addresses. func changeDistributionTarget(cur realm) { rbac.UpdateRoleAddress(cross(cur), "staker", newStakerAddr) rbac.UpdateRoleAddress(cross(cur), "gov_staker", newGovStakerAddr) rbac.UpdateRoleAddress(cross(cur), "devops", newDevOpsAddr) rbac.UpdateRoleAddress(cross(cur), "community_pool", newCommunityPoolAddr) } ``` ## Security - Admin or governance authorization is required for role management - Ownership transfer is restricted to the current owner and pending owner - Role updates are synchronized with the access package - Role validation is performed before updates
  4. #4assert.gno
  5. #5package rbac import ( "strings" ufmt "gno.land/p/nt/ufmt/v0" prbac "gno.land/p/gnoswap/rbac/v1" ) // assertIsOwner panics if address is not the current owner. func assertIsOwner(addr address) { if manager.Owner() != addr { panic(makeErrorWithDetails( errCallerIsNotOwner, ufmt.Sprintf("caller: %s", addr.String()), )) } } // assertIsPendingOwner panics if address is not the pending owner. func assertIsPendingOwner(addr address) { if manager.PendingOwner() != addr { panic(makeErrorWithDetails( errCallerIsNotPendingOwner, ufmt.Sprintf("caller: %s", addr.String()), )) } } // assertIsAdmin panics if address is not authorized for admin role. func assertIsAdmin(addr address) { if !manager.IsAuthorized(prbac.ROLE_ADMIN.String(), addr) { panic( makeErrorWithDetails( errCallerIsNotAdmin, ufmt.Sprintf("caller: %s", addr.String()), ), ) } } // assertIsValidRoleName panics if roleName is invalid (empty or whitespace-only). func assertIsValidRoleName(roleName string) { if strings.TrimSpace(roleName) == "" { panic(makeErrorWithDetails( errInvalidRoleName, ufmt.Sprintf("role name: %q", roleName), )) } } func assertIsValidAddress(addr address) { if !addr.IsValid() { panic(makeErrorWithDetails( errInvalidAddress, ufmt.Sprintf("address: %s", addr.String()), )) } } func assertNotAdminRole(roleName string) { if strings.TrimSpace(roleName) == prbac.ROLE_ADMIN.String() { panic(makeErrorWithDetails( errAdminRoleUpdateForbidden, ufmt.Sprintf("role name: %s", roleName), )) } } func assertIsAdminOrGovernance(addr address) { if manager.IsAuthorized(prbac.ROLE_ADMIN.String(), addr) || manager.IsAuthorized(prbac.ROLE_GOVERNANCE.String(), addr) { return } panic(makeErrorWithDetails( errCallerIsNotAdminOrGovernance, ufmt.Sprintf("caller: %s", addr.String()), )) }
  6. #6consts.gno
  7. #7package rbac import "chain" // User-controlled addresses for protocol roles. const ( // ADMIN is the initial admin address for RBAC management. ADMIN address = "g1qvtuwyxcwezfqu3fp56pr5krgfjjyumeeecmzg" // DEV_OPS is the initial DevOps address for operational tasks. DEV_OPS address = "g1kg87wr06tmw7uvlyk4yrj3r2pzmt2j83d47nu7" ) // Derived package addresses — computed deterministically from deployment paths // so they stay correct if packages are renamed or re-deployed. var ( EMISSION_ADDR = chain.PackageAddress("gno.land/r/gnoswap/emission") POOL_ADDR = chain.PackageAddress("gno.land/r/gnoswap/pool") POSITION_ADDR = chain.PackageAddress("gno.land/r/gnoswap/position") ROUTER_ADDR = chain.PackageAddress("gno.land/r/gnoswap/router") STAKER_ADDR = chain.PackageAddress("gno.land/r/gnoswap/staker") PROTOCOL_FEE_ADDR = chain.PackageAddress("gno.land/r/gnoswap/protocol_fee") COMMUNITY_POOL_ADDR = chain.PackageAddress("gno.land/r/gnoswap/community_pool/v1") GOV_GOVERNANCE_ADDR = chain.PackageAddress("gno.land/r/gnoswap/gov/governance") GOV_STAKER_ADDR = chain.PackageAddress("gno.land/r/gnoswap/gov/staker") GOV_XGNS_ADDR = chain.PackageAddress("gno.land/r/gnoswap/gov/xgns") LAUNCHPAD_ADDR = chain.PackageAddress("gno.land/r/gnoswap/launchpad") )
  8. #8errors.gno
  9. #9package rbac import ( ufmt "gno.land/p/nt/ufmt/v0" ) const ( errCallerIsNotOwner = "caller is not owner" errCallerIsNotAdmin = "caller is not admin" errCallerIsNotAdminOrGovernance = "caller is not admin or governance" errCallerIsNotPendingOwner = "caller is not pending owner" errInvalidAddress = "invalid address" errInvalidRoleName = "invalid role name" errAdminRoleUpdateForbidden = "admin role is managed by owner" ) // makeErrorWithDetails combines an error with additional details. func makeErrorWithDetails(message string, details string) error { return ufmt.Errorf("%s || %s", message, details) }
  10. #10gnomod.toml
  11. #11module = "gno.land/r/gnoswap/rbac/v1" gno = "0.9"
  12. #12ownership.gno
  13. #13package rbac import ( ufmt "gno.land/p/nt/ufmt/v0" prbac "gno.land/p/gnoswap/rbac/v1" "gno.land/r/gnoswap/access/v1" ) // IsOwner reports whether addr is the current owner address. // // Parameters: // - addr: address to compare with the configured owner // // Returns: // - owner: true when addr equals the current owner; false otherwise func IsOwner(addr address) bool { return manager.Owner() == addr } // IsPendingOwner reports whether addr is the address awaiting ownership acceptance. // // Parameters: // - addr: address to compare with the configured pending owner // // Returns: // - pending: true when addr equals the pending owner; false otherwise func IsPendingOwner(addr address) bool { return manager.PendingOwner() == addr } // GetOwner returns the currently configured owner address. // // Returns: // - owner: current owner address func GetOwner() address { return manager.Owner() } // GetPendingOwner returns the address awaiting ownership acceptance. // // Returns: // - pendingOwner: pending owner address, or the zero address when no transfer is pending func GetPendingOwner() address { return manager.PendingOwner() } // AcceptOwnership completes a pending ownership transfer and synchronizes the // admin role with the new owner. // // Parameters: // - cur: current realm context; callers use cross(cur) when crossing into this realm // // Only callable by the pending owner. func AcceptOwnership(cur realm) { caller := cur.Previous().Address() assertIsPendingOwner(caller) err := manager.AcceptOwnershipBy(caller) if err != nil { panic(err) } newOwner := manager.Owner() err = manager.UpdateRoleAddress(prbac.ROLE_ADMIN.String(), newOwner) if err != nil { panic(makeErrorWithDetails( err.Error(), ufmt.Sprintf( "role name: %s, address: %s", prbac.ROLE_ADMIN.String(), newOwner.String()), )) } access.SetRoleAddress(cross(cur), prbac.ROLE_ADMIN.String(), newOwner) } // TransferOwnership starts a two-step ownership transfer to addr. // // Parameters: // - cur: current realm context; callers use cross(cur) when crossing into this realm // - addr: valid address that will become pending owner and may later accept ownership // // Only callable by the current owner. func TransferOwnership(cur realm, addr address) { caller := cur.Previous().Address() assertIsOwner(caller) assertIsValidAddress(addr) err := manager.TransferOwnershipBy(addr, caller) if err != nil { panic(err) } }
  14. #14rbac.gno
  15. #15package rbac import ( "chain" "gno.land/r/gnoswap/access/v1" prbac "gno.land/p/gnoswap/rbac/v1" ufmt "gno.land/p/nt/ufmt/v0" ) var manager *prbac.RBAC func init(cur realm) { initRbac(cur) } // initRbac initializes RBAC manager with default admin and role mappings. func initRbac(cur realm) { manager = prbac.NewRBACWithAddress(ADMIN) // Prepare initial roles for one-time initialization for role, addr := range _defaultRoleAddresses { roleName := role.String() err := manager.RegisterRole(roleName, addr) if err != nil { panic(makeErrorWithDetails( err.Error(), ufmt.Sprintf("role name: %s, address: %s", roleName, addr.String()), )) } // Update access package with the role address access.SetRoleAddress(cross(cur), roleName, addr) } } // RegisterRole registers a new role in the RBAC system. // // Parameters: // - cur: Current realm context; callers use cross(cur) when crossing into this realm. // - roleName: nonempty role name to register in the RBAC manager. // - roleAddress: address assigned to the new role and mirrored in access control. // // Only callable by admin or governance. func RegisterRole(cur realm, roleName string, roleAddress address) { prev := cur.Previous() caller := prev.Address() assertIsAdminOrGovernance(caller) assertIsValidRoleName(roleName) assertIsValidAddress(roleAddress) err := manager.RegisterRole(roleName, roleAddress) if err != nil { if err.Error() == "role already exists" { panic(ufmt.Sprintf("role %s already exists", roleName)) } panic(makeErrorWithDetails( errInvalidRoleName, ufmt.Sprintf("role name: %s", roleName), )) } // Set the role in access control access.SetRoleAddress(cross(cur), roleName, roleAddress) chain.Emit( "RegisterRole", "prevAddr", caller.String(), "prevRealm", prev.PkgPath(), "roleName", roleName, "roleAddress", roleAddress.String(), ) } // UpdateRoleAddress updates the address assigned to a role. // // Parameters: // - cur: Current realm context; callers use cross(cur) when crossing into this realm. // - roleName: existing non-admin role name whose address is changed. // - addr: valid replacement address assigned to the role and access-control mapping. // // Only callable by admin or governance. func UpdateRoleAddress(cur realm, roleName string, addr address) { prev := cur.Previous() caller := prev.Address() assertIsAdminOrGovernance(caller) assertIsValidRoleName(roleName) assertIsValidAddress(addr) assertNotAdminRole(roleName) err := manager.UpdateRoleAddress(roleName, addr) if err != nil { panic(makeErrorWithDetails( err.Error(), ufmt.Sprintf("role name: %s, address: %s", roleName, addr.String()), )) } // Set the role address in access control access.SetRoleAddress(cross(cur), roleName, addr) chain.Emit( "UpdateRoleAddress", "prevAddr", caller.String(), "prevRealm", prev.PkgPath(), "roleName", roleName, "roleAddress", addr.String(), ) } // RemoveRole removes a role from the RBAC system. // // Parameters: // - cur: Current realm context; callers use cross(cur) when crossing into this realm. // - roleName: existing non-admin role name to remove from RBAC and access control. // // Only callable by admin or governance. func RemoveRole(cur realm, roleName string) { prev := cur.Previous() caller := prev.Address() assertIsAdminOrGovernance(caller) assertIsValidRoleName(roleName) assertNotAdminRole(roleName) err := manager.RemoveRole(roleName) if err != nil { panic(makeErrorWithDetails( err.Error(), ufmt.Sprintf("role name: %s", roleName), )) } // Remove the role from access control access.RemoveRole(cross(cur), roleName) chain.Emit( "RemoveRole", "prevAddr", caller.String(), "prevRealm", prev.PkgPath(), "roleName", roleName, "roleAddress", "", ) } // GetRoleAddress returns the address assigned to roleName. // // Parameters: // - roleName: exact role name to look up in the RBAC manager. // // Returns: // - roleAddress: address assigned to roleName, or the empty address when absent. // - error: nil when roleName exists; otherwise the manager's role-not-found error. func GetRoleAddress(roleName string) (address, error) { return manager.GetRoleAddress(roleName) }
  16. #16render.gno
  17. #17package rbac import ( prbac "gno.land/p/gnoswap/rbac/v1" "gno.land/p/moul/md/v0" "gno.land/p/moul/mdtable/v0" ) // Render returns ownership and assignments for the protocol's fixed system roles. func Render(path string) string { if path != "" { return "404\n" } roles := []prbac.SystemRole{ prbac.ROLE_ADMIN, prbac.ROLE_DEVOPS, prbac.ROLE_COMMUNITY_POOL, prbac.ROLE_GOVERNANCE, prbac.ROLE_GOV_STAKER, prbac.ROLE_XGNS, prbac.ROLE_POOL, prbac.ROLE_POSITION, prbac.ROLE_ROUTER, prbac.ROLE_STAKER, prbac.ROLE_EMISSION, prbac.ROLE_LAUNCHPAD, prbac.ROLE_PROTOCOL_FEE, } ownershipTable := mdtable.Table{ Headers: []string{"Field", "Address"}, Rows: [][]string{ {"Owner", renderOwnershipAddress(GetOwner())}, {"Pending owner", renderOwnershipAddress(GetPendingOwner())}, }, } out := md.H1("Gnoswap RBAC") + "\n" + md.H2("Ownership") + "\n" + ownershipTable.String() + "\n" + md.H2("System roles") roleTable := mdtable.Table{ Headers: []string{"Role", "Address"}, } for _, role := range roles { roleTable.Append([]string{role.String(), renderRoleAddress(role)}) } return out + "\n" + roleTable.String() } func renderOwnershipAddress(addr address) string { if addr == address("") { return "None" } return addr.String() } func renderRoleAddress(role prbac.SystemRole) string { addr, err := GetRoleAddress(role.String()) if err != nil || addr == address("") { return "Unassigned" } return md.InlineCode(addr.String()) }
  18. #18role.gno
  19. #19package rbac import prbac "gno.land/p/gnoswap/rbac/v1" // MUST BE IMMUTABLE, DO NOT MODIFY. // _defaultRoleAddresses maps system roles to their default addresses. // Used during RBAC initialization to set up the protocol role structure. var _defaultRoleAddresses = map[prbac.SystemRole]address{ prbac.ROLE_ADMIN: ADMIN, prbac.ROLE_DEVOPS: DEV_OPS, prbac.ROLE_COMMUNITY_POOL: COMMUNITY_POOL_ADDR, prbac.ROLE_GOVERNANCE: GOV_GOVERNANCE_ADDR, prbac.ROLE_GOV_STAKER: GOV_STAKER_ADDR, prbac.ROLE_XGNS: GOV_XGNS_ADDR, prbac.ROLE_POOL: POOL_ADDR, prbac.ROLE_POSITION: POSITION_ADDR, prbac.ROLE_ROUTER: ROUTER_ADDR, prbac.ROLE_STAKER: STAKER_ADDR, prbac.ROLE_EMISSION: EMISSION_ADDR, prbac.ROLE_LAUNCHPAD: LAUNCHPAD_ADDR, prbac.ROLE_PROTOCOL_FEE: PROTOCOL_FEE_ADDR, }
  20. #20/gno.MemPackageType
  21. #21 MPUserAll

Result log

msg:0,success:true,log:,events:[]

← Back to block 77,246