Part 10: Dependencies

So far we have worked with, and deployed, one contract at a time. However, Solidity allows you to inherit from other contracts, which is especially useful when you a more generic functionality in a basic core contract, a functionality you want to inherit and re-use in another, more specific use case.

A Contract that is Controlled by it’s Owner

The Donator2 contract is better than Donator, because it allows to withdraw the donations that the contract accepts. But not much better: at any given moment anyone can withdraw the entire donations balance, no questioned asked.

If you recall the withdraw function from the contract:

//demo only allows ANYONE to withdraw
function withdrawAll() external {
    require(msg.sender.send(this.balance));
      }

The withdrawAll function just sends the entire balane, this.balance, to whoever request it. Send everything to msg.sender, without any further verification.

A better implemntation would be to allow only the owner of the contract to withdraw the funds. An owne, in the Ethereum world, is an account. An address. The owner is the account that the contract creation transaction was sent from.

Restricting persmission to run some actions only to the owner is a very common design pattern. There are many open-source implemntations of this pattern, and we will work here with the OpenZeppelin library.

Note

When you adapt an open sourced Solidity contract from github, or elsewhere, be careful. You should trust only respected, known authors, and always review the code yourself. Smart contracts can induce some smart bugs, which can lead directly to loosing money. Yes, the Ethereum platform has many innovations, but loosing money from software bugs is not one of them. However, it’s not fun to loose Ether like this. Be careful from intentional or unintentional bugs. See some really clever examples in Underhanded Solidity Coding Contest

This is the OpenZeppelin Ownable.sol Contract:

    pragma solidity ^0.4.11;


  /**
   * @title Ownable
   * @dev The Ownable contract has an owner address, and provides basic authorization control
   * functions, this simplifies the implementation of "user permissions".
   */
  contract Ownable {
    address public owner;


    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    function Ownable() {
      owner = msg.sender;
    }


    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
      require(msg.sender == owner);
      _;
    }


    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) onlyOwner public {
      require(newOwner != address(0));
      OwnershipTransferred(owner, newOwner);
      owner = newOwner;
    }

  }

Save it to your conatracts directory:

$ nano contracts Ownable.sol

Inheritence & Imports

Create the new improved Donator3:

// TUTORIAL CONTRACT DO NOT USE IN PRODUCTION
/// @title Donations collecting contract
import "./Ownable.sol";


contract Donator3 is Ownable {
    uint public donationsTotal;
    uint public donationsUsd;
    uint public donationsCount;
    uint public defaultUsdRate;

    function Donator3() {
        defaultUsdRate = 350;
    }

    // fallback function
    function () payable {
        donate(defaultUsdRate);
    }

    modifier nonZeroValue() { if (!(msg.value > 0)) throw; _; }

    function donate(uint usd_rate) public payable nonZeroValue {
        donationsTotal += msg.value;
        donationsCount += 1;
        defaultUsdRate = usd_rate;
        uint inUsd = msg.value * usd_rate / 1 ether;
        donationsUsd += inUsd;
    }

    // only allows the owner to withdraw
    function withdrawAll() external onlyOwner {
        require(msg.sender.send(this.balance));
    }
}

Almost the same code of Donator2, with 3 important additions:

[1] An import statement: import "./Ownable.sol" ``: Used to when you use constructs from other source files, a common practice in almost any programming language. The format of local path with ``./Filename.sol is used for an import of a file in the same directory.

Note

Solidity supports quite comprehensive import options. See the Solidity documentation of Importing other Source Files

[2] Subclassing: contract Donator3 is Ownable {...}

[3] Use a parent member in the subclass:

function withdrawAll() external onlyOwner {
      require(msg.sender.send(this.balance));
        }

The onlyOwner modifier was not defined in Donator3, but it is inhereted, and thus can be used in the subclass.

Your contracts directory should look as follows:

$ ls contracts
Donator2.sol  Donator3.sol  Donator.sol  Greeter.sol  Ownable.sol

Compile the project:

$ populus compile
> Found 5 contract source files
- contracts/Donator.sol
- contracts/Donator2.sol
- contracts/Donator3.sol
- contracts/Greeter.sol
- contracts/Ownable.sol
> Compiled 5 contracts
- contracts/Donator.sol:Donator
- contracts/Donator2.sol:Donator2
- contracts/Donator3.sol:Donator3
- contracts/Greeter.sol:Greeter
- contracts/Ownable.sol:Ownable

Compilation works, solc successfuly found Ownable.sol and imported it.

Testing the Subclass Contract

The test is similar to a regular contract test. All the parents’ memebers are inherited and available for testing (if a parent member was overidden, use super to access the parent member)

Add a test:

$ nano tests/test_donator3.py

The test should look as follows:

import pytest
from ethereum.tester import TransactionFailed

ONE_ETH_IN_WEI = 10**18

def test_ownership(chain):
    donator3, deploy_tx_hash = chain.provider.get_or_deploy_contract('Donator3')
    w3 = chain.web3
    owner = w3.eth.coinbase # alias

    # prep: set a second test account, unlocked, with Wei for gas
    password = "demopassword"
    non_owner =  w3.personal.newAccount(password=password)
    w3.personal.unlockAccount(non_owner,passphrase=password)
    w3.eth.sendTransaction({'value':ONE_ETH_IN_WEI,'to':non_owner,'from':w3.eth.coinbase})

    # prep: initial contract balance
    donation = 42 * ONE_ETH_IN_WEI
    effective_usd_rate = 5
    transaction = {'value': donation, 'from':w3.eth.coinbase}
    donator3.transact(transaction).donate(effective_usd_rate)
    assert w3.eth.getBalance(donator3.address) == donation

    # test: non owner withdraw, should fail
    with pytest.raises(TransactionFailed):
        donator3.transact({'from':non_owner}).withdrawAll()
    assert w3.eth.getBalance(donator3.address) == donation

    # test: owner withdraw, ok
    donator3.transact({'from':owner}).withdrawAll()
    assert w3.eth.getBalance(donator3.address) == 0

The test is similar to the previous tests. Py.test and the Populus plugin provide a chain fixture, as an argument to the test function which is a handle to the tester ephemeral chain. Donator3 is deployed, and the test function gets a contract object to doantor3.

Then the test creates a second account, non_owner, unlocks it, and send to this account 1 Ether to pay for the gas. Next, send 42 Ether to the contract.

Note

The test was deployed with the default account, the coinbase. So coinbase, or the alias owner, is the owner of the contract.

When the test tries to withdraw with non_owner, which is not the owner, the transaction fails:

# test: non owner withdraw, should fail
with pytest.raises(TransactionFailed):
    donator3.transact({'from':non_owner}).withdrawAll()
assert w3.eth.getBalance(donator3.address) == donation

When the owner tries to withdraw it works, and the balance is back to 0:

# test: owner withdraw, ok
donator3.transact({'from':owner}).withdrawAll()
assert w3.eth.getBalance(donator3.address) == 0

Run the test:

$ py.test --disable-pytest-warnings

===================================== test session starts ======================
platform linux -- Python 3.5.2, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
rootdir: /home/mary/projects/donations, inifile: pytest.ini
plugins: populus-1.8.0, hypothesis-3.14.0
collected 5 items

tests/test_donator.py ....
tests/test_donator3.py .
=========================== 5 passed, 24 warnings in 3.52 seconds ==============

Passed.

The 2nd test will test the``Ownable`` function that allows to transfer ownership. Only the current owner can run it. Let’s test it.

Edit the test file:

$ nano tests/test_donator3.py

And add the following test function:

def test_transfer_ownership(chain):
    donator3, deploy_tx_hash = chain.provider.get_or_deploy_contract('Donator4')
    w3 = chain.web3
    first_owner = w3.eth.coinbase # alias

    # set unlocked test accounts, with Wei for gas
    password = "demopassword"
    second_owner =  w3.personal.newAccount(password=password)
    w3.personal.unlockAccount(second_owner,passphrase=password)
    w3.eth.sendTransaction({'value':ONE_ETH_IN_WEI,'to':second_owner,'from':w3.eth.coinbase})

    # initial contract balance
    donation = 42 * ONE_ETH_IN_WEI
    effective_usd_rate = 5
    transaction = {'value': donation, 'from':w3.eth.coinbase}
    donator3.transact(transaction).donate(effective_usd_rate)
    assert w3.eth.getBalance(donator3.address) == donation

    # test: transfer ownership
    assert donator3.call().owner == first_owner
    transaction = {'from':first_owner}
    donator3.transact(transaction).transferOwnership(second_owner)
    assert donator3.call().owner == second_owner

    # test: first owner withdraw, should fail after transfer ownership
    with pytest.raises(TransactionFailed):
        donator3.transact({'from':first_owner}).withdrawAll()
    assert w3.eth.getBalance(donator3.address) == donation

    # test: second owner withdraw, ok after transfer ownership
    donator3.transact({'from':second_owner}).withdrawAll()
    assert w3.eth.getBalance(donator3.address) == 0

    # test: transfer ownership by non owner, should fail
    transaction = {'from':first_owner}
    with pytest.raises(TransactionFailed):
        donator3.transact(transaction).transferOwnership(second_owner)
    assert donator3.call().owner == second_owner

Run the test:

$ py.test --disable-pytest-warnings

The test should fail:

          # transfer ownership
>       assert donator3.call().owner == first_owner
E       AssertionError: assert functools.partial(<function call_contract_function at 0x7f6245b39bf8>, <web3.contract.PopulusContract object at 0x7f624466e748>, 'owner', {'to': '0xc305c901078781c232a2a521c2af7980f8385ee9'}) == '0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1'

Yes. donator3.call().owner is wrong. Confusing. Reminder: owner should be accessed as a function, and not as a property, The compiler builds these functions for every public state variable.

Fix every occurence of call().owner to call().owner(). E.g., into:

assert donator3.call().owner() == first_owner

Then Run again:

$ py.test --disable-pytest-warnings

Fails again:

          # test: transfer ownership
>       assert donator3.call().owner() == first_owner
E       AssertionError: assert '0x82A978B3f5...f472EE55B42F1' == '0x82a978b3f59...f472ee55b42f1'
E         - 0x82A978B3f5962A5b0957d9ee9eEf472EE55B42F1
E

In the Ethereum world, it does not matter if an address is uppercase or lowercase (capitalisation is used for checksum, to avoid errors in client applications). We will use all lower case.

Fix the test as follows:

def test_transfer_ownership(chain):
    donator3, deploy_tx_hash = chain.provider.get_or_deploy_contract('Donator4')
    w3 = chain.web3
    first_owner = w3.eth.coinbase # alias

    # set unlocked test accounts, with Wei for gas
    password = "demopassword"
    second_owner =  w3.personal.newAccount(password=password)
    w3.personal.unlockAccount(second_owner,passphrase=password)
    w3.eth.sendTransaction({'value':ONE_ETH_IN_WEI,'to':second_owner,'from':w3.eth.coinbase})

    # initial contract balance
    donation = 42 * ONE_ETH_IN_WEI
    effective_usd_rate = 5
    transaction = {'value': donation, 'from':w3.eth.coinbase}
    donator3.transact(transaction).donate(effective_usd_rate)
    assert w3.eth.getBalance(donator3.address) == donation

    # test: transfer ownership
    assert donator3.call().owner().lower() == first_owner.lower()
    transaction = {'from':first_owner}
    donator3.transact(transaction).transferOwnership(second_owner)
    assert donator3.call().owner().lower() == second_owner.lower()

    # test: first owner withdraw, should fail after transfer ownership
    with pytest.raises(TransactionFailed):
        donator3.transact({'from':first_owner}).withdrawAll()
    assert w3.eth.getBalance(donator3.address) == donation

    # test: second owner withdraw, ok after transfer ownership
    donator3.transact({'from':second_owner}).withdrawAll()
    assert w3.eth.getBalance(donator3.address) == 0

    # test: transfer ownership by non owner, should fail
    transaction = {'from':first_owner}
    with pytest.raises(TransactionFailed):
        donator3.transact(transaction).transferOwnership(second_owner)
    assert donator3.call().owner().lower() == second_owner.lower()

Run the test:

$ py.test --disable-pytest-warnings

================================================== test session starts ==============
platform linux -- Python 3.5.2, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
rootdir: /home/may/projects/donations, inifile: pytest.ini
plugins: populus-1.8.0, hypothesis-3.14.0
collected 6 items

tests/test_donator.py ....
tests/test_donator3.py ..

========================================== 6 passed, 29 warnings in 1.93 seconds ====

Ok, all the inherited members passed: The Ownable constructor that sets owner ran when you deployed it’s subclass, Donator3. The parent modifier onlyOwner works as a modifier to a subclass function, and the transferOwnership parent’s funcion can be called by clients via the subclass interface.

Note

If you will deploy Donator3 to a local chain, say horton, and look at the registrar.json, you will not see an entry for Ownable. The reason is that although Solidity has a full complex multiple inheritence model (and super), the final result is once contract. Solidity just copies the inherited code to this contract.

Interim Summary

  • You used an open sourced contract
  • You imported one contract to another
  • You added an ownership control to a contract
  • You used inheritence and tested it