For god’s sake, can’t we fix Solidity?

Smart contracts are here: we need to support transitioning developers into the new paradigm.

Matt Kindy
Topl
5 min readAug 1, 2017

--

I’m a coder, born and raised.

My dad encouraged me to learn C probably before I had figured out how to write English properly. Our household was always littered with hard drives, monitors, and — let’s be honest — games. It was this environment that eventually led me back to computation after a brief stint with physics in college, caused me spend a bit more time toying with Bitcoin than completing problem sets in 2012, and made me excited about Ethereum when it was first conceived. Ethereum and Bitcoin were what inspired me to work on security research and cryptography in the first place.

You can imagine my chagrin when I realised that the main programming language for smart contracts negates many of the security guarantees of blockchains at-large.

Let’s get down to brass tacks. Solidity seems to be inspired by Go and Javascript and ends up as the sandboxed child of both, with few of the best qualities and some new terrible ones. A marginally complex sample of Solidity code will end up looking something like this (borrowed, with minor edits, from Solidity documentation):

pragma solidity ^0.4.11;contract SimpleAuction {
address public beneficiary;
uint public auctionStart;
uint public biddingTime;
address public highestBidder;
uint public highestBid;
mapping(address => uint) pendingReturns;
bool ended;
// Events that will be fired on changes.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
function SimpleAuction(
uint _biddingTime,
address _beneficiary
) {
beneficiary = _beneficiary;
auctionStart = now;
biddingTime = _biddingTime;
}
function bid() payable {
require(now <= (auctionStart + biddingTime));
require(msg.value > highestBid);
if (highestBidder != 0) {
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
HighestBidIncreased(msg.sender, msg.value);
}
function withdraw() returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
function auctionEnd() {
require(now >= (auctionStart + biddingTime));
require(!ended);
ended = true;
AuctionEnded(highestBidder, highestBid);
beneficiary.transfer(highestBid);
}
}

One of the statements commonly made about Solidity is that it feels similar to Javascript. Look, if you’re one of the people saying this, I’m not trying to claim that the syntax is totally foreign, but saying that it’s like Javascript is such a bizarre statement to make. Yes, there are your normal function and if and your friendly braces, and no, there aren’t those funny := operators.

The one reliable way to anger a Javascript developer

But then we get into the fact that semicolons aren’t optional, return and declaration types are explicit, the literal 0 is a byte (not an int), and pretty soon you’ll realise that almost any other general-purpose language (syntax, style, and use) makes for a better comparison. And thank goodness for that, considering that the static typing helps tremendously with deciphering author intent.

So what are we actually borrowing from Javascript? Certainly not any of the functional core. I imagine we’ve just gained some comfort in knowing that by talking about Javascript, we have made Solidity somehow more accessible.

And perhaps herein lies the problem: by trying to make smart contract writing as superficially accessible as possible, we’ve unwittingly assigned the critical task of designing once-deployable, unfamiliarly-patterned financial software to full stack devs who are used to building things that have completely incomparable degrees of adverse effects from failure.

Consider the following — well documented —EVM security considerations (credit Solidity team, but also check out the “minor” issues):

  • Re-entrancy
  • Contract stalling due to excessive gas consumption
  • Callstack depth attacks

Right now, re-entrancy is a completely foreign concept to anyone who hasn’t spent significant time learning and working with the EVM.

And there still aren’t any tools — not even the Solidity compiler! — that provide warnings for it.

(edit: Christian Reitwießner corrected me — the Remix IDE offers these warnings)

Oyente, at least, is in beta, while solint seems to have fallen by the wayside, if it was ever really there. And, of course, the biggest issue is that many of the best-practice tools that do exist are not widely considered mandatory, which is absurd. The best we’ve got is a section in the Solidity documentation on the Checks-Effects-Interactions pattern.

So the problem of finding a competent developer is exacerbated, no doubt, by the fact that decent Solidity developers are often being asked to sign NDAs and the like. This makes it just a tad difficult for them to show the appropriate knowledge of these strange patterns, since they can’t reveal any code they’ve actually, you know, coded.

Of course, Emin Gün Sirer has also weighed in on this:

There are some takeaways for toolchain developers as well: the Solidity compiler, or lint-like tools, need to detect and warn about these kinds of anti-patterns.

At a higher level, I see no good reason why the EVM should enable the default function of a contract to engage in arbitrarily complex behaviors. In particular, the EVM can simply prohibit a contract B and all of its callees C, D, …, Z, from making calls back to contract A when B is invoked by A, unless explicitly permitted to do so by A. That is, a default ban on cross-contract reentrancy, unless optionally disabled. Contract A still gets to call its own internal functions all day long, but if it calls out to another function, there is no coming back.

Solidity also led some very experienced developers to the tar pits with the recent exploit of a bug in the Parity-endorsed wallet. The cause? Solidity sets functions to be default-public, and some constructor logic for the wallet was extracted into a separate library. Function calls were automatically forwarded for any public functions — and the constructor was public by Solidity default.

And these are just a few of the concerns.

At this point, I can already hear the sharpening of pitchforks — but, moving forward, what should we do to fix this?

For starters — and I’m going to sound a little familiar with this next refrain — we need compiler level warnings on re-entrancy. If I’m working in Scala with Intellij IDEA, by default it warns me about potential side effects in monadic transformations if I’ve added them, intentionally or otherwise.

How about explicit annotations for functions that depend on calls to other contracts?

Perhaps we should consider introducing functional-style libraries (a la Java 8) to further improve the language and inculcate the value of eliminating side effects?

Let’s not put the burden of learning strange new patterns solely on the shoulders of new developers. We should expect more from the most widely used smart-contract language in the world.

If you enjoyed this somewhat cathartic rant, please recommend and share it! To keep up with Topl and to learn more about our own terrible problems and tell us why we’re not qualified to comment on Ethereum, you can follow us on Twitter, our blog, check out out our website, or join our Slack. Thanks!

--

--

Matt Kindy
Topl
Writer for

Senior Software & ML Engineer @ Praetorian