A couple weeks ago, I decided I’d take a crack at building a very simple Ethereum contract, one that allows user to play a lottery-like game. I did this because I’m honestly not impressed with the developer landscape, I felt I could prove a point about how easy it is to write good, secure, effective code in Solidity — and build something some people might actually be interested in using.
I won’t say I was completely correct. My initial estimations put this at about a week’s worth of side-project work (e.g. I have a day job), but like most software projects, it took longer than I expected. Overall, it took 13 days to get to what I’d be confident calling “stable”, across which was spread probably 20–30 hours worth of actual coding.
You can find the application in its current and future forms here: https://github.com/DeviateFish/Lotto
This will be the first in a many part series of reflections on the process of writing an Ethereum application, starting with the contracts themselves.
The contract itself is roughly divided into 4 parts: A round, a factory that produces rounds, the game logic, and finally, the root contract.
The round holds the game logic for a single round: what picks are valid, how picks are chosen, how much they cost, when the game ends, how winners are paid, etc. The meat of the contract is the PRNG used to generate numbers, (I’ll dive into that one in a little more detail in a bit), but there are some other bits worth exploring.
The first is the mechanism to ensure the owner plays fair. To create a round, the owner (or curator) needs to generate a hidden piece of entropy, or salt. They then pick a number N between 1 and 255 and hash the salt with keccak256 N times. This is the saltHash. Then, to they need to generate the hash of salt..N..salt with keccak256 as well. This becomes the saltNHash.
The purpose of all this is to provide proof that the curator has selected some secret value to be their input into the PRNG when the winners are chosen. By keeping it a secret, no one but the curator can predict the winning numbers before they are drawn.
The second part of this takes us back to the PRNG, and how it generates numbers as tickets are picked. In an earlier iteration, the accumulated entropy in the contract could be predicted fairly easily, something I fixed in the final iteration. The problem was that the PRNG was advancing the entropy using values that were easy to predict (msg.sender, block.number, etc). The entropy would always be advanced, regardless of whether or not it was being used to pick random values. This would let the curator predict what the accumulated entropy would become during the last block (for example), and then pick the winning numbers just before close.
To solve this, I changed the sources of entropy to things far harder to control: block.difficulty, block.timestamp, block.coinbase, and one additional variable source of entropy: for picked tickets, the picks themselves; for random tickets, the sender’s address. By using the picks as a source of entropy, they can no longer be gamed: you might still be able to predict the outcome, but you can no longer do it before you have committed to some numbers.
These two parts come together when the round is closed and it’s time to pick winning numbers. The curator provides the salt and N, and the contract verifies these are indeed the pre-images for saltHash and saltNHash — and then uses the salt and the accumulated entropy to produce the winning numbers.
The factory is probably the simplest piece of code in the project. It has one job: create new LotteryRound instances, and hand control of them over to the sender. It also announces the creation and address of the new round by broadcasting a LotteryRoundCreated event, to enable watching the contract for new rounds.
The game logic is one of the trickier pieces. It needs to enable someone to create and close individual rounds, as well as manage the payment of winners. It also needs to guarantee that the owner cannot simply make off with the funds, but still let funds be transferred from one round to the next in the event that no one wins. On the other hand, to enable this game to be upgraded in the future, the contract needs to be able to be swapped out.
To accomplish this, I simply made the rule that the game logic cannot be upgraded if a) there is currently a round in progress, or b) the contract is holding a balance greater than the price of a ticket.
These two rules together ensure that neither the logic contract nor the factory contract can be swapped out while a round is in progress, or while the contract is carrying a balance forward into the next round. This also comes with a commitment: the rules of the game cannot change until a winner has been produced.
The game logic also controls accounting things, like attempting to pay owners, paying the curator his fee, carrying balances from unwon rounds to the next, and so forth.
The final contract is the Lotto contract. This one mostly exists to provide the ability to upgrade the game logic while maintaining a fixed identity on the blockchain. When a game is won and the winners paid, the opportunity to replace the game logic with new rules is presented.
This contract also maintains a public history of all previous rounds, though it will no longer have control over them.
All-in-all, I’m pretty satisfied with the quality of the contracts I wrote. I wrote comprehensive unit tests for all methods in each contract — a topic I’ll cover in my next post — and even wrote a couple end-to-end integration tests. You’ll notice a set of “Debug…” contracts in the repository, as well. This are related to the testing story.
I think the only thing I’m not satisfied with is the speed of development. Mostly, I find that this is due to the framework I chose, or perhaps is a symptom of the immature toolchains currently available. You’ll also note that I have required a custom fork of embark as a dependency, as the base version did not give me enough control over the complex deployment required to make this thing testable. I also needed to make several changes to embark itself, which I contributed back to the repository (custom configurations for test, better logging, etc).
Another particular sticking point was the amount of boilerplate needed to do simple things. Interacting with Ethereum is inherently asynchronous, and yet the web3 library doesn’t even use Promises. Another aside: I fixed that too, but that’s a topic for yet another post.
The final area I ran into issues with was the actual compilation and deployment of the contracts onto the main network. I’m not a huge fan of testnet, so I tend to do all my debugging in testrpc or live. However, my deployment pipeline basically consisted of Remix -> deployment.js -> Ethereum Wallet. Not ideal
Overall, I’d give the whole experience a 6/10. I’ll probably work through throwing a simple frontend on this app to call it “complete”, and then try out another framework and see how it compares.