MAIAN: automatic tool for finding trace vulnerabilities in Ethereum smart contracts
Smart contracts---stateful executable objects hosted on blockchains like Ethereum---carry billions of dollars worth of coins and cannot be updated once deployed. We present a new systematic characterization of a class of trace vulnerabilities, which result from analyzing multiple invocations of a contract over its lifetime. We focus attention on three example properties of such trace vulnerabilities: finding contracts that either lock funds indefinitely, leak them carelessly to arbitrary users, or can be killed by anyone. We implemented MAIAN, the first tool for precisely specifying and reasoning about trace properties, which employs inter-procedural symbolic analysis and concrete validator for exhibiting real exploits. Our analysis of nearly one million contracts flags 34,200 (2,365 distinct) contracts vulnerable, in 10 seconds per contract. On a subset of3,759 contracts which we sampled for concrete validation and manual analysis, we reproduce real exploits at a true positive rate of 89 the infamous Parity bug that indirectly locked 200 million dollars worth in Ether, which previous analyses failed to capture.READ FULL TEXT VIEW PDF
Symbolic analysis of security exploits in smart contracts has demonstrat...
Modern blockchains, such as Ethereum, enable the execution of so-called ...
In the last year we have seen a great deal of both academic and practica...
Solidity is an object-oriented and high-level language for writing smart...
Callbacks are essential in many programming environments, but drasticall...
Vulnerability detection and safety of smart contracts are of paramount
In this paper, we present the first large-scale and systematic study to
MAIAN: automatic tool for finding trace vulnerabilities in Ethereum smart contracts
Papers of smart contract security analysis (and tools)
Cryptocurrencies feature a distributed protocol for a set of computers to agree on the state of a public ledger called the blockchain. Prototypically, these distributed ledgers map accounts or addresses (the public half of a cryptographic key pair) with quantities of virtual “coins”. Miners, or the computing nodes, facilitate recording the state of a payment network, encoding transactions that transfer coins from one address to another. A significant number of blockchain protocols now exist, and as of writing the market value of the associated coins is over $300 billion US, creating a lucrative attack target.
Smart contracts extend the idea of a blockchain to a compute platform for decentralized execution of general-purpose applications. Contracts are programs that run on blockchains: their code and state is stored on the ledger, and they can send and receive coins. Smart contracts have been popularized by the Ethereum blockchain. Recently, sophisticated applications of smart contracts have arisen, especially in the area of token management due to the development of the ERC20 token standard. This standard allows the uniform management of custom tokens, enabling, e.g., decentralized exchanges and complex wallets. Today, over a million smart contracts operate on the Ethereum network, and this count is growing.
Smart contracts offer a particularly unique combination of security challenges. Once deployed they cannot be upgraded or patched,222Other than by “hard forks”, which are essentially decisions of the community to change the protocol and are extremely rare. unlike traditional consumer device software. Secondly, they are written in a new ecosystem of languages and runtime environments, the de facto standard for which is the Ethereum Virtual Machine and its programming language called Solidity. Contracts are relatively difficult to test, especially since their runtimes allow them to interact with other smart contracts and external off-chain services; they can be invoked repeatedly by transactions from a large number of users. Third, since coins on a blockchain often have significant value, attackers are highly incentivized to find and exploit bugs in contracts that process or hold them directly for profit. The attack on the DAO contract cost the Ethereum community $60 million US; and several more recent ones have had impact of a similar scale .
In this work, we present a systematic characterization of a class of vulnerabilities that we call as trace vulnerabilities. Unlike many previous works that have applied static and dynamic analyses to find bugs in contracts automatically [2, 3, 4, 5], our work focuses on detecting vulnerabilities across a long sequence of invocations of a contract. We label vulnerable contracts with three categories — greedy, prodigal, and suicidal — which either lock funds indefinitely, leak them to arbitrary users, or be susceptible to by killed by any user. Our precisely defined properties capture many well-known examples of known anecdotal bugs [6, 7, 1], but broadly cover a class of examples that were not known in prior work or public reports. More importantly, our characterization allows us to concretely check for bugs by running the contract, which aids determining confirmed true positives.
We build an analysis tool called for finding these vulnerabilities directly from the bytecode of Ethereum smart contracts, without requiring source code access. In total, across the three categories of vulnerabilities, has been used to analyze contracts live of the public Ethereum blockchain. Our techniques are powerful enough to find the infamous Parity bug that indirectly caused million dollars worth of Ether, which is not found by previous analyses. A total of (distinct) contracts are flagged as potentially buggy, directly carry the equivalent of millions of dollars worth of Ether. As in the case of the Parity bug, they may put a larger amount to risk, since contracts interact with one another. For contracts we tried to concretely validate, has found over confirmed vulnerabilities with % true positive rate. All vulnerabilities are uncovered on average within 10 seconds of analysis per contract.
We make the following contributions:
We identify three classes of trace vulnerabilities, which can be captured as properties of a execution traces — potentially infinite sequence of invocations of a contract. Previous techniques and tools  are not designed to find these bugs because they only model behavior for a single call to a contract.
We provide formal high-order properties to check which admit a mechanized symbolic analysis procedure for detection. We fully implement , a tool for symbolic analysis of smart contract bytecode (without access to source code).
We test close to one million contracts, finding thousands of confirmed true positives within a few seconds of analysis time per contract. Testing trace properties with is practical.
We define a new class of trace vulnerabilities, showing three specific examples of properties that can be checked in this broader class. We present our approach and tool to reason about the class of trace vulnerabilities.
Smart contracts in Ethereum run on Ethereum Virtual Machine (EVM), a stack-based execution runtime . Different source languages compile to the EVM semantics, the predominant of them being Solidity . A smart contract embodies the concept of an autonomous agent, identified by its program logic, its identifying address, and its associated balance in Ether. Contracts, like other addresses, can receive Ether from external agents storing it in their balance field; they can can also send Ether to other addresses via transactions. A smart contract is created by the owner who sends an initializing transaction, which contains the contract bytecode and has no specified recipient. Due to the persistent nature of the blockchain, once initialized, the contract code cannot be updated. Contracts live perpetually unless they are explicitly terminated by executing the SUICIDE bytecode instruction, after which they are no longer invocable or called dead. When alive, contracts can be invoked many times. Each invocation is triggered by sending a transaction to the contract address, together with input data and a fee (known as gas) . The mining network executes separate instances of the contract code and agrees on the outputs of the invocation via the standard blockchain consensus protocol, i.e., Nakamoto consensus [10, 11]. The result of the computation is replicated via the blockchain and grants a transaction fee to the miners as per block reward rates established periodically.
The EVM allows contract functions to have local state, while the contracts may have global variables stored on the blockchain. Contracts can invoke other contracts via message calls; outputs of these calls, considered to be a part of the same transaction, are returned to the caller during the runtime. Importantly, calls are also used to send Ether to other contracts and non-contract addresses. The balance of a contract can be read by anyone, but is only updated via calls from other contracts and externally initiated transactions.
Contracts can be executed repeatedly over their lifetime. A transaction can run one invocation of the contract and an execution trace is a (possibly infinite) sequence of runs of a contract recorded on the blockchain. Our work shows the importance of reasoning about execution traces of contracts with a class of vulnerabilities that has not been addressed in prior works, and provides an automatic tool to detect these issues.
While trace vulnerabilities are a broader class, we our focus attention on three example properties to check of contract traces. Specifically, we flag contracts which (a) can be killed by arbitrary addresses, (b) have no way to release Ether after a certain execution state, and (c) release Ether to arbitrary addresses carelessly.
Note that any characterization of bugs must be taken with a grain of salt, since one can always argue that the exposed behavior embodies intent — as was debated in the case of the DAO bug . Our characterization of vulnerabilities is based, in part, on anecdotal incidents reported publicly [7, 12, 6]. To the best of our knowledge, however, our characterization is the first to precisely define checkable properties of such incidents and measure their prevalence. Note that there are several valid reasons for contracts for being killable, holding funds indefinitely under certain conditions, or giving them out to addresses not known at the time of deployment. For instance, a common security best practice is that when under attack, a contract should be killed and should return funds to a trusted address, such as that of the owner. Similarly, benign contracts such as bounties or games, often hold funds for long periods of time (until a bounty is awarded) and release them to addresses that are not known statically. Our characterization admits these benign behaviors and flags egregious violations described next, for which we are unable to find justifiable intent.
Contracts often return funds to owners (when under attack), to addresses that have sent Ether to it in past (e.g., in lotteries), or to addresses that exhibit a specific solution (e.g., in bounties). However, when a contract gives away Ether to an arbitrary address— which is not an owner, has never deposited Ether in the contract, and has provided no data that is difficult to fabricate by an arbitrary observer—we deem this as a vulnerability. We are interested in finding such contracts, which we call as prodigal.
Consider the Bounty contract with code fragment given in Figure 1. This contract collects Ether from different sources and rewards bounty to a selected set of recipients. In the contract, the function payout sends to a list of recipients specified amounts of Ether. It is clear from the function definition that the recipients and the amounts are provided as inputs, and anybody can call the function (i.e., the function does not have restrictions on the sender). The message sender of the transaction is not checked for; the only check is on the size of lists. Therefore, any user can invoke this function with a list of recipients of her choice, and completely drain its Ether.
The above contract requires a single function invocation to leak its Ether. However, there are examples of contracts which need two or more invocations (calls with specific arguments) to cause a leak. Examples of such contracts are presented in Section 5.
A contract often enables a security fallback option of being killed by its owner (or trusted addresses) in emergency situations like when being drained of its Ether due to attacks, or when malfunctioning. However, if a contract can be killed by any arbitrary account, which would make it to execute the SUICIDE instruction, we consider it vulnerable and call it suicidal.
The recent Parity fiasco is a concrete example of such type of a contract. A supposedly innocent Ethereum user  killed a library contract on which the main Parity contract relies, thus rendering the latter non-functional and locking all its Ether. To understand the suicidal side of the library contract, focus on the shortened code fragment of this contract given in Figure 2. To kill the contract, the user invokes two different functions: one to set the ownership,333The bug would have been prevented has the function initMultiowned been properly initialized by the authors. and one to actually kill the contract. That is, the user first calls initMultiowned, providing empty array for _owners, and zero for _required. This effectively means that the contract has no owners and that nobody has to agree to execute a specific contract function. Then the user invokes the function kill. This function needs _required number of owners to agree to kill the contract, before the actual suicide command at line 22 is executed. However, since in the previous call to initMultiowned, the value of _required was set to zero, suicide is executed, and thus the contract is killed.
We refer to contracts that remain alive and lock Ether indefinitely, allowing it be released under no conditions, as greedy. In the example of the Parity contract, many other multisigWallet-like contracts which held Ether, used functions from the Parity library contract to release funds to their users. After the Parity library contracts was killed, the wallet contracts could no longer access the library, thus became greedy. This vulnerability resulted in locking of $200M US worth of Ether indefinitely!
Greedy contracts can arise out of more direct errors as well. The most common such errors occur in contracts that accept Ether but either completely lack instructions that send Ether out (e.g. send, call, transfer), or such instructions are not reachable. An example of contract that lacks commands that release Ether, that has already locked Ether is given in Figure 3.
When a contract is killed, its code and global variables are cleared from the blockchain, thus preventing any further execution of its code. However, all killed contracts continue to receive transactions. Although such transactions can no longer invoke the code of the contract, if Ether is sent along them, it is added to the contract balance, and similarly to the above case, it is locked indefinitely. Killed contract or contracts that do not contain any code, but have non-zero Ether we call posthumous. It is the onus of the sender to check if the contract is alive before sending Ether, and evidence shows that this is not always the case. Because posthumous contracts require no further static analysis beyond that for identifying suicidal contracts, we do not treat this as a separate class of bugs. We merely list all posthumous contracts on the live Ethereum blockchain we have found in Section 5.
Each run of the contract, called an invocation, may exercise an execution path in the contract code under a given input context. Note that prior works have considered bugs that are properties of one invocation, ignoring the chain of effects across a trace of invocations [2, 14, 15, 16, 5, 17].
We develop a tool that uses systematic techniques to find contracts that violate specific properties of traces. The violations are either:
(a) of safety properties, asserting that there exists a trace from a specified blockchain state that causes the contract to violate certain conditions; and
(b) of liveness properties, asserting whether some actions cannot be taken in any execution starting from a specified blockchain state.
We formulate the three kinds of vulnerable contracts as these safety and liveness trace properties in Section 3. Our technique of finding vulnerabilities, implemented as a tool called and described in Section 4, consists of two major components: symbolic analysis and concrete validation. The symbolic analysis component takes contract bytecode and analysis specifications as inputs. The specifications include vulnerability category to search for and depth of the search space, which further we refer to as invocation depth, along with a few other analysis parameters we outline in Section 4. To develop our symbolic analysis component, we implement a custom Ethereum Virtual Machine, which facilitates symbolic execution of contract bytecode . With every contract candidate, our component runs possible execution traces symbolically, until it finds a trace which satisfies a set of predetermined properties. The input context to every execution trace is a set of symbolic variables. Once a contract is flagged, the component returns concrete values for these variables. Our final step is to run the contract concretely and validate the result for true positives; this step is implemented by our concrete validation component. The concrete validation component takes the inputs generated by symbolic analysis component and checks the exploit of the contract on a private fork of Ethereum blockchain. Essentially, it is a testbed environment used to confirm the correctness of the bugs. As a result, at the end of validation the candidate contract is determined as true or false positive, but the contract state on main blockchain is not affected since no changes are committed to the official Ethereum blockchain.
A life cycle of a smart contract can be represented by a sequence of the contract’s states, which describe the values of the contract’s fields, as well as its balance, interleaved with instructions and irreversible actions it performs modifying the global context of the blockchain, such transferring Ether or committing suicide. One can consider a contract to be buggy with respect to a certain class of unwelcome high-level scenarios (e.g., “leaking” funds) if some of its finite execution traces fail to satisfy a certain condition. Trace properties characterised this way are traditionally qualified as trace-safety ones, meaning that “during a final execution nothing bad happens”. Proving the absence of some other high-level bugs will, however, require establishing a statement of a different kind, namely, “something good must eventually happen”. Such properties are known as liveness ones and require reasoning about progress in executions. An example of such property would be an assertion that a contract can always execute a finite number of steps in order to perform an action of interest, such as tranferring money, in order to be considered non-greedy.
In this section, we formally define the execution model of Ethereum smart contracts, allowing one to pinpoint the vulnerabilities characterised in Section 2.2. The key idea of our bug-catching approach is to formulate the erroneous behaviours as predicates of observed contract traces, rather than individual configurations and instruction invocations, occurring in the process of an execution. By doing so, we are able to (a) capture the prodigal/suicidal contracts via conditions that relate the unwelcome agents gaining, at some point, access to a contract’s funds or suicide functionality by finding a way around a planned semantics, and (b) respond about repeating behavioural patterns in the contract life cycles, allowing us to detect greedy contracts.
We begin with defining cotnract execution traces by adopting a low-level execution semantics of an EVM-like language in the form of EtherLite-like calculus . EtherLite implements a small-step stack machine, operating on top of a global configuration of the blockchain, which used to retrieve contract codes and ascribe Ether balance to accounts, as well as manipulations with the local contract configuration. As customary in Ethereum, such agent is represented by its address , and might be a contract itself. For the purpose of this work, we simplify the semantics of EtherLite by eliding the executions resulting in exceptions, as reasoning about such is orthogonal to the properties of interest. Therefore, the configurations of the EtherLite abstract machine are defined as follows:
That is, a contract execution configuration consists of an activation record stack and a blockchain context . An activation record stack is a list of tuples , where and are the address and the code of the contract currently being executed, is a program counter pointing to the next instruction to be executed, is a local operand stack, and is the last message used to invoke the contract execution. Among other fields, stores the identity of the , the amount of the ether being transferred (represented as a natural number), as well as auxiliary fields () used to provide additional arguments for a contract call, which we will be omitting for the sake of brevity. Finally, a simplified context of a blockchain is encoded as a finite partial mapping from an account to its balance and contract code and its mutable state, mapping the field names to the corresponding values,444For simplicity of presentation, we treat all contract state as persistent, eliding operations with auxiliary memory, such as MLOAD/MSTORE. which both are optional (hence, marked with ?) and are only present for contract-storing blockchain records. We will further refer to the union of a contract’s fields entries and its balance entry as a contract state .
Figure 5 presents selected rules for a smart contract execution in EtherLite.555The remaining rules can be found in the work by Luu et al. . The rules for storing and loading values to/from a contract’s field are standard. Upon calling another account, a rule Call is executed, which required the amount of Ether to be transferred to be not larger than the contract ’s current balance, and changes the activation record stack and the global blockchain context accordingly. Finally, the rule SuicideNonEmptyStack provides the semantics for the instruction (for the case of a non-empty activation record stack), in which case all funds of the terminated contract are transferred to the caller’s .
An important addition we made to the semantics of EtherLite are execution labels, which allow to distinguish between specific transitions being taken, as well as their parameters, and are defined as follows:
For instance, a transition label of the form captures the fact that a currently running contract has transferred control to another contract , by sending it a message , while the label would mean a suicide of the current contract, with transfer of all of its funds to the account (a contract’s or not) .
With the labelled operational semantics at hand, we can now provide a definition of partial contract execution traces as sequences of interleaved contract states and transition labels as follows:
A partial projected trace of a contract in an initial blockchain state and an incoming message is defined as a sequence , such that for every , , where is the blockchain state at the occurrence of a configuration of the form, in an execution sequence starting from the configuration , and is a label of an immediate next transition.
In other words, captures the states of a contract , interleaved with the transitions taken “on its behalf” and represented by the corresponding labels, starting from the initial blockchain and triggered by the message . The notation stands for a projection to the corresponding components of the contract entry in . States and transitions of contracts other than and involved into the same execution are, thus, ignored.
Given a (partial) projected trace , we say that it is complete, if it corresponds to an execution, whose last configuration is for some . The following definition captures the behaviors of multiple subsequent transactions with respect to a contract of interest.
A contract trace , for a sequence of messages , is a concatenation of single-transaction traces , where , is a blockchain state at the end of an execution starting from a configuration , and all traces are complete for .
As stated, the definition does not require a trace to end with a complete execution at the last transaction. For convenience, we will refer to the last element of a trace by and to its length as .
The notion of contract traces allows us to formally capture the definitions of buggy behaviors, described previously in Section 2.2. First, we turn our attention to the prodigal/suicidal contracts, which can be uniformly captured by the following higher-order trace predicate.
A contract with an address is considered to be leaky with respect to predicates , and , and a blockchain state (denoted ) iff there exists a sequence of messages , such that for a trace :
the precondition holds,
the side condition holds for all ,
the postcondition holds for .
Definition 3.3 of leaky contracts is relative with respect to a current state of a blockchain: a contract that is currently leaky may stop being such in the future. Also, notice that the “triggering” initial message serves as an argument for all three parameter predicates. We will now show how two behaviors observed earlier can be encoded via specific choices of , , and .666In most of the cases, it is sufficient to take , but in Section 6 we hint certain properties that require a non-trivial side condition.
A contract is considered prodigal if it sends Ether, immediately or after a series of transitions (possibly spanning multiple transactions), to an arbitrary sender. This intuition can be encoded via the following choice of , , and for Definition 3.3:
According to the instantiation of the parameter predicates above, a prodigal contract is exposed by a trace that is triggered by a message , whose sender does not appear in the contract’s state (), i.e., it is not the owner, and the Ether payload of is zero. To expose the erroneous behavior of the contract, the postcondition checks that the transition of a contract is such that it transfer funds or control (i.e., corresponds to CALL, DELEGATECALL or SUICIDE instructions ) with the recipient being the sender of the initial message. In the case of sending funds via CALL we also check that the amount being transferred is non zero. In other words, the initial caller , unknown to the contract, got himself some funds without any monetary contribution! In principle, we could ensure minimality of a trace, subject to the property, by imposing a non-trivial side condition , although this does not affect the class of contracts exposed by this definition.
A definition of a suicidal contract is very similar to the one of a prodigal contract. It is delivered by the following choice of predicates:
That is, a contract is suicidal if its code contains the
instruction and the corresponding transition can be triggered by a message sender, that does not appear in the contract’s state at the moment of receiving the message,i.e., at the initial moment .
A contract is considered locking at a certain blockchain state , if at any execution originating from prohibits certain transitions to be taken. Since disproving liveness properties of this kind with a finite counterexample is impossible in general, we formulate our definition as an under-approximation of the property of interest, considering only final traces up to a certain length:
A contract with an address is considered to be locking with respect to predicates and , the transaction number , and a blockchain state (denoted ) iff for all sequences of messages of length less or equal than , the corresponding trace satisfies:
the precondition ,
the side condition for all .
Notice that, unlike Definition 3.3, this Definition does not require a postcondition, as it is designed to under-approximate potentially infinite traces, up to a certain length ,777We discuss viable choices of in Section 5. so the “final state” is irrelevant.
In order to specify a property asserting that in an interaction with up to transactions, a contract does not allow to release its funds, we instantiate the predicates from Definition 3.4 as follows:
Intuitively, the definition of a greedy contract is dual to the notion of a prodigal one, as witnessed by the above formulation: at any trace starting from an initial state, where the contract holds a non-zero balance, no transition transferring the corresponding funds (i.e., matched by the side condition ) can be taken, no matter what is the ’s identity. That is, this definition covers the case of contract’s owner as well: no one can withdraw any funds from the contract.
is a symbolic analyzer for smart contract execution traces, for the properties defined in Section 3. It operates by taking as input a contract in its bytecode form and a concrete starting block value from the Ethereum blockchain as the input context, flagging contracts that are outlined in Section 2.2. When reasoning about contract traces, follows the EtherLite rules, described in Section 3.1, executing them symbolically. During the execution, which starts from a contract state satisfying the precondition of property of interest (cf. Definitions 3.3 and 3.4), it checks if there exists an execution trace which violates the property and a set of candidate values for input transactions that trigger the property violation. For the sake of tractability of the analysis, it does not keep track of the entire blockchain context (including the state of other contracts), treating only the contract’s transaction inputs and certain block parameters as symbolic. To reduce the number of false positives and confirm concrete exploits for vulnerabilities, calls its concrete validation routine, which we outline in Section 4.2.
Our work concerns finding properties of traces that involve multiple invocations of a contract. We leverage static symbolic analysis to perform this step in a way that allows reasoning across contract calls and across multiple blocks. We start our analysis given a contract bytecode and a starting concrete context capturing values of the blockchain. reasons about values read from input transaction fields and block parameters888Those being CALLVALUE, CALLER, NUMBER, TIMESTAMP, BLOCKHASH, BALANCE, ADDRESS, and ORIGIN. in a symbolic way—specifically, it denotes the set of all concrete values that the input variable can take as a symbolic variable. It then symbolically interprets the relationship of other variables computed in the contract as a symbolic expression over symbolic variables. For instance, the code y :=x + 4 results in a symbolic value for y if x is a symbolic expression; otherwise it is executed as concrete value. Conceptually, one can imagine the analysis as maintaining two memories mapping variables to values: one is a symbolic memory mapping variables to their symbolic expressions, the other mapping variables to their concrete values.
The symbolic interpretation searches the space of all execution paths in a trace with a depth-first search. The search is a best effort to increase coverage and find property violating traces. Our goal is neither to be sound, i.e., search all possible paths at the expense of false positives, nor to be provably complete, i.e., have only true positives at the expense of coverage . From a practical perspective, we make design choices that strike a balance between these two goals.
The symbolic execution starts from the entry point of the contract, and considers all functions which can be invoked externally as an entry point. More precisely, the symbolic execution starts at the first instruction in the bytecode, proceeding sequentially until the execution path ends in terminating instruction. Such instruction can be valid (e.g., STOP, RETURN), in which case we assume to have reached the end of some contract function, and thus restart the symbolic execution again from the first bytecode instruction to simulate the next function call. On the other hand, the terminating instruction can be invalid (e.g., non-existing instruction code or invalid jump destination), in which case we terminate the search down this path and backtrack in the depth-first search procedure to try another path. When execution reaches a branch, concretely evaluates the branch condition if all the variables used in the conditional expression are concrete. This uniquely determines the direction for continuing the symbolic execution. If the condition involves a symbolic expression, queries an external SMT solver to check for the satisfiability of the symbolic conditional expression as well as its negation. Here, if the symbolic conditional expression as well as its negation are satisfiable, both branches are visited in the depth-first search; otherwise, only the satisfiable branch is explored in the depth first search. On occasions, the satisfiability of the expression cannot be decided in a pre-defined timeout used by our tool; in such case, we terminate the search down this path and backtrack in the depth-first search procedure to try another path. We maintain a symbolic path constraint which captures the conditions necessary to execute the path being analyzed in a standard way. implements support for 121 out of the 133 bytecode instructions in Ethereum’s stack-based low-level language.
At a call instruction, control follows transfer to the target. If the target of the transfer is a symbolic expression, backtracks in its depth-first search. Calls outside a contract, however, are not simulated and returns are marked symbolic. Therefore, depth-first search is inter-procedural, but not inter-contract.
The memory mappings, both symbolic and concrete, record all the contract memory as well blockchain storage. During the symbolic interpretation, when a global or blockchain storage is accessed for the first time on a path, its concrete value is read from the main Ethereum blockchain into local mappings. This ensures that subsequent reads or writes are kept local to the path being presently explored.
The EVM machine supports a flat byte-addressable memory, and each address has a bit-width of 256 bits. The accesses are in 32-byte sized words which encodes as bit-vector constraints to the SMT solver. Due to unavailability of source code, does not have any prior information about higher-level datatypes in the memory. All types default to 256-bit integers in the encoding used by . Furthermore, attempts to recover more advanced types such as dynamic arrays by using the following heuristic: if a symbolic variable, say, is used in constant arithmetic to create an expression (say ) that loads from memory (as an argument to the CALLDATALOAD instruction), then it detects such an access as a dynamic memory array access. Here, uses the SMT solver to generate concrete values for the symbolic expression, making the optimistic assumption that the size of the array to be an integer in the range . The parameter is configurable, and defaults to . Apart from this case, whenever accesses in the memory involve a symbolic address, makes no attempt at alias analysis and simply terminates the path being search and backtracks in its depth-first search.
Contracts have several sources of non-deterministic inputs such as the block timestamp, etc. While these are treated as symbolic, these are not exactly under the control of the external users. does not use their concrete values as it needs to reason about invocations of the contract across multiple invocations, i.e., at different blocks.
Finally, when the depth-first search in the space of the contract execution reaches a state where the desired property is violated, it flags the contract as a buggy candidate. The symbolic path constraint, along with the necessary property conditions, are asserted for satisfiability to the SMT solver. We use Z3  as our solver, which provides concrete values that make the input formula satisfiable. We use these values as the concrete data for our symbolic inputs, including the symbolic transaction data.
takes the following steps to bound the search in the (potentially infinite) path space. First, the call depth is limited to the constant called max_call_depth, which defaults to 3 but can be configured for empirical tests. Second, we limit the total number of jumps or control transfers on one path explored to a configurable constant max_cfg_nodes, default set to . This is necessary to avoid being stuck in loops, for instance. Third, we set a timeout of seconds per call to our SMT solver. Lastly, the total time spent on a contract is limited to configurable constant max_analysis_time, default set to seconds.
To speed up the state search, we implement pruning with memorization. Whenever the search encounters that the particular configuration (i.e., contract storage, memory, and stack) has been seen before, it does not further explore that part of the path space.
In the concrete validation step, creates a private fork of the original Ethereum blockchain with the last block as the input context. It then runs the contract with the concrete values of the transactions generated by the symbolic analysis to check if the property holds in the concrete execution. If the concrete execution fails to exhibit a violation of the trace property, we mark the contract as a false positive; otherwise, the contract is marked as a true positive. To implement the validating framework, we added a new functionality to the official go-ethereum package  which allows us to fork the Ethereum main chain at a block height of our choice. Once we fork the main chain, we mine on that fork without connecting to any peers on the Ethereum network, and thus we are able to mine our own transactions without committing them to the main chain.
The validation framework checks if a contract indeed leaks Ether by sending to it the transactions with inputs provided by the symbolic analysis engine. The transactions are sent by one of our accounts created previously. Once the transactions are executed, the validation framework checks whether the contract has sent Ether to our account. If a verifying contract does not have Ether, our framework first sends Ether to the contract and only then runs the exploit.
In a similar fashion, the framework checks if a contract can be killed after executing the transactions provided by the symbolic analysis engine on the forked chain. Note, once a contract is killed, its bytecode is reset to ’0x’. Our framework uses precisely this test to confirm the correctness of the exploit.
A strategy similar to the above two cannot be used to validate the exploits on contracts that lock Ether. However, during the bug finding process, our symbolic execution engine checks firsthand whether a contract accepts Ether. The validation framework can, thus, check if a contract is true positive by confirming that it accepts Ether and does not have CALL, DELEGATECALL, or SUICIDE opcodes in its bytecode. In Section 5 we give examples of such contracts.
We analyzed smart contracts, obtained by downloading the Ethereum blockchain from the first block utill block number , which is the last block as of December , 2017. Ethereum blockchain has only contract bytecodes. To obtain the original (Solidity) source codes, we refer to the Etherscan service  and obtain source for contracts. Only around of the contracts have source code, highlighting the utility of as a bytecode analyzer.
Recall that our concrete validation component can analyze a contract from a particular block height where the contract is alive (i.e., initialized, but not killed). To simplify our validation process for a large number of contracts flagged by the symbolic analysis component, we perform our concrete validation at block height of , further denoted as BH. At this block height, we find that most of the flagged contracts are alive, including the Parity library contract  that our tool successfully finds. This contract was killed at a block height of . All contracts existing on blockchain at a block height of are tested, but only contracts that are alive at BH are concretely validated.999We also concretely validate the flagged candidates which were killed before BH as well.
supports parallel analysis of contracts, and scales linearly in the number of available cores. We run it on a Linux box, with 64-bit Ubuntu 16.04.3 LTS, 64GB RAM and 40 CPUs Intel(R) Xeon(R) E5-2680 email@example.comGHz. In most of our experiments we run the tool on cores. On average, requires around 10.0 seconds to analyze a contract for the three aforementioned bugs: 5.5 seconds to check if a contract is prodigal, 3.2 seconds for suicidal, and seconds for greedy.
The number of contracts has increased tenfold from Dec, to Dec, and -fold since Dec,
. However, the distribution of Ether balance across contracts follows a skewed distribution. Less thanof the contracts have more than of the Ether in the ecosystem. This suggests that a vulnerability in any one of these high-profile contracts can affect a large fraction of the entire Ether balance. Note that contracts interact with each other, therefore, a vulnerability in one contract may affect many others holding Ether, as demonstrated by the recent infamous Parity library which was used by wallet contracts with million US worth of Ether .
|Category||#Candidates flagged (distinct)||Candidates without source||#Validated||% of true positives|
Table 1 summarizes the contracts flagged by . Given the large number of flagged contracts, we select a random subset for concrete validation, and report on the true positive rates obtained. We report the number of distinct contracts, calculated by comparing the hash of the bytecode; however, all percentages are calculated on the original number of contracts (with duplicates).
Our tool has flagged candidates contracts ( distinct) which may leak Ether to an arbitrary Ethereum address, with a true positive rate of around . At block height BH, of these contracts hold some Ether. The concrete validation described in Section 4.2 succeeds for exploits for out of — these are true positives, whereas are false positives. The remaining contracts leak Ether to an address different from the caller’s address. Note that all of the 37 true positive contracts are alive as of this writing. For ethical reasons, no exploits were done on the main blockchain.
Of the remaining contracts which presently do not have Ether on the public Ethereum blockchain, have been killed and have not been published (as of block height BH). To validate the remaining alive contracts (in total ) on a private fork, first we send them Ether from our mining account, and find that contracts can receive Ether.101010These are live and we could update them with funds in testing. We then concretely validate whether these contract leak Ether to an arbitrary address. A total of out of () contracts are confirmed to be true positives; () are false positives.
For each of the contracts killed by the block height BH, the concrete validation proceeds as follows. We create a private test fork of the blockchain, starting from a snapshot at a block height where the contract is alive. We send Ether to the contract from one of our addresses address, and check if the contract leaks Ether to an arbitrary address. We repeat this procedure for each contract, and find that all candidate contracts are true positives.
flags contracts ( distinct), including the ParityWalletLibrary contract, as found susceptible to being killed by an arbitrary address, with a nearly true positive rate. Out of contracts, are alive at BH. Our concrete validation engine on a private fork of Ethereum confirm that contracts (or ) are true positives, i.e., they can be killed by any arbitrary Ethereum account, while contracts (or ) are false positives. The list of true positives includes the recent ParityWalletLibrary contract which was killed at block height by an arbitrary account. Of the contracts flagged, have been killed by BH; we repeat the procedure described previously and cofirmed all of them as true positives.
Our tool flags greedy candidates ( distinct), which amounts to around of the contracts present on the blockchain. The first observation is that deems all but these as accepting Ether but having states that release them (not locking indefinitely). To validate a candidate contract as a true positive one has to show that the contract does not release/send Ether to any address for any valid trace. However, concrete validation may not cover all possible traces, and thus it cannot be used to confirm if a contract is greedy. Therefore, we take a different strategy and divide them into two categories:
Contracts that accept Ether, but in their bytecode do not have any of the instructions that release Ether (such instructions include CALL, SUICIDE, or DELEGATECALL).
Contracts that accept Ether, and in their bytecode have at least one of CALL, SUICIDE or DELEGATECALL.
flagged distinct contracts from the first category. We validate that these contracts can receive Ether (we send Ether to them in a transaction with input data according to the one provided by the symbolic execution routine). Our experiments show that out of (e.g., ) can receive Ether and thus are true positives. On the other hand, the tool flagged distinct contracts from the second category, which are harder to confirm by testing alone. We resort to manual analysis for a subset of these which have source code. Among these, only have Solidity source code. With manual inspection we find that none of them are true positive — some traces can reach the CALL code, but failed to reach it in its path exploration. The reasons for these are mentioned in the Section 5.3. By extrapolation (weighted average across validated), we obtain true positive rate among greedy contracts of .
Recall that posthumous are contracts that are dead on the blockchain (have been killed) but still have non-zero Ether balance. We can find such contracts by querying the blockchain, i.e., by collecting all contracts without executable code, but with non-zero balance. We found contracts at a block height of that do not have any compiled code on the blockchain but have positive Ether balance. Interestingly, among these, contracts have received Ether after they became dead.
Apart from examples presented in section 2.2, we now present true and false postive cases studies. Note that we only present the contracts with source code for readability. However, the fraction of flagged contracts with source codes is very low ().
In Figure 6, we give an example of a prodigal contract. The function tap seems to lock Ether because the condition at line , semantically, can never be true. However, the compiler optimization of Solidity allows this condition to pass when an input greater than 20 bytes is used to call the function tap. Note, on a bytecode level, the EVM can only load chunks of 32 bytes of input data. At line 3 in tap the first 20 bytes of nickname are assigned to the global variable prev, while neglecting the remaining 12 bytes. The error occurs because EVM at line 4, correctly nullifies the 12 bytes in prev, but not in nickname. Thus if nickname has non-zero values in these 12 bytes then the inequality is true. This contract so far has lost Ether to different addresses on real Ethereum blockchain.
A contract may also leak Ether by getting killed since the semantic of SUICIDE instruction enforce it to send all of its balance to an address provided to the instruction. In Figure 7, the contract Thing is inherited from a base contract Mortal. The contract implements a review system in which public reviews an ongoing topic. Among others, the contract has a kill function inherited from its base contract which is used to send its balance to its owner if its killed. The function mortal, supposedly a constructor, is misspelled, and thus anyone can call mortal to become the owner of the contract. Since the derived contract Thing inherits functions from contract Mortal, this vulnerability in the base contract allows an arbitrary Ethereum account to become the owner of the derived contract, to kill it, and to receive its Ether.
A contract can be killed by exploiting an unprotected SUICIDE instruction. A trivial example is a public kill function which hosts the suicide instruction. Sometimes, SUICIDE is protected by a weak condition, such as in the contract Dividend given in Figure 8. This contract allows users to buy shares or withdraw their investment. The logic of withdrawing investment is implemented by the withdraw function. However, this function has a self_destruct instruction which can be executed once the last investment has been made more than weeks ago. Hence, if an investor calls this function after weeks of the last investment, all the funds go to the owner of the contract and all the records of investors are cleared from the blockchain. Though the ether is safe with the owner , there would be no record of any investment for the owner to return ether to investors.
In the previous example, one invocation of withdraw function was sufficient to kill the contract. There are, however, contracts which require two or more function invocations to be killed. For instance, the contract Mortal given in Figure 7 checks whether it is the owner that calls the kill function. Hence, it requires an attacker to become the owner of the contract to kill it. So, this contract requires two invocations to be killed: one call to the function mortal used to become an owner of the contract and one call to the function kill to kill the contract. A more secure contract would leverage the mortal function to a constructor so that the function is called only once when the contract is deployed. Note, the recent Parity bug similarly also requires two invocations .
The contract SimpleStorage, given in Figure 9, is an example of a contract that locks Ether indefinitely. When an arbitrary address sends Ether along with a transaction invoking the set function, the contract balance increases by the amount of Ether sent. However, the contract does not have any instruction to release Ether, and thus locks it on the blockchain.
The payable keyword has been introduced in Solidity recently to prevent functions from accepting Ether by default, i.e., a function not associated with payable keyword throws if Ether is sent in a transaction. However, although this contract does not have any function associated with the payable keyword, it accepts Ether since it had been compiled with an older version of Solidity compiler (with no support for payable).
We manually analyze cases where ’s concrete validation fails to trigger the necessary violation with the produced concrete values, if source code is available.
In both of the classes, false positives arise due to two reasons:
Our tool performs inter-procedural analysis within a contract, but does not transfer control in cross-contract calls. For calls from one contract to a function of another contract, assigns symbolic variables to the return values. This is imprecise, because real executions may only return one value (say true) when the call succeeds.
may assign values to symbolic variables related to block state (e.g., timestamp and blocknumber) in cases where these values are used to decide the control flow. Thus, we may get false positives because those values may be different at the concrete validation stage. For instance, in Figure 11, the _guess value depends on the values of block parameters, which cannot be forced to take on the concrete values found by our analyzer.
The large share of false positives is attributed to two causes:
Detecting a trace which leads to release of Ether may need three or more function invocations. For instance, in Figure 10, the function confirmTransaction has to be executed by the majority of owners for the contract to execute the transaction. Our default invocation depth is the reason for missing a possible reachable state.
Our tool is not able to recover the subtype for the generic bytes type in the EVM semantics.
Some contracts release funds only if a random number (usually generated using transaction and block parameters) matches a predetermined value unlike in the case of the contract in Figure 11. In that contract the variable _guess is also a symbolic variable, hence, the solver can find a solution for condition on line . If there is a concrete value in place of _guess, the solver times out since the constraint involves a hash function (hard to invert by the SMT solver).
The symbolic execution engine of flags contracts. With concrete validation engine or manual inspection, we have confirmed that around of prodigal, of suicidal and of greedy contracts are true positive. The importance of analyzing the bytecode of the contracts, rather than Solidity source code, is demonstrated by the fact that only of all contracts have source code. Further, among all flagged contracts, only have verified source codes according to the widely used platform Etherscan, or in percentages only , and , in the three categories of prodigal, suicidal, and greedy, respectively. We refer the reader to Table 1 for the exact summary of these results.
Furthermore, the maximal amount of Ether that could have been withdrawn from prodigal and suicidal contracts, before the block height BH, is nearly Ether, or million US dollars111111Calculated at USD/Eth . according to the exchange rate at the time of this writing. In addition, Ether ( million US dollars) is locked inside posthumous contracts currently on the blockchain, of which Ether ( US dollars) have been sent to dead contracts after they have been killed.
Finally, the analysis given in Table 2 shows the number of flagged contracts for different invocation depths from to . We tested contracts being for greedy, and for remaining categories, inferring that increasing depth improves results marginally, and an invocation depth of is an optimal tradeoff point.
The early work by Delmolino et al.  distinguishes the following classes of problems: (a) contracts that do not refund their users, (b) missing encryptions of sensitive user data and (c) lack of incentives for the users to take certain actions. The property (a) is the closest to our notion of greedy. While that outlines the problem and demonstrates it on series of simple examples taught in a class, they do not provide a systematic approach for detection of smart contracts prone to this issue. Later works on contract safety and security identify potential bugs, related to the concurrent transactional executions , mishandled exceptions , overly extensive gas consumption  and implementations of fraudulent financial schemes .121212See the works [27, 28] for a survey of known contract issues.
In contrast to all those work, which focus on bad implementation practices or misused language semantics, we believe, our characterisation of several classes of contract bugs, such as greedy, prodigal, etc, is novel, as they are stated in terms of properties execution traces rather than particular instructions taken/states reached.
Oyente [2, 3] was the first symbolic execution-based tool that provided analysis targeting several specific issues: (a) mishandled exceptions, (b) transaction-ordering dependence, (c) timestamp dependence and (d) reentrancy , thus remedying the corner cases of Solidity/EVM semantics (a) as well as some programming anti-patterns (b)–(d).
Other tools for symbolic analysis of EVM and/or EVM have been developed more recently: Manticore , Mythrill [15, 16], Securify , and KEVM [30, 31], all focusing on detecting low-level safety violations and vulnerabilities, such as integer overflows, reentrancy, and unhandled exceptions, etc, neither of them requiring reasoning about contract execution traces. A very recent work by Grossman et al.  similar to our in spirit and providing a dynamic analysis of execution traces, focuses exclusively on detecting non-callback-free contracts (i.e., prone to reentrancy attacks)—a vulnerability that is by now well studied.
Concurrently with our work, Kalra et al. developed Zeus , a framework for automated verification of smart contracts using abstract interpretation and symbolic model checking, accepting user-provided policies to verify for. Unlike , Zeus conducts policy checking at a level of LLVM-like intermediate representation of a contract, obtained from Solidity code, and leverages a suite of standard tools, such as off-the-shelf constraint and SMT solvers [33, 34, 19]. Zeus does not provide a general framework for checking trace properties, or under-approximating liveness properties.
Various versions of EVM semantics  were implemented in Coq , Isabelle/HOL [36, 37], , Idris , and Why3 [40, 41], followed by subsequent mechanised contract verification efforts. However, none of those efforts considered trace properties in the spirit of what we defined in Section 3.
Several contract languages were proposed recently that distinguish between global actions (e.g., sending Ether or terminating a contract) and instructions for ordinary computations [42, 43], for the sake of simplified reasoning about contract executions. For instance, the work on the contract language Scilla  shows how to encode in Coq  and formally prove a property, which is very similar to a contract being non-leaky, as per Definition 3.3 instantiated with a non-trivial side condition .
We characterize vulnerabilities in smart contracts that are checkable as properties of an entire execution trace (possibly infinite sequence of their invocations). We show three examples of such trace vulnerabilities, leading to greedy, prodigal and suicidal contracts. Analyzing contracts, our new tool flags thousands of contracts vulnerable at a high true positive rate.
K. L. McMillan, “Interpolants and Symbolic Model Checking,” inVMCAI, ser. LNCS, vol. 4349. Springer, 2007, pp. 89–90.