Looking at ownership in the EVM

Kelvin Fichter
7 min readOct 17, 2018

--

I’ve been thinking a lot about how smart contracts handle state and how they represent ownership. Ethereum pushed forward a popular model for representing ownership (mappings), but I’ve found that this model can actually be fairly limiting. With things like ewasm, sharding, and rent on Ethereum’s horizon, I thought that a revisit of this state model was appropriate.

Status Quo

First, let’s talk about how Ethereum deals with state, and particularly the representation of ownership. Every smart contract on Ethereum can manage a store of persistent state using storage variables. Storage in Ethereum is basically just represented as a key-value database, and variables can be modified whenever the contract is called.

I’ll explain some of my points by going through the main CryptoKitties smart contract, KittyCore. This contract stores information about which user owns which kitty in a mapping. This is pretty much the standard way to represent ownership. Let’s take a look:

/// @dev A mapping from cat IDs to the address that owns them. All cats have
/// some valid owner address, even gen0 cats are created with a non-zero owner.
mapping (uint256 => address) public kittyIndexToOwner;

This mapping is a critical part of the CryptoKitties contract! Whenever you transfer a kitty from one user to another (e.g. by using the transfer function), the contract updates this mapping:

/// @dev Assigns ownership of a specific Kitty to an address.
function _transfer(address _from, address _to, uint256 _tokenId) internal {
… // stuff
// transfer ownership
kittyIndexToOwner[_tokenId] = _to;
… // more stuff
}

This is how everyone knows who owns which kitty. But if you think about this for a few minutes, it’s a little weird. It’s kinda like you’re transferring a representation of a CryptoKitty instead of the kitty itself. There is no kitty “object” that you can move around and send from one account to the next, just the some data that sits in the KittyCore contract. Whenever you transfer a kitty, you’re really just updating the CryptoKitties contract to change the owner of that kitty.

There is no kitty.

This weirdness becomes even more clear when we look at the pattern for sending a kitty to a smart contract.

Here’s what I might expect: some user has a kitty, they send that kitty to the smart contract along with some extra data, the smart contract reacts by doing something.

Here’s what actually needs to happen: some user has a kitty, they first give the receiving smart contract approval to transfer the kitty on the user’s behalf, then the user calls a smart contract function with some data and the smart contract transfers the kitty to itself before doing anything else. wat?

Why?

First note that we have the exact same annoying approve/transfer pattern when it comes to transferring ERC20s. If you’re somewhat familiar with Ethereum, you probably understand why this is necessary — there’s basically no other good way to do it. You just can’t do the first (simpler) flow. But interestingly we don’t have this problem when moving around ETH. Smart contract developers will know that you can have users send ETH along with a transaction, and that the amount transferred can be accessed with msg.value. This is exactly the type of behavior that makes sense. What gives?

It’s really about how Ethereum handles state, especially state that represents value. ETH is the only “first class citizen” in this regard — that is, ETH is the only thing that Ethereum really recognizes as an asset that can be owned. Everything else is relegated to the land of “virtual” ownership. This might seem like a minor annoyance when you’re designing smart contracts, but it can have serious impact, especially with some new developments on the horizon.

Rent

Take, for example, the current conversations around rent in Ethereum. It’s been widely known that smart contract storage isn’t really being accurately priced. The EVM is charging a lot for storage up-front, but charging nothing afterwards. As a result, it’s just way too cheap to put data on Ethereum for long periods of time. Even worse, the incentives to remove data (like the gas refund) aren’t strong enough, and projects like GasToken are making this problem painfully obvious.

People are talking about new schemes to fix these issues by charging rent for storage. Generally, this makes storage cheaper up-front, but requires that someone make regular payments to keep that storage alive. If you ever fail to pay your contract’s rent, your contract’s data gets “evicted.” This isn’t deleting the entire contract data forever. Instead, the clients will keep a very small Merkle root that of all the data in your contract, which you can use to recover your contract by providing the original data (and paying your rent).

Now let’s see what this means for something like CryptoKitties. The CryptoKitties contract stores a lot of data — the search page on cryptokitties.co claims that it lists over a million kitties. Each kitty is represented by two 32 byte words, plus another word for the kittyIndexToOwner mapping:

/// @dev The main Kitty struct. Every cat in CryptoKitties is represented by a copy
/// of this structure, so great care was taken to ensure that it fits neatly into
/// exactly two 256-bit words. Note that the order of the members in this structure
/// is important because of the byte-packing rules used by Ethereum.
/// Ref: http://solidity.readthedocs.io/en/develop/miscellaneous.html
struct Kitty {
uint256 genes;
uint64 birthTime;
uint64 cooldownEndBlock;
uint32 matronId;
uint32 sireId;
uint32 siringWithId;
uint16 cooldownIndex;
uint16 generation;
}

That’s 96 bytes per kitty, about 100 megabytes for the entire contract. The storage rent on that much data is likely pretty expensive. Using one estimate from a few months ago, it’s probably about 100 ETH (currently ~$20k) annually.

Imagine that the maintainers of CryptoKitties stopped paying rent on the contract for some reason, and the contract data goes poof. How would you go about getting your kitty back? Well, you’d have to pay the rent to recover the contract… If you’re the only person who cares about their kitty, that’s 100 ETH a year. Even if you crowd funded the rent, you’re still paying a lot of money to support other people’s kitties. This makes zero sense. You should only have to pay for your kitty — not everyone else’s!

Sharding

This same problem pops up when we’re talking about sharding. One of the more interesting things in the world of sharded blockchains is the train-and-hotel problem. It’s actually pretty simple — let’s say the contract to buy train tickets is on one shard, but the contract to buy hotel rooms is on another. You’d like to book both a train ticket and a hotel room simultaneously — a hotel room but no train ticket is pretty useless (and vice versa). What do we do?

Well, one proposed solution is called “yanking” — you can temporarily move a train seat into the shard that contains the hotel contract, book both simultaneously, then move the seat back to its original shard. However, if train tickets were represented the same way CryptoKitties are, then you’d need to move the entire train contract onto the other shard temporarily. That also makes no sense (and annoys everyone who needs the train contract to stay still so they can somehow get to work on time, looking at you MTA).

Plasma

There’s even some applicability to plasma! If you read my previous story about why EVM-on-Plasma is so hard, then you might have an idea why: it’s not always clear who gets to move a contract from the plasma chain to the root chain. As a result, plasma smart contracts probably need to break things down to a point where ownership of each “state object” (e.g. kitty) becomes clear. I have no clue who should have control over the whole CryptoKitties contract, but I do know who should have control over my individual kitty.

This sort of state model is definitely useful when trying to design general computation inside a plasma chain. Some people are already working on a model based on this sort of theory. It might actually not even be possible to get close to general state transitions any other way.

What do we do?

This, again, is a symptom of the way state (specifically ownership) is represented. CryptoKitties could modify their contract so that each kitty is represented as its own contract, but that only helps so much. You still wouldn’t get to send kitties (or any other asset, besides ETH) along with function calls. It’s also much harder to manage this sort of system (upgradeability becomes much more complicated).

I’d like to see more systems where we get the best of both worlds. Ideally, each kitty should be represented as an individual object that can be modified in certain ways. It seems fine to have the controlling logic for each kitty handled by a single piece of upgradeable code, but each kitty should be a thing that’s separate from all other kitties. A lot of the theory here comes from the bitcoin UTXO model, but I think it could be more elegantly adapted for stateful applications.

It also makes sense to have all (standardized) representations of value, not just ETH, treated as a first class citizens. I should be able to transfer a kitty to a contract along with a function call instead of going through the whole approve/transfer flow. msg.kitty plz.

Unfortunately I’m not really an expert in the inner workings of the EVM. I’m sure there are plenty of annoying reasons why this is non-trivial for any blockchain, let alone a retrofit onto the EVM. If you, or other people you know, are thinking about this sort of stuff, I’d love to chat! Links to additional conversations or resources on this topic are always much appreciated.

--

--