Ownable

Ownable design pattern

Ownable is a design pattern of ownership of smart contract, meaning the ownership can be transferred and renounced. Initialize your Mamba project.


(.venv) $ mkdir ownable
(.venv) $ cd ownable
(.venv) $ mamba init

Edit contracts/Ownable.vy.


(.venv) $ edit contracts/Ownable.vy

Add this code.


"""
@title An Ownable design pattern
@license MIT
@author Arjuna Sky Kok
@notice Translated from OpenZeppelin's Ownable code: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
"""
_owner: address


event OwnershipTransferred:
    previousOwner: indexed(address)
    newOwner: indexed(address)


@external
def __init__():
    self._owner = msg.sender
    log OwnershipTransferred(ZERO_ADDRESS, msg.sender)

@external
@view
def owner() -> address:
    return self._owner

@internal
def onlyOwner(sender: address):
    assert self._owner == sender, "Ownable: caller is not the owner"

@external
def renounceOwnership():
    self.onlyOwner(msg.sender)
    log OwnershipTransferred(self._owner, ZERO_ADDRESS)
    self._owner = ZERO_ADDRESS

@external
def transferOwnership(newOwner: address):
    self.onlyOwner(msg.sender)
    log OwnershipTransferred(self._owner, newOwner)
    self._owner = newOwner

The code is straightforward. You have a state variable, _owner. This variable points to the owner of the smart contract. It is initialized in the initialization method to the one who deploys the smart contract. The method, renounceOwnership, is to set the _owner variable to zero address. It means the ownership of this smart contract is renounced. No one owns the smart contract. The method, transferOwnership, is to set the _owner variable to the new address. The owner of the smart contract will be changed to this new address. Every time the ownership is changed, the smart contract emits the event, OwnershipTransferred.

Let's create a test. Edit test/test_ownable.py.


from black_mamba.testlib import contract, eth_tester
import pytest
from eth_tester.exceptions import TransactionFailed


def test_init(eth_tester):
    accounts = eth_tester.get_accounts()
    ownable_contract = contract("Ownable", [])
    assert ownable_contract.functions.owner().call() == accounts[0]
    log = ownable_contract.events.OwnershipTransferred.getLogs()[0]
    assert log["args"]["previousOwner"] == "0x0000000000000000000000000000000000000000"
    assert log["args"]["newOwner"] == accounts[0]

def test_renounce_ownership(eth_tester):
    accounts = eth_tester.get_accounts()
    ownable_contract = contract("Ownable", [])
    ownable_contract.functions.renounceOwnership().transact()
    assert ownable_contract.functions.owner().call() == "0x0000000000000000000000000000000000000000"
    log = ownable_contract.events.OwnershipTransferred.getLogs()[0]
    assert log["args"]["previousOwner"] == accounts[0]
    assert log["args"]["newOwner"] == "0x0000000000000000000000000000000000000000"

def test_renounce_ownership_fails(eth_tester):
    accounts = eth_tester.get_accounts()
    ownable_contract = contract("Ownable", [])
    with pytest.raises(TransactionFailed):
        ownable_contract.functions.renounceOwnership().transact({ "from": accounts[1] })

def test_transfer_ownership(eth_tester):
    accounts = eth_tester.get_accounts()
    ownable_contract = contract("Ownable", [])
    ownable_contract.functions.transferOwnership(accounts[1]).transact()
    assert ownable_contract.functions.owner().call() == accounts[1]
    log = ownable_contract.events.OwnershipTransferred.getLogs()[0]
    assert log["args"]["previousOwner"] == accounts[0]
    assert log["args"]["newOwner"] == accounts[1]

def test_transfer_ownership_fails(eth_tester):
    accounts = eth_tester.get_accounts()
    ownable_contract = contract("Ownable", [])
    with pytest.raises(TransactionFailed):
        ownable_contract.functions.transferOwnership(accounts[1]).transact({ "from": accounts[2] })

To get the events of the smart contract, we use events method of the smart contract object to get all events. In this smart contract, we have only 1 event. The event we are looking for is OwnershipTransferred. From there, you can execute the getLogs method. It gives all logs as an array.

To execute the methods of the smart contract, we use functions method to get all methods of the smart contract. The method to read the owner can be executed with the call method. The method to change the state of the smart contract can be executed with the transact method.

To execute the test, you can use pytest.


(.venv) $ py.test test/test_ownable.py