Part 3: Installing a Package

Introduction

In this tutorial we will be creating our own mintable ERC20 token. However, instead of writing our own ERC20 implementation we’ll be taking advantage of an existing implementation through the use of populus’s package management features.

Setting up the project folder

Create a new directory for your project and run $ populus project init to populate the initial project structure.

$ populus init
Wrote default populus configuration to `./populus.json`.<Paste>
Created Directory: ./contracts
Created Example Contract: ./contracts/Greeter.sol
Created Directory: ./tests
Created Example Tests: ./tests/test_greeter.py

Now, delete the ./contracts/Greeter.sol and ./tests/test_greeter.py files as we won’t be using the Greeter contracts in this tutorial.

Once you’ve removed those files create a new solidity source file ./contracts/MintableToken.sol and paste in the following solidity source code.

pragma solidity ^0.4.0;

import {owned} from "example-package-owned/contracts/owned.sol";
import {StandardToken} from "example-package-standard-token/contracts/StandardToken.sol";

contract MintableToken is StandardToken(0), owned {
  function mint(address who, uint value) public onlyowner returns (bool) {
    balances[who] += value;
    totalSupply += value;
    Transfer(0x0, who, value);
    return true;
  }
}

If you are familiar with solidity, the two import statements should stand out to you. These two statements will currently cause an error during compilation. Let’s see.

$ populus compile
============ Compiling ==============
> Loading source files from: ./contracts

Traceback (most recent call last):
  ...
                > command: `solc --optimize --combined-json bin,bin-runtime,abi,devdoc,userdoc contracts/MintableToken.sol`
                > return code: `1`
                > stderr:

                > stdout:
                contracts/MintableToken.sol:3:1: Error: Source "example-package-owned/contracts/owned.sol" not found: File not found.
import {owned} from "example-package-owned/contracts/owned.sol";
^--------------------------------------------------------------^
contracts/MintableToken.sol:4:1: Error: Source "example-package-standard-token/contracts/StandardToken.sol" not found: File not found.
import {StandardToken} from "example-package-standard-token/contracts/StandardToken.sol";
^---------------------------------------------------------------------------------------^

The solidity compiler clearly gets angry that we’re trying to import files that don’t exist. In order to install these file and make solidity happy we’ll first need to generate a package manifest using the $ populus package init command.

You will be presented with an interactive prompt to populus various pieces of project information. There will now be a new file in the root of your project named ethpm.json that should look something like this.

{
  "authors": [
        "Piper Merriam <pipermerriam@gmail.com>"
  ],
  "description": "Mintable ERC20 token contract",
  "keywords": [
        "ERC20",
        "tokens"
  ],
  "license": "MIT",
  "links": {},
  "manifest_version": "1",
  "package_name": "mintable-standard-token",
  "version": "1.0.0"
}

Now we are ready to install some dependencies using the $ populus package install command. We want to install both the example-package-owned and example-package-standard-token packages.

$ populus package install example-package-owned example-package-standard-token
Installed Packages: owned, standard-token

If you look in your project directory you should also see a new folder ./installed_packages.

$ tree .
.
├── contracts
│   └── MintableToken.sol
├── ethpm.json
├── installed_packages
│   ├── example-package-owned
│   │   ├── build_identifier.lock
│   │   ├── contracts
│   │   │   └── owned.sol
│   │   ├── install_identifier.lock
│   │   ├── installed_packages
│   │   └── lock.json
│   └── example-package-standard-token
│       ├── build_identifier.lock
│       ├── contracts
│       │   ├── AbstractToken.sol
│       │   └── StandardToken.sol
│       ├── install_identifier.lock
│       ├── installed_packages
│       └── lock.json
├── populus.json
└── tests

9 directories, 12 files

And if you look in your ethpm.json file you should see two dependencies.

{
  "authors": [
    "Piper Merriam <pipermerriam@gmail.com>"
  ],
  "dependencies": {
    "example-package-owned": "1.0.0",
    "example-package-standard-token": "1.0.0"
  },
  "description": "Mintable ERC20 token contract",
  "keywords": [
    "ERC20",
    "tokens"
  ],
  "license": "MIT",
  "links": {},
  "manifest_version": "1",
  "package_name": "mintable-token",
  "version": "1.0.0"
}

Now, we can try to compile our project again and everything should work.

$ populus compile
============ Compiling ==============
> Loading source files from: ./contracts

> Found 1 contract source files
- contracts/MintableToken.sol

> Compiled 4 contracts
- MintableToken
- StandardToken
- Token
- owned

> Wrote compiled assets to: ./build/contracts.json/contracts.json

Lets go ahead and write a quick test for our new minting functionality. Add the following test code to a new file ./tests/test_token_minting.py

import pytest

def test_minting_tokens(chain, accounts):
    provider = chain.provider
    mintable_token, deploy_txn_hash = provider.get_or_deploy_contract(
        'MintableToken',
        deploy_kwargs={"_totalSupply": 0},
    )

    assert mintable_token.call().balanceOf(accounts[0]) == 0
    assert mintable_token.call().balanceOf(accounts[1]) == 0
    assert mintable_token.call().totalSupply() == 0

    chain.wait.for_receipt(mintable_token.transact().mint(
        who=accounts[0],
        value=12345,
    ))
    chain.wait.for_receipt(mintable_token.transact().mint(
        who=accounts[1],
        value=54321,
    ))

    assert mintable_token.call().balanceOf(accounts[0]) == 12345
    assert mintable_token.call().balanceOf(accounts[1]) == 54321
    assert mintable_token.call().totalSupply() == 66666

def test_only_owner_can_mint(chain, accounts):
    provider = chain.provider
    mintable_token, deploy_txn_hash = provider.get_or_deploy_contract(
        'MintableToken',
        deploy_kwargs={"_totalSupply": 0},
    )

    with pytest.raises(Exception):
        mintable_token.transact({'from': accounts[1]}).mint(
            who=accounts[0],
            value=12345,
        )

And you can the tests with the py.test command.

$ py.test tests/
========================= test session starts ========================
platform darwin -- Python 3.5.2, pytest-3.0.4, py-1.4.31, pluggy-0.4.0
rootdir: /Users/piper/sites/scratch/populus-tutorial-3, inifile:
plugins: populus-1.5.0
collected 2 items

tests/test_token_minting.py ..

======================= 2 passed in 0.74 seconds =====================

Fin.