From a6731c38346d537753e5809a5d91b347a4b01e4e Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 29 Sep 2023 00:09:34 +0200 Subject: [PATCH] Update Access Control docs (#719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: update format and add api * fix: typo * feat: add counterfactual deployment doc * feat: add API entries * feat: add events * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/guides/deployment.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/guides/deployment.adoc Co-authored-by: Andrew Fleming * feat: update from reviews * feat: apply review updates * feat: update docs * feat: update docs * feat: update from account docs * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/guides/deployment.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/guides/deployment.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/guides/deployment.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/guides/deployment.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * feat: apply review updates * fix: account casing * feat: add headers * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * feat: add link * feat: move API * feat: add event references * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Andrew Fleming * refactor: update wording * Update docs/antora.yml Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * feat: apply update reviews * Update docs/modules/ROOT/pages/api/account.adoc Co-authored-by: Martín Triay * refactor: UI * fix: UI * feat: focus on SRC6 * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * feat: apply review updates * Update docs/modules/ROOT/pages/accounts.adoc Co-authored-by: Martín Triay * feat: apply review updates * Update docs/modules/ROOT/pages/access.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/access.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/access.adoc Co-authored-by: Martín Triay * feat: apply review updates * feat: apply review updates * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/access.adoc Co-authored-by: Martín Triay * Update docs/modules/ROOT/pages/access.adoc Co-authored-by: Martín Triay * feat: apply review updates * feat: remove sn_keccak in comments * feat: replace cairo-2 with replace-0.7.0 * feat: remove grayed-out areas * Update docs/modules/ROOT/pages/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * Update docs/modules/ROOT/pages/api/access.adoc Co-authored-by: Andrew Fleming * feat: apply review updates * Update docs/modules/ROOT/pages/access.adoc Co-authored-by: Andrew Fleming --------- Co-authored-by: Andrew Fleming Co-authored-by: Martín Triay --- docs/modules/ROOT/nav.adoc | 3 +- docs/modules/ROOT/pages/access.adoc | 892 ++++++++--------------- docs/modules/ROOT/pages/api/access.adoc | 477 ++++++++++++ docs/modules/ROOT/pages/api/account.adoc | 48 +- 4 files changed, 822 insertions(+), 598 deletions(-) create mode 100644 docs/modules/ROOT/pages/api/access.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 961aba815..331fae8d5 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -8,7 +8,8 @@ ** xref:/guides/deployment.adoc[Counterfactual deployments] ** xref:/api/account.adoc[API Reference] -// * xref:access.adoc[Access Control] +* xref:access.adoc[Access Control] +** xref:/api/access.adoc[API Reference] // * Tokens // ** xref:erc20.adoc[ERC20] diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index bc8996b98..cf9b65989 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -1,672 +1,414 @@ -:ownable-cairo: link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/access/ownable/library.cairo[Ownable] - +:ownable-cairo: link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-0.7.0/src/access/ownable/ownable.cairo[Ownable] +:sn_keccak: https://docs.starknet.io/documentation/architecture_and_concepts/Cryptography/hash-functions/#starknet_keccak[sn_keccak] :extensibility-pattern: xref:extensibility.adoc#the_pattern = Access -CAUTION: Expect these modules to evolve. - -Access control--that is, "who is allowed to do this thing"--is incredibly important in the world of smart contracts. +Access control--that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. -It is therefore critical to understand how you implement it, lest someone else https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/[steals your whole system]. - -== Table of Contents - -* <> - ** <> - ** <> - ** <> -* <> - ** <> - ** <> - ** <> - ** <> - ** <> +It is therefore critical to understand how you implement it, lest someone else +https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/[steals your whole system]. -== Ownable +== Ownership and `Ownable` -The most common and basic form of access control is the concept of ownership: there's an account that is the `owner` of a contract and can do administrative tasks on it. +The most common and basic form of access control is the concept of ownership: there's an account that is the `owner` +of a contract and can do administrative tasks on it. This approach is perfectly reasonable for contracts that have a single administrative user. OpenZeppelin Contracts for Cairo provides {ownable-cairo} for implementing ownership in your contracts. -=== Quickstart +=== Usage -Integrating {ownable-cairo} into a contract first requires assigning an owner. -The implementing contract's constructor should set the initial owner by passing the owner's address to Ownable's <> like this: +Integrating this module into a contract first requires assigning an owner. +The implementing contract's constructor should set the initial owner by passing the owner's address to Ownable's +xref:/api/access.adoc#AccessControl-initializer[`initializer`] like this: -[,cairo] +[,javascript] ---- -from openzeppelin.access.ownable.library import Ownable +use openzeppelin::access::ownable::Ownable; -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(owner: felt) { - Ownable.initializer(owner); - return (); -} ----- +#[starknet::contract] +mod MyContract { + use starknet::ContractAddress; + use super::Ownable; -To restrict a function's access to the owner only, add in the `assert_only_owner` method: + #[storage] + struct Storage {} -[,cairo] ----- -from openzeppelin.access.ownable.library import Ownable + #[constructor] + fn constructor(self: @ContractState, owner: ContractAddress) { + let mut unsafe_state = Ownable::unsafe_new_contract_state(); + Ownable::InternalImpl::initializer(ref unsafe_state, owner); + } -func protected_function{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - Ownable.assert_only_owner(); - return (); + (...) } ---- -=== Ownable library API +To restrict a function's access to the owner only, add in the `assert_only_owner` method: -[,cairo] +[,javascript] ---- -func initializer(owner: felt) { -} - -func assert_only_owner() { -} - -func owner() -> (owner: felt) { -} +#[starknet::contract] +mod MyContract { + use openzeppelin::access::ownable::Ownable; -func transfer_ownership(new_owner: felt) { -} + (...) -func renounce_ownership() { -} + #[external(v0)] + fn foo(ref self: ContractState) { + // This function can only be called by the owner + let unsafe_state = Ownable::unsafe_new_contract_state(); + Ownable::InternalImpl::assert_only_owner(@unsafe_state); -func _transfer_ownership(new_owner: felt) { + (...) + } } ---- -==== `initializer` - -Initializes Ownable access control and should be called in the implementing contract's constructor. -Assigns `owner` as the initial owner address of the contract. - -This must be called only once. - -Parameters: - -[,cairo] ----- -owner: felt ----- - -Returns: None. - -==== `assert_only_owner` - -Reverts if called by any account other than the owner. -In case of renounced ownership, any call from the default zero address will also be reverted. - -Parameters: None. - -Returns: None. - -==== `owner` - -Returns the address of the current owner. - -Parameters: None. - -Returns: - -[,cairo] ----- -owner: felt ----- - -==== `transfer_ownership` - -Transfers ownership of the contract to a new account (`new_owner`). -Can only be called by the current owner. - -Emits a <> event. - -Parameters: - -[,cairo] ----- -new_owner: felt ----- - -Returns: None. - -==== `renounce_ownership` - -Leaves the contract without owner. -It will not be possible to call functions with `assert_only_owner` anymore. -Can only be called by the current owner. - -Emits a <> event. - -Parameters: None. - -Returns: None. - -[#transfer-ownership-internal] -==== `_transfer_ownership` +=== Interface -Transfers ownership of the contract to a new account (`new_owner`). {extensibility-pattern}[`internal`] function without access restriction. +This is the full interface of the `Ownable` implementation: -Emits a <> event. - -Parameters: - -[,cairo] +[,javascript] ---- -new_owner: felt ----- - -Returns: None. +trait IOwnable { + /// Returns the current owner. + fn owner() -> ContractAddress; -=== Ownable events + /// Transfers the ownership from the current owner to a new owner. + fn transfer_ownership(new_owner: ContractAddress); -[,cairo] ----- -func OwnershipTransferred(previousOwner: felt, newOwner: felt) { + /// Renounces the ownership of the contract. + fn renounce_ownership(); } ---- -==== OwnershipTransferred - -Emitted when ownership of a contract is transferred from `previousOwner` to `newOwner`. +Ownable also lets you: -Parameters: +- `transfer_ownership` from the owner account to a new one, and +- `renounce_ownership` for the owner to relinquish this administrative privilege, a common pattern +after an initial stage with centralized administration is over. -[,cairo] ----- -previousOwner: felt -newOwner: felt ----- +WARNING: Removing the owner altogether will mean that administrative tasks that are protected by `assert_only_owner` +will no longer be callable! -== AccessControl +== Role-Based `AccessControl` -While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of authorization are often needed. -You may want for an account to have permission to ban users from a system, but not create new tokens. -https://en.wikipedia.org/wiki/Role-based_access_control[Role-Based Access Control (RBAC)] offers flexibility in this regard. +While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of +authorization are often needed. You may want for an account to have permission to ban users from a system, but not +create new tokens. https://en.wikipedia.org/wiki/Role-based_access_control[Role-Based Access Control (RBAC)] offers +flexibility in this regard. In essence, we will be defining multiple roles, each allowed to perform different sets of actions. -An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for instead of simply using <>. -This check can be enforced through <>. +An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for +instead of simply using xref:/api/access.adoc#Ownable-assert_only_owner[`assert_only_owner`]. This check can be enforced through xref:/api/access.adoc#AccessControl-assert_only_role[`assert_only_role`]. Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. -Most software uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges. +Most software uses access control systems that are role-based: some users are regular users, some may be supervisors +or managers, and a few will often have administrative privileges. === Usage -For each role that you want to define, you will create a new _role identifier_ that is used to grant, revoke, and check if an account has that role (see <> for information on creating identifiers). - -Here's a simple example of implementing `AccessControl` on a portion of an link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/token/erc20/presets/ERC20.cairo[ERC20 token contract] which defines and sets the 'minter' role: - -[,cairo] ----- -from openzeppelin.token.erc20.library import ERC20 - -from openzeppelin.access.accesscontrol.library import AccessControl - - -const MINTER_ROLE = 0x4f96f87f6963bb246f2c30526628466840c642dc5c50d5a67777c6cc0e44ab5 - -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - name: felt, symbol: felt, decimals: felt, minter: felt -) { - ERC20.initializer(name, symbol, decimals); - AccessControl.initializer(); - AccessControl._grant_role(MINTER_ROLE, minter); - return (); -} - -@external -func mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - to: felt, amount: Uint256 -) { - AccessControl.assert_only_role(MINTER_ROLE); - ERC20._mint(to, amount); - return (); +For each role that you want to define, you will create a new _role identifier_ that is used to grant, revoke, and +check if an account has that role. See xref:#creating_role_identifiers[Creating role identifiers] for information +on creating identifiers. + +Here's a simple example of implementing `AccessControl` on a portion of an ERC20 token contract which defines +and sets a 'minter' role: + +[,javascript] +---- +const MINTER_ROLE: felt252 = selector!('MINTER_ROLE'); + +#[starknet::contract] +mod MyContract { + use openzeppelin::access::accesscontrol::AccessControl::InternalImpl::assert_only_role; + use openzeppelin::access::accesscontrol::AccessControl; + use openzeppelin::token::erc20::ERC20; + use starknet::ContractAddress; + use super::MINTER_ROLE; + + #[storage] + struct Storage {} + + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress, + minter: ContractAddress + ) { + // ERC20 related initialization + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::InternalImpl::initializer(ref erc20_state, name, symbol); + ERC20::InternalImpl::_mint(ref erc20_state, recipient, initial_supply); + + // AccessControl related initialization + let mut access_state = AccessControl::unsafe_new_contract_state(); + AccessControl::InternalImpl::initializer(ref access_state); + AccessControl::InternalImpl::_grant_role( + ref access_state, + MINTER_ROLE, + minter + ); + } + + // This function can only be called by a minter + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + let access_state = AccessControl::unsafe_new_contract_state(); + assert_only_role(@access_state, MINTER_ROLE); + + let mut erc20_state = AccessControl::unsafe_new_contract_state(); + ERC20::InternalImpl::_mint(ref erc20_state, recipient, amount); + } } ---- -CAUTION: Make sure you fully understand how <> works before using it on your system, or copy-pasting the examples from this guide. - -While clear and explicit, this isn't anything we wouldn't have been able to achieve with <>. -Indeed, where `AccessControl` shines is in scenarios where granular permissions are required, which can be implemented by defining _multiple_ roles. - -Let's augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using `assert_only_role`: - -[,cairo] ----- -from openzeppelin.token.erc20.library import ERC20 - -from openzeppelin.access.accesscontrol.library import AccessControl - - -const MINTER_ROLE = 0x4f96f87f6963bb246f2c30526628466840c642dc5c50d5a67777c6cc0e44ab5 -const BURNER_ROLE = 0x7823a2d975ffa03bed39c38809ec681dc0ae931ebe0048c321d4a8440aed509 - -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - name: felt, symbol: felt, decimals: felt, minter: felt, burner: felt -) { - ERC20.initializer(name, symbol, decimals); - AccessControl.initializer(); - AccessControl._grant_role(MINTER_ROLE, minter); - AccessControl._grant_role(BURNER_ROLE, burner); - return (); -} - -@external -func mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - to: felt, amount: Uint256 -) { - AccessControl.assert_only_role(MINTER_ROLE); - ERC20._mint(to, amount); - return (); -} - -@external -func burn{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - from_: felt, amount: Uint256 -) { - AccessControl.assert_only_role(BURNER_ROLE); - ERC20._burn(from_, amount); - return (); +CAUTION: Make sure you fully understand how xref:api/access.adoc#AccessControl[AccessControl] works before +using it on your system, or copy-pasting the examples from this guide. + +While clear and explicit, this isn't anything we wouldn't have been able to achieve with +xref:api/access.adoc#Ownable[Ownable]. Where `AccessControl` shines the most is in scenarios where granular +permissions are required, which can be implemented by defining _multiple_ roles. + +Let's augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: + +[,javascript] +---- +const MINTER_ROLE: felt252 = selector!('MINTER_ROLE'); +const BURNER_ROLE: felt252 = selector!('BURNER_ROLE'); + +#[starknet::contract] +mod MyContract { + use openzeppelin::access::accesscontrol::AccessControl::InternalImpl::assert_only_role; + use openzeppelin::access::accesscontrol::AccessControl; + use openzeppelin::token::erc20::ERC20; + use starknet::ContractAddress; + use super::{MINTER_ROLE, BURNER_ROLE}; + + #[storage] + struct Storage {} + + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress, + minter: ContractAddress, + burner: ContractAddress + ) { + // ERC20 related initialization + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::InternalImpl::initializer(ref erc20_state, name, symbol); + ERC20::InternalImpl::_mint(ref erc20_state, recipient, initial_supply); + + // AccessControl related initialization + let mut access_state = AccessControl::unsafe_new_contract_state(); + AccessControl::InternalImpl::initializer(ref access_state); + AccessControl::InternalImpl::_grant_role( + ref access_state, + MINTER_ROLE, + minter + ); + AccessControl::InternalImpl::_grant_role( + ref access_state, + BURNER_ROLE, + burner + ); + } + + + // This function can only be called by a minter + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + let access_state = AccessControl::unsafe_new_contract_state(); + assert_only_role(@access_state, MINTER_ROLE); + + let mut erc20_state = AccessControl::unsafe_new_contract_state(); + ERC20::InternalImpl::_mint(ref erc20_state, recipient, amount); + } + + // This function can only be called by a burner + #[external(v0)] + fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { + let access_state = AccessControl::unsafe_new_contract_state(); + assert_only_role(@access_state, BURNER_ROLE); + + let mut erc20_state = AccessControl::unsafe_new_contract_state(); + ERC20::InternalImpl::_burn(ref erc20_state, account, amount); + } } ---- So clean! -By splitting concerns this way, more granular levels of permission may be implemented than were possible with the simpler ownership approach to access control. -Limiting what each component of a system is able to do is known as the https://en.wikipedia.org/wiki/Principle_of_least_privilege[principle of least privilege], and is a good security practice. -Note that each account may still have more than one role, if so desired. +By splitting concerns this way, more granular levels of permission may be implemented than were possible with the +simpler ownership approach to access control. Limiting what each component of a system is able to do is known +as the https://en.wikipedia.org/wiki/Principle_of_least_privilege[principle of least privilege], and is a good +security practice. Note that each account may still have more than one role, if so desired. === Granting and revoking roles -The ERC20 token example above uses `_grant_role`, an {extensibility-pattern}[`internal`] function that is useful when programmatically assigning roles (such as during construction). -But what if we later want to grant the 'minter' role to additional accounts? +The ERC20 token example above uses xref:api/access.adoc#AccessControl-_grant_role[`_grant_role`], +an `internal` function that is useful when programmatically assigning +roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? -By default, *accounts with a role cannot grant it or revoke it from other accounts*: all having a role does is making the `assert_only_role` check pass. -To grant and revoke roles dynamically, you will need help from the role's _admin_. +By default, *accounts with a role cannot grant it or revoke it from other accounts*: all having a role does is making +the xref:api/access.adoc#AccessControl-assert_only_role[`assert_only_role`] check pass. To grant and revoke roles dynamically, you will need help from the role's _admin_. -Every role has an associated admin role, which grants permission to call the `grant_role` and `revoke_role` functions. +Every role has an associated admin role, which grants permission to call the +xref:api/access.adoc#AccessControl-grant_role[`grant_role`] and +xref:api/access.adoc#AccessControl-revoke_role[`revoke_role`] functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. -A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it. +A role's admin can even be the same role itself, which would cause accounts with that role to be able +to also grant and revoke it. -This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. -`AccessControl` includes a special role with the role identifier of `0`, called `DEFAULT_ADMIN_ROLE`, which acts as the *default admin role for all roles*. -An account with this role will be able to manage any other role, unless `_set_role_admin` is used to select a new admin role. +This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also +provides an easy way to manage simpler applications. `AccessControl` includes a special role with the role identifier +of `0`, called `DEFAULT_ADMIN_ROLE`, which acts as the *default admin role for all roles*. +An account with this role will be able to manage any other role, unless +xref:api/access.adoc#AccessControl-_set_role_admin[`_set_role_admin`] is used to select a new admin role. Let's take a look at the ERC20 token example, this time taking advantage of the default admin role: -[,cairo] ----- -from openzeppelin.token.erc20.library import ERC20 - -from openzeppelin.access.accesscontrol.library import AccessControl - -from openzeppelin.utils.constants import DEFAULT_ADMIN_ROLE - - -const MINTER_ROLE = 0x4f96f87f6963bb246f2c30526628466840c642dc5c50d5a67777c6cc0e44ab5 -const BURNER_ROLE = 0x7823a2d975ffa03bed39c38809ec681dc0ae931ebe0048c321d4a8440aed509 - -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - name: felt, symbol: felt, decimals: felt, admin: felt, -) { - ERC20.initializer(name, symbol, decimals); - AccessControl.initializer(); - - AccessControl._grant_role(DEFAULT_ADMIN_ROLE, admin); - return (); -} - -@external -func mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - to: felt, amount: Uint256 -) { - AccessControl.assert_only_role(MINTER_ROLE); - ERC20._mint(to, amount); - return (); -} - -@external -func burn{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - from_: felt, amount: Uint256 -) { - AccessControl.assert_only_role(BURNER_ROLE); - ERC20._burn(from_, amount); - return (); +[,javascript] +---- +const MINTER_ROLE: felt252 = selector!('MINTER_ROLE'); +const BURNER_ROLE: felt252 = selector!('BURNER_ROLE'); + +#[starknet::contract] +mod MyContract { + use openzeppelin::access::accesscontrol::AccessControl::InternalImpl::assert_only_role; + use openzeppelin::access::accesscontrol::AccessControl; + use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE; + use openzeppelin::token::erc20::ERC20; + use starknet::ContractAddress; + use super::{MINTER_ROLE, BURNER_ROLE}; + + #[storage] + struct Storage {} + + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress, + admin: ContractAddress + ) { + // ERC20 related initialization + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::InternalImpl::initializer(ref erc20_state, name, symbol); + ERC20::InternalImpl::_mint(ref erc20_state, recipient, initial_supply); + + // AccessControl related initialization + let mut access_state = AccessControl::unsafe_new_contract_state(); + AccessControl::InternalImpl::initializer(ref access_state); + AccessControl::InternalImpl::_grant_role( + ref access_state, + DEFAULT_ADMIN_ROLE, + admin + ); + } + + // This function can only be called by a minter + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + let access_state = AccessControl::unsafe_new_contract_state(); + assert_only_role(@access_state, MINTER_ROLE); + + let mut erc20_state = AccessControl::unsafe_new_contract_state(); + ERC20::InternalImpl::_mint(ref erc20_state, recipient, amount); + } + + // This function can only be called by a burner + #[external(v0)] + fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { + let access_state = AccessControl::unsafe_new_contract_state(); + assert_only_role(@access_state, BURNER_ROLE); + + let mut erc20_state = AccessControl::unsafe_new_contract_state(); + ERC20::InternalImpl::_burn(ref erc20_state, account, amount); + } + + // These function can only be called by the roles' admin + #[external(v0)] + fn grant_role(ref self: ContractState, role: felt252, account: ContractAddress) { + let mut unsafe_state = AccessControl::unsafe_new_contract_state(); + AccessControl::AccessControlImpl::grant_role(ref unsafe_state, role, account); + } + #[external(v0)] + fn revoke_role(ref self: ContractState, role: felt252, account: ContractAddress) { + let mut unsafe_state = AccessControl::unsafe_new_contract_state(); + AccessControl::AccessControlImpl::revoke_role(ref unsafe_state, role, account); + } } ---- Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. -However, because those roles' admin role is the default admin role, and that role was granted to the 'admin', that same account can call `grant_role` to give minting or burning permission, and `revoke_role` to remove it. - -Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary over time. -It can also be used to support use cases such as https://en.wikipedia.org/wiki/Know_your_customer[KYC], where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. - -The following example uses the link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/tests/mocks/AccessControl.cairo[AccessControl mock contract] which exposes the role management functions. -To grant and revoke roles in Python, for example: +However, because those roles' admin role is the default admin role, and that role was granted to the 'admin', that +same account can call `grant_role` to give minting or burning permission, and `revoke_role` to remove it. -[,python] ----- -MINTER_ROLE = 0x4f96f87f6963bb246f2c30526628466840c642dc5c50d5a67777c6cc0e44ab5 -BURNER_ROLE = 0x7823a2d975ffa03bed39c38809ec681dc0ae931ebe0048c321d4a8440aed509 - -# grants MINTER_ROLE and BURNER_ROLE to account1 and account2 respectively -await signer.send_transactions( - admin, [ - (accesscontrol.contract_address, 'grantRole', [MINTER_ROLE, account1.contract_address]), - (accesscontrol.contract_address, 'grantRole', [BURNER_ROLE, account2.contract_address]) - ] -) - -# revokes MINTER_ROLE from account1 -await signer.send_transaction( - admin, - accesscontrol.contract_address, - 'revokeRole', - [MINTER_ROLE, account1.contract_address] -) ----- +Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary +over time. It can also be used to support use cases such as https://en.wikipedia.org/wiki/Know_your_customer[KYC], +where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. === Creating role identifiers -In the Solidity implementation of AccessControl, contracts generally refer to the https://docs.soliditylang.org/en/latest/units-and-global-variables.html?highlight=keccak256#mathematical-and-cryptographic-functions[keccak256 hash] of a role as the role identifier. +In the Solidity implementation of AccessControl, contracts generally refer to the +https://docs.soliditylang.org/en/latest/units-and-global-variables.html?highlight=keccak256#mathematical-and-cryptographic-functions[keccak256 hash] +of a role as the role identifier. + For example: -[,solidity] +[,javascript] ---- bytes32 public constant SOME_ROLE = keccak256("SOME_ROLE") ---- These identifiers take up 32 bytes (256 bits). -Cairo field elements store a maximum of 252 bits. -Even further, a declared _constant_ field element in a StarkNet contract stores even less (see https://github.com/starkware-libs/cairo-lang/blob/release-v0.6.1/src/starkware/cairo/lang/cairo_constants.py#L1[cairo_constants]). +Cairo field elements (`felt252`) store a maximum of 252 bits. With this discrepancy, this library maintains an agnostic stance on how contracts should create identifiers. Some ideas to consider: -* Use the first or last 251 bits of keccak256 hash digests. -* Use Cairo's https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/hash.cairo[hash2]. - -=== AccessControl library API - -[,cairo] ----- -func initializer() { -} - -func assert_only_role(role: felt) { -} - -func has_role(role: felt, user: felt) -> (has_role: felt) { -} - -func get_role_admin(role: felt) -> (admin: felt) { -} - -func grant_role(role: felt, user: felt) { -} - -func revoke_role(role: felt, user: felt) { -} - -func renounce_role(role: felt, user: felt) { -} - -func _grant_role(role: felt, user: felt) { -} - -func _revoke_role(role: felt, user: felt) { -} - -func _set_role_admin(role: felt, admin_role: felt) { -} ----- - -[#initializer-accesscontrol] -==== `initializer` - -Initializes AccessControl and should be called in the implementing contract's constructor. - -This must only be called once. - -Parameters: None. - -Returns: None. - -==== `assert_only_role` - -Checks that an account has a specific role. -Reverts with a message including the required role. - -Parameters: - -[,cairo] ----- -role: felt ----- - -Returns: None. - -==== has_role - -Returns `TRUE` if `user` has been granted `role`, `FALSE` otherwise. - -Parameters: - -[,cairo] ----- -role: felt -user: felt ----- - -Returns: - -[,cairo] ----- -has_role: felt ----- - -==== `get_role_admin` - -Returns the admin role that controls `role`. -See <> and <>. - -To change a role's admin, use <>. - -Parameters: - -[,cairo] ----- -role: felt ----- - -Returns: - -[,cairo] ----- -admin: felt ----- - -==== `grant_role` - -Grants `role` to `user`. - -If `user` had not been already granted `role`, emits a <> event. - -Requirements: - -* The caller must have ``role``'s admin role. - -Parameters: - -[,cairo] ----- -role: felt -user: felt ----- - -Returns: None. - -==== `revoke_role` - -Revokes `role` from `user`. - -If `user` had been granted `role`, emits a <> event. - -Requirements: - -* The caller must have ``role``'s admin role. - -Parameters: - -[,cairo] ----- -role: felt -user: felt ----- - -Returns: None. - -==== `renounce_role` - -Revokes `role` from the calling `user`. - -Roles are often managed via <> and <>: this function'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). - -If the calling `user` had been revoked `role`, emits a <> event. - -Requirements: - -* The caller must be `user`. - -Parameters: - -[,cairo] ----- -role: felt -user: felt ----- - -Returns: None. - -[#grantrole-internal] -==== `_grant_role` - -Grants `role` to `user`. - -{extensibility-pattern}[`internal`] function without access restriction. - -Emits a <> event. - -Parameters: - -[,cairo] ----- -role: felt -user: felt ----- - -Returns: None. - -[#revokerole-internal] -==== `_revoke_role` - -Revokes `role` from `user`. - -{extensibility-pattern}[`internal`] function without access restriction. - -Emits a <> event. - -Parameters: - -[,cairo] ----- -role: felt -user: felt ----- - -Returns: None. - -[#setroleadmin] -==== `_set_role_admin` +* Use {sn_keccak} instead. +* Use Cairo friendly hashing algorithms like Poseidon, which are implemented in the +https://github.com/starkware-libs/cairo/blob/main/corelib/src/poseidon.cairo[Cairo corelib]. -{extensibility-pattern}[`internal`] function that sets `admin_role` as ``role``'s admin role. +TIP: The `selector!` macro can be used to compute {sn_keccak} in Cairo. -Emits a <> event. +=== Interface -Parameters: +This is the full interface of the `AccessControl` implementation: -[,cairo] +[,javascript] ---- -role: felt -admin_role: felt ----- - -Returns: None. +trait IAccessControl { + /// Returns whether the account has the role or not. + fn has_role(role: felt252, account: ContractAddress) -> bool; -=== AccessControl events + /// Returns the adming role that controls `role`. + fn get_role_admin(role: felt252) -> felt252; -[,cairo] ----- -func RoleGranted(role: felt, account: felt, sender: felt) { -} + /// Grants `role` to `account`. + fn grant_role(role: felt252, account: ContractAddress); -func RoleRevoked(role: felt, account: felt, sender: felt) { -} + /// Revokes `role` from `account`. + fn revoke_role(role: felt252, account: ContractAddress); -func RoleAdminChanged(role: felt, previousAdminRole: felt, newAdminRole: felt) { + /// Revokes `role` from self. + fn renounce_role(role: felt252, account: ContractAddress); } ---- -==== `RoleGranted` - -Emitted when `account` is granted `role`. - -`sender` is the account that originated the contract call, an admin role bearer. - -Parameters: - -[,cairo] ----- -role: felt -account: felt -sender: felt ----- - -==== `RoleRevoked` - -Emitted when account is revoked role. - -`sender` is the account that originated the contract call: - -* If using <>, it is the admin role bearer. -* If using <>, it is the role bearer (i.e. -`account`). - -[,cairo] ----- -role: felt -account: felt -sender: felt ----- - -==== `RoleAdminChanged` - -Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`. - -`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite `RoleAdminChanged` not being emitted signaling this. - -[,cairo] ----- -role: felt -previousAdminRole: felt -newAdminRole: felt ----- +`AccessControl` also lets you `renounce_role` from the calling account. +The method expects an account as input as an extra security measure, to ensure you are +not renouncing a role from an unintended account. diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc new file mode 100644 index 000000000..1a00ffa31 --- /dev/null +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -0,0 +1,477 @@ +:github-icon: pass:[] +:AccessControl: xref:AccessControl[AccessControl] +:Ownable: xref:Ownable[Ownable] +:src5: https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md[SRC5] +:inner-src5: xref:api/introspection.adoc#ISRC5[SRC5 ID] +:_set_role_admin: xref:#AccessControl-_set_role_admin[_set_role_admin] + += Access Control + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/access + +This directory provides ways to restrict who can access the functions of a contract or when they can do it. + +- {Ownable} is a simple mechanism with a single "owner" role that can be assigned to a single account. +This mechanism can be useful in simple scenarios, but fine-grained access needs are likely to outgrow it. +- {AccessControl} provides a general role-based access control mechanism. Multiple hierarchical roles can be created and +assigned each to multiple accounts. + +== Authorization + +[.contract] +[[Ownable]] +=== `++Ownable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-0.7.0/src/access/ownable/ownable.cairo[{github-icon},role=heading-link] + +```javascript +use openzeppelin::access::ownable::Ownable; +``` + +`Ownable` provides a basic access control mechanism where an account + (an owner) can be granted exclusive access to specific functions. + +This module includes the `assert_only_owner` internal to restrict a function to be used only by the owner. + +[.contract-index] +.External Functions +-- +.OwnableImpl + +* xref:Ownable-owner[`++owner(self)++`] +* xref:Ownable-transfer_ownership[`++transfer_ownership(self, new_owner)++`] +* xref:Ownable-renounce_ownership[`++renounce_ownership(self)++`] +-- + +[.contract-index] +.Internal Functions +-- +.InternalImpl + +* xref:Ownable-initializer[`++initializer(self, owner)++`] +* xref:Ownable-assert_only_owner[`++assert_only_owner(self)++`] +* xref:Ownable-_transfer_ownership[`++_transfer_ownership(self, new_owner)++`] +-- + +[.contract-index] +.Events +-- +* xref:Ownable-OwnershipTransferred[`++OwnershipTransferred(previous_owner, new_owner)++`] +-- + +[#Ownable-External-Functions] +==== External Functions + +[.contract-item] +[[Ownable-owner]] +==== `[.contract-item-name]#++owner++#++(self: @ContractState) → ContractAddress++` [.item-kind]#external# + +Returns the address of the current owner. + +[.contract-item] +[[Ownable-transfer_ownership]] +==== `[.contract-item-name]#++transfer_ownership++#++(ref self: ContractState, new_owner: ContractAddress)++` [.item-kind]#external# + +Transfers ownership of the contract to a new account (`new_owner`). +Can only be called by the current owner. + +Emits an xref:Ownable-OwnershipTransferred[OwnershipTransferred] event. + +[.contract-item] +[[Ownable-renounce_ownership]] +==== `[.contract-item-name]#++renounce_ownership++#++(ref self: ContractState)++` [.item-kind]#external# + +Leaves the contract without owner. It will not be possible to call +`assert_only_owner` functions anymore. Can only be called by the current owner. + +NOTE: Renouncing ownership will leave the contract without an owner, +thereby removing any functionality that is only available to the owner. + +[#Ownable-Internal-Functions] +==== Internal Functions + +[.contract-item] +[[Ownable-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, owner: ContractAddress)++` [.item-kind]#internal# + +Initializes the contract and sets `owner` as the initial owner. + +Emits an xref:Ownable-OwnershipTransferred[OwnershipTransferred] event. + +[.contract-item] +[[Ownable-assert_only_owner]] +==== `[.contract-item-name]#++assert_only_owner++#++(self: @ContractState)++` [.item-kind]#internal# + +Panics if called by any account other than the owner. + +[.contract-item] +[[Ownable-_transfer_ownership]] +==== `[.contract-item-name]#++_transfer_ownership++#++(ref self: ContractState, new_owner: ContractAddress)++` [.item-kind]#internal# + +Transfers ownership of the contract to a new account (`new_owner`). +Internal function without access restriction. + +Emits an xref:Ownable-OwnershipTransferred[OwnershipTransferred] event. + +[#Ownable-Events] +==== Events + +[.contract-item] +[[Ownable-OwnershipTransferred]] +==== `[.contract-item-name]#++OwnershipTransferred++#++(previous_owner: ContractAddress, new_owner: ContractAddress)++` [.item-kind]#event# + +Emitted when the ownership is transferred. + +[.contract] +[[IAccessControl]] +=== `++IAccessControl++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/05429e4fd34a250ce7a01450190c53275e5c1c0b/src/access/accesscontrol/interface.cairo#L10[{github-icon},role=heading-link] + +:grant_role: xref:#IAccessControl-grant_role[grant_role] +:revoke_role: xref:#IAccessControl-revoke_role[revoke_role] +:RoleGranted: xref:#IAccessControl-RoleGranted[RoleGranted] +:RoleRevoked: xref:#IAccessControl-RoleRevoked[RoleRevoked] +:RoleAdminChanged: xref:#IAccessControl-RoleAdminChanged[RoleAdminChanged] + +```javascript +use openzeppelin::access::accesscontrol::interface::IAccessControl; +``` + +External interface of AccessControl. + +[.contract-index] +.{inner-src5} +-- +0x23700be02858dbe2ac4dc9c9f66d0b6b0ed81ec7f970ca6844500a56ff61751 +-- + +[.contract-index] +.Functions +-- +* xref:IAccessControl-has_role[`++has_role(role, account)++`] +* xref:IAccessControl-get_role_admin[`++get_role_admin(role)++`] +* xref:IAccessControl-grant_role[`++grant_role(role, account)++`] +* xref:IAccessControl-revoke_role[`++revoke_role(role, account)++`] +* xref:IAccessControl-renounce_role[`++renounce_role(role, account)++`] +-- + +[.contract-index] +.Events +-- +* xref:IAccessControl-RoleAdminChanged[`++RoleAdminChanged(role, previous_admin_role, new_admin_role)++`] +* xref:IAccessControl-RoleGranted[`++RoleGranted(role, account, sender)++`] +* xref:IAccessControl-RoleRevoked[`++RoleRevoked(role, account, sender)++`] + +-- + +[#IAccessControl-Functions] +==== Functions + +[.contract-item] +[[IAccessControl-has_role]] +==== `[.contract-item-name]#++has_role++#++(role: felt252, account: ContractAddress) → bool++` [.item-kind]#external# + +Returns `true` if `account` has been granted `role`. + +[.contract-item] +[[IAccessControl-get_role_admin]] +==== `[.contract-item-name]#++get_role_admin++#++(role: felt252) → felt252++` [.item-kind]#external# + +Returns the admin role that controls `role`. See {grant_role} and +{revoke_role}. + +To change a role's admin, use {_set_role_admin}. + +[.contract-item] +[[IAccessControl-grant_role]] +==== `[.contract-item-name]#++grant_role++#++(role: felt252, account: ContractAddress)++` [.item-kind]#external# + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a {RoleGranted} +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +[.contract-item] +[[IAccessControl-revoke_role]] +==== `[.contract-item-name]#++revoke_role++#++(role: felt252, account: ContractAddress)++` [.item-kind]#external# + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a {RoleRevoked} event. + +Requirements: + +- the caller must have ``role``'s admin role. + +[.contract-item] +[[IAccessControl-renounce_role]] +==== `[.contract-item-name]#++renounce_role++#++(role: felt252, account: ContractAddress)++` [.item-kind]#external# + +Revokes `role` from the calling account. + +Roles are often managed via {grant_role} and {revoke_role}. This function'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). + +If the calling account had been granted `role`, emits a {RoleRevoked} +event. + +Requirements: + +- the caller must be `account`. + +[#IAccessControl-Events] +==== Events + +[.contract-item] +[[IAccessControl-RoleAdminChanged]] +==== `[.contract-item-name]#++RoleAdminChanged++#++(role: felt252, previous_admin_role: ContractAddress, new_admin_role: ContractAddress)++` [.item-kind]#event# + +Emitted when `new_admin_role` is set as ``role``'s admin role, replacing `previous_admin_role` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite +{RoleAdminChanged} not being emitted signaling this. + +[.contract-item] +[[IAccessControl-RoleGranted]] +==== `[.contract-item-name]#++RoleGranted++#++(role: felt252, account: ContractAddress, sender: ContractAddress)++` [.item-kind]#event# + +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call, an admin role +bearer. + +[.contract-item] +[[IAccessControl-RoleRevoked]] +==== `[.contract-item-name]#++RoleRevoked++#++(role: felt252, account: ContractAddress, sender: ContractAddress)++` [.item-kind]#event# + +Emitted when `account` is revoked `role`. + +`sender` is the account that originated the contract call: + +- if using `revoke_role`, it is the admin role bearer. +- if using `renounce_role`, it is the role bearer (i.e. `account`). + +[.contract] +[[AccessControl]] +=== `++AccessControl++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-0.7.0/src/access/accesscontrol/accesscontrol.cairo[{github-icon},role=heading-link] + +:assert_only_role: xref:#AccessControl-assert_only_role +:grant_role: xref:#AccessControl-grant_role[grant_role] +:revoke_role: xref:#AccessControl-revoke_role[revoke_role] + +```javascript +use openzeppelin::access::accesscontrol::AccessControl; +``` + +Contract module that allows children to implement role-based access control mechanisms. +Roles are referred to by their `felt252` identifier: + +```javascript +const MY_ROLE: felt252 = selector!('MY_ROLE'); +``` + +Roles can be used to represent a set of permissions. To restrict access to a +function call, use {assert_only_role}[`assert_only_role`]: + +```javascript +use openzeppelin::access::accesscontrol::AccessControl::InternalImpl::assert_only_role; +use openzeppelin::access::accesscontrol::AccessControl; +use openzeppelin::token::erc20::ERC20; + +#[external(v0)] +fn foo(ref self: ContractState, account: ContractAddress, amount: u256) { + let access_state = AccessControl::unsafe_new_contract_state(); + assert_only_role(@access_state, BURNER_ROLE); + + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::InternalImpl::_burn(ref erc20_state, account, amount); +} +``` + +Roles can be granted and revoked dynamically via the {grant_role} and +{revoke_role} functions. Each role has an associated admin role, and only +accounts that have a role's admin role can call {grant_role} and {revoke_role}. + +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 +{_set_role_admin}. + +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. + +[.contract-index] +.External Functions +-- +.AccessControlImpl + +* xref:#AccessControl-has_role[`++has_role(self, role, account)++`] +* xref:#AccessControl-get_role_admin[`++get_role_admin(self, role)++`] +* xref:#AccessControl-grant_role[`++grant_role(self, role, account)++`] +* xref:#AccessControl-revoke_role[`++revoke_role(self, role, account)++`] +* xref:#AccessControl-renounce_role[`++renounce_role(self, role, account)++`] + +.SRC5Impl +* xref:#AccessControl-supports_interface[`++supports_interface(self, interface_id: felt252)++`] +-- + +[.contract-index] +.Internal Functions +-- +.InternalImpl + +* xref:#AccessControl-initializer[`++initializer(self)++`] +* xref:#AccessControl-_set_role_admin[`++_set_role_admin(self, role, admin_role)++`] +* xref:#AccessControl-_grant_role[`++_grant_role(self, role, account)++`] +* xref:#AccessControl-_revoke_role[`++_revoke_role(self, role, account)++`] +* xref:#AccessControl-assert_only_role[`++assert_only_role(self, role)++`] +-- + +[.contract-index] +.Events +-- +.IAccessControl +* xref:#AccessControl-RoleAdminChanged[`++RoleAdminChanged(role, previous_admin_role, new_admin_role)++`] +* xref:#AccessControl-RoleGranted[`++RoleGranted(role, account, sender)++`] +* xref:#AccessControl-RoleRevoked[`++RoleRevoked(role, account, sender)++`] +-- + +[#AccessControl-External-Functions] +==== External Functions + +[.contract-item] +[[AccessControl-has_role]] +==== `[.contract-item-name]#++has_role++#++(self: @ContractState, role: felt252, account: ContractAddress) → bool++` [.item-kind]#external# + +Returns `true` if `account` has been granted `role`. + +[.contract-item] +[[AccessControl-get_role_admin]] +==== `[.contract-item-name]#++get_role_admin++#++(self: @ContractState, role: felt252) → felt252++` [.item-kind]#external# + +Returns the admin role that controls `role`. See {grant_role} and +{revoke_role}. + +To change a role's admin, use {_set_role_admin}. + +[.contract-item] +[[AccessControl-grant_role]] +==== `[.contract-item-name]#++grant_role++#++(ref self: ContractState, role: felt252, account: ContractAddress)++` [.item-kind]#external# + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a {RoleGranted} +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a {RoleGranted} event. + +[.contract-item] +[[AccessControl-revoke_role]] +==== `[.contract-item-name]#++revoke_role++#++(ref self: ContractState, role: felt252, account: ContractAddress)++` [.item-kind]#external# + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a {RoleRevoked} event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a {RoleRevoked} event. + +[.contract-item] +[[AccessControl-renounce_role]] +==== `[.contract-item-name]#++renounce_role++#++(ref self: ContractState, role: felt252, account: ContractAddress)++` [.item-kind]#external# + +Revokes `role` from the calling account. + +Roles are often managed via {grant_role} and {revoke_role}. This function'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). + +If the calling account had been revoked `role`, emits a {RoleRevoked} +event. + +Requirements: + +- the caller must be `account`. + +May emit a {RoleRevoked} event. + +[.contract-item] +[[AccessControl-supports_interface]] +==== `[.contract-item-name]#++supports_interface++#++(self: @ContractState, interface_id: felt252) → bool++` [.item-kind]#external# + +Returns whether a contract implements a given interface or not. + +[#AccessControl-Internal-Functions] +==== Internal Functions + +[.contract-item] +[[AccessControl-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState)++` [.item-kind]#internal# + +Initializes the contract by registering the xref:#IAccessControl[IAccessControl] interface ID. + +[.contract-item] +[[AccessControl-_set_role_admin]] +==== `[.contract-item-name]#++_set_role_admin++#++(ref self: ContractState, role: felt252, admin_role: felt252)++` [.item-kind]#internal# + +Sets `admin_role` as ``role``'s admin role. + +Emits a {RoleAdminChanged} event. + +[.contract-item] +[[AccessControl-_grant_role]] +==== `[.contract-item-name]#++_grant_role++#++(ref self: ContractState, role: felt252, account: ContractAddress)++` [.item-kind]#internal# + +Grants `role` to `account`. + +Internal function without access restriction. + +May emit a {RoleGranted} event. + +[.contract-item] +[[AccessControl-_revoke_role]] +==== `[.contract-item-name]#++_revoke_role++#++(ref self: ContractState, role: felt252, account: ContractAddress)++` [.item-kind]#internal# + +Revokes `role` from `account`. + +Internal function without access restriction. + +May emit a {RoleRevoked} event. + +[.contract-item] +[[AccessControl-assert_only_role]] +==== `[.contract-item-name]#++assert_only_role++#++(self: @ContractState, role: felt252)++` [.item-kind]#internal# + +Panics if called by any account without the given `role`. + +[#AccessControl-Events] +==== Events + +[.contract-item] +[[AccessControl-RoleAdminChanged]] +==== `[.contract-item-name]#++RoleAdminChanged++#++(role: felt252, previous_admin_role: ContractAddress, new_admin_role: ContractAddress)++` [.item-kind]#event# + +See xref:IAccessControl-RoleAdminChanged[IAccessControl::RoleAdminChanged]. + +[.contract-item] +[[AccessControl-RoleGranted]] +==== `[.contract-item-name]#++RoleGranted++#++(role: felt252, account: ContractAddress, sender: ContractAddress)++` [.item-kind]#event# + +See xref:IAccessControl-RoleGranted[IAccessControl::RoleGranted]. + +[.contract-item] +[[AccessControl-RoleRevoked]] +==== `[.contract-item-name]#++RoleRevoked++#++(role: felt252, account: ContractAddress, sender: ContractAddress)++` [.item-kind]#event# + +See xref:IAccessControl-RoleRevoked[IAccessControl::RoleRevoked]. diff --git a/docs/modules/ROOT/pages/api/account.adoc b/docs/modules/ROOT/pages/api/account.adoc index 52ed25191..942158af4 100644 --- a/docs/modules/ROOT/pages/api/account.adoc +++ b/docs/modules/ROOT/pages/api/account.adoc @@ -1,5 +1,6 @@ :github-icon: pass:[] :snip6: https://github.com/ericnordelo/SNIPs/blob/feat/standard-account/SNIPS/snip-6.md[SNIP-6] +:inner-src5: xref:api/introspection.adoc#ISRC5[SRC5 ID] = Account @@ -9,7 +10,7 @@ Reference of interfaces, presets, and utilities related to account contracts. [.contract] [[ISRC6]] -=== `++ISRC6++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/account/interface.cairo#L12[{github-icon},role=heading-link] +=== `++ISRC6++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-0.7.0/src/account/interface.cairo#L12[{github-icon},role=heading-link] ```javascript use openzeppelin::account::interface::ISRC6; @@ -17,6 +18,12 @@ use openzeppelin::account::interface::ISRC6; Interface of the SRC6 Standard Account as defined in the {snip6}. +[.contract-index] +.{inner-src5} +-- +0x2ceccef7f994940b3962a6c67e0ba4fcd37df7d131417c604f91e03caecc1cd +-- + [.contract-index] .Functions -- @@ -56,7 +63,7 @@ Returns the short string `'VALID'` if valid, otherwise it reverts. [.contract] [[Account]] -=== `++Account++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/account/account.cairo#L27[{github-icon},role=heading-link] +=== `++Account++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-0.7.0/src/account/account.cairo#L27[{github-icon},role=heading-link] :OwnerAdded: xref:Account-OwnerAdded[OwnerAdded] :OwnerRemoved: xref:Account-OwnerRemoved[OwnerRemoved] @@ -67,9 +74,9 @@ use openzeppelin::account::Account; Account contract implementation extending xref:ISRC6[`ISRC6`]. [.contract-index] -.Utilities +.Constructor -- -* xref:#Account-assert_only_self[`++InternalImpl::assert_only_self(self)++`] +* xref:#Account-constructor[`++constructor(self, _public_key)++`] -- [.contract-index] @@ -77,24 +84,20 @@ Account contract implementation extending xref:ISRC6[`ISRC6`]. -- * xref:#Account-\\__validate_deploy__[`++__validate_deploy__(self, hash, signature)++`] -[.contract-subindex-inherited] .SRC6Impl * xref:#Account-\\__execute__[`++__execute__(self, calls)++`] * xref:#Account-\\__validate__[`++__validate__(self, calls)++`] * xref:#Account-is_valid_signature[`++is_valid_signature(self, hash, signature)++`] -[.contract-subindex-inherited] .SRC5Impl * xref:#Account-supports_interface[`++supports_interface(self, interface_id)++`] -[.contract-subindex-inherited] .DeclarerImpl * xref:#Account-\\__validate_declare__[`++__validate_declare__(self, class_hash)++`] -[.contract-subindex-inherited] .PublicKeyImpl * xref:#Account-set_public_key[`++set_public_key(self, new_public_key)++`] @@ -104,15 +107,13 @@ Account contract implementation extending xref:ISRC6[`ISRC6`]. [.contract-index] .Internal Functions -- -* xref:#Account-constructor[`++constructor(self, _public_key)++`] - -[.contract-subindex-inherited] .InternalImpl * xref:#Account-initializer[`++initializer(self, _public_key)++`] * xref:#Account-validate_transaction[`++validate_transaction(self)++`] * xref:#Account-_set_public_key[`++_set_public_key(self, new_public_key)++`] * xref:#Account-_is_valid_signature[`++_is_valid_signature(self, hash, signature)++`] +* xref:#Account-assert_only_self[`++assert_only_self(self)++`] -- [.contract-index] @@ -122,17 +123,8 @@ Account contract implementation extending xref:ISRC6[`ISRC6`]. * xref:#Account-OwnerRemoved[`++OwnerRemoved(removed_owner_guid)++`] -- -[#Account-Utilities] -==== Utilities - -[.contract-item] -[[Account-assert_only_self]] -==== `[.contract-item-name]#++assert_only_self++#++(self: @ContractState)++` [.item-kind]#internal# - -Validates that the caller is the account itself. Otherwise it reverts. - -[#Account-Functions] -==== Functions +[#Account-Constructor] +==== Constructor [.contract-item] [[Account-constructor]] @@ -142,6 +134,9 @@ Initializes the account with the given public key, and registers the ISRC6 inter Emits an {OwnerAdded} event. +[#Account-External-Functions] +==== External Functions + [.contract-item] [[Account-__validate_deploy__]] ==== `[.contract-item-name]#++__validate_deploy__++#++(self: @ContractState, class_hash: felt252, contract_address_salt: felt252, _public_key: felt252) → felt252++` [.item-kind]#external# @@ -197,6 +192,9 @@ Emits both an {OwnerRemoved} and an {OwnerAdded} event. Returns the current public key of the account. +[#Account-Internal-Functions] +==== Internal Functions + [.contract-item] [[Account-initializer]] ==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, _public_key: felt252)++` [.item-kind]#internal# @@ -230,6 +228,12 @@ CAUTION: The usage of this method outside the `set_public_key` function is disco Validates the provided `signature` for the `hash`, using the account current public key. +[.contract-item] +[[Account-assert_only_self]] +==== `[.contract-item-name]#++assert_only_self++#++(self: @ContractState)++` [.item-kind]#internal# + +Validates that the caller is the account itself. Otherwise it reverts. + [#Account-Events] ==== Events