Transaction
4F681A22D8C4AC…CB6BDC0108EC
Block 77,241 · index 0 · indexed
Summary
- Hash
- 4F681A22D8C4AC4BA2E07847E7772BDD7DB6924178DC894BF6A1CB6BDC0108EC
- Block
- 77,241
- Size
- 26591 bytes
- Gas used
- 35,489,406 / 42,587,252
- Fee
- 42588ugnot
- Status
- success
Messages
- Package
- gno.land/p/gnoswap/rbac/v1
Arguments · 19
- #1rbac
- #2README.md
- #3# RBAC Role-Based Access Control package for Gno smart contracts. ## Overview RBAC system enabling dynamic role management with address-based authorization and two-step ownership transfer. ## Features - Dynamic role registration with address assignment - Address-based authorization checks - Two-step ownership transfer (Ownable2Step pattern) - System role protection (cannot be removed) - Runtime role address updates ## Core API ```go // Create RBAC manager with an explicit owner address. func NewRBACWithAddress(addr address) *RBAC // Role management func (rb *RBAC) RegisterRole(roleName string, addr address) error func (rb *RBAC) UpdateRoleAddress(roleName string, addr address) error func (rb *RBAC) RemoveRole(roleName string) error // Authorization func (rb *RBAC) IsAuthorized(roleName string, addr address) bool // Role queries func (rb *RBAC) GetRoleAddress(roleName string) (address, error) func (rb *RBAC) GetAllRoleAddresses() map[string]address // Ownership management func (rb *RBAC) Owner() address func (rb *RBAC) PendingOwner() address func (rb *RBAC) TransferOwnershipBy(newOwner, caller address) error func (rb *RBAC) AcceptOwnershipBy(addr address) error func (rb *RBAC) DropOwnershipBy(addr address) error ``` ## Usage ```go // Create manager with owner manager := rbac.NewRBACWithAddress(adminAddr) // Register role with address err := manager.RegisterRole("editor", editorAddr) if err != nil { // handle error } // Check authorization if manager.IsAuthorized("editor", callerAddr) { // caller is authorized as editor } // Update role address err = manager.UpdateRoleAddress("editor", newEditorAddr) // Get role address addr, err := manager.GetRoleAddress("editor") ``` ## System Roles Reserved system-role names cannot be removed after they are registered. A new RBAC manager starts with no role entries; register each system role explicitly with its address. - `admin`, `governance`, `devops` - `pool`, `position`, `router`, `staker` - `emission`, `launchpad`, `protocol_fee` - `gov_staker`, `xgns`, `community_pool` ## Errors | Error | Description | |-------|-------------| | `ErrInvalidRoleName` | Role name is empty or whitespace-only | | `ErrRoleAlreadyExists` | Role already registered | | `ErrRoleDoesNotExist` | Role not found | | `ErrCannotRemoveSystemRole` | Cannot remove a registered system role | | `ErrInvalidAddress` | Invalid address for `UpdateRoleAddress` or ownership transfer; `RegisterRole` stores its supplied address without validation | | `ErrUnauthorized` | Caller is not owner | | `ErrNoPendingOwner` | No pending owner | | `ErrPendingUnauthorized` | Caller is not pending owner | ## Security - Address-based role authorization - Two-step ownership transfer prevents accidental transfers - System roles protected from removal - Role name validation (no empty/whitespace names)
- #4doc.gno
- #5// Package rbac provides a simple address-based Role-Based Access Control (RBAC) // system for Gno smart contracts. It enables dynamic registration, update, and removal // of roles with assigned addresses. // // ## Overview // // The RBAC package provides a manager that maintains an internal registry of roles. // Each role is identified by a unique name and is associated with a single address. // Authorization is performed by checking if a given address matches the role's assigned address. // // Key components of this package include: // // 1. **Role**: Represents a role with a name and an assigned address. // 2. **RBAC Manager**: The core type (RBAC) that manages role registration, address // assignment, authorization verification, and role removal. // 3. **Ownable2Step**: Provides two-step ownership transfer functionality integrated // into the RBAC manager. // // ## Key Features // // - **Dynamic Role Management**: Roles can be registered, updated, and removed at runtime // without requiring contract redeployment. // - **Address-Based Authorization**: Each role is associated with a single address, // - **System Roles**: Reserved system-role names (admin, devops, pool, etc.) // cannot be removed once registered. The constructor does not pre-populate // these role entries; callers register their addresses explicitly. // - **Two-Step Ownership Transfer**: Built-in secure ownership transfer mechanism // requiring explicit acceptance by the new owner. // - **Encapsulation**: Internal state (roles registry) is encapsulated within the RBAC // manager, preventing unintended external modifications. // // ## Workflow // // Typical usage of the RBAC package includes the following steps: // // 1. **Initialization**: Create a new RBAC manager with // `NewRBACWithAddress(addr)`, passing the intended owner explicitly. // 2. **Role Registration**: Register roles using `RegisterRole(roleName, address)`. // 3. **Authorization Check**: Verify if an address is authorized for a role using // `IsAuthorized(roleName, address)`. // 4. **Role Management**: Update role addresses with `UpdateRoleAddress` or remove // non-system roles with `RemoveRole`. // // ## Example Usage // // The following example demonstrates how to use the RBAC package: // // ```gno // package main // // import ( // // "gno.land/p/gnoswap/rbac/v1" // // ) // // func main() { // // The owner is supplied explicitly; no constructor derives it from // // an implicit origin caller. // ownerAddr := address("g1...") // manager := rbac.NewRBACWithAddress(ownerAddr) // // // Define example addresses // adminAddr := address("g1...") // userAddr := address("g1...") // // // Register an "admin" role with adminAddr // if err := manager.RegisterRole("admin", adminAddr); err != nil { // panic(err) // } // // // Register a custom "editor" role with userAddr // if err := manager.RegisterRole("editor", userAddr); err != nil { // panic(err) // } // // // Check if adminAddr is authorized for the "admin" role // if manager.IsAuthorized("admin", adminAddr) { // println("Admin access granted") // } // // // Check if userAddr is authorized for the "admin" role // if !manager.IsAuthorized("admin", userAddr) { // println("User does not have admin access") // } // // // Update the editor role to a different address // newEditorAddr := address("g1...") // if err := manager.UpdateRoleAddress("editor", newEditorAddr); err != nil { // panic(err) // } // // // Get all role addresses // allRoles := manager.GetAllRoleAddresses() // for roleName, addr := range allRoles { // println(roleName, "->", addr.String()) // } // } // // ``` // // ## System Roles // // The package reserves the following system-role names. A new RBAC manager // starts with no role entries, so callers must register each role and address // explicitly. Once registered, a system role cannot be removed: // // - admin: System administrator role // - devops: DevOps operations role // - community_pool: Community pool management role // - governance: Governance system role // - gov_staker: Governance staker role // - xgns: xGNS token role // - pool: Pool management role // - position: Position management role // - router: Router role // - staker: Staker role // - emission: Emission management role // - launchpad: Launchpad role // - protocol_fee: Protocol fee management role // // ## Error Handling // // The package defines several error types: // // - ErrRoleAlreadyExists: Attempting to register a role that already exists. // - ErrRoleDoesNotExist: Attempting to access or modify a non-existent role. // - ErrCannotRemoveSystemRole: Attempting to remove a registered system role. // - ErrInvalidAddress: Providing an invalid address to UpdateRoleAddress or // an ownership-transfer method. RegisterRole stores its supplied address // without validating it. // - ErrUnauthorized: Caller is not the owner when owner permission is required. // - ErrNoPendingOwner: Attempting to accept ownership when no transfer is pending. // - ErrPendingUnauthorized: Caller is not the pending owner when accepting ownership. // // ## Ownership Management // // The RBAC manager includes built-in two-step ownership transfer functionality: // // 1. Current owner calls TransferOwnershipBy to initiate transfer. // 2. New owner calls AcceptOwnershipBy to complete the transfer. // 3. Owner can drop ownership entirely using DropOwnershipBy. // // ## Limitations and Considerations // // - Each role can only have one assigned address. For multi-address authorization, // consider creating multiple roles or implementing a wrapper. // - System roles are protected and cannot be removed to ensure system stability. // - `RegisterRole` does not validate the supplied address. Address validation // is performed by `UpdateRoleAddress` and ownership-transfer methods. // // Package rbac is intended for use in Gno smart contracts requiring simple, // address-based access control with role management capabilities. package rbac
- #6errors.gno
- #7package rbac const ( ErrNoPendingOwner = "no pending owner" ErrUnauthorized = "caller is not owner" ErrPendingUnauthorized = "caller is not pending owner" ErrInvalidAddress = "invalid address" ErrInvalidRoleName = "invalid role name" ErrRoleDoesNotExist = "role does not exist" ErrRoleAlreadyExists = "role already exists" ErrCannotRemoveSystemRole = "cannot remove system role" )
- #8gnomod.toml
- #9module = "gno.land/p/gnoswap/rbac/v1" gno = "0.9"
- #10ownable.gno
- #11package rbac import ( "chain" "errors" ) const ( OwnershipTransferEvent = "OwnershipTransfer" OwnershipTransferStartedEvent = "OwnershipTransferStarted" ) // Ownable2Step implements a two-step ownership transfer mechanism. // It requires the new owner to explicitly accept ownership before the transfer is completed, // preventing accidental transfers to incorrect addresses. // // Note: This package does not verify callers. Consuming realms must extract the actual // caller from the live realm context and pass it to these methods. type Ownable2Step struct { owner address pendingOwner address } // newOwnable2StepWithAddress creates a new Ownable2Step instance with addr as owner. func newOwnable2StepWithAddress(addr address) *Ownable2Step { return &Ownable2Step{ owner: addr, pendingOwner: "", } } // TransferOwnershipBy initiates ownership transfer by setting newOwner as pending owner. // The newOwner must call AcceptOwnershipBy to complete the transfer. // // Parameters: // - newOwner: Non-zero, syntactically valid address to record as the pending owner. // - caller: Address authorized to initiate the transfer; it must equal the current owner. // // Errors: // - ErrUnauthorized: caller is not the current owner // - ErrInvalidAddress: newOwner is empty or has an invalid format // // Returns: // - error: nil when the pending owner is set; otherwise ErrUnauthorized or ErrInvalidAddress. func (o *Ownable2Step) TransferOwnershipBy(newOwner, caller address) error { if !o.IsOwner(caller) { return errors.New(ErrUnauthorized) } if newOwner == zeroAddress || !newOwner.IsValid() { return errors.New(ErrInvalidAddress) } o.pendingOwner = newOwner chain.Emit( OwnershipTransferStartedEvent, "from", o.owner.String(), "to", newOwner.String(), ) return nil } // AcceptOwnershipBy completes the ownership transfer. // Must be called by the pending owner. // // Parameters: // - caller: Address attempting to accept ownership; it must equal the recorded pending owner. // // Errors: // - ErrNoPendingOwner: no ownership transfer is pending // - ErrPendingUnauthorized: caller is not the pending owner // // Returns: // - error: nil when ownership is transferred to caller; otherwise ErrNoPendingOwner or ErrPendingUnauthorized. func (o *Ownable2Step) AcceptOwnershipBy(caller address) error { if o.pendingOwner == zeroAddress { return errors.New(ErrNoPendingOwner) } if !o.IsPendingOwner(caller) { return errors.New(ErrPendingUnauthorized) } prevOwner := o.owner o.owner = o.pendingOwner o.pendingOwner = "" chain.Emit( OwnershipTransferEvent, "from", prevOwner.String(), "to", o.owner.String(), ) return nil } // DropOwnershipBy removes the owner, disabling all owner-only actions. // This is irreversible - when ownership is dropped, no future owner-only operations can be performed. // // Parameters: // - caller: Address requesting the drop; it must equal the current owner. // // Errors: // - ErrUnauthorized: caller is not the current owner // // Returns: // - error: nil when owner and pending owner are cleared; otherwise ErrUnauthorized. func (o *Ownable2Step) DropOwnershipBy(caller address) error { if !o.IsOwner(caller) { return errors.New(ErrUnauthorized) } prevOwner := o.owner o.owner = "" o.pendingOwner = "" chain.Emit( OwnershipTransferEvent, "from", prevOwner.String(), "to", "", ) return nil } // Owner returns the current owner address. Returns empty address if ownership has been dropped. // // Returns: // - address: Current owner address, or the empty address after ownership is dropped. func (o *Ownable2Step) Owner() address { return o.owner } // PendingOwner returns the pending owner address during ownership transfer. Returns empty address if no transfer is pending. // // Returns: // - address: Pending owner address, or the empty address when no transfer is pending. func (o *Ownable2Step) PendingOwner() address { return o.pendingOwner } // IsOwner returns true if the provided caller address is the current owner. // // Parameters: // - caller: Address to compare with the stored current owner address. // // Returns: // - bool: true when caller exactly equals the current owner address; false otherwise. func (o *Ownable2Step) IsOwner(caller address) bool { return o.owner == caller } // IsPendingOwner returns true if the provided caller address is the pending owner. // // Parameters: // - caller: Address to compare with the stored pending owner address. // // Returns: // - bool: true when caller exactly equals the pending owner address; false otherwise. func (o *Ownable2Step) IsPendingOwner(caller address) bool { return o.pendingOwner == caller }
- #12rbac.gno
- #13package rbac import ( "errors" "strings" ) // RBAC encapsulates and manages roles and their permissions. // It combines role management with two-step ownership transfer functionality. type RBAC struct { ownable *Ownable2Step // roles maps role names to their respective `Role` objects roles map[string]*Role } // NewRBACWithAddress creates a new RBAC instance with addr as owner. // // Parameters: // - addr: Address stored as the initial owner of the RBAC instance. // // Returns: // - *RBAC: New RBAC manager with an empty role registry and addr as owner. func NewRBACWithAddress(addr address) *RBAC { return &RBAC{ ownable: newOwnable2StepWithAddress(addr), roles: make(map[string]*Role), } } // IsAuthorized checks if addr has the specified roleName. Returns false if the role does not exist. // // Parameters: // - roleName: Exact role name to look up in the role registry. // - addr: Address to compare with the address assigned to roleName. // // Returns: // - bool: true when roleName exists and its assigned address equals addr; false when it does not. func (rb *RBAC) IsAuthorized(roleName string, addr address) bool { role, exists := rb.roles[roleName] if !exists { return false } return role.IsAuthorized(addr) } // RegisterRole registers a new role with given role name and address. // // Parameters: // - roleName: Role identifier; leading and trailing whitespace is removed before validation and storage. // - addr: Address initially assigned to the role; this method stores it without address validation. // // Errors: // `RegisterRole` returns an error in the following situations: // - `ErrInvalidRoleName`: role name is an empty string or contains only whitespace // - `ErrRoleAlreadyExists`: the role to be registered already exists in RBAC. // - A system-role name may be registered when absent, but remains protected // from removal; this package does not pre-register system roles. // // Returns: // - error: nil when the trimmed role is registered; otherwise ErrInvalidRoleName or ErrRoleAlreadyExists. func (rb *RBAC) RegisterRole(roleName string, addr address) error { roleName = strings.TrimSpace(roleName) if roleName == "" { return errors.New(ErrInvalidRoleName) } if rb.existsRole(roleName) { return errors.New(ErrRoleAlreadyExists) } rb.roles[roleName] = NewRole(roleName, addr) return nil } // UpdateRoleAddress assigns addr to roleName. // // Parameters: // - roleName: Existing role identifier; leading and trailing whitespace is removed before lookup. // - addr: Non-zero, syntactically valid address to assign to the role. // // Errors: // - `ErrInvalidRoleName`: role name is an empty string or contains only whitespace // - `ErrRoleDoesNotExist`: the specified role does not exist in the RBAC system // - `ErrInvalidAddress`: addr is empty or has an invalid format // // Returns: // - error: nil when the existing role is updated; otherwise an error identifying invalid input or a missing role. func (rb *RBAC) UpdateRoleAddress(roleName string, addr address) error { roleName = strings.TrimSpace(roleName) if roleName == "" { return errors.New(ErrInvalidRoleName) } role, exists := rb.roles[roleName] if !exists { return errors.New(ErrRoleDoesNotExist) } if addr == zeroAddress || !addr.IsValid() { return errors.New(ErrInvalidAddress) } role.setAddress(addr) return nil } // RemoveRole removes roleName from the RBAC system. // // Parameters: // - roleName: Role identifier to remove; leading and trailing whitespace is removed before lookup. // // Errors: // - `ErrInvalidRoleName`: role name is an empty string or contains only whitespace // - `ErrRoleDoesNotExist`: the specified role does not exist in the RBAC system // - `ErrCannotRemoveSystemRole`: attempting to remove a system role (e.g., admin, governance, pool, etc.) // // Returns: // - error: nil when a non-system role is removed; otherwise an error for invalid, missing, or protected roles. func (rb *RBAC) RemoveRole(roleName string) error { roleName = strings.TrimSpace(roleName) if roleName == "" { return errors.New(ErrInvalidRoleName) } if !rb.existsRole(roleName) { return errors.New(ErrRoleDoesNotExist) } // Check if it's a system role if IsSystemRole(roleName) { return errors.New(ErrCannotRemoveSystemRole) } // Simply delete the role since permissions are no longer managed here delete(rb.roles, roleName) return nil } // GetAllRoleAddresses returns a map of all role names to their assigned addresses. // // Returns: // - map[string]address: Newly allocated map containing each registered role name and its assigned address. func (rb *RBAC) GetAllRoleAddresses() map[string]address { addresses := make(map[string]address) for roleName, role := range rb.roles { addresses[roleName] = role.Address() } return addresses } // GetRoleAddress returns the address assigned to roleName. // // Parameters: // - roleName: Exact role name to look up; this method does not trim whitespace. // // Errors: // - `ErrRoleDoesNotExist`: the specified role does not exist in the RBAC system // // Returns: // - address: Address assigned to roleName, or the empty address when the role is absent. // - error: nil when roleName exists; otherwise ErrRoleDoesNotExist. func (rb *RBAC) GetRoleAddress(roleName string) (address, error) { role, exists := rb.roles[roleName] if !exists { return "", errors.New(ErrRoleDoesNotExist) } return role.Address(), nil } // Owner returns the current owner address. // // Returns: // - address: Current RBAC owner address, or the empty address if ownership has been dropped. func (rb *RBAC) Owner() address { return rb.ownable.Owner() } // PendingOwner returns the pending owner address during ownership transfer. // // Returns: // - address: Pending owner address, or the empty address when no transfer is pending. func (rb *RBAC) PendingOwner() address { return rb.ownable.PendingOwner() } // AcceptOwnershipBy completes the ownership transfer process. // Must be called by the pending owner. // // Parameters: // - addr: Address attempting to accept ownership; it must equal the recorded pending owner. // // Errors: // - `ErrNoPendingOwner`: no ownership transfer is pending // - `ErrPendingUnauthorized`: addr is not the pending owner // // Returns: // - error: nil when ownership is transferred to addr; otherwise ErrNoPendingOwner or ErrPendingUnauthorized. func (rb *RBAC) AcceptOwnershipBy(addr address) error { return rb.ownable.AcceptOwnershipBy(addr) } // DropOwnershipBy removes the owner, effectively disabling owner-only actions. // This is irreversible and will prevent any future owner-only operations. // // Parameters: // - addr: Address requesting the drop; it must equal the current owner. // // Errors: // - `ErrUnauthorized`: addr is not the current owner // // Returns: // - error: nil when owner and pending owner are cleared; otherwise ErrUnauthorized. func (rb *RBAC) DropOwnershipBy(addr address) error { return rb.ownable.DropOwnershipBy(addr) } // TransferOwnershipBy initiates the two-step ownership transfer process. // The newOwner must call AcceptOwnershipBy to complete the transfer. // // Parameters: // - newOwner: Non-zero, syntactically valid address to record as the pending owner. // - caller: Address authorized to initiate the transfer; it must equal the current owner. // // Errors: // - `ErrUnauthorized`: caller is not the current owner // - `ErrInvalidAddress`: newOwner is empty or has an invalid format // // Returns: // - error: nil when the pending owner is set; otherwise ErrUnauthorized or ErrInvalidAddress. func (rb *RBAC) TransferOwnershipBy(newOwner, caller address) error { return rb.ownable.TransferOwnershipBy(newOwner, caller) } // existsRole checks if name exists in the RBAC system. func (rb *RBAC) existsRole(name string) bool { _, exists := rb.roles[name] return exists }
- #14role.gno
- #15package rbac const zeroAddress = address("") // Role represents a role with a name and an assigned address. type Role struct { // name represents the role's identifier name string address address } // NewRole creates a new Role instance with roleName. // // Parameters: // - roleName: Role identifier stored in the new Role without normalization. // - addr: Address stored as the role's initial assignment without validation. // // Returns: // - *Role: New role containing roleName and its initially assigned addr. func NewRole(roleName string, addr address) *Role { return &Role{ name: roleName, address: addr, } } // Name returns the role's name. // // Returns: // - string: Role identifier stored in the Role. func (r *Role) Name() string { return r.name } // Address returns the address assigned to this role. Returns empty address if no address is assigned. // // Returns: // - address: Address currently assigned to the role, or the empty address when unassigned. func (r *Role) Address() address { return r.address } // IsEmpty returns true if no address is assigned to this role. // // Returns: // - bool: true when the stored role address equals the empty address; false when an address is assigned. func (r *Role) IsEmpty() bool { return r.Address() == zeroAddress } // IsAuthorized returns true if addr matches the role's assigned address. // // Parameters: // - addr: Address to compare with the role's stored assignment. // // Returns: // - bool: true when addr exactly equals the assigned address; false otherwise. func (r *Role) IsAuthorized(addr address) bool { return r.Address() == addr } // setAddress assigns addr to this role. func (r *Role) setAddress(addr address) { r.address = addr }
- #16types.gno
- #17package rbac // SystemRole represents a reserved system-role name that cannot be removed once registered. type SystemRole string const ( ROLE_ADMIN SystemRole = "admin" ROLE_DEVOPS SystemRole = "devops" ROLE_COMMUNITY_POOL SystemRole = "community_pool" ROLE_GOVERNANCE SystemRole = "governance" ROLE_GOV_STAKER SystemRole = "gov_staker" ROLE_XGNS SystemRole = "xgns" ROLE_POOL SystemRole = "pool" ROLE_POSITION SystemRole = "position" ROLE_ROUTER SystemRole = "router" ROLE_STAKER SystemRole = "staker" ROLE_EMISSION SystemRole = "emission" ROLE_LAUNCHPAD SystemRole = "launchpad" ROLE_PROTOCOL_FEE SystemRole = "protocol_fee" ) // MUST BE IMMUTABLE, DO NOT MODIFY. // _systemRoleNames is a map of system role names to SystemRole values by performance. var _systemRoleNames = map[string]SystemRole{ "admin": ROLE_ADMIN, "devops": ROLE_DEVOPS, "community_pool": ROLE_COMMUNITY_POOL, "governance": ROLE_GOVERNANCE, "gov_staker": ROLE_GOV_STAKER, "xgns": ROLE_XGNS, "pool": ROLE_POOL, "position": ROLE_POSITION, "router": ROLE_ROUTER, "staker": ROLE_STAKER, "emission": ROLE_EMISSION, "launchpad": ROLE_LAUNCHPAD, "protocol_fee": ROLE_PROTOCOL_FEE, } // String returns the string representation of the SystemRole. // Returns "Unknown" if the role is not a valid system role. // // Returns: // - string: Registered system-role name, or "Unknown" when r is not in the system-role registry. func (r SystemRole) String() string { roleName := string(r) if _, ok := _systemRoleNames[roleName]; !ok { return "Unknown" } return roleName } // IsSystemRole returns true if roleName is a system role. // // Parameters: // - roleName: Exact string key to check against the reserved system-role registry. // // Returns: // - bool: true when roleName names a registered system role; false otherwise. func IsSystemRole(roleName string) bool { _, ok := _systemRoleNames[roleName] return ok }
- #18/gno.MemPackageType
- #19 MPUserAll
Result log
msg:0,success:true,log:,events:[]