OpenZeppelin Ownable

OpenZeppelin Doc

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 provides Ownable for implementing ownership in your contracts.

// contracts/MyContract.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

contract MyContract is Ownable {
    function normalThing() public {
        // anyone can call this normalThing()
    }

    function specialThing() public onlyOwner {
        // only the owner can call specialThing()!
    }
}

By default, the owner of an Ownable contract is the account that deployed it, which is usually exactly what you want.

Ownable also lets you:

  • transferOwnership from the owner account to a new one, and

  • renounceOwnership for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over.

Warning Removing the owner altogether will mean that administrative tasks that are protected by onlyOwner will no longer be callable!

Note that a contract can also be the owner of another one! This opens the door to using, for example, a Gnosis Safe, an Aragon DAO, or a totally custom contract that you create.

In this way you can use composability to add additional layers of access control complexity to your contracts. Instead of having a single regular Ethereum account (Externally Owned Account, or EOA) as the owner, you could use a 2-of-3 multisig run by your project leads, for example. Prominent projects in the space, such as MakerDAO, use systems similar to this one.

OpenZeppelin Ownable.sol

State Variables and Constants

There is only one state variable in this contract:

address private _owner;

Note that when a contract inherits Ownable, _owner will be stored in storage slot 0.

Constructor

constructor() {
    _transferOwnership(_msgSender());
}

During initialization, the msg.sender (the deployer) becomes owner.

transferOwnership()

/**
 * @dev Transfers ownership of the contract to a new account (`newOwner`).
 * Can only be called by the current owner.
 */
function transferOwnership(address newOwner) public virtual onlyOwner {
    require(newOwner != address(0), "Ownable: new owner is the zero address");
    _transferOwnership(newOwner);
}
/**
 * @dev Transfers ownership of the contract to a new account (`newOwner`).
 * Internal function without access restriction.
 */
function _transferOwnership(address newOwner) internal virtual {
    address oldOwner = _owner;
    _owner = newOwner;
    emit OwnershipTransferred(oldOwner, newOwner);
}

This is a one-step ownership transfer function. In general this pattern is not recommended because owner's private key can be stolen, etc. We want to use something like 2-of-3 signature and timelock delay for doing ownership transfer.

renounceOwnership()

/**
 * @dev Leaves the contract without owner. It will not be possible to call
 * `onlyOwner` functions. Can only be called by the current owner.
 *
 * NOTE: Renouncing ownership will leave the contract without an owner,
 * thereby disabling any functionality that is only available to the owner.
 */
function renounceOwnership() public virtual onlyOwner {
    _transferOwnership(address(0));
}

"Renouncing" ownership means setting the zero address as the owner. Note that after calling renounceOwnership(), any call to transferOwnership() would revert because of the following check in transferOwnership():

require(newOwner != address(0), "Ownable: new owner is the zero address");

Last updated