Adventures with Dumb Contracts

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

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

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

Give me an example

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

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

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

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.

Codeslinger. Melbourne based Solidity developer for hire.