Introducing Ethereum Script 2.0

This post will provide the groundwork for a major rework of the Ethereum scripting language, which will substantially modify the way ES works although still keeping many of the core components working in the exact same way. The rework is necessary as a result of multiple concerns which have been raised about the way the language is currently designed, primarily in the areas of simplicity, optimization, efficiency and future-compatibility, although it does also have some side-benefits such as improved function support. This is not the last iteration of ES2; there will likely be many incremental structural improvements that can be made to the spec, but it does serve as a strong starting point.

As an important clarification, this rework will have little effect on the Ethereum CLL, the stripped-down-Python-like language in which you can write Namecoin in five lines of code. The CLL will still stay the same as it is now. We will need to make updates to the compiler (an alpha version of which is now available in Python at or as a friendly web interface at in order to make sure the CLL continues to compile to new versions of ES, but you as an Ethereum contract developer working in E-CLL should not need to see any changes at all.

Problems with ES1

Over the last month of working with ES1, several problems with the language’s design have become apparent. In no particular order, they are as follows:

  • Too many opcodes – looking at the specification as it appears today, ES1 now has exactly 50 opcodes – less than the 80 opcodes found in Bitcoin Script, but still far more than the theoretically minimal 4-7 opcodes needed to have a functional Turing-complete scripting language. Some of those opcodes are necessary because we want the scripting language to have access to a lot of data – for example, the transaction value, the transaction source, the transaction data, the previous block hash, etc; like it or not, there needs to be a certain degree of complexity in the language definition to provide all of these hooks. Other opcodes, however, are excessive, and complex; as an example, consider the current definition of SHA256 or ECVERIFY. With the way the language is designed right now, that is necessary for efficiency; otherwise, one would have to write SHA256 in Ethereum script by hand, which might take many thousands of BASEFEEs. But ideally, there should be some way of eliminating much of the bloat.
  • Not future-compatible – the existence of the special crypto opcodes does make ES1 much more efficient for certain specialized applications; thanks to them, computing SHA3 takes only 40x BASEFEE instead of the many thousands of basefees that it would take if SHA3 was implemented in ES directly; same with SHA256, RIPEMD160 and secp256k1 elliptic curve operations. However, it is absolutely not future-compatible. Even though these existing crypto operations will only take 40x BASEFEE, SHA4 will take several thousand BASEFEEs, as will ed25519 signatures, the quantum-proof NTRU, SCIP and Zerocoin math, and any other constructs that will appear over the coming years. There should be some natural mechanism for folding such innovations in over time.
  • Not deduplication-friendly – the Ethereum blockchain is likely to become extremely bloated over time, especially with every contract writing its own code even when the bulk of the code will likely be thousands of people trying to do the exact same thing. Ideally, all instances where code is written twice should pass through some process of deduplication, where the code is only stored once and only a pointer to the code is stored twice. In theory, Ethereum’s Patricia trees do this already. In practice, however, code needs to be in exactly the same place in order for this to happen, and the existence of jumps means that it is often difficult to abitrarily copy/paste code without making appropriate modifications. Furthermore, there is no incentivization mechanism to convince people to reuse existing code.
  • Not optimization-friendly – this is a very similar criterion to future-compatibility and deduplication-friendliness in some ways. However, here optimization refers to a more automatic process of detecting bits of code that are reused many times, and replacing them with memoized or compiled machine code versions.

Beginnings of a Solution: Deduplication

The first issue that we can handle is that of deduplication. As described above, Ethereum Patricia trees provide deduplication already, but the problem is that achieving the full benefits of the deduplication requires the code to be formatted in a very special way. For example, if the code in contract A from index 0 to index 15 is the same as the code in contract B from index 48 to index 63, then deduplication happens. However, if the code in contract B is offset at all modulo 16 (eg. from index 49 to index 64), then no deduplication takes place at all. In order to remedy this, there is one relatively simple solution: move from a dumb hexary Patricia tree to a more semantically oriented data structure. That is, the tree represented in the database should mirror the abstract syntax tree of the code.

To understand what I am saying here, consider some existing ES1 code:


In the Patricia tree, it looks like this:


And here is what the code looks like structurally. This is easiest to show by simply giving the E-CLL it was compiled from:

if tx.value < 25 * 10^18:
if[[0]] or[0] < 1000:
    stop[[0]] =[1]

No relation at all. Thus, if another contract wanted to use some semantic sub-component of this code, it would almost certainly have to re-implement the whole thing. However, if the tree structure looked somewhat more like this:


Then if someone wanted to reuse some particular piece of code they easily could. Note that this is just an illustrative example; in this particular case it probably does not make sense to deduplicate since pointers need to be at least 20 bytes long to be cryptographically secure, but in the case of larger scripts where an inner clause might contain a few thousand opcodes it makes perfect sense.

Immutability and Purely Functional Code

Another modification is that code should be immutable, and thus separate from data; if multiple contracts rely on the same code, the contract that originally controls that code should not have the ability to sneak in changes later on. The pointer to which code a running contract should start with, however, should be mutable.

A third common optimization-friendly technique is the make a programming language purely functional, so functions cannot have any side effects outside of themselves with the exception of return values. For example, the following is a pure function:

def factorial(n):
    prod = 1
    for i in range(1,n+1):
        prod *= i
    return prod

However, this is not:

x = 0
def next_integer():
    x += 1
    return x

And this most certainly is not:

import os
def happy_fluffy_function():
    bal = float(os.popen('bitcoind getbalance').read())
    os.popen('bitcoind sendtoaddress 1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T %.8f' % (bal - 0.0001))
    os.popen('rm -rf ~')

Ethereum cannot be purely functional, since Ethereum contracts do necessarily have state – a contract can modify its long-term storage and it can send transactions. However, Ethereum script is a unique situation because Ethereum is not just a scripting environment – it is an incentivized scripting environment. Thus, we can allow applications like modifying storage and sending transactions, but discourage them with fees, and thus ensure that most script components are purely functional simply to cut costs, even while allowing non-purity in those situations where it makes sense.

What is interesting is that these two changes work together. The immutability of code also makes it easier to construct a restricted subset of the scripting language which is functional, and then such functional code could be deduplicated and optimized at will.

Ethereum Script 2.0

So, what’s going to change? First of all, the basic stack-machine concept is going to roughly stay the same. The main data structure of the system will continue to be the stack, and most of your beloved opcodes will not change significantly. The only differences in the stack machine are the following:

  1. Crypto opcodes are removed. Instead, we will have to have someone write SHA256, RIPEMD160, SHA3 and ECC in ES as a formality, and we can have our interpreters include an optimization replacing it with good old-fashioned machine-code hashes and sigs right from the start.
  2. Memory is removed. Instead, we are bringing back DUPN (grabs the next value in the code, say N, and pushes a copy of the item N items down the stack to the top of the stack) and SWAPN (swaps the top item and the nth item).
  3. JMP and JMPI are removed.
  4. RUN, IF, WHILE and SETROOT are added (see below for further definition)

Another change is in how transactions are serialized. Now, transactions appear as follows:

  • SEND: [ 0, nonce, to, value, [ data0 … datan ], v, r, s ]
  • MKCODE: [ 1, nonce, [ data0 … datan ], v, r, s ]
  • MKCONTRACT: [ 2, nonce, coderoot, v, r, s ]

The address of a contract is defined by the last 20 bytes of the hash of the transaction that produced it, as before. Additionally, the nonce no longer needs to be equal to the nonce stored in the account balance representation; it only needs to be equal to or greater than that value.

Now, suppose that you wanted to make a simple contract that just keeps track of how much ether it received from various addresses. In E-CLL that’s:[tx.sender] = tx.value

In ES2, instantiating this contract now takes two transactions:

[ 1, 0, [ TXVALUE TXSENDER SSTORE ], v, r, s]

[ 2, 1, 761fd7f977e42780e893ea44484c4b64492d8383, v, r, s ]

What happens here is that the first transaction instantiates a code node in the Patricia tree. The hash sha3(rlp.encode([ TXVALUE TXSENDER SSTORE ]))[12:] is 761fd7f977e42780e893ea44484c4b64492d8383, so that is the “address” where the code node is stored. The second transaction basically says to initialize a contract whose code is located at that code node. Thus, when a transaction gets sent to the contract, that is the code that will run.

Now, we come to the interesting part: the definitions of IF and RUN. The explanation is simple: IF loads the next two values in the code, then pops the top item from the stack. If the top item is nonzero, then it runs the code item at the first code value. Otherwise, it runs the code item at the second code value. WHILE is similar, but instead loads only one code value and keeps running the code while the top item on the stack is nonzero. Finally, RUN just takes one code value and runs the code without asking for anything. And that’s all you need to know. Here is one way to do a Namecoin contract in new Ethereum script:

Z: [ STOP ]
Y: [ ]

The contract would then have its root be M. But wait, you might say, this makes the interpreter recursive. As it turns out, however, it does not – you can simulate the recursion using a data structure called a “continuation stack”. Here’s what the full stack trace of that code might look like, assuming the transaction is [ X, Y ] sending V where X > 100, V > 10^18 * 25 and[X] is not set:

{ stack: [], cstack: [[M, 0]], op: RUN }
{ stack: [], cstack: [[M, 2], [A, 0]], op: TXVALUE }
{ stack: [V], cstack: [[M, 2], [A, 1]], op: PUSH }
{ stack: [V, 25], cstack: [[M, 2], [A, 3]], op: PUSH }
{ stack: [V, 25, 10], cstack: [[M, 2], [A, 5]], op: PUSH }
{ stack: [V, 25, 10, 18], cstack: [[M, 2], [A, 7]], op: EXP }
{ stack: [V, 25, 10^18], cstack: [[M, 2], [A, 8]], op: MUL }
{ stack: [V, 25*10^18], cstack: [[M, 2], [A, 9]], op: LT }
{ stack: [0], cstack: [[M, 2], [A, 10]], op: NULL }
{ stack: [0], cstack: [[M, 2]], op: IF }
{ stack: [0], cstack: [[M, 5], [Y, 0]], op: NULL }

{ stack: [0], cstack: [[M, 5]], op: RUN }
{ stack: [], cstack: [[M, 7], [B, 0]], op: PUSH }
{ stack: [0], cstack: [[M, 7], [B, 2]], op: TXDATA }
{ stack: [X], cstack: [[M, 7], [B, 3]], op: SLOAD }
{ stack: [0], cstack: [[M, 7], [B, 4]], op: NOT }
{ stack: [1], cstack: [[M, 7], [B, 5]], op: PUSH }
{ stack: [1, 0], cstack: [[M, 7], [B, 7]], op: TXDATA }
{ stack: [1, X], cstack: [[M, 7], [B, 8]], op: PUSH }
{ stack: [1, X, 100], cstack: [[M, 7], [B, 10]], op: LT }
{ stack: [1, 0], cstack: [[M, 7], [B, 11]], op: NOT }
{ stack: [1, 1], cstack: [[M, 7], [B, 12]], op: MUL }
{ stack: [1], cstack: [[M, 7], [B, 13]], op: NOT }
{ stack: [1], cstack: [[M, 7], [B, 14]], op: NULL }
{ stack: [0], cstack: [[M, 7]], op: IF }
{ stack: [0], cstack: [[M, 9], [Y, 0]], op: NULL }

{ stack: [], cstack: [[M, 10]], op: RUN }
{ stack: [], cstack: [[M, 12], [C, 0]], op: PUSH }
{ stack: [1], cstack: [[M, 12], [C, 2]], op: TXDATA }
{ stack: [Y], cstack: [[M, 12], [C, 3]], op: PUSH }
{ stack: [Y,0], cstack: [[M, 12], [C, 5]], op: TXDATA }
{ stack: [Y,X], cstack: [[M, 12], [C, 6]], op: SSTORE }
{ stack: [], cstack: [[M, 12], [C, 7]], op: NULL }
{ stack: [], cstack: [[M, 12]], op: NULL }
{ stack: [], cstack: [], op: NULL }

And that’s all there is to it. Cumbersome to read, but actually quite easy to implement in any statically or dynamically types programming language or perhaps even ultimately in an ASIC.


In the above design, there is still one major area where optimizations can be made: making the references compact. What the clear and simple style of the above contract hid is that those pointers to A, B, C, M and Z aren’t just compact single letters; they are 20-byte hashes. From an efficiency standpoint, what we just did is thus actually substantially worse than what we had before, at least from the point of view of special cases where code is not nearly-duplicated millions of times. Also, there is still no incentive for people writing contracts to write their code in such a way that other programmers later on can optimize; if I wanted to code the above in a way that would minimize fees, I would just put A, B and C into the contract directly rather than separating them out into functions. There are two possible solutions:

  1. Instead of using H(x) = SHA3(rlp.encode(x))[12:], use H(x) = SHA3(rlp.encode(x))[12:] if len(rlp.encode(x)) >= 20 else x. To summarize, if something is less than 20 bytes long, we include it directly.
  2. A concept of “libraries”. The idea behind libraries is that a group of a few scripts can be published together, in a format [ [ ... code ... ], [ ... code ... ], ... ], and these scripts can internally refer to each other with their indices in the list alone. This completely alleviates the problem, but at some cost of harming deduplication, since sub-codes may need to be stored twice. Some intelligent thought into exactly how to improve on this concept to provide both deduplication and reference efficiency will be required; perhaps one solution would be for the library to store a list of hashes, and then for the continuation stack to store [ lib, libIndex, codeIndex ] instead of [ hash, index ].

Other optimizations are likely possible. For example, one important weakness of the design described above is that it does not support recursion, offering only while loops to provide Turing-completeness. It might seem to, since you can call any function, but if you try to actually try to implement recursion in ES2 as described above you soon notice that implementing recursion would require finding the fixed point of an iterated hash (ie. finding x such that H(a + H( c + ... H(x) ... + d) + b) = x), a problem which is generally assumed to be cryptographically impossible. The “library” concept described above does actually fix this at least internally to one library; ideally, a more perfect solution would exist, although it is not necessary. Finally, some research should go into the question of making functions first-class; this basically means changing the IF and RUN opcode to pull the destination from the stack rather than from fixed code. This may be a major usability improvement, since you can then code higher-order functions that take functions as arguments like map, but it may also be harmful from an optimization standpoint since code becomes harder to analyze and determine whether or not a given computation is purely functional.


Finally, there is one last question to be resolved. The primary purposes of ES2 as described above are twofold: deduplication and optimization. However, optimizations by themselves are not enough; in order for people to actually benefit from the optimizations, and to be incentivized to code in patterns that are optimization-friendly, we need to have a fee structure that supports this. From a deduplication perspective, we already have this; if you are the second person to create a Namecoin-like contract, and you want to use A, you can just link to A without paying the fee to instantiate it yourself. However, from an optimization perspective, we are far from done. If we create SHA3 in ES, and then have the interpreter intelligently replace it with a contract, then the interpreter does get much faster, but the person using SHA3 still needs to pay thousands of BASEFEEs. Thus, we need a mechanism for reducing the fee of specific computations that have been heavily optimized.

Our current strategy with fees is to have miners or ether holders vote on the basefee, and in theory this system can easily be expanded to include the option to vote on reduced fees for specific scripts. However, this does need to be done intelligently. For example, EXP can be replaced with a contract of the following form:


However, the runtime of this contract depends on the exponent – with an exponent in the range [4,7] the while loop runs three times, in the range [1024, 2047] the while loop runs eleven times, and in the range [2^255, 2^256-1] it runs 256 times. Thus, it would be highly dangerous to have a mechanism which can be used to simply set a fixed fee for any contract, since that can be exploited to, say, impose a fixed fee for a contract computing the Ackermann function (a function notorious in the world of mathematics because the cost of computing or writing down its output grows so fast that with inputs as low as 5 it becomes larger than the size of the universe). Thus, a percentage discount system, where some contracts can enjoy half as large a basefee, may make more sense. Ultimately, however, a contract cannot be optimized down to below the cost of calling the optimized code, so we may want to have a fixed fee component. A compromise approach might be to have a discount system, but combined with a rule that no contract can have its fee reduced below 20x the BASEFEE.

So how would fee voting work? One approach would be to store the discount of a code item along side that code item’s code, as a number from 1 to 232, where 232 represents no discount at all and 1 represents the highest discounting level of 4294967296x (it may be prudent to set the maximum at 65536x instead for safety). Miners would be authorized to make special “discount transactions” changing the discounting number of any code item by a maximum of 1/65536x of its previous value. With such a system, it would take about 40000 blocks or about one month to halve the fee of any given script, a sufficient level of friction to prevent mining attacks and give everyone a chance to upgrade to new clients with more advanced optimizers while still making it possible to update fees as required to ensure future-compatibility.

Note that the above description is not clean, and is still very much not fleshed out; a lot of care will need to be made in making it maximally elegant and easy to implement. An important point is that optimizers will likely end up replacing entire swaths of ES2 code blocks with more efficient machine code, but under the system described above will still need to pay attention to ES2 code blocks in order to determine what the fee is. One solution is to have a miner policy offering discounts only to contracts which maintain exactly the same fee when run regardless of their input; perhaps other solutions exist as well. However, one thing is clear: the problem is not an easy one.


  1. Pingback: Introducing Ethereum Script 2.0 | Bitcoin Buzz Feeds

  2. This is just the eternal problem with stack-based VMs.
    Stack machines are easy to construct and easy to compile to, but extremely difficult to optimize and translate to register based CPUs.
    I recommend you a change in your approach. In the actual state of things, I would have start just with a plain interpreter for the CCL with no underline VMs at all, because:
    1.- You can choose what VM to use later if you want to be future-proof.
    2.- It is not so slow if you use a correct syntax that can be parsed and converted to function-pointers on the fly.

    For the first, you can take the example of the extremely efficent and yet simple VM powering LUA (and there is LUAJIT, able to compete against compiled languages).
    For the second I recommend you to check out the internals of Picolisp, an extremely fast interpreter capable of outperfoming any VMs of current languages like Python.

    I think that if you continue with the idea of a stack-based VM, a hard future is awaiting to you. DAOs like Wetube will need tens of thousands of lines to be implemented and nobody will use your language if it has so many drawbacks (like not having functions or objects and lists), plus the problem of oversaturating nodes with lots of wasted CPU-cycles just to attend your VM.

    Those are just by 2 cents. I’d love Ethereum to success.

    • So, the first major point here is that VM and interpreter are synonyms. So the real question is, do we want an interpreter/VM for a low-level language, or an interpreter/VM for a high-level language? I personally much prefer the low-level language approach for 2 reasons:

      1. Low-level languages are simpler, so the Ethereum spec can be simpler.
      2. People’s preferences of what to code in change. The CLL is only the beginning; we also want a high-level language with functions, data structures and the like and have that be an efficient way to code. Ultimately, the CLL should be expanded slowly over time, so that eventually it can become a nearly-complete implementation of Python. And if people prefer Lisp, or Javascript, or any other language that is their choice. Thus, eventually people are going to be writing compilers from their language to our (fixed) language, and if there are going to be compilers to then the compilation target should be as minimalistic and general as possible.

      Also, I do want to change the stack machine to support nested scoping and first-class functions if possible; I am currently looking at Postscript and how it handles variables and objects and seeing if those ideas can be worked in, since our language seems to be quite similar to postscript already.

      Since you seem to be very knowledgeable in the area, I would love to add you into our language discussion group. If you are interested, can you add me (vbuterins) on skype?

      • Your approach to have an assembler is very good when you are defining a general purpose language, but then:

        Why invent another VM? Developing and optimizing a VM is not an easy task and it is not something that you are going to do in 1 year. Look at the maturity of actual VMs and the history behind them and you’ll see the enormous resources put into them. Sun needed several years to optimize the JVM, SBCL took generations to grow up, LuaJIT needed external help from software companies, Python is not even yet there (Pypy is just a promise right now, barely can get 6x performance in the best of the cases), Perl 6 is stacked for years, Haskell gave up and directly compiled to C. Don’t be so pretentious to think that you (and me) can do so in months… it is not going to happen… unless we take a very simplistic approach (look at picolisp).
        Yes, we can game with a stack VM for some weeks and have results. But the problem with stack-based machines is that their development is logarithmic: very easy at the start, very hard in the end.

        Ok, I can help you with this… but then you need an open mind and think what is the best for the future. What you say about having an assembler language and different compilers for it is the actual status-quo. It seems to work because everybody do so, but in my opinion it doesn’t. Who is using the Python VM apart from Python? The same with the rest of the VMs except JVM and NET, and why those? Only because they are fast. You are not going to be fast in the near future, so forget about mass adoption.

        What I propose is to look this in a reverse mode, and I propose two options to choose one:
        1.- Select a programming language that allows to change its syntax and allow constraints if the use of function and packages. The only language that I know that can do so are in the Lisp family. And we have SBCL which is *extremely* fast (competing against C) at the same time that it is *dynamic*. Languages on top of Lisp can be made, take for example CLPython ( ), a Python implementation on top of Lisp that is as fast as Lisp because it is just a *tweak* of the syntax, something that only Lisp can do thanks to *macros*.
        2.- Use an existing VM, for example the LuaJIT VM is really very nice and lightweigt and limit what the scripts can do.

        Now, for the second option (which is probably the most appealing to you), you *must* implement a form of limitation and clearly discriminate and detect who deserves to be trusted and who does not. You don’t want to run an infinite loop for the entire ethereum net, right? What are you going to do? Limit how many times a loop can run? Charge infinite fees for doing so? Not everything is money.
        So I propose use a distributed CA approach to CPU consumption like we are doing with Bitcloud. Please read:
        Now, we can enter into the details on how to automate trusts/revocations.
        We have been thinking the same kind of problems for months just for storage… only we realised that the only solutions are distributed CAs.

        Please add me to your lists and we can continue to talk.

    • > Stack machines are easy to construct and easy to compile to, but extremely difficult to optimize and translate to register based CPUs.

      I disagree. I have written stack machine to register compilers for at least six CPU architectures. There’s only one algorithm needed that’s not in the classical compiler literature – the register to canonical stack shuffle. We also have experience of compiling C to stack machines.

  3. Also, do you limit you language to make money transactions/contracts only? Is the number of opcodes used the only measurement for calculating the fees?
    If that is the case, I don’t see the point of using Etherum for a sophisticated DAC, whose payments and fees must be calculated by sophisticated algorithms that requires other kind of inputs. For example, Wetube must check QoS and completeness of transmissions for paid content from Bitcloud, but also must check the same for non-paid content to maintain a reputation system. I don’t see how your language can provide that.

    Am I confused anyway?

    Remember that Bitcoin 0.9 already provides a form of escrow and the release of it could be in charge of random nodes acting as agents. No need to go to your language if there is not a real benefit over that, specially taking in consideration that it has no sense to fragment the code of a DAO over several languages/tools.
    If you want full DAOs to run on your language, you must provide much more flexibility than you’re offering right now, like IO over TCP and some functional libraries. Also, you’d need to allow access to external libraries, otherwise it is not of a real use.
    In a sense, I think that for your language to be useful, it must be complete. Otherwise people will prefer to make a fork and use their own coin.

  4. Pingback: Changes in Ethereum scripting language | bitslog

  5. I am not quite following the notation being used here. You have code that says stuff like “RUN A” and “PUSH 12” which implies kind of a mix of Postfix and Prefix. Shouldn’t these be “A RUN” and just a simple “12”? I would like a list of your operators with a stack description, i.e. MUL (integer1, integer2 — integer3) MUL integer3 = integer1 * integer2

    Operators that take multiple inputs can be described PICK ( any1 any2 … anyN integer=N — any1 any2 … anyN any1) PICK takes the index from the top of the stack, pulls the value on the stack that is that many items deep, and pushes a copy of that value on the top of the stack.

  6. Pingback: More Thoughts on Scripting and Future-Compatibility « Ethereum Blog

  7. Pingback: nervous | Ethernity

  8. With regards to the high level oocodes, I think you should take them out of the language spec and provide a “userland” opcode which takes the name of a function which the script will request, and a number of basefees that they are prepared to pay for it. The idea being that then different implementations can provide different library facilities and for different costs. And you get to leave the potentially thousands of high level convenience functions out of the spec.

  9. Pingback: Pyethereum and Serpent Programming Guide « Ethereum Blog

  10. In the section on ‘Immutability and Purely Functional Code’, you write “The pointer to which code a running contract should start with, however, should be mutable.” I am concerned that this mechanism would make it impossible to verify that a contract cannot be changed against my will after I have committed to be a party to that contract. I doubt it does, but can you comment on how much mutability this mechanism permits, and on its implications for the analysis of a contract’s code?

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s