Transaction

C137B4BF81EE7C…4F45256D8D16

Block 77,245 · index 0 · indexed

Summary

Hash
C137B4BF81EE7C25E2B9C764053C92A1F1FF41ABEF403D53EE684F45256D8D16
Block
77,245
Size
18659 bytes
Gas used
19,346,492 / 23,215,755
Fee
23216ugnot
Status
success

Messages

#1AddPackagegno.land/r/gnoswap/access/v113 arguments

Arguments · 13

  1. #1access
  2. #2README.md
  3. #3# Access Centralized role-based access control system for GnoSwap protocol contracts. ## Overview The Access package provides a unified permission management system for all GnoSwap protocol contracts. It manages role-to-address mappings and provides convenient assertion functions for authorization checks throughout the protocol. This package acts as a centralized registry where protocol components and user-controlled accounts can be assigned role addresses. Other contracts can then query this registry to verify permissions before executing privileged operations. Admin role ownership is managed by the RBAC realm and is updated on ownership transfer; this package only stores the latest role address. The `admin` and `devops` roles are initialized with user-controlled addresses. Most other system roles use deterministic package addresses, while custom roles may use any valid address. ## Architecture The access control system consists of: 1. **Role Registry**: Maps role names (strings) to role addresses (contracts or accounts) 2. **Role Management**: Functions to set/remove roles (RBAC-only) 3. **Authorization Checks**: Functions to verify if an address has a specific role 4. **Assert Helpers**: Convenience functions that panic on authorization failure ## System Roles The following roles are used across the GnoSwap protocol: - **admin**: Protocol administrator with elevated privileges - **devops**: DevOps operations for system maintenance - **governance**: Governance contract for protocol decisions - **router**: Swap router for token exchanges - **pool**: Pool management contract - **position**: Position NFT management - **staker**: Liquidity staking contract - **emission**: GNS token emission controller - **protocol_fee**: Protocol fee collection and distribution - **community_pool**: Community treasury management - **launchpad**: Token launchpad for new projects - **gov_staker**: Governance staking contract - **xgns**: xGNS token contract for governance ## Key Functions ### Role Management (RBAC Only) #### `SetRoleAddress` Sets or updates a role's address. Creates new role if it doesn't exist. The `admin` role is updated by RBAC ownership transfers and should not be managed directly by other contracts. ```go // Only callable by RBAC contract access.SetRoleAddress(cross(cur), "router", routerAddress) ``` #### `RemoveRole` Removes a role from the system. ```go // Only callable by RBAC contract access.RemoveRole(cross(cur), "old_role") ``` ### Role Query Functions #### `GetAddress` Returns the address for a role and whether it exists. ```go addr, exists := access.GetAddress("router") if !exists { // Handle missing role } ``` #### `MustGetAddress` Returns the address for a role or panics if it doesn't exist. ```go // Panics if role doesn't exist routerAddr := access.MustGetAddress("router") ``` #### `GetRoleAddresses` Returns a copy of all role-to-address mappings. ```go allRoles := access.GetRoleAddresses() for roleName, addr := range allRoles { println(roleName, "->", addr) } ``` ### Authorization Functions #### `IsAuthorized` Checks if an address has a specific role (non-panicking). ```go if access.IsAuthorized("admin", caller) { // Caller is admin } ``` ### Assert Functions (Panic on Failure) These functions panic with a descriptive error if authorization fails: #### `AssertIsAdmin` Requires admin role. ```go access.AssertIsAdmin(caller) ``` #### `AssertIsGovernance` Requires governance role. ```go access.AssertIsGovernance(caller) ``` #### `AssertIsAdminOrGovernance` Requires either admin or governance role. ```go access.AssertIsAdminOrGovernance(caller) ``` #### Role-Specific Assertions ```go access.AssertIsRouter(caller) access.AssertIsPool(caller) access.AssertIsPosition(caller) access.AssertIsStaker(caller) access.AssertIsEmission(caller) access.AssertIsProtocolFee(caller) access.AssertIsLaunchpad(caller) access.AssertIsGovStaker(caller) access.AssertIsGovXGNS(caller) ``` #### `AssertIsAuthorized` Generic authorization check for any role. ```go access.AssertIsAuthorized("custom_role", caller) ``` #### `AssertHasAnyRole` Requires the caller to have at least one of the specified roles. ```go access.AssertHasAnyRole(caller, "admin", "governance", "devops") ``` ### Validation Functions #### `AssertIsValidAddress` Panics if the address is invalid. ```go access.AssertIsValidAddress(addr) ``` ## Usage Examples ### Example 1: Protecting Admin Functions ```go package pool import "gno.land/r/gnoswap/access/v1" func SetPoolFeeRate(cur realm, rate uint64) { caller := cur.Previous().Address() access.AssertIsAdminOrGovernance(caller) // Admin/governance authorized, proceed setFeeRate(rate) } ``` ### Example 2: Role-Based Function Access ```go package staker import "gno.land/r/gnoswap/access/v1" func DistributeRewards(cur realm, amount uint64) { caller := cur.Previous().Address() access.AssertIsEmission(caller) // Only emission contract can distribute distributeToStakers(amount) } ``` ### Example 3: Multi-Role Authorization ```go package common import "gno.land/r/gnoswap/access/v1" func EmergencyPause(cur realm) { caller := cur.Previous().Address() access.AssertHasAnyRole(caller, "admin", "devops", "governance") // Any of the authorized roles can pause pauseProtocol() } ``` ### Example 4: Non-Panicking Authorization Check ```go package router import "gno.land/r/gnoswap/access/v1" func GetSwapFee(caller address) uint64 { // Lower fee for admin if access.IsAuthorized("admin", caller) { return 0 // Admin gets free swaps } return standardFee } ``` ## Security Model ### Centralized Management - All role assignments are managed through this single contract - Provides a unified view of permissions across the entire protocol - Prevents inconsistencies in authorization logic ### RBAC-Only Updates - Only the RBAC contract can modify role assignments - Uses package address verification to enforce this restriction - Prevents unauthorized role manipulation ### Explicit Authorization - All authorization checks are explicit and auditable - Panic-based assertions make authorization failures obvious - No implicit or default permissions ## Integration with RBAC The Access contract works in conjunction with the RBAC (Role-Based Access Control) package: 1. **RBAC**: Manages role definitions and ownership transfer 2. **Access**: Provides centralized role-to-address registry and authorization checks Role updates flow: `RBAC.UpdateRoleAddress()` → `Access.SetRoleAddress()` ## Best Practices 1. **Use Assertions for Critical Functions**: Always use assert functions for operations that should only proceed with proper authorization 2. **Check Existence Before Use**: Use `GetAddress` when you need to handle missing roles gracefully 3. **Document Role Requirements**: Clearly document which roles are required for each function 4. **Avoid Hardcoding Addresses**: Always use role-based checks instead of hardcoding addresses 5. **Test Authorization**: Thoroughly test all authorization paths in your contracts ## Error Handling Authorization failures result in panics with descriptive error messages: - `"unauthorized: caller X is not Y"` - Caller doesn't have required role - `"role X not found"` - Role lookup failed because the role has not been registered - `"role X does not exist"` - `RemoveRole` was asked to remove an unknown role - `"invalid address: X"` - Address validation failed ## Limitations - Role names are case-sensitive strings - Each role can only map to one address at a time - Role changes take effect immediately (no timelock)
  4. #4access.gno
  5. #5package access import ( "chain" "strings" ufmt "gno.land/p/nt/ufmt/v0" ) var roleAddresses map[string]address func init() { roleAddresses = make(map[string]address) } // SetRoleAddress sets or updates a role's address. // It trims surrounding whitespace from roleName, creates missing roles, and // replaces the address for an existing role. // // Parameters: // - cur: current realm context; callers use cross(cur) when crossing into this realm // - roleName: role identifier; surrounding whitespace is ignored and an empty name panics // - roleAddress: non-empty, valid address to associate with roleName // // Only callable by the RBAC contract. func SetRoleAddress(cur realm, roleName string, roleAddress address) { prev := cur.Previous() assertIsRBAC(prev.Address()) // capture old value for event oldAddr, _ := roleAddresses[strings.TrimSpace(roleName)] if err := setRoleAddress(roleName, roleAddress); err != nil { panic(err) } chain.Emit( "SetRoleAddress", "prevAddr", prev.Address().String(), "role", roleName, "prevRoleAddr", oldAddr.String(), "roleAddress", roleAddress.String(), ) } // setRoleAddress is the internal implementation of SetRoleAddress. // Separated for testability. func setRoleAddress(roleName string, roleAddress address) error { roleName = strings.TrimSpace(roleName) if roleName == "" { panic("role name cannot be empty") } // Validate address if !roleAddress.IsValid() || roleAddress == address("") { return ufmt.Errorf(errInvalidAddress, roleName, roleAddress) } roleAddresses[roleName] = roleAddress return nil } // RemoveRole removes a role from the system after trimming its name. // // Parameters: // - cur: current realm context; callers use cross(cur) when crossing into this realm // - roleName: role identifier to trim and remove; an empty or unknown name panics // // Only callable by the RBAC contract. func RemoveRole(cur realm, roleName string) { prev := cur.Previous() assertIsRBAC(prev.Address()) // Validate role name roleName = strings.TrimSpace(roleName) if roleName == "" { panic("role name cannot be empty") } if _, ok := roleAddresses[roleName]; !ok { panic(ufmt.Errorf("role %s does not exist", roleName)) } delete(roleAddresses, roleName) chain.Emit( "RemoveRole", "prevAddr", prev.Address().String(), "role", roleName, ) } // IsAuthorized reports whether caller is the address currently mapped to role. // // Parameters: // - role: role name to trim and look up // - caller: address to compare with the mapped role address // // Returns: // - authorized: true when role exists and caller matches its address; false when the role is absent or does not match func IsAuthorized(role string, caller address) bool { addr, ok := GetAddress(role) if !ok { return false } return caller == addr } // GetAddress returns the address mapped to a role and whether that mapping exists. // // Parameters: // - role: role name to trim before lookup // // Returns: // - addr: mapped address, or the zero address when role is not present // - exists: true when role has a stored address; false otherwise func GetAddress(role string) (address, bool) { role = strings.TrimSpace(role) addr, ok := roleAddresses[role] return addr, ok } // GetRoleAddresses returns an independent map copy of all stored role addresses. // // Returns: // - roleAddresses: map from normalized role names to their configured addresses func GetRoleAddresses() map[string]address { addresses := make(map[string]address) for role, data := range roleAddresses { addresses[role] = data } return addresses } // MustGetAddress returns the address mapped to a role or panics if it is absent. // Surrounding whitespace is ignored; an empty or unknown role is considered absent. // // Parameters: // - role: role name to trim and require in the role mapping // // Returns: // - addr: configured address for role func MustGetAddress(role string) address { role = strings.TrimSpace(role) addr, ok := GetAddress(role) if !ok { panic(ufmt.Errorf(errRoleNotFound, role)) } return addr }
  6. #6assert.gno
  7. #7package access import ( "chain" prbac "gno.land/p/gnoswap/rbac/v1" ufmt "gno.land/p/nt/ufmt/v0" ) // rbacPackagePath is the package path of the RBAC contract // Used to verify that role management functions are called only by RBAC const rbacPackagePath = "gno.land/r/gnoswap/rbac/v1" // AssertIsRlmCurrent panics if the realm token is not the current crossing frame. // // Parameters: // - _: leading realm-call discriminator; callers pass 0 // - rlm: realm context token that must represent the current crossing frame func AssertIsRlmCurrent(_ int, rlm realm) { if !rlm.IsCurrent() { panic(errSpoofedRealm) } } // AssertIsAdminOrGovernance panics unless caller is the configured admin or governance address. // // Parameters: // - caller: address whose authorization is checked against the admin and governance roles func AssertIsAdminOrGovernance(caller address) { if IsAuthorized(prbac.ROLE_ADMIN.String(), caller) || IsAuthorized(prbac.ROLE_GOVERNANCE.String(), caller) { return } panic(ufmt.Errorf(errUnauthorizedAdminOrGov, caller)) } // AssertIsAdmin panics unless caller is the configured admin address. // // Parameters: // - caller: address whose authorization is checked against the admin role func AssertIsAdmin(caller address) { AssertIsAuthorized(prbac.ROLE_ADMIN.String(), caller) } // AssertIsGovernance panics unless caller is the configured governance address. // // Parameters: // - caller: address whose authorization is checked against the governance role func AssertIsGovernance(caller address) { AssertIsAuthorized(prbac.ROLE_GOVERNANCE.String(), caller) } // AssertIsGovStaker panics unless caller is the configured governance-staker address. // // Parameters: // - caller: address whose authorization is checked against the governance-staker role func AssertIsGovStaker(caller address) { AssertIsAuthorized(prbac.ROLE_GOV_STAKER.String(), caller) } // AssertIsRouter panics unless caller is the configured router address. // // Parameters: // - caller: address whose authorization is checked against the router role func AssertIsRouter(caller address) { AssertIsAuthorized(prbac.ROLE_ROUTER.String(), caller) } // AssertIsPool panics unless caller is the configured pool address. // // Parameters: // - caller: address whose authorization is checked against the pool role func AssertIsPool(caller address) { AssertIsAuthorized(prbac.ROLE_POOL.String(), caller) } // AssertIsPosition panics unless caller is the configured position address. // // Parameters: // - caller: address whose authorization is checked against the position role func AssertIsPosition(caller address) { AssertIsAuthorized(prbac.ROLE_POSITION.String(), caller) } // AssertIsStaker panics unless caller is the configured staker address. // // Parameters: // - caller: address whose authorization is checked against the staker role func AssertIsStaker(caller address) { AssertIsAuthorized(prbac.ROLE_STAKER.String(), caller) } // AssertIsLaunchpad panics unless caller is the configured launchpad address. // // Parameters: // - caller: address whose authorization is checked against the launchpad role func AssertIsLaunchpad(caller address) { AssertIsAuthorized(prbac.ROLE_LAUNCHPAD.String(), caller) } // AssertIsEmission panics unless caller is the configured emission address. // // Parameters: // - caller: address whose authorization is checked against the emission role func AssertIsEmission(caller address) { AssertIsAuthorized(prbac.ROLE_EMISSION.String(), caller) } // AssertIsProtocolFee panics unless caller is the configured protocol-fee address. // // Parameters: // - caller: address whose authorization is checked against the protocol-fee role func AssertIsProtocolFee(caller address) { AssertIsAuthorized(prbac.ROLE_PROTOCOL_FEE.String(), caller) } // AssertIsGovXGNS panics unless caller is the configured xGNS governance address. // // Parameters: // - caller: address whose authorization is checked against the xGNS governance role func AssertIsGovXGNS(caller address) { AssertIsAuthorized(prbac.ROLE_XGNS.String(), caller) } // AssertIsAuthorized panics if caller does not have the specified role or if the role is absent. // // Parameters: // - roleName: role identifier whose configured address is required // - caller: address that must match the configured address for roleName func AssertIsAuthorized(roleName string, caller address) { addr, ok := GetAddress(roleName) if !ok { panic(ufmt.Errorf(errRoleNotFound, roleName)) } if caller != addr { panic(ufmt.Errorf(errUnauthorized, caller, roleName)) } } // AssertHasAnyRole checks roleNames in order and panics unless caller matches one. // It panics immediately if a checked role is absent, even if a later role might match. // // Parameters: // - caller: address compared against each configured role address // - roleNames: ordered role identifiers to check; each missing role causes a panic func AssertHasAnyRole(caller address, roleNames ...string) { for _, roleName := range roleNames { addr, ok := GetAddress(roleName) if !ok { panic(ufmt.Errorf(errRoleNotFound, roleName)) } if caller == addr { return } } panic(ufmt.Errorf(errUnauthorizedAnyRole, caller, roleNames)) } // AssertIsValidAddress panics if addr is not a valid address. // // Parameters: // - addr: address value to validate func AssertIsValidAddress(addr address) { if !addr.IsValid() { panic(ufmt.Errorf(errInvalidAddressShort, addr)) } } // assertIsRBAC panics if the caller is not the RBAC contract. // Used internally to protect role management functions. func assertIsRBAC(caller address) { rbacAddress := chain.PackageAddress(rbacPackagePath) if caller != rbacAddress { panic(ufmt.Errorf(errUnauthorizedRBAC, caller)) } }
  8. #8errors.gno
  9. #9package access const ( errSpoofedRealm = "rlm does not match the current crossing frame" errInvalidAddress = "invalid address for role %s: %s" errRoleNotFound = "role %s not found" errUnauthorized = "unauthorized: caller %s is not %s" errUnauthorizedAdminOrGov = "unauthorized: caller %s is not admin or governance" errUnauthorizedAnyRole = "unauthorized: caller %s is not any of the roles %v" errUnauthorizedRBAC = "unauthorized: caller %s is not rbac" errInvalidAddressShort = "invalid address: %s" )
  10. #10gnomod.toml
  11. #11module = "gno.land/r/gnoswap/access/v1" gno = "0.9"
  12. #12/gno.MemPackageType
  13. #13 MPUserAll

Result log

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

← Back to block 77,245