Any remotely popular functional programming language created over the last years shows a better design (and taste) than this one.
And if that only attracts a certain type of programmers? (pun intended) That is, programmers valuing languages like Haskell/OCaml/F#/Kotlin/Rust, who would only ever touch C with verified compilers and 5 different static analysis tools?
Well, that's exactly kind of people you to attract to write your security-sensitive code.
Solidity is ostensibly designed to let people write smart contracts for Ethereum.
More realistically, it is a marketing tool for enabling and onboarding people onto the Ethereum platform, which Ethereum benefits monetarily (enormously so) from. Security and design are secondary goals to the extent that they help prevent disasters which hurt adoption or churn developers away.
Through this lens it's not a mystery why the language is "hacky". Perhaps being a good language is not even the driving goal.
Curious question of a non-native speaker: What does this phrase mean? (in general, and in this context)
+1 : My feelings after going thru basics of ethereum.
If they were to be as safe as possible, there would be less news, and they would push the traversal of those dialectics further into the history, when more people are using Ethereum and these kinds of events can do more damage.
It's beta. Don't worry about it. Come back in 10 years when the Heroku of Ethereum is released. Buy a couple Ethers now so you'll be rich then.
The problem is that it's just not easy to get all those details right from day 1, and interest in Ethereum has grown way faster than its capacity to produce a reliable, mature set of tools around it.
This is different from the kinds of objects that you would instantiate in a language like Java, where best practices suggest defining as immutable unless you have a good reason not to.
For starters, not all functions on the contract are supposed to be mutating. Marking those that are as such would catch accidental mistakes when unnecessary mutations happen.
Then there's the issue with structs, collections etc all also being mutable by default, which is separate of contracts.
Finally, none of this actually requires any mutability at all. A contract could treated as a pure function that takes initial state as input, and returns the new state as output. Such a function can be written without any mutations at all, by constructing the new state piece by piece from the old one.
Sure, it would be a more verbose (although functional languages usually offer shortcuts like "give me a copy of this object, but with these fields set to different values", making it not so verbose after all). But on the other hand, it means that you have to explicitly consider the effect of every operation of the contract on every bit of state. "Don't repeat yourself" is not always a good thing - when it comes to security, you often do actually want to make people do redundant things, e.g.: http://www.atlasobscura.com/articles/pointing-and-calling-ja....
Unsurprisingly, Tezos is actually written by OCaml people who seem to value the correctness of the contract language.
But not the kind of people you want to focus on to get traction for your venture-backed startup if you want a relatively quick and profitable exit
It's also quite hard for programmers to reason about performance or complexity in most functional languages. In the EVM, all functions should either be O(1) or bounded O(n) and should strive to use the most economical bytecode available to perform each task.
Disclaimer: I am a regular contributor to Solidity.
This is why I criticized the VM design as well as the language.
Yes, much effort went into making "cycle counting" as simple and straight forward as possible.
But I don't see any comparable effort doing the same for safety and correctness. (Apart from providing crypto primitives by the VM, which is neat, but only the tip of the iceberg in terms of language design.)
> It's also quite hard for programmers to reason about performance or complexity in most functional languages
This is true for Haskell, but "most" other functional languages don't suffer this issue: SML, OCaml, F#, Rust, etc.
Also note that lots of effort already went into creating lambda calculus variants where performance is especially easy to reason about. However, dismissing functional langauges as a whole already demonstrates that none that the research played any role in the design of EVM and Solidity.
In case you are interested, here is a nice starting point about the state of the art of 2014: "Structure and Efficiency of Computer Programs" https://www.cs.cmu.edu/~rwh/papers/secp/secp.pdf
I don't have much experience with the ML family of languages, so I may have jumped to conclusions based on my previous experience with Haskell/Elm, but I did not intend it to sound as a dismissal of functional languages at all.
In fact, there is a growing community of researchers working on functional language design for the EVM, but I would wager that it's way more complex to build such a compiler than something a bit more "close to the metal", especially since no one had much experience with the implications of the memory/storage/execution model of Ethereum at the time work started on Solidity, let alone the kinds of vulnerabilities these contracts would be exposed to.
Hindsight is 20/20.
Perhaps the Ethereum devs didn't have this experience, but this experience and knowledge was out there. The E programming language has been a vehicle for smart contract and security research for over 20 years. How many Ethereum and/or Solidity devs read the E research, learned the E language and played around with it?
There is also decades of research on formalized smart contract languages. Nick Szabo is well known in this space, and he wrote a draft paper on a great contract language back in 2002:
http://nakamotoinstitute.org/contract-language/
How many of the devs have read this paper?
What are the big ideas from E that Solidity is missing?
Also, the article by Nick Szabo has some interesting ideas, but as far as I understand, it does not describe a language for programming arbitrary smart contracts, just some notation for common financial contracts.
Yet another data point for 'why a TC language is a bad choice for smart contracts'.
That would be cool. Even cooler would be if they created a code style culture around small contracts such that a contract should print out a legal-looking document with its own source code, when echoed to a printer.
My bet they never took any PL course, and have very vague understanding of what consequences certain design decisions would produce. "20 years of PHP coding" or something like that.
Any decent PL course would emphasize the first-class-everything (procedures , obviously), lexical scooping for proper closures, strong but dynamic typing (no implicit coersions) etc. No matter which sect one belongs to - ML or Scheme - these are universal fundamentals, the well-researched foundation.
PHP/Javascript coders usually do not understand the principles and implications behind languages they use. Otherwise they would develop a healthy aversion, like to a tasteless, vulgar porn.
Sane people with PL background would use a dialect of Scheme as a DSL (the numerical tower and all the nice things) or at least Lua (because packers are afraid of parenthesis and see nothing behind of them).
Actually, Scheme (or a simplified dialect such as the one inside Julia - flisp) would be almost ideal choice for financial DSLs.
For those who are cosplaying a PL expert (with 20 years of PHP and Javascript coding under their belt) there is a good read: http://users.rcn.com/david-moon/PLOT3/
Your comment is an example of virtue signaling, which is equally boring.
Debugging is also a nightmare. There's no way to step through function calls or set break points, even though it's basically just Javascript. Multiple classes of bugs will generate the same "invalid opcode" error that is only visible at run-time with no indication of what line even triggered the error.
This is why I invested in the Tezos crowdsale. Smart contracts are a great idea but there's gotta be a better way.
Sadly, it's not a common practice to develop contracts with full use of such tooling. See [0] for an analysis of the recent 2017 ICOs and their use of the tooling in crowdsale contracts. A few gems exist, that do quite thorough testing including a CI approach.
[0] https://medium.com/@bocytko/would-you-trust-your-money-to-a-...
I think everyone does. I certainly do. But pushing through that actually helps your designing, because you start thinking of things in terms of how the API will look, how each building block leading to the final computation will look, and the need to be able to unit-test it as you go forces you to write smaller, more tightly-focused components automatically, which ends up making more maintainable code that is easier to reason about
The only small correction I would make concerns this:
> Functions can mutate state by default. Both are overridable by explicit specifiers, much like C++ "const", but you have to remember to do so. Even then, the current implementation doesn't enforce this for functions.
When interacting with Ethereum you can do two things: send a transaction, or call a function against your local copy of the blockchain. The former is mutating, costs ether (in gas fees) and requires the transaction to be included in a block. The latter is entirely local, cost-free, and any mutations to state are ignored.
The 'constant' modifier in Solidity serves only to tag the resulting ABI of a function so that calls to it from common interfaces default to local calls instead of sending transactions. It's not intended to enforce that calls to that contract are read-only, only to indicate to callers that it should be called locally instead of creating a transaction for it.
Actually, let me rephrase this. For better or worse, Solidity is a shipping product now, so you're constrained in your ability to make breaking changes. However, its status as the "official" blessed EVM language can be changed without breaking compatibility. So I guess a better question is, are you planning to replace Solidity with a language that has more emphasis on safety in its design (even if it's called Solidity 2.0 or whatever... clearly it would have to significantly break backwards compatibility).
Now, I know that most feedback that you hear is to just take an existing functional language. But after looking at Solidity and EVM specs, I also see why this is not necessarily feasible. And, as one of your contributors has pointed out in this thread, functional style can often be too high-level to keep track of the underlying opcodes and their gas cost.
But even sticking with the imperative approach, I think it's clear that there's a lot that could be changed, starting with adopting some key principles like "explicit is better than implicit", "don't pick a default unless it's absolutely safe" etc. Looking at the design of Solidity, it's clear to me that the primary inspiration was JS with a dash of C++. The one thing that's common to both is that they allow and encourage "clever" code. Perhaps looking at Ada, Eiffel, and even COBOL for inspiration would be more appropriate for this niche.
In any case, if you're interested in specific suggestions on how to redesign, I do have some thoughts, and would be happy to share them.
There are also proposals out for a number of the issues you have mentioned. I believe that people _do_ want to make Solidity into a better language for safe code authoring, but some things require EVM changes which requires a hard fork, and other things simply require time (discussion and engineering). The impression I get is that at this point there _are_ some voices of reason involved in the Solidity language process, but it will take time before those voices can enact all of the changes listed here.
Also, keep in mind that when Ethereum launched it did so like any scrappy startup, with an MVP that was "good enough" to move things forward. Now that Ethereum is a leading contender in the blockchain race it is starting to have to pay for some of those early decisions.
On the blockchain, the VM only cares about the bytecodes you submit, not the code itself.
https://solidity.readthedocs.io/en/develop/solidity-in-depth...
Daily LoL: Googling "secure language" brings up mostly results about Java.
https://publications.lib.chalmers.se/records/fulltext/234939...
You can just skip messing with the termination checker (just don't mark your functions total) in Idris and write Haskell-ish code. The occasional rough edge aside, it's not hard to write the kind of programs we're talking about here: and if you want to prevent DDoS-like infinite looping, put the "total" annotations back in.
Starting from something like the lambda-calculus seems more sensible than starting from something like Javascript.
The E language has been a vehicle for smart contract research for decades. Joe-E is a more recent incarnation of many of the same ideas, that's a secure subset of Java.
Even replacing 256bit ints with arbitrary precision "bigints" wouldn't add too much of a cost if it's a native type of the underlying VM (as it should be for such an application IMO). It might even reduce code size by removing overflow tests.
But I would still expect arithmetic to be overflow-checked by default, as in e.g. VB.NET. This would mean that careless arithmetic on unvalidated inputs could still cause the contract to fail - but at least it would be a controllable failure, with all state changes being reversed, and hence not exploitable.
That's not still the case with `const` and `let` in Javascript, is it? Lexical scoping still exists, but Block scoping is default now in JS
The default best practice, you mean? Declaring with 'var' will still get you the same lexical scoping rules as always.
I believe OP meant 'inherited from pre-ES6 JS'
That being said, "const" is already hardwired.
I don't think that one even matters, that the language has side-effecting expressions (through impure functions) is enough to make an undefined OOE problematic.
> Integers are fixed-size and wrap around, so it's possible to have overflow and underflow bugs. Granted, with 256 bits of precision by default that's harder to do than usual... but still pretty easy if you e.g. do arithmetic on two inputs.
That ignores the problematically trivial "type inference" of `var`: type is decided based on the smallest type which can hold the literal. So any implicitly typed variable initialised to 0 is an 8-bit unsigned integer.
In reality, all closures get the same variable, and if it is the loop counter or derived from it, and closures are executed later, then they will all see the same value - the one that said variable had after the last iteration. It's even worse if closures try to write to it.
This is something that is peculiar to JS. In most other languages, there's no similar issue, because either local variables can't be declared inside loops (or, indeed, at all, treating assignment as implicit declaration), as in Python or Ruby; or if they can, then their scope is what you'd expect it to be, as in C#, Java etc.
Writing async code using closures as continuations is not unique to JS, either. All mainstream languages support this now.
for(var i=0; i < textures.length; i++) {
textures[i].onload = function (i) {
pattern[i] = ctx.createPattern(textures[i], 'repeat');
}
}
The enlightenment: for(var i=0; i < textures.length; i++) {
createPattern(i);
}
function createPattern(i) {
textures[i].onload = function() {
pattern[i] = ctx.createPattern(textures[i], 'repeat');
}
}
In JavaScript you can now however replace var with let and it will be block scoped and the WTF code will work. It's sad though as people learning JavaScript now will not get the beauty of it and will be stuck in callback hell. Function scope in JavaScript pushes you in the right direction. It's like instead of teaching people how to fly, we give them roads so they can walk, like they are used to.A common criticism is the way for loops and arrays are implemented are broken? Well actually they should rarely be used in contracts, its not like you can send ether/btc to an array recipients or in a for loop.
You do not need a complete language like F# and C++ to write contracts, The hacked contract should have had a simple modifier describing only the owner can run this command.
Once again the type system doesn't need to be perfect, if your code on the blockchain is broken you pay a transaction fee and the contract doesn't execute it.
For a language that was made less than 3 years ago, it amazing to see the progression. It will be a language that people hire for in the near future (already happening). Companies are notorious looking for 5 years experience.
Its hard to read the "I told you so replies", when the price of ETH has remained relatively stable during the news
It's interesting to see how the first virtualmachine specification for the first financial smart contract platform has overlooked all the pitfalls of the language-design industry and it doesn't look that a redesign is going to be addressed very soon because of the difficulty of the redeployment (as naturally backwards compatibility must be the first concern).
> Its hard to read the "I told you so replies", when the price of ETH has remained relatively stable during the news
Haha, so if the price is stable, this means that Solidity is not embarrassing? I'm sorry but this just means that people only care about security when their funds are the ones affected. Not about "potential" security problems or "design flaws" which sound too technical to them.
By the way, I went and looked at the offending contract. The pull request that introduced the line with the bug added 2228 new lines of code while removing 918 lines. In fact, the diff is so large that GitHub doesn't even show parts of it by default (including the part with the bug). The pull request was reviewed by a single other person, and committed on the same day it was submitted.
I don't think I'm comfortable with the barrier being that low, at all.
Not at all. Deconstruction of PL design is a popular pastime here.
> A common criticism is the way for loops and arrays are implemented are broken? Well actually they should rarely be used in contracts
If they cannot be used safely, they shouldn't be in a language.
If they can be used safely, but it requires caution and is to be rarely done, then the syntax should reflect that by not making it so easy to use them.
> Once again the type system doesn't need to be perfect, if your code on the blockchain is broken you pay a transaction fee and the contract doesn't execute it.
The type system enforces invariants that go beyond code being broken in a sense that it doesn't run. Sometimes broken code does run, with catastrophic consequences - remember Mars lander crash? That was one example of an error that could be prevented by a sufficiently advanced type system - in F#, for example, types can reflect units of measurement, and the language will plainly not allow you to do nonsensical things like add together feet and meters, without explicitly converting one way or the other.
By its very nature, smart contracts on a blockchain that deals with money have a huge potential for this sort of thing - a contract that doesn't trigger an error, but which silently behaves differently from what was intended by the author. The consequences, as we can see here, can be staggering, as in millions of dollars in damages.
It follows that the language that is used to write them should use every known mechanism to discover such errors early. This includes strong typing, immutability, explicitness, design by contract etc.
> For a language that was made less than 3 years ago
The language was not made in a vacuum 3 years ago. Other languages preceded it, and provided valuable lessons in PL design. Why these lessons weren't heeded is a reasonable question to ask.
A great motivation: it's always better to write half-assed implementations of rarely used features because they are rarely used.
> You do not need a complete language like F# and C++ to write contracts
Agreed. You need a small verifiable DSL, not an entire new language.
> Once again the type system doesn't need to be perfect
That's the spirit when designing a language!
> it amazing to see the progression
Well... yeah, if you started with the goal of executing without crashing it can only improve over time.
All state is mutable by default (this includes struct fields, array elements, and locals). Functions can mutate state by default. Both are overridable by explicit specifiers, much like C++ "const", but you have to remember to do so. Even then, the current implementation doesn't enforce this for functions.
Integers are fixed-size and wrap around, so it's possible to have overflow and underflow bugs. Granted, with 256 bits of precision by default that's harder to do than usual... but still pretty easy if you e.g. do arithmetic on two inputs.
Operators have different semantics depending on whether the operands are literals or not. For example, 1/2 is 0.5, but x/y for x==1 and y==2 is 0. Precision of the operation is also determined in this manner - literals are arbitrary-precision, other values are constrained by their types.
Copy is by reference or by value depending on where the operands are stored. This is implicit - the operation looks exactly the same in code, so unless you look at declarations, you don't know what it actually does. Because mutability is pervasive, this can can have far-reaching effects.
Map data type doesn't throw on non-existing keys, it just returns the default value.
The language has suffixes for literals to denote various units (e.g. "10 seconds" or "1000 ether"). This is purely syntactic sugar, however, and is not reflected in the type system in any way, so "10 second + 1000 ether" is valid code.
Statements allow, but do not require, braces around bodies. This means that dangling "else" is potentially an issue, as is anything else from the same class of bugs (such as the infamous Apple "goto fail" bug).
Functions can be called recursively with no special effort, but the stack size is rather limited, and it looks like there are no tail calls. So there's the whole class of bugs where recursion depth is defined by contract inputs.
Order of evaluation is not defined for expressions. This in a language that has value-returning mutating operators like ++!
Scoping rules are inherited from JS, meaning that you can declare variables inside blocks, but their scope is always the enclosing function. This is more of an annoyance than a real problem, because they don't have closures, which is where JS makes it very easy to shoot yourself in the foot with this approach to scoping.