Adventures with Dumb Contracts

Andrew Parker
6 min readJan 16, 2019

Well guys, I’ve only gone and done it — I’ve launched a DApp on the Ethereum mainnet, and there’s no better excuse than that to come here and share some codeslinger wisdom. So put your Solidity hat on and lets talk about dumb contracts.

Photo by Henry Hustava on Unsplash

Adventureum

Rather than diving straight into some juicy code or abstract design concepts, I’ll introduce you to the DApp that brought us here today. It’s a friendly, simple little game I’m calling Adventureum, a clever portmanteau of Adventure and Ethereum, because it’s crypto so for some reason that kind of horrible naming style is acceptable.

The game is like an old text-based adventure game, where players are given some text describing their situation, and then given a few choices for what they can do next. You click on a choice and it takes you to the next situation.

But the entire story isn’t finished, and when a player clicks on an option that doesn’t lead anywhere yet, they are given the chance to write the next situation, building on the story. The player will make a transaction, storing the data on-chain for future players, and this is where dumb contracts come in handy.

(As a side note, at the time of writing I only just published this game, so you’ll reach an empty pathway within a few clicks)

Gas use and storing data

Anyone with a little experience developing for Ethereum will be well aware of the most expensive (by far!) operation on the EVM —writing to storage. The more data you write, the more it costs to store, if I’m going to be asking my users to write big chunks of text for a text-based game, that could be a problem. We don’t want to punish our users for their enthusiasm or creativity.

Of course… in a text-based adventure game, our smart contract probably doesn’t need to interact with the content of the text. Sure, it needs to know which text belongs to which part of the story, but it’s very unlikely that we’ll be parsing strings of text on chain.

The text is for the players, but we deal in code.

What’s the way around it? We could do some magic with IPFS, storing a reference to an IPFS hash, and make it all come out on the front-end, but that can be a little convoluted, and puts a little more stress on our front-end needing to be good. Stuff can go wrong with IPFS, plus this solution generally isn’t as elegant as it needs to be. The nicer solution is….

Dumb contracts

Dumb contracts! I’ll be honest, there may be another name for this that I’m unaware of, but I heard this one once and I like it. A dumb contract is a smart contract pattern that takes advantage of Events, and their relative cheapness compared to writing to storage. It costs many times more gas to write to storage than it does to emit unindexed event parameters, and since event logs are fully and readily available, it seems like a perfect place to store game text.

Give me an example

Rather than abstractly talking about this, let’s use an example. Suppose you wanted to store Shakespeare’s Hamlet on the Ethereum blockchain, indexed by paragraph. I honestly have no idea why you’d want to do this, but let’s go ahead.

Using traditional smart contract storage methods, you’d probably have something like this:

contract Hamlet_store{
string[] public paragraphs;
function writeText(string memory paragraphText) public {
paragraphs.push(paragraphText);
}
}

When a paragraph is written you store it in an array, and then just read from that array when you want to retrieve the paragraph. If you wanted to do some more complex stuff in your smart contract with page numbers, you’d always end up referring back to that array index, and reading from the array.

If we use part of the conversation between Hamlet and Horatio, the paragraph that starts with “And therefore as a stranger give it welcome” and ends with “So grace and mercy at your most need help you, Swear.” and write it with the function above, it’s going to cost 551896 gas to make that transaction.

Compare that with our dumb contract pattern, which would look something like this:

contract Hamlet_event{
event Paragraph(uint indexed num, string paragraphText);
uint paragraphs;
function writeParagraph(string memory paragraphText) public{
emit Paragraph(paragraphs,paragraphText);
paragraphs++;
}
}

The only thing that’s stored in the smart contract is the index of the paragraph, the text itself bounces off and lands in the log. Submitting the same paragraph as before, we end up with a cost of only 99182 gas.

But crucially, the stored text is still just as easy to access from the front-end, regardless of which method you use. So if you opt for the regular method your users are paying an 80% surcharge for no reason.

The upcoming Constantinople update for Ethereum its supposed to have some changes to storage costs, so this could affect the numbers a little. But to quote Hamlet once again,

There are more things in heaven and earth, Horatio,
Than are dreamt of in your philosophy.

As developers in such a rapidly-evolving space, it’s important to build things with the technology that’s available, and not wait to see how things turn out. You can sit around speculating ’til the cows come home, wondering where smart contracts will be in the future, or you can start coding now and help build that future one project at a time.

Back to the game

Anyway to wrap up, as most of you may have inferred, I used this dumb contract pattern to keep the game situations in the Event logs. There’s a little bit of logic in the smart contract which keeps track of which choice leads to which situation, but that’s about it.

One other thing you need to know about when using dumb contracts is that there are only certain types of variables which can be emitted in events. One type that isn’t allowed is a dynamic array of strings, string[] like_this. We can have regular old strings, but chuck them in a dynamic array and there will be no events this evening.

Part of my game design is that a player creating part of the story can define any number of choices that lead on from that situation. So storing the main situation text isn’t an issue, but we need a way to get a dynamic array of strings in there. There are a few hacky ways of tricking an array like this through an event emission, but the one I like the most uses a dynamic array of bytes32s.

We can emit events with a bytes32[] as a parameter, and for most developers, converting between string and bytes probably won’t explode your brain. So all we have to do is convert our player’s choices to bytes before they’re sent to the chain, and then de-convert (de-vert?) them back into normal people text whenever the game needs them.

Since we’re talking front-end, Web3.js has two methods that do just that, web3.utils.fromAscii for turning strings into bytes, and web3.utils.toAscii for getting them back again.

It means that the data in the logs won’t be human readable, but the takeaway from this whole article should be that it really doesn’t matter how these things are stored, as long as the data is stored in a reliable way and can be retrieved when we need it.

Caveat regarding bytes32

A small caveat with the bytes32 method is that it limits the length, the choices can’t be more than 32 characters each. For my game design, this is fine, I don’t want players putting an essay in their choice anyway.

But if bytes32 isn’t going to work for you, remember that before data enters the smart contract, or after it’s read, you can do whatever you want with it. Returning to our Hamlet example, we could automatically convert everything to Spanish before storing it, and then convert back to English after reading from storage. Assuming the translation was actually correct, the user would have no way of knowing.

So you can deconstruct larger strings into smaller ones, then convert them to bytes32s, or you can mush em all together into one big mega-string then split em again after you read them… the exact solution isn’t the point here, just that there is usually a solution.

Game on

I hope you found this post useful. The dumb contract pattern is one I read about some time last year and I only implemented it for the first time in this project, and the genesis of this project was only a week ago. So it’s been bouncing around my head for a while, and hopefully one of you will get inspired and use it to save some users’ gas some day.

Those curious about the game’s contract code, or anything else mentioned in this article, feel free to contact me. If I’ve got anything wrong, I’m never to shy to admit my mistakes, so please point them out.

‘til next time.

--

--

Andrew Parker

Codeslinger. Melbourne based Solidity developer for hire.