Ownable
The following Ownable
component is a simple component that allows the contract to set an owner and provides a _assert_is_owner
function that can be used to ensure that the caller is the owner.
It can also be used to renounce ownership of a contract, meaning that no one will be able to satisfy the _assert_is_owner
function.
use starknet::ContractAddress;
#[starknet::interface]
pub trait IOwnable<TContractState> {
fn owner(self: @TContractState) -> ContractAddress;
fn transfer_ownership(ref self: TContractState, new: ContractAddress);
fn renounce_ownership(ref self: TContractState);
}
mod Errors {
pub const UNAUTHORIZED: felt252 = 'Not owner';
pub const ZERO_ADDRESS_OWNER: felt252 = 'Owner cannot be zero';
pub const ZERO_ADDRESS_CALLER: felt252 = 'Caller cannot be zero';
}
#[starknet::component]
pub mod ownable_component {
use starknet::{ContractAddress, get_caller_address};
use super::Errors;
use core::num::traits::Zero;
#[storage]
struct Storage {
ownable_owner: ContractAddress,
}
#[derive(Drop, starknet::Event)]
struct OwnershipTransferredEvent {
previous: ContractAddress,
new: ContractAddress
}
#[derive(Drop, starknet::Event)]
struct OwnershipRenouncedEvent {
previous: ContractAddress
}
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
OwnershipTransferredEvent: OwnershipTransferredEvent,
OwnershipRenouncedEvent: OwnershipRenouncedEvent
}
#[embeddable_as(Ownable)]
impl OwnableImpl<
TContractState, +HasComponent<TContractState>
> of super::IOwnable<ComponentState<TContractState>> {
fn owner(self: @ComponentState<TContractState>) -> ContractAddress {
self.ownable_owner.read()
}
fn transfer_ownership(ref self: ComponentState<TContractState>, new: ContractAddress) {
self._assert_only_owner();
self._transfer_ownership(new);
}
fn renounce_ownership(ref self: ComponentState<TContractState>) {
self._assert_only_owner();
self._renounce_ownership();
}
}
#[generate_trait]
pub impl OwnableInternalImpl<
TContractState, +HasComponent<TContractState>
> of OwnableInternalTrait<TContractState> {
fn _assert_only_owner(self: @ComponentState<TContractState>) {
let caller = get_caller_address();
assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER);
assert(caller == self.ownable_owner.read(), Errors::UNAUTHORIZED);
}
fn _init(ref self: ComponentState<TContractState>, owner: ContractAddress) {
assert(!owner.is_zero(), Errors::ZERO_ADDRESS_OWNER);
self.ownable_owner.write(owner);
}
fn _transfer_ownership(ref self: ComponentState<TContractState>, new: ContractAddress) {
assert(!new.is_zero(), Errors::ZERO_ADDRESS_OWNER);
let previous = self.ownable_owner.read();
self.ownable_owner.write(new);
self
.emit(
Event::OwnershipTransferredEvent(OwnershipTransferredEvent { previous, new })
);
}
fn _renounce_ownership(ref self: ComponentState<TContractState>) {
let previous = self.ownable_owner.read();
self.ownable_owner.write(Zero::zero());
self.emit(Event::OwnershipRenouncedEvent(OwnershipRenouncedEvent { previous }));
}
}
}
A mock contract that uses the Ownable
component:
contract
#[starknet::interface]
pub trait IOwned<TContractState> {
fn do_something(ref self: TContractState);
}
#[starknet::contract]
pub mod OwnedContract {
use components::ownable::{IOwnable, ownable_component, ownable_component::OwnableInternalTrait};
component!(path: ownable_component, storage: ownable, event: OwnableEvent);
#[abi(embed_v0)]
impl OwnableImpl = ownable_component::Ownable<ContractState>;
impl OwnableInternalImpl = ownable_component::OwnableInternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
ownable: ownable_component::Storage,
}
#[constructor]
fn constructor(ref self: ContractState) {
self.ownable._init(starknet::get_caller_address());
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
OwnableEvent: ownable_component::Event,
}
#[abi(embed_v0)]
impl Owned of super::IOwned<ContractState> {
fn do_something(ref self: ContractState) {
self.ownable._assert_only_owner();
// ...
}
}
}