Ethereum dapp development 101 — How I built BiggestG.com


This tutorial is about how I built https://biggestg.com

Biggest G is a dapp (distributed application) where user’s can compete to become the Biggest G™. It works by having an Ethereum smart contract store a single person’s name, which my website displays (but does not control). If someone wants to become the Biggest G, they can do so by calling the set function on my smart contract. Every time someone changes the name, the price to change it again doubles. That’s it!

In other words, it’s a vanity game for rich Ethereum users to show off how much money they’re willing to give me to become internet famous.

In this tutorial (and the recommended reading) you will learn everything you need to know to get started writing, testing, and deploying your own Ethereum smart contracts. We will be writing the contracts in Solidity, and using the Truffle Framework for development.

In the next tutorial, I’ll teach you how to build a website that interacts with an Ethereum smart contract by running your own production full-node, as well as how you can use MetaMask to accept Ether from people directly in the browser!


Prerequisites

This tutorial has a few pre-requisites. First, you need to have the following software installed:

  1. Parity client — This is the software that will allow you to interact with Ethereum blockchains
  2. Truffle suite — This is a development framework for Ethereum. You don’t strictly need it, but Ethereum development is such a painful process to begin with that you’re gonna want to take advantage of every tool that is available to you.

Second, you need to read the Ethereum Whitepaper. I would also recommend reading all of the Solidity documentation.

This tutorial’s source code is available in entirety here.


Contract Code

The contract itself is written in Solidity which is a programming language that targets the Ethereum VM. If you want to follow along, you can either clone my repo or run truffle init in a new directory. If you do the latter, you’ll need to add two files to the contracts folder that truffle creates:

  1. Ownable.sol
  2. SingleMessage.sol

We’ll discuss the source code for both of those files below:

SingleMessage.sol

Lets step through that piece by piece. The first line

pragma solidity ^0.4.18;

specifies that this contract should only ever be used with a version of the Solidity compiler higher than 0.4.18

The next line:

contract SingleMessage is Ownable {

is the beginning of a contract declaration, which is basically the same as declaring a class in another language. Similar to Object-Oriented programming languages like Java, the base unit of execution in Solidity is a contract. You can’t write a “script” without a contract, and the only unit of code that you can deploy to the network is a contract.

Note that the “is Ownable” portion of the declaration specifies that the SingleMessage contract inherits from the Ownable contract (and yes, multiple-inheritance is supported)

Lets take a look at the Ownable contract to see what functionality it adds to our contract.

IsOwnable.sol

Again, we see that it starts off with a contract declaration (this time with no other contracts it inherits from). Immediately after that we see the line

address public owner;

This creates a “state” variable for the contract. State variables are stored inside of Ethereum’s internal key-value store so they will remain permanently stored in the blockchain. State variables are like the database for your long-lived contracts. In this case, we’re adding a permanent field to the contract specifying the address of the wallet (or contract) that currently owns this contract. This section of the Solidity documentation has a good explanation of the different types of storage in Solidity.

The function

function Ownable() Public {
owner = msg.sender;
}

is the constructor function for the contract (notice how it has the same name as the contract itself) and is marked public because otherwise the solidity compiler will warn you that its visibility is not explicitly set, but this function is called once, and only once, when the contract is created and can never be called again. Thus, by setting the owner field to msg.sender on creation of the contract, any contract that inherits from this contract will automatically have its owner set to the person who initiated the transaction that created the contract (AKA msg.sender ). Also note that we are able to reference the state variable owner directly, there is no need to do something like self.owner or this.owner as you would in Python and JavaScript respectively.

Interestingly, msg is not a parameter of the function, yet we’re still able to reference it. That’s because it’s one of the three Solidity “global” variables that are always available during contract execution: msg, tx , and block . For now, all you need to know is that msg contains information about the transaction that initiated contract execution.

The only other function in the contract is

1 function transferOwnership(address newOwner) public onlyOwner {
2 require(newOwner != address(0));
3 OwnershipTransferred(owner, newOwner);
4 owner = newOwner;
5 }

Unlike our previous function, this one accepts a single parameter newOwner of type address which is one of Solidity’s primitive types and is a 256-bit number that represents the address of a contract or wallet. The function is marked public, because the owner of the contract will need to be able to call this function if he/she ever wants to change the contracts owner. There is also a function modifier onlyOwner , but we’ll come back to that in a minute.

Line 2 require(newOwner != address(0)); is a sanity check that prevents ownership being transferred to a nil value. The require function is built-in, and is an acceptable way of ending execution if something is not as expected. Note that require statements with no additional information are the primary form of error handling in solidity, so prepare yourself for a frustrating debugging experience when things go wrong :)

Line 3 owner = newOwner; is simple, it simply overrides the permanent state value we talked about earlier with the address of the new owner.

But what about line 3, OwnershipTransferred(owner, newOwner); ? If you look back at the original contract code you’ll note that the OwnershipTransferred event is declared right after the owner state variable

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

Defining an event is kind of like defining a function, except you don’t get to write the body of it. You simply specify the signature, and then when you call the event, the value of all the arguments you passed to it are emitted as an “event”.

Events are stored in the blockchain, but they are NOT accessible to contracts. They are, however, accessible to Ethereum clients and can be used as a cheap form of storage, as well as a way to update frontends / UI.s. Read this post for more information if you’re curious.

Note that when we say an operation is “cheap” in Solidity terms that means the operation does not consume much “gas” which is the “currency” of execution in the Ethereum VM. Every time someone sends a message / transaction to your contract, they will specify a “gas limit” which is the maximum amount of gas that your contract can consume trying to process their request. If your contract uses more gas than they are willing to pay, the operation will fail. An important consequence of this is that while we pay a one-time fee to deploy your contract, its ultimately our user’s who pay for our code to run.

The final piece of the isOwnable contract that we haven’t discussed yet is the onlyOwner portion of the function declaration. This is called a function modifier, and its similar to a decorator in other languages in that it allows us to modify the behavior of a function with another function. In this case, its behavior is obvious from the name, it makes sure that the function can only be called by the owner. That makes sense because this is a public function so we wouldn’t want anyone to be able to take over your contract just by calling it.

We can understand how the modifier works by looking at its declaration

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

In this case, the onlyOwner modifier code will run BEFORE the function its modifying is run, and it will verify that the transaction was sent by the owner of the contract. The code for the function that is being modified will run where the _; is. That means if we wanted to check if the send was the owner AFTER the modified function was run, we could rewrite the modifier like this

modifier onlyOwner() {
_;
require(msg.sender == owner);
}

That’s the entire IsOwnable contract! Note that I didn’t write that particular contract, its a commonly used helper contract from the zeppelin-solidity library which is a great source of pre-written, audited Solidity code (with a particular emphasis on ICOs).

Lets get back to our main contract

Now that we know a little about Solidity, the behavior of the SingleMessage contract should be self-evident, but I will explain a few new things.

First, the set and withdraw methods are marked as external, not public. This marks them as public in the sense that they can be called from outside the contract, but unlike public functions, they can’t be called from the contract itself. This has some minor performance implications for the Ethereum VM, but as a rule of thumb use external when the function won’t be called by the function itself, and public when the function will be called by other people as well as the contract. There is also a private keyword which can be used to create helper functions which only the contract itself can call.

The set function is also marked as payable. This simply marks the function as being able to receive funds. Otherwise, if someone tries to send funds to the contract it will fail.

We now know all the required syntax to understand the withdraw function, but lets talk about it because it’s important and easy to screw up from a security perspective. First off, the function is annotated with the onlyOwner modifier which means that only the owner of the contract can call it. This protects us from 99% of shenanigans, and the other two requirestatements implement some basic sanity checking before we transfer funds.

But what if we wanted to implement a more complicated withdraw function? Lets imagine a contract that allows users to deposit and withdraw funds at their own volition:

mapping (address => uint256) fundsByAddress;

A mapping is kind of like a hash map in that it maps a key to a value in constant time (but there are important distinctions which you can read about here)

The deposit function is simple:

function deposit() payable {
uint256 userFunds = fundsByAddress[msg.sender];
fundsByAddress[msg.sender] = userFunds + msg.value;
}

Again, we mark the function as payable so that it can receive funds. We create a temporary variable userFunds which is located on the stack and gives us a shorter name for referencing how many funds the user has.

Interestingly, notice that there is no logic for for initializing the user’s funds “the first time” they deposit funds. That’s because fundsByAddress[msg.sender] for a user that has never deposited funds before is 0 (instead of nullor something similar). That’s because all variables in Solidity have “zero-values” which they’re automatically initialized to (similar to Golang if you’ve ever programmed in that language).

At first glance, the withdraw function seems pretty reasonable too:

1 function withdraw(uint256 amountInWei) external {
2 require(amountInWei <= fundsByAddress[msg.sender]);
3 msg.sender.transfer(amountInWei);
4 fundsByAddress[msg.Sender] = fundsByAddress - amountInWei;
5 }

Make sure the user has enough funds, send them the funds, and then decrement how many funds they have. Can you see anything wrong with it?

This is fairly surprising, but the way it’s currently written a user could drain ALL the funds for all of the users out of the contract (assuming they didn’t hit the gas limit). The reason is that msg.sender doesn’t necessarily point to a user’s wallet. It could be a smart contract, and that contract could call the withdraw function again!

So the execution flow would look like this:

Note that by the end of that execution flow, withdraw has been run four times (and could continue to run many more times), but line 4 where we actually decrement the user’s funds hasn’t even run yet!

How can we fix this? Just swap the ordering.

1 function withdraw(uint256 amountInWei) external {
2 require(amountInWei <= fundsByAddress[msg.sender]);
3 fundsByAddress[msg.Sender] = fundsByAddress - amountInWei;
4 msg.sender.transfer(amountInWei);
5 }

Hopefully this demonstrates how dangerous Solidity contracts can be, so make sure you always test them thoroughly before deploying them!


Testing the Contact

Speaking of tests, how do we test a Solidity contract? Most people write tests in Javascript, but it’s also possible to write them in Solidity.

Before writing any tests, make sure that your code compiles without any errors or warning by running truffle compile. Let’s take a look at some test code (this test code would go in test/TestSingleMessage.js and could be run with run with truffle test for those of you who are following along):

At a high level:

  1. We have to import our contract using the special artifacts.require() function. This is just some truffle Magic™.
  2. We call the contract function with the name of the contract we’re using, and a function which runs the tests. That function receives a single argument which is a list of accounts that we have available to us on whichever client we’re running the tests on.
  3. We can use the beforeEach hook to instantiate a new version of our contract before each test so we’re always working with a clean slate.
  4. We use the it function to declare new individual tests
  5. We frequently have to use await when calling methods on the contract object; they’re asynchronous because they need to call out to our Ethereum client using the JSON-RPC interface

Deploying the Contract

Now that we’ve written and tested our contract, we need to deploy it.

Truffle handles the problem of deploying smart contracts via a concept called “migrations” which are kind of like database migrations, but different enough to be confusing. When you initialize a truffle project using truffle init , truffle will automatically create your first default migration:

This migration is confusing because its not immediately clear what it’s doing (it’s actually deploying its own “Migrations” contract!). If you want the details, you can read about them here, but for now don’t worry about it, just know that it will be the first step every time you deploy your contract (its also sometimes worth commenting out line 4 when deploying to the production Ethereum network due truffle timing out in-between migration steps).

To deploy OUR contract we will need to create a new migration. You can do this by running truffle create migration which will create a new .js file in your migrations directory.

The migration script for my SingleMessage contract looks like this:

We import our contract using artifacts.require , instantiate some constants, log what they are, and then use the deployer object that is automatically injected by truffle to deploy our contract.

The function signature for the deploy contract is that the first argument is the Contract that you’re trying to deploy. THE subsequent arguments are the values you want to pass to the contracts constructor function. The number of arguments you pass must match the number of arguments that the constructor accepts or you’ll spend hours debugging why your contract exhibits undefined behavior when you try and interact with it

Inside of the asynchronous callback of the deploy function we await for the contract to be deployed, then we call the getter functions for the state variables that we defined in the contracts. If you remember the contract from earlier, we never actually wrote any getter functions. Solidity just auto-generates getter functions for any public state variables.

So in summary this script: prints out the arguments we’re passing to the contract constructor, deploys the contract (and passes the arguments to the constructor), waits for the contract to be deployed, and then prints out the live state of the contract on the chain so that we can verify it was initialized properly.

Now we’re ready to run the migration (I.E deploy the contract), but first we have to pick a network to deploy it too. If you’re using the Parity / Truffle toolchain you have a few options:

  1. Truffle development network
  2. Kovan network
  3. Production Ethereum network

Lets start with the first one. The Truffle development chain is basically a slightly-modified Ethereum blockchain built into the truffle tool thats designed to make testing your contracts really easy.

To get started, run truffle develop

This will start a command-line prompt with the Truffle development chain running in the background. Once you’re in this command prompt, you can run any valid truffle command (without the Truffle prefix) AND you can use the Web3 Javascript API to interact with your contract.

Start by typing migrate , you should see output like:

truffle(develop)> migrate
Using network 'develop'.
Running migration: 1_initial_migration.js
Saving successful migration to network...
...0xaba9cf5e2d46db92655bf1170146029cc34055babd30fc95000663d8b5463d6a
Saving artifacts...
Running migration: 1512863228_initial.js
Contract arguments: {
initialMessage: 'Hello world!',
initialPriceInWei: 1,
maxLength: 200 }
Replacing SingleMessage...
...0x51eae26485e83ef5eee593425d7d163f286544841c80007e8f929fff1680630d
SingleMessage: 0xf12b5dd4ead5f743c6baa640b0216200e89b60da
public vars from contract: { message: 'Hello world!',
priceInWei: { [String: '1'] s: 1, e: 0, c: [ 1 ] },
maxLength: { [String: '200'] s: 1, e: 2, c: [ 200 ] } }
Saving successful migration to network...
...0x0a0e316370fb4e3442a34b7dcd221d102ee4eb07df50da6a72b9027c41b2663a
Saving artifacts...

This means that the contract was successfully deployed to the Truffle development chain!

Try interacting with it by running the following commands:

var contract = SingleMessage.at('0xf12b5dd4ead5f743c6baa640b0216200e89b60da');

Note: Replace the address above with the address that was provided to you in the terminal.

contract.message()

contract.message.call()

contract.priceInWei()

contract.set('New message!', {value: 0}) Why did this fail? (You didn’t pay enough Ether)

contract.message()

contract.set('New message!', {value: 1}) Why did this succeed? (You paid enough Ether)

contract.message()

Now that we’ve verified on our local Truffle development environment, we’re ready to test our contract in a more formal environment, so lets try the Kovan network next.

You can think of the Kovan network as behaving exactly like the production Ethereum blockchain, with a few minor modifications to make testing easier.

First, you’ll need a wallet for the Kovan network. You can get one by running parity account new --chain kovan which will prompt you for a password and then print out your public key, as well as store your private key (encrypted by your password!) on disk.

Next, you’ll need to get some Kovan Ether into the wallet you just created. The only way to do that is via Kovan faucets.

Once you’ve acquired some Kovan Ether, you’re ready to deploy the contract to the network!

The first step is to boot up the parity client so that its connected to the Kovan network. You can try and do this on your computer if you have a lot of disk space, but I recommend renting a dedicated host on a cloud provider to make your life (I’m using a DigitalOcean droplet).

To start parity on the Kovan chain, run parity --network kovan —-unlock <YOUR_KOVAN_PUBLIC_KEY> --password <PATH_TO_FILE_THAT_HAS_YOUR_PASSWORD>

Then add the following configuration to your truffle.js file

If you’re curious about the configuration in the truffle.js file, the host / port portions are pretty self-explanatory, and the network_id: * simply tells Truffle to accept any network ID it finds at that local. The gas field specifies the gas_limit for the migration and is basically an upper bound on how much Ether ( gas_limit * gas_price — you read the white paper right?) you’re willing to spend for the transaction that deploys your contract. If you’re feeling fast and loose with your money, 4700000 is a good default because its the maximum that the network allows currently and any excess gas will be refunded to you at the end of the transaction.

Once you’re ready to rock and roll, run truffle migrate --network kovan and you should see similar output as before, but this time your contract will be deployed on the Kovan chain instead.

Once its deployed, you should be able to use the address in the terminal to look up your contract on https://kovan.etherscan.io (Etherscan is a web UI for inspecting the Ethereum block chain in real time. The Kovan prefix makes Etherscan inspect the Kovan chain instead of the default production chain)

For example, here is my SingleMessage contract deployed to the Kovan chain: https://kovan.etherscan.io/address/0x6c273582e2f2f34dc0442eb00883397f18cb4a2e


Verifying the Contract

You’ll notice that unlike your contract, mine is “verified” and you can inspect the source code (as well as the state of all public variables) right there in the browser. That’s because I’ve “verified” my contract by providing the source code to Etherscan.io

If you want people to trust your contract, you should verify it too so people can review the source code and be confident it will behave the way you say it will.

The way contract verification works is you give Etherscan a copy of your source code, the version of the Solidity compiler that you used, and all of your constructor arguments, and it compiles that into the Ethereum VM bytecode that would be required to deploy that contract. If the bytecode it generates matches what you deployed to the blockchain, then your contract becomes verified.

Luckily, verifying your source code isn’t too hard. If all of your solidity source code is not in one file, then you will first need to “flatten” your code (generate a file that has all your code and its imports merged into a single file). You can copy and paste your code (and all of its dependencies) together manually, but I recommend using: https://github.com/alcuadrado/truffle-flattener instead.

Once you’ve generated a flattened version of your source code, you’ll also need to generate an ABI-encoded version of your constructor arguments. There are plenty of different ways to do that, including using the Web3 JS libraries, but I’m lazy and prefer to use this website: https://abi.sonnguyen.ws/

Fill in the type (and value that you passed in your migration script) for each of the constructor arguments that your contract constructor accepts and then click “Generate ABI”

Then just go back to your contract on https://kovan.etherscan.io and paste in your source-code/the ABI-encoded arguments in the appropriate fields. You’ll also need to specify the correct name for your contract (“SingleMessage”) in my case and the version of the Solidity compiler that you used (run truffle version to find out). If it didn’t work, change the “Enable Optimization” flag and then try again. If it still doesn’t work…good luck, you have a lot of difficult debugging to do ;)

At this point you should have:

  1. A Solidity contract
  2. A test-suite in JavaScript
  3. Tested deploying the script to the Truffle development chain and interacting with it via the command-line
  4. Tested deploying the script to the Kovan chain

Stay tuned for Part #2 where I’ll teach you how to run your own full production Ethereum node and use the Web3JS API’s and MetaMask to create a website that people can use to interact with your contract in the browser!