Ethereum Application Development: Smart Contracts with Truffle Framework (1/3)

I recently deployed a simple Ethereum Decentralized Application (DApp) called Wishereum. The application is a statically hosted single page vuejs app with a smart contract running on the Rinkeby testnet.

The following is my development story starting with the Wish.sol smart contract.

The full project repo can be found here: https://github.com/mattlockyer/wishereum-project

Remix to Truffle to Tests

The first step I took when developing my smart contract was to gather all the requirements for my app:

  1. A user can make a wish and throw away some ETH

The next step was to hit up the remix editor from the ethereum foundation. Having already learned some Solidity development from reading the docs and tutorials online, I quickly came up with a minimum viable contract for my application.

pragma solidity ^0.4.11;contract Wish {

//Wish Object
struct WishStruct {
string wish;
uint256 amount;
address wisher;
}

//public
WishStruct[] public wishes;
uint256 public totalWishes;

//private
uint256 private balance;
address private owner;
mapping(address => uint256[]) private indicies;

//modifiers
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
//constructor
function Wish() {
owner = msg.sender;
}

//payable
function() payable {
balance += msg.value;
}

/**************************************
* public methods
**************************************/
function makeWish(string wish) payable returns (bool) {
if (msg.value < 1 finney) revert();
balance += msg.value;
indicies[msg.sender].push(wishes.length);
wishes.push(WishStruct(wish, msg.value, msg.sender));
totalWishes = wishes.length;
return true;
}

function getIndicies() constant returns (uint256[]) {
return indicies[msg.sender];
}

/**************************************
* private (onlyOwner) methods
**************************************/
function getBalance() onlyOwner constant returns (uint256) {
return balance;
}

function sendBalance(address dest) onlyOwner {
dest.transfer(balance);
balance = 0;
}

}

Fields Explained

Starting from the top the first thing I define is WishStruct. Since everything in the blockchain costs money, you need to define minimal structs with your main constraint being efficient retrieval of objects, so no nested queries. Even though calls to the blockchain are “free”, they are slow. If made through light client APIs like MetaMask or Parity extensions, you really don’t want to abuse them. Best practice is to store the response locally so the client isn’t refetching (something I’ll go into in another post).

Next I define the public array of all WishStruct objects wishes. Public variables are compiled with getters which means you can use .call from any web3 implementation to get the value. When calling arrays you must provide the index so it will look something like this on the frontend:

const wish = await wishContract.wishes.call(index);

Next I define the totalWishes uint to keep track of how many wishes there are total. So let’s say a client visited the site for the first time, they would grab the total number of wishes and then load each wish object by index.

There are a couple of fields for managing the balance of ETH in the smart contract and the owner, fairly standard.

The last field is a private mapping of address to uint[]. I tried to make this public and use the generated getter but it didn’t work so instead I opted for a constant function.

function getIndicies() constant returns (uint256[]) {
return indicies[msg.sender];
}

Handling Use Cases

Given this contract, how does it serve the use cases I originally drafted?

  1. The makeWish function is payable so you can send ETH along with some data.

Even Simple Can Be Difficult

Despite being a very simple contract, there are likely still vulnerabilities I am not aware of. For example a short address attack might be possible on some of the functions, I am not checking the data payload length to make sure it is genuine.

Warning: I am not a security expert and you should always have your contracts audited by a third party before deploying to the mainnet.

Enter Truffle

The next step in checking that my contract behaves as expected was to boot up truffle in my development directory and write some tests.

truffle init

Rename a few files, MetaCoin -> Wish.sol etc…

So I end up with:

/
contracts/Wish.sol
test/wish.js

Update the migrations file 2_deploy_contracts.js to the following:

const Wish = artifacts.require("./Wish.sol");module.exports = function(deployer) {
deployer.deploy(Wish);
};

Copy and paste the contract from Remix to Wish.sol and then run:

truffle compile

Check if everything went as planned. If so, dive into the tests.

Mocha + Chai Truffle Tests

I’ve already written a post on writing truffle tests so I won’t go into too much detail here but the basic javascript tests for Wish.sol are here:

const Wish = artifacts.require("./Wish.sol");contract('Wish', (accounts) => {

let wish;
const owner = accounts[0];
const random = accounts[1];
const finney = 1000000000000000;
it('should have a deployed address', async () => {
wish = await Wish.deployed();
assert(wish !== undefined, 'wish contract should be defined');
});
it('should have an initial balance of zero', async () => {
const balance = await wish.getBalance.call();
assert(balance.toNumber() === 0, 'balance should be zero');
});
it('non-owner cannot get balance', async () => {
try {
const balance = await wish.getBalance.call({ from: random });
} catch (e) {
assert(true, 'non-owner cannot get balance');
}
});

/**************************************
* Wishes
**************************************/
it('should have undefined tx if amount too low', async () => {
let tx;
try {
tx = await wish.makeWish('random wish', { value: finney - 1, from: random });
} catch (e) {
console.log('exception caught');
}
assert(tx === undefined, 'non-owner cannot get balance');
});

it('make a lot of wishes', async () => {
let tx;
for (let i = 0; i < 10; i++) {
tx = await wish.makeWish('random wish number ' + (i+1), { value: finney, from: random });
assert(tx !== undefined, 'wish transaction');
tx = await wish.makeWish('owner wish number ' + (i+1), { value: finney, from: owner });
assert(tx !== undefined, 'wish transaction');
}
assert(true, 'wishes made');
});


it('should return the random users wishes', async () => {
const indicies = await wish.getIndicies.call({ from: random });
for (let i in indicies) {
const w = await wish.wishes.call(indicies[i].toNumber());
}
assert(true, 'wishes returned');
});

it('should return last 10 wishes', async () => {
const totalWishes = await wish.totalWishes.call();
for (let i = totalWishes - 1; i >= totalWishes - 10; i--) {
const w = await wish.wishes.call(i);
console.log(w[0]);
}
assert(true, 'wishes returned');
});
});

You can run these tests using truffle test but you’ll have to also be running testrpc at the same time.

What happens each time truffle runs a test is that it will compile the contracts again, deploy them to the test blockchain of testrpc and in your test you will be able to reference the latest instance using Artifact.deployed() where the Artifact is your contract artifact. The artifact.require step is a convenience method to load the json in your /build and wrap it in a TruffleContract instance for testing.

Recap

Before doing any of this, sketch out your idea, use cases and some UI / UX user flows. Identify all the cases and then list out a minimal schema for your contract.

Note: there’s also the off-chain on-chain question / debate ongoing. My personal opinion leans toward storing minimal amounts of information on the blockchain and fetching resources from elsewhere using UUIDs and hashes.

Once you have your schema and a basic idea of the functions in your contract, I highly recommend starting in the remix editor, keeping your contract(s) as minimal as possible, then moving to Truffle and TestRPC locally and writing tests.

Go forth, code some law or invent some money.

Building Blockchain Solutions to Real World Problems - "The revolution will not be centralized."