Skip to content

Commit

Permalink
docs: OwnableTwoStep usage and impl section
Browse files Browse the repository at this point in the history
  • Loading branch information
milancermak committed Nov 9, 2023
1 parent 7a6e465 commit 44236d2
Showing 1 changed file with 132 additions and 3 deletions.
135 changes: 132 additions & 3 deletions docs/modules/ROOT/pages/access.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
:ownable-cairo: link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.8.0-beta.0/src/access/ownable/ownable.cairo[Ownable]
:two-step-ownable-cairo: link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.8.0-beta.0/src/access/ownable/ownable.cairo[Two-step 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

Expand Down Expand Up @@ -107,6 +108,134 @@ after an initial stage with centralized administration is over.
WARNING: Removing the owner altogether will mean that administrative tasks that are protected by `assert_only_owner`
will no longer be callable!

== `TwoStepOwnable`

The `TwoStepOwnable` offers a more robust way of transferring ownership when compared to `Ownable`.
The difference lies in the fact that the new owner has to first xref:/api/access.adoc#OwnableTwoStep-accept_ownership[accept]
their ownership. Only after this call is made, the ownersihp is transferred. This mechanism helps to prevent
unintended and irreversible owner transfers.

=== Usage

Integrating this component 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:

[,javascript]
----
#[starknet::contract]
mod MyContract {
use openzeppelin::access::ownable::OwnableComponent;
use starknet::ContractAddress;
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
#[abi(embed_v0)]
impl OwnableTwoStepImpl = OwnableComponent::OwnableTwoStepImpl<ContractState>;
#[abi(embed_v0)]
impl OwnableTwoStepCamelOnlyImpl =
OwnableComponent::OwnableTwoStepCamelOnlyImpl<ContractState>;
impl InternalImpl = OwnableComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
ownable: OwnableComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: OwnableComponent::Event
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
// Set the initial owner of the contract
self.ownable.initializer(owner);
}
(...)
}
----

To restrict a function's access to the owner only, add in the `assert_only_owner` method:

[,javascript]
----
#[starknet::contract]
mod MyContract {
(...)
#[external(v0)]
fn only_owner_allowed(ref self: ContractState) {
// This function can only be called by the owner
self.ownable.assert_only_owner();
(...)
}
}
----

=== Interface

This is the full interface of the `TwoStepOwnable` implementation:

[,javascript]
----
trait IOwnableTwoStep {
/// Returns the current owner
fn owner() -> ContractAddress;
/// Returns the pending owner.
fn pending_owner() -> ContractAddress;
/// Finishes the two-step ownership transfer process
/// by accepting the ownership.
fn accept_ownership(ref self: TState);
/// Starts the two-step ownership transfer process
/// by setting the pending owner.
fn transfer_ownership(ref self: TState, new_owner: ContractAddress);
/// Renounces the ownership of the contract.
fn renounce_ownership(ref self: TState);
}
----

TODO: what else, if anything, should be mentioned here? just copy-paste from Ownable?
maybe mention that `transfer_ownership` can be called multiple times to overwrite the
proposed owner? and that after accepting, the pending owner is set to zero?

=== Upgrading from `Ownable`

If your contract already is xref:ownership_and_ownable[Ownable] and you want to xref:/upgrades.adoc[upgrade] it to `TwoStepOwnable`,
the only thing necessary is to replace the declared implementations. Change

[,javascript]
----
#[abi(embed_v0)]
impl OwnableImpl = OwnableComponent::OwnableImpl<ContractState>;
#[abi(embed_v0)]
impl OwnableCamelOnlyImpl =
OwnableComponent::OwnableCamelOnlyImpl<ContractState>;
----

into

[,javascript]
----
#[abi(embed_v0)]
impl OwnableTwoStepImpl = OwnableComponent::OwnableTwoStepImpl<ContractState>;
#[abi(embed_v0)]
impl OwnableTwoStepCamelOnlyImpl =
OwnableComponent::OwnableTwoStepCamelOnlyImpl<ContractState>;
----

After the upgrade, the set `owner` will be preserved and you will gain the functionality of
a two-step ownable implementation.

== Role-Based `AccessControl`

While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of
Expand Down Expand Up @@ -327,8 +456,8 @@ roles (such as during construction). But what if we later want to grant the 'min
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
xref:api/access.adoc#AccessControl-grant_role[`grant_role`] and
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.
Expand All @@ -338,7 +467,7 @@ 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
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:
Expand Down

0 comments on commit 44236d2

Please sign in to comment.