- 
                Notifications
    
You must be signed in to change notification settings  - Fork 14
 
Unshielded Access Control #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
  
     Merged
                    Changes from 46 commits
      Commits
    
    
            Show all changes
          
          
            47 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      ffb9fe8
              
                commit initial design
              
              
                emnul 35397ac
              
                Undo package.json changes
              
              
                emnul 9010249
              
                fmt files
              
              
                emnul 3cddc66
              
                fmt package.json
              
              
                emnul 5fd5213
              
                fix yarn.lock
              
              
                emnul b6d03b5
              
                Apply documentation suggestions from code review
              
              
                emnul 770e321
              
                Rename _checkRole -> assertOnlyRole
              
              
                emnul a847331
              
                Update module documentation
              
              
                emnul d78686a
              
                Remove Initializable module
              
              
                emnul 359ece1
              
                Remove Initializable requirements from method docs
              
              
                emnul 685d062
              
                error message consistency
              
              
                emnul b2079a3
              
                Update method docs
              
              
                emnul fbd6004
              
                Fix simulator bug
              
              
                emnul dcab470
              
                Update method docs
              
              
                emnul c792cc3
              
                Add tests
              
              
                emnul 20316be
              
                Remove unused files
              
              
                emnul 82e3f12
              
                Fmt files
              
              
                emnul c48e7c0
              
                Update method docs
              
              
                emnul b1c1e58
              
                Update asciidoc attributes
              
              
                emnul 6ea39a7
              
                Update nav and add API reference
              
              
                emnul 55fa0b3
              
                Update module docs
              
              
                emnul 38abf97
              
                Add AccessControl Overview, update AccessControl API
              
              
                emnul 9feb5fb
              
                Update imports
              
              
                emnul 2a47555
              
                Apply fmt suggestions to AccessControl.compact
              
              
                emnul 5452fa4
              
                Add @return to method docs
              
              
                emnul b9dc2a5
              
                Reword method docs
              
              
                emnul 32ac7df
              
                Update _checkRole tests
              
              
                emnul 348d6f8
              
                refactor callerTypes
              
              
                emnul 9980a9c
              
                use _lowercase instead of SCREAMING_CASE
              
              
                emnul 31513c4
              
                Refactor code code block
              
              
                emnul 7ebeb83
              
                Remove unnecessary expect
              
              
                emnul ded8bdf
              
                refactor describe each
              
              
                emnul 45ba4a8
              
                Add CUSTOM_ADMIN
              
              
                emnul 4beddb0
              
                Fmt file
              
              
                emnul 247e043
              
                Add beforeEach block
              
              
                emnul ef5a62b
              
                remove extra setter
              
              
                emnul 930dccd
              
                Check new admin and previous admin permissions
              
              
                emnul b582214
              
                fmt file
              
              
                emnul c18a3a2
              
                Add more tests, refactor code
              
              
                emnul 08a2465
              
                fmt file
              
              
                emnul 232847c
              
                Apply documentation suggestions from code review
              
              
                emnul 7437b73
              
                Update requirements and test
              
              
                emnul affb1e3
              
                Update AccessControl docs
              
              
                emnul 4440a68
              
                Merge branch 'main' into unshielded-access-control
              
              
                emnul dbd9f0c
              
                Annihilate typos
              
              
                emnul 5eb3e51
              
                Update module docs
              
              
                emnul 1008842
              
                Merge branch 'main' into unshielded-access-control
              
              
                emnul File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| { | ||
| "name": "@openzeppelin-compact/access-control", | ||
| "private": true, | ||
| "type": "module", | ||
| "main": "dist/index.js", | ||
| "module": "dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "require": "./dist/index.js", | ||
| "import": "./dist/index.js", | ||
| "default": "./dist/index.js" | ||
| } | ||
| }, | ||
| "scripts": { | ||
| "compact": "compact-compiler", | ||
| "build": "compact-builder && tsc", | ||
| "test": "vitest run", | ||
| "types": "tsc -p tsconfig.json --noEmit", | ||
| "clean": "git clean -fXd" | ||
| }, | ||
| "dependencies": { | ||
| "@openzeppelin-compact/compact": "workspace:^" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "22.14.0", | ||
| "ts-node": "^10.9.2", | ||
| "typescript": "^5.2.2", | ||
| "vitest": "^3.1.3" | ||
| } | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,321 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| 
     | 
||
| pragma language_version >= 0.15.0; | ||
| 
     | 
||
| /** | ||
| * @module AccessControl | ||
| * @description An unshielded AccessControl library. | ||
| * This module provides a role-based access control mechanism, where roles can be used to | ||
| * represent a set of permissions. | ||
| * | ||
| * Roles are referred to by their `Bytes<32>` identifier. These should be exposed | ||
| * in the top-level contract and be unique. One way to achieve this is by | ||
| * using `export sealed ledger` hash digests that are initialized in the top-level contract: | ||
| * | ||
| * ```typescript | ||
| * import CompactStandardLibrary; | ||
| * import "./node_modules/@openzeppelin-compact/accessControl/src/AccessControl" prefix AccessControl_; | ||
| * | ||
| * export sealed ledger MY_ROLE: Bytes<32>; | ||
| * | ||
| * constructor() { | ||
| * MY_ROLE = persistent_hash<Bytes<32>>(pad(32, "MY_ROLE")); | ||
| * } | ||
| * ``` | ||
| * | ||
| * To restrict access to a circuit, use {assertOnlyRole}: | ||
| * | ||
| * ```typescript | ||
| * circuit foo(): [] { | ||
| * assertOnlyRole(MY_ROLE); | ||
| * ... | ||
| * } | ||
| * ``` | ||
| * | ||
| * Roles can be granted and revoked dynamically via the {grantRole} and | ||
| * {revokeRole} circuits. Each role has an associated admin role, and only | ||
| * accounts that have a role's admin role can call {grantRole} and {revokeRole}. | ||
| * | ||
| * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means | ||
| * that only accounts with this role will be able to grant or revoke other | ||
| * roles. More complex role relationships can be created by using | ||
| * {_setRoleAdmin}. To set a custom `DEFAULT_ADMIN_ROLE`, implement the `Initializable` | ||
| * module and set `DEFAULT_ADMIN_ROLE` in the `initialize()` circuit. | ||
| * | ||
| * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to | ||
| * grant and revoke this role. Extra precautions should be taken to secure | ||
| * accounts that have been granted it. | ||
| * | ||
| * @notice Roles can only be granted to ZswapCoinPublicKeys | ||
| * through the main role approval circuits (`grantRole` and `_grantRole`). | ||
| * In other words, role approvals to contract addresses are disallowed through these | ||
| * circuits. | ||
| * This is because Compact currently does not support contract-to-contract calls which means | ||
| * if a contract is granted a role, the contract cannot directly call the protected | ||
| * circuit. | ||
| * | ||
| * @notice This module does offer an experimental circuit that allows roles to be granted | ||
| * to contract addresses (`_unsafeGrantRole`). | ||
| * Note that the circuit name is very explicit ("unsafe") with this experimental circuit. | ||
| * Until contract-to-contract calls are supported, | ||
| * there is no direct way for a contract to call protected circuits. | ||
| * | ||
| * @notice The unsafe circuits are planned to become deprecated once contract-to-contract calls | ||
| * are supported. | ||
| * | ||
| * @notice Missing Features and Improvements: | ||
| * | ||
| * - Role events | ||
| * - An ERC165-like interface | ||
| */ | ||
| module AccessControl { | ||
| import CompactStandardLibrary; | ||
| import "../../node_modules/@openzeppelin-compact/utils/src/Utils" prefix Utils_; | ||
| 
     | 
||
| /** | ||
| * @description Mapping from a role identifier -> account -> its permissions. | ||
| * @type {Bytes<32>} roleId - A hash representing a role identifier. | ||
| * @type {Map<Either<ZswapCoinPublicKey, ContractAddress>, Boolean>} hasRole - A mapping from an account to a | ||
| * Boolean determining if the account is approved for a role. | ||
| * @type {Map<roleId, hasRole>} | ||
| * @type {Map<Bytes<32>, Map<Either<ZswapCoinPublicKey, ContractAddress>, Boolean>} _operatorRoles | ||
| */ | ||
| export ledger _operatorRoles: Map<Bytes<32>, Map<Either<ZswapCoinPublicKey, ContractAddress>, Boolean>>; | ||
| 
     | 
||
| /** | ||
| * @description Mapping from a role identifier to an admin role identifier. | ||
| * @type {Bytes<32>} roleId - A hash representing a role identifier. | ||
| * @type {Bytes<32>} adminId - A hash representing an admin identifier. | ||
| * @type {Map<roleId, adminId>} | ||
| * @type {Map<Bytes<32>, Bytes<32>>} _adminRoles | ||
| */ | ||
| export ledger _adminRoles: Map<Bytes<32>, Bytes<32>>; | ||
| 
     | 
||
| export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; | ||
| 
     | 
||
| /** | ||
| * @description Returns `true` if `account` has been granted `roleId`. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Either<ZswapCoinPublicKey, ContractAddress>} account - The account to query. | ||
| * @return {Boolean} - Whether the account has the specified role. | ||
| */ | ||
| export circuit hasRole(roleId: Bytes<32>, account: Either<ZswapCoinPublicKey, ContractAddress>): Boolean { | ||
| if ( | ||
| _operatorRoles.member(roleId) && | ||
| _operatorRoles | ||
| .lookup(roleId) | ||
| .member(account) | ||
| ) { | ||
| return _operatorRoles | ||
| .lookup(roleId) | ||
| .lookup(account); | ||
| } else { | ||
| return false; | ||
| } | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Reverts if `own_public_key()` is missing `roleId`. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * Requirements: | ||
| * | ||
| * - The caller must have `roleId`. | ||
| * - The caller must not be a ContractAddress | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @return {[]} - Empty tuple. | ||
| */ | ||
| export circuit assertOnlyRole(roleId: Bytes<32>): [] { | ||
| _checkRole(roleId, left<ZswapCoinPublicKey,ContractAddress>(own_public_key())); | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Reverts if `account` is missing `roleId`. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * Requirements: | ||
| * | ||
| * - `account` must have `roleId`. | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Either<ZswapCoinPublicKey, ContractAddress>} account - The account to query. | ||
| * @return {[]} - Empty tuple. | ||
| */ | ||
| export circuit _checkRole(roleId: Bytes<32>, account: Either<ZswapCoinPublicKey, ContractAddress>): [] { | ||
| assert hasRole(roleId, account) "AccessControl: unauthorized account"; | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Returns the admin role that controls `roleId` or | ||
| * a byte array with all zero bytes if `roleId` doesn't exist. See {grantRole} and {revokeRole}. | ||
| * | ||
| * To change a role’s admin use {_setRoleAdmin}. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @return {Bytes<32>} roleAdmin - The admin role that controls `roleId`. | ||
| */ | ||
| export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { | ||
| if (_adminRoles.member(roleId)) { | ||
| return _adminRoles.lookup(roleId); | ||
| } | ||
| return default<Bytes<32>>; | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Grants `roleId` to `account`. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * Requirements: | ||
| * | ||
| * - `account` must not be a ContractAddress. | ||
| * - The caller must have `roleId`'s admin role. | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Either<ZswapCoinPublicKey, ContractAddress>} account - A ZswapCoinPublicKey or ContractAddress. | ||
| * @return {[]} - Empty tuple. | ||
| */ | ||
| export circuit grantRole(roleId: Bytes<32>, account: Either<ZswapCoinPublicKey, ContractAddress>): [] { | ||
| assertOnlyRole(getRoleAdmin(roleId)); | ||
| _grantRole(roleId, account); | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Revokes `roleId` from `account`. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * Requirements: | ||
| * | ||
| * - The caller must have `roleId`'s admin role. | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Either<ZswapCoinPublicKey, ContractAddress>} account - A ZswapCoinPublicKey or ContractAddress. | ||
| * @return {[]} - Empty tuple. | ||
| */ | ||
| export circuit revokeRole(roleId: Bytes<32>, account: Either<ZswapCoinPublicKey, ContractAddress>): [] { | ||
| assertOnlyRole(getRoleAdmin(roleId)); | ||
| _revokeRole(roleId, account); | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Revokes `roleId` from the calling account. | ||
| * | ||
| * @notice Roles are often managed via {grantRole} and {revokeRole}: this circuit's | ||
| * purpose is to provide a mechanism for accounts to lose their privileges | ||
| * if they are compromised (such as when a trusted device is misplaced). | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * Requirements: | ||
| * | ||
| * - The caller must be `callerConfirmation`. | ||
| * - The caller must not be a `ContractAddress`. | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Either<ZswapCoinPublicKey, ContractAddress>} callerConfirmation - A ZswapCoinPublicKey or ContractAddress. | ||
| * @return {[]} - Empty tuple. | ||
| */ | ||
| export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either<ZswapCoinPublicKey, ContractAddress>): [] { | ||
| assert callerConfirmation == left<ZswapCoinPublicKey,ContractAddress>(own_public_key()) "AccessControl: bad confirmation"; | ||
| 
     | 
||
| _revokeRole(roleId, callerConfirmation); | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Sets `adminRole` as `roleId`'s admin role. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Bytes<32>} adminRole - The admin role identifier. | ||
| * @return {[]} - Empty tuple. | ||
| */ | ||
| export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { | ||
| _adminRoles.insert(roleId, adminRole); | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. | ||
| * Internal circuit without access restriction. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * Requirements: | ||
| * | ||
| * - `account` must not be a ContractAddress. | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Either<ZswapCoinPublicKey, ContractAddress>} account - A ZswapCoinPublicKey or ContractAddress. | ||
| * @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted. | ||
| */ | ||
| export circuit _grantRole(roleId: Bytes<32>, account: Either<ZswapCoinPublicKey, ContractAddress>): Boolean { | ||
| assert !Utils_isContractAddress(account) "AccessControl: unsafe role approval"; | ||
| return _unsafeGrantRole(roleId, account); | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. | ||
| * Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * @notice External smart contracts cannot call the token contract at this time, so granting a role to an ContractAddress may | ||
| * render a circuit permanently inaccessible. | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Either<ZswapCoinPublicKey, ContractAddress>} account - A ZswapCoinPublicKey or ContractAddress. | ||
| * @return {Boolean} roleGranted - A boolean indicating if `role` was granted. | ||
| */ | ||
| export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either<ZswapCoinPublicKey, ContractAddress>): Boolean { | ||
| if (hasRole(roleId, account)) { | ||
| return false; | ||
| } | ||
| 
     | 
||
| if (!_operatorRoles.member(roleId)) { | ||
| _operatorRoles.insert( | ||
| roleId, | ||
| default<Map< | ||
| Either<ZswapCoinPublicKey, ContractAddress>, | ||
| Boolean | ||
| >> | ||
| ); | ||
| _operatorRoles | ||
| .lookup(roleId) | ||
| .insert(account, true); | ||
| return true; | ||
| } | ||
| 
     | 
||
| _operatorRoles.lookup(roleId).insert(account, true); | ||
| return true; | ||
| } | ||
| 
     | 
||
| /** | ||
| * @description Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked. | ||
| * Internal circuit without access restriction. | ||
| * | ||
| * @circuitInfo | ||
| * | ||
| * @param {Bytes<32>} roleId - The role identifier. | ||
| * @param {Bytes<32>} adminRole - The admin role identifier. | ||
| * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. | ||
| */ | ||
| export circuit _revokeRole(roleId: Bytes<32>, account: Either<ZswapCoinPublicKey, ContractAddress>): Boolean { | ||
| if (!hasRole(roleId, account)) { | ||
| return false; | ||
| } | ||
| 
     | 
||
| _operatorRoles | ||
| .lookup(roleId) | ||
| .insert(account, false); | ||
| return true; | ||
| } | ||
| } | ||
      
      Oops, something went wrong.
        
    
  
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.