Exploiting The Laws of Order in Smart Contracts

10/27/2018 ∙ by Aashish Kolluri, et al. ∙ 0

We investigate a family of bugs in blockchain-based smart contracts, which we call event-ordering (or EO) bugs. These bugs are intimately related to the dynamic ordering of contract events, i.e., calls of its functions on the blockchain, and enable potential exploits of millions of USD worth of Ether. Known examples of such bugs and prior techniques to detect them have been restricted to a small number of event orderings, typicall 1 or 2. Our work provides a new formulation of this general class of EO bugs as finding concurrency properties arising in long permutations of such events. The technical challenge in detecting our formulation of EO bugs is the inherent combinatorial blowup in path and state space analysis, even for simple contracts. We propose the first use of partial-order reduction techniques, using happen-before relations extracted automatically for contracts, along with several other optimizations built on a dynamic symbolic execution technique. We build an automatic tool called ETHRACER that requires no hints from users and runs directly on Ethereum bytecode. It flags 7-11 contracts analyzed in roughly 18.5 minutes per contract, providing compact event traces that human analysts can run as witnesses. These witnesses are so compact that confirmations require only a few minutes of human effort. Half of the flagged contracts have subtle EO bugs, including in ERC-20 contracts that carry hundreds of millions of dollars worth of Ether. Thus, ETHRACER is effective at detecting a subtle yet dangerous class of bugs which existing tools miss.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 1

page 14

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

I Introduction

A blockchain/cryptocurrency protocol enables a distributed network of mutually-untrusting computational nodes (miners) to agree on the current state and complete history of a replicated public ledger. The dominant consensus algorithm was invented to facilitate decentralized payments in virtual currencies [29], but it has since been extended to the decentralized applications commonly known as smart contracts [40]. A typical smart contract on a blockchain is a stateful program, i.e., a package of code and the mutable data that describes the contract’s current state, similar to an object in an OOP language. Both the code and the data are stored in a replicated fashion on the blockchain. Every smart contract transaction (invocation of contract code) is totally ordered, as agreed upon by a majority of miners, and replicated across the system.

Smart contracts implement some domain-specific logic to act as automatic and trustworthy mediators. Typical applications include multi-party accounting, voting, arbitration mechanisms, auctions, and puzzle-solving games with distribution of rewards. The dominant smart contract-enabled blockchain today is Ethereum [42], whose native token Ether has a market capitalization over billion USD.111As of this writing, one Ether is 203 USD. Over a million smart contracts have been deployed to Ethereum’s blockchain. Numerous publicly reported attacks have resulted in hundreds of millions dollars’ worth of Ether being stolen or otherwise lost [10, 22]. Further, contracts cannot be patched once deployed. This emphasizes the importance of pre-deployment security audit and analysis of smart contracts.

This paper investigates a class of vulnerabilities in smart contracts that arise due to their inherent concurrent execution model. Contracts can be invoked by multiple users concurrently, and the ordering of multiple submitted transactions is non-deterministically decided by miners through a consensus protocol. Contracts can invoke other contracts synchronously and call off-chain services asynchronously which return in no pre-determined order. As Ethereum contracts are stateful, mutations of contract data persist between invocations. Therefore, predicting the result from a set of transactions invoking a contract requires reasoning about the non-deterministic order of concurrently-interacting transactions. Developers often write contracts assuming a certain serialized execution order of contracts, missing undesirable behaviors only observable in complex interleavings. Such reasoning has classically been difficult for human auditors. Accordingly, tools that allow developers, auditors, and smart contract users to increase confidence that contracts behave as expected are useful.

Certain concurrency bugs in Ethereum smart contract are known. For instance, prior work has highlighted how a pair of transactions, when reordered, can cause contracts to exhibit differing Ether transfers as output [22]. Similarly, susceptibility of contracts to asynchronous callbacks has been identified previously [4, 34]. However, the full generality of bugs arising from unexpected ordering of eventsi.e. calls to contract functions invoked via transactions and callbacks — has neither been systematically tested nor fully understood yet.

The key encumbering challenge is that analyzing contracts under multiple events spread over many transactions leads to combinatorial blowup in state-space to be checked. Existing tools are thus designed to avoid search of large path space, by checking for properties of often single or a pair of events. For instance, the infamous re-entrancy bugs such as theDao can be found with checking if a function can call itself within a single transaction execution [16], while transaction ordering bugs reported by the Oyente tool check a pair of events [22].

Problem & Approach

In this work, we develop new and efficient analysis techniques for Ethereum smart contracts under multiple events. Our work generalizes beyond several previous classes or errors into a broader category of concurrency errors we call event-ordering (EO) bugs. The core idea is to check whether changing the ordering of input events (function invocations) of a contract results in differing outputs. If a contract exhibits differing outputs under reordered input events, it is flagged as an EO bug; otherwise, event re-ordering produces no impact on outputs and so the contract is EO-safe.

Our formulation of EO bugs deepens the connection between contracts and concurrent objects in traditional programming languages [34, 16]. Specifically, we show how to directly juxtapose the events of a contract with atomic operations of shared memory objects. This results in phrasing properties of contracts directly as traditional concurrency properties. For instance, we observe that asynchronous calls to off-chain services follow a call/return pattern and can lead to a violation of linearizability [17, 35]. Further, we show that contracts are susceptible to event races because of improper enforcement of synchronization properties [33, 12]. We find hundreds of live contracts with previously unknown errors, highlighting that programmers often desire these properties but fail to enforce them in implementation.

To tackle combinatorial path and state explosion, we develop a number of optimization techniques. Furthering the “contracts-as-concurrent-objects” analogy, we show that partial-order reduction techniques can be applied to contract analysis. Specifically, if two functions can only be invoked in certain order (or else an exception results), or re-ordering a set of functions yields the same output, then event combinations eliminate repeatedly enumerating such sequences. This concept is captured by the classical happens-before (HB) relation [21]. Unlike traditional programming languages which have explicit synchronization primitives, smart contracts try to implement desirable concurrency controls using ad-hoc program logic and global state. We show how to recover the intrinsic HB-relation encoded in contract logic, and that it substantially reduces the event combinations to check.

EthRacer

Our central practical contribution is an automatic tool to find EO bugs called EthRacer. We use it to measure the prevalence of these vulnerabilities over ten thousand contracts. Less than of live contracts are accompanied by source code; hence, our tool does not require source and analyzes Ethereum bytecode directly. This enables third-party audit and testing of contracts without source. We take a dynamic testing approach, constructing inputs and systematically trying all possible function orderings until a budgeted timeout is reached. Done naïvely, this approach would quickly lead to an intractable analysis even for relatively small contracts, since function calls to a contract can have orderings. Our approach combines symbolic execution of contract events with fast randomized fuzzing of event sequences. Our key optimizations exponentially reduce the search space by eliminating orderings which violate the recovered HB-relation between events or re-order pure (side-effect-free) events. Further, EthRacer prioritizes the search for linearizability violations in asynchronous callbacks. EthRacer reports only true EO violations, accompanied by witnesses of event values that can be concretely executed.

Empirical Results

First, we show that most contracts do not exhibit differences in outputs, and that when contracts do exhibit different outputs upon re-ordering, they are likely to have an unintended behavior in more than of cases. We find a total of () violations of synchronization properties and () violations of linearizability properties in our analysis of over ten thousand contracts. Therefore, our formulation of properties catches a subtle and dangerous class of EO bugs, without excessively triggering alarms.

Second, we show that our characterization of EO bugs substantially generalizes beyond bugs known from prior works. A direct comparison to prior work on Oyente, which focuses on specific sub-class of synchronization violations, shows that EthRacer find all the true EO bugs Oyente finds and many more ( in total) which are not detected by prior work. Similarly, it finds linearizability violations that are not captured by any prior work, generalizing this class of errors beyond DAO-style re-entrancy [10] or id-tracking bugs that fail to pair asynchronous calls and callbacks [34]. We find many bugs in popular ERC-20 [13] compliant contract (ubiquitously used for “ICO”s), casino/lottery, bounties, contests, and escrow service contracts. The flagged contracts include both old and recent; these contracts have processed millions of transactions, holding hundreds of millions of dollars worth of Ether over their lifetime. The results stem directly from our efficiency-enhancing techniques — for of the contracts we analyzed, EthRacer produced results at an average of minutes per contract.

Lastly, our EthRacer minimizes human effort in analysis of results. When EthRacer reports EO violations, it provides concrete witnesses that exhibit differing outputs. Typically there are very few (e.g., 1–3) cases that require human inspection to confirm and fix, a process that typically requires only a few minutes per contract. Since contracts are not patchable after deployment, we believe that EthRacer is useful auditing tool: it flags about of contracts it analyzes, reporting less witnesses for analysts to inspect, over half of which have yielded subtle EO bugs.

Ii Motivation

Ii-a Ethereum Smart Contracts

Smart contracts in Ethereum are identified by addresses. An Ethereum user invokes a particular function of a smart contract by creating a signed transaction to the contract’s address. The transaction specifies which function is being executed and its arguments. The user submits the transaction to the Ethereum network, and at some point in the future, a miner in the network chooses to process the transaction. Accordingly, the miner takes the current state of the contract from the blockchain, executes the called function, and stores the updated state of the contract back into the blockchain.

It is entirely possible for two users to submit transactions that interact with the same contract at the same time. Neither of the users knows which transactions will first be processed by the miners. What is guaranteed is that if a user’s transaction is incorporated into the blockchain, then its effect will be reflected atomically. In other words, a miner will not execute part of the first transaction, switch to running the second at some intermediate contract state, and then return to finish off the remaining computation in the first transaction. It is easy to assume that this transaction atomicity removes the need to reason about the concurrent execution environment, but this is not the case, as we explain next.

Ii-B Event-Ordering Bugs

Contracts can be seen as objects with mutable state and set of interfaces for users to access it. As explained, their interfaces can be invoked by many users simultaneously; the order in which the “calls” will be invoked is determined entirely by the mining network. Phrased this way, one can readily see the analogy between concurrent data structures in traditional programming languages and smart contracts. Traditional programming languages provide programming abstractions to allow concurrent data structures to ensure certain desired properties. We explain two properties here which are desirable by smart contracts, violations of which result in errors defined as event-ordering (or EO) bugs.

Linearizability

Linearizability is a well-known property used in concurrent data structures [17]. Loosely stated, it means that an operation should appear to execute atomically or instantaneously from each user’s view. A simple example of this is the operation of incrementing a global counter, shared between two threads in a classical programming language. The counter implementation reads the counter value, increment by one, and write the result back. If two users invoke simultaneously when the counter value is zero, a poor implementation may result in a final value of rather than , if reads of two user requests execute before both the writes; the last write would overwrite the other. Linearizability guarantees that no interleavings would result in such outcome. Specifically, the result should match the sequential execution of one request being served after another, either order being legal. Traditional language and hardware instructions, such as atomic swap and compare-and-exchange operations, allow programmers to achieve linearizability through mutual exclusion.

1contract Casino {
2  ...
3  function bet() payable {
4    // make sure we can pay out the player
5    if (address(this).balance < msg.value * 100 ) throw; 
6    bytes32 oid = oraclize_query(...); // random 
7    bets[oid] = msg.value; 
8    players[oid] = msg.sender; 
9  } 
10
11  function __callback(bytes32 myid, string result) 
12    onlyOraclize onlyIfNotProcessed(myid) {
13    ...
14    if (parseInt(result) % 200 == 42) 
15    players[myid].send( bets[myid] * 100 ); 
16  }
17  ...
18}
Figure 1: Contract Casino with a linearizability violation

Ethereum contracts desire linearizability, but the transaction atomicity provided by the platform does not guarantee it. Consider the Casino snippet in Figure 1, which was simplified from a real game-of-chance smart contract. Casino

accepts bets from one or more players and, with 200-to-1 odds (Line 

1), repays winners 100 fold (Line 1). The Casino aims to be honest, so it rejects any bet that it would be unable to honor if won (Line 1). The fairness of any game of chance depends crucially on how random values are generated. Casino utilizes a trusted off-chain random number generator which is invoked by the Oraclize API [31] query (Line 1). The random-number oracle is not actually queried in Line 1; instead calling oraclize_query does two things: generates the unique transaction id tag oid and notifies a trusted off-chain monitor (the Oraclize service) that Casino wishes to query the random-number oracle for transaction oid. Due to the semantics of Ethereum, the off-chain monitor will be notified only once Casino’s code has finished running (Line 1). Before this occurs, Casino stores transaction-related information for its later retrieval (Lines 11). Later, once the off-chain oracle has been queried, the off-chain Oraclize service will make a fresh call back into the Ethereum chain at Casino’s callback function __callback (Line 1).

The __callback function “returns” asynchronously. After an initial bettor initiates an oracle query, other bettors can place their bets while the off-chain oracle is queried. These further wagers will initiate further oracle queries, and depending on the behavior of the off-chain oracles, their corresponding callbacks may not be invoked in the same order as they are called. The designers of the Oraclize API are aware of this, which is why each transaction is given a unique ID that is both returned from oraclize_query (Line 1) and passed to the callback (the myid parameter in Line 1), thereby “pairing” the two. Failing to pair callbacks can lead to previously-published vulnerabilities [34], but this error is avoided by Casino by the use of myId in Line 1, which carefully disambiguates bets from multiple users.

Even though the call/return are correctly paired, there is a bug that can occur when multiple players place bets concurrently. Suppose that the contract currently has 100 Ether and that two players wish to bet 1 Ether. Consider the following execution of functions: ; ; ; , where the subscript denotes the paired identifier (oid/myId) in the Oraclize interface. Both bets are accepted (Line 1), but if both bets win (Line 1), player two will not be paid in full. A fairer Casino implementation should have considered all pending bets when determining if it can accept another.

This example highlights exactly why linearizability is desirable—the call-return of Oraclize calls for two users should appear to execute sequentially (or atomically). This contract yields differing outputs if the responses of callbacks are received out-of-order. The ordering presented above yields an insufficient balance after paying off player 1 (Line 1). In the alternative ordering ; ; ; , will decline the second bet due to the check on Line 1, making Casino fair.

1contract IOU {
2 // Approves the transfer of tokens
3 function approve(address _spender, uint256 _val) { 
4   allowed[msg.sender][_spender] = _val;
5   return true;
6 }
7 // Transfers tokens
8 function transferFrom(address _from, address _to,
9                       uint256 _val) {
10   require(
11     allowed[_from][msg.sender] >= _val
12     && balances[_from] >= _val
13     && _val > 0);
14   balances[_from] -= _val;
15   balances[_to] += _val;
16   allowed [_from][msg.sender] -= _val; 
17   return true;
18 }
19}
Figure 2: Contract IOU with a synchronization violation
Synchronization

Synchronization is the idea that two processes order their execution, such that certain operations execute in a desired order. The classical example of this is the producer-consumer problem for a shared queue, where a producer process should not write to a full buffer, and a consumer process does not read out of an empty one. Typically, synchronization is implemented with abstractions such as semaphores in traditional languages.

Smart contracts implicitly desire to order multiple user requests in a particular sequence, but sometime fail to. As an example, Figure 2 shows a shortened version of a contract that implements ERC-20-compliant tokens in Ethereum dubbed “IOU”s. As of today, ERC-20-compliant contracts manage tokens valued in the billions of USD. The snippet in Figure 2 allows an owner “” of IOUs to delegate control of a certain _val of IOU tokens to a specified _spender’ (e.g., an expense account). calls the approve function (Line 2) to allocate _val IOU tokens to , and the function transferFrom allows to send a portion (_val) of the IOU allocation to an address of ’s choice (_to). The approver is allowed to update the allocation any time: for instance, may initially approve IOU and later reduce the amount to by calling approve again. Although this may seem like a reasonable idea, as pointed out on public forums [1], this contract has undesirable semantics, since can execute a transferFrom between the two calls to approve, thereby spending the first allocation of and then having another to spend. In other words, two different executions of calls to approve and transferFrom lead to different outcomes:

Execution 1 Execution 2
approve$_O$(S, 300) approve$_O$(S, 300)
approve$_O$(S, 100) transferFrom$_S$(O, S, 100)
transferFrom$_S$(O, S, 100) approve$_O$(S, 100)
has spent 100 and has spent 100 and
can spend 0 more can spend 100 more

The community has proposed a fix that corresponds directly to the desired synchronization property: forbidding a change an allocation once made [1]. The recommendation ensures that all calls to transferFrom by all users should be permitted strictly after all approve have happened. Newer ERC-20 contracts have deployed this fix and behave more reasonably if such order is enforced, since the second approve is rejected in the second ordering.

Iii Overview

We define a class of bugs which we call event-ordering bugs, generalizing the examples presented in Section II. We explain the inherent path and state space exploration complexity, and propose our design to address these systematically.

Iii-a Problem

A contract function can be invoked by an external transaction, an internal call from another contract, or via an off-chain asynchronous callbacks. We call these invocations as events. Under a received event, a contract executes atomically, which we call as run. A sequence of events invokes a sequence of contract runs, which is referred to as a trace in this paper. Each run of a contract can modify its Ether balance and global state which is stored on the blockchain, as well as generate new transactions that transfer Ether or call other contracts. We say that the output of a run or a trace are values of the contract balance, state, and any resulting transactions. We define these terms more precisely in Section IV-A.

An event-ordering (EO) bug exists in a contract if two different traces, consisting of the same set of concrete events, produce different outputs. These bugs may arise as a result of violations of both linearizability and synchronization properties. We seek to check if a given smart contract has an event-ordering (EO) bug.

Note that our problem formulation is more general than previous works which introduced a sub-class of synchronization mistakes called transaction ordering bugs (TOD) [22] (see Section VI-D for details). TOD bugs capture only a pair of events and are restricted only to differences in contract’s balance. Our EO bug formulation is also orthogonal to the concept of re-entrancy bugs, such as the infamous DAO, since it does not correspond to re-ordering of events. A re-entrancy bug occurs when a contract exhibits different outputs under two different values of a single event, one of which outputs a transaction which could subsequently cause a critical function to re-enter itself. Re-entrancy bugs are detected by several tools that analyze runs from different values for a single event [22, 16].

Figure 3: Main components of EthRacer.

Our goal is to efficiently check if re-ordering of input events of a contract across multiple transactions leads to differing outputs. We design a novel analysis technique and an automatic tool called EthRacer to find such contracts. As shown in Figure 3, EthRacer works directly on the EVM bytecode of Ethereum smart contracts, given as input along with the public blockchain data. When an EO violation is detected, the output of EthRacer is minimally two traces, consisting of at least two distinct events, which concretely exhibit different outputs when executed on the Ethereum virtual machine (EVM) from the given blockchain state.

Intent vs. Bugs

Unlike vulnerabilities arising due to language design, such as memory safety, the bugs uncovered in the present work tend to arise from logical errors between a contract’s intended behavior and its actual implementation. Smart contracts are usually deployed anonymously and without public documentation; the true motivations for their deployment can be very obscure. It is possible that our flagged test cases detected as having output differences are desired by the developer. Without documentation, to say nothing of a formal specification, it is difficult to give a precise definition for “intended behavior”, forcing us to fall back on the notion of “know a bug when we see it” [39]. In reality, the debate between intention and formal behavior is sometimes so contentious that it is resolved in the community by agreeing on a so-called hard fork [10].

Therefore, our focus in this work is merely on efficient analysis techniques that minimize the number of test configurations the developer or potential user has to manually inspect, before the contract is deployed irrevocably.

Iii-B Our Solution

We take a dynamic testing approach to finding EO bugs. This is primarily motivated by our goal to produce concrete witnesses that human analysts can replay to inspect and confirm EO bugs with no false violations. The technical barrier to finding reordering bugs is that the path and the state space of the analysis blows up combinatorially. To illustrate this, we consider a baseline solution that (say) can reason about the behavior of the contract under different values of a single input event. One effective state-of-the-art technique for such analysis is based on dynamic symbolic execution (DSE), implemented by many existing analyses for contracts [30, 18, 19].

In concept, such a DSE engine can enumerate different paths and input values (symbolically) in the program, starting from one event entry point function, and check if two different paths could lead to different outputs. However, this baseline solution does not address the class of EO bugs sought directly. EO bugs are a result of two or more events with possibly different entry points, leading to two different paths and outputs. Therefore, even with a powerful DSE engine, checking the space of events requires reasoning about traces with all permutations of these events. Different traces can result in different intermediate values global variables or states, leading to different paths. Enumerating such a path space is prohibitively expensive; for instance, even a (relatively short) sequence of calls to the contract with (say) 20 functions callable via events can have millions of traces to inspect.222E.g., for a typical contract with 20 functions, each with 2 different inputs, the number of traces composed of 6 events is . Therefore, we seek techniques that can eliminate a large part of this path and state space exploration, making search tractable.

Key Ideas

The first observation is that contract functions are often written in a way that have a certain intended order of valid execution. Executing them in a different order simply results in the contract throwing an exception. This can be captured as a partial-order between events or as a happens-before (or ) relation. Two events and are in a relation if executing them in one order produces a valid trace while the other is invalid. The insight is that if we find pairs of events and that are in relation, all333The savings can be exponential, since there are exponentially many permutations with a shared pair of re-ordered functions. permutations of events involving these in an invalid order will result in exception—therefore, these are redundant to test repeatedly. To maximize the benefit of this observation, we augment the baseline DSE to recognize events which are in the relation. The symbolic analysis reasons about a large set of event values encoded as symbolic path constraints, which helps to identify partially-ordered events much better than fuzzing with concrete values. We present our novel and efficient algorithm to extract a new notion of relations we define in Section IV-C.

Our second observation is that certain events produce the same outputs irrespective of which order they appear in any trace. A simple, but highly effective, optimization in our experiments is to recognize events runs that do not write or read global state. Such events can be arbitrarily re-ordered without changing outputs, so testing permutations that simply re-order these is unnecessary. Similarly, events that do not write to any shared global state can be arbitrarily re-ordered. Hence, they are also not considered for extracting HB relation.

The final key observation deals primarily with optimizing for linearizability violations. This optimization aims to prioritize the search for event traces involving asynchronous callbacks and reduces flagging EO violations that may be benign. If we only consider traces where each Oraclize and its matching callbacks are sequentially or atomically executed, these orders likely yield intended (benign) outputs. Such traces, in which events corresponding to different user requests are not “out-of-order”, are defined as linearizable. We explain this concept of linearizability more precisely as applied to contracts in Section IV-D. The task of analysis reduces to finding if there exist traces that do not produce outputs same as that of some linearizable trace. Orders which result in the same output as a linearizable traces are likely not bugs (can be suppressed in reporting), and those which produce differing outputs are flagged as an EO bug. So, our search procedure prioritizes constructing linearizable traces first and then symbolically analyzing for event runs which may yield different outputs to those linearizable traces.

In summary, EthRacer uses the baseline DSE technique to prune and prioritize its search in the path space of runs in multi-event traces. The final concrete values generated from DSE are run on the Ethereum Virtual Machine (EVM) from the provided blockchain state. As a result, in practice, testing for EO bugs takes about minutes for with EthRacer with traces of length , and can be configured to test for longer traces arbitrarily. We detail the tool design details in Section II.

Iv EthRacer Design

The notion of concurrency in contracts requires a careful mapping to the familiar concepts of concurrency. We begin by recalling the contract concurrency model and by precisely defining terminology used informally thus far. We utilize the notation used in a distilled form of Ethereum Virtual Machine (EVM) semantics called EtherLite calculus [22]. Then, we move to design detail of our tool EthRacer.

Iv-a Contract Concurrency Model & Terminology

Recall that each event executes atomically and deterministically in Ethereum [42]. A miner runs the contract function, conceptually as a single thread, with provided inputs of an event until it either terminates or throws an exception.444Here, we do not draw a difference between different origins of exceptions, i.e., those raised programmatically or those triggered by insufficient gas. If it terminates successfully, all the changes to the global variables of that execution are committed to the blockchain; if an exception is thrown, none of the changes during the execution under that event are committed to the blockchain. In this sense, the execution of one event is atomic. The source of concurrency lies in the non-deterministic choices that each miner makes in ordering transaction in a proposed block.

Contract states and instances

We recall the definitions of global blockchain state, contracts and messages from EtherLite calculus by Luu et al. [22].

A global blockchain state 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, which both are optional (marked with “?”) and are only present for contract-storing blockchain records. We refer to the union of a contract’s field entries and its balance entry as a contract state ,555We will also overload the notation, referring to a state of a contract in a blockchain state as , thus, ignoring its component. and denote a triple of a contract with an account , the code of which is and state is , as contract instance .

Messages (ranged over by ) are encoded as mappings from identifiers to heterogeneous values. In particular, each message stores the identity of its and destination (), the amount of Ether being transferred (represented as a natural number), a contract function name to be invoked, as well as auxiliary fields () containing additional arguments for a contract function, which we will be omitting for brevity.

We will refer to a value of an entry of a message  as .

Contract Events

The notion of events captures external inputs that can force control into an entry point of a contract, and defined as follows.

Definition 1 (Events).

An event for a contract instance is a pair , where is a name of a function, defined in , and is a message passed containing arguments to , such that .666In EVM, function names are not different from other fields of the message, but here we make this separation explicit for the sake of clarity.

Below, we will often refer to the -component of an event as its context. Our contract events are coarse-grained: they are only identified by entry points and inputs to a specific contract. Event executions of a contract may invoke other contracts, which only return values as parameters. Externally called contracts may modify their own local state, but such external state is not modelled.

Event runs and traces

The run of an event at a contract instance brings it to a new state , denoted . A sequence of events is called an event trace. An evaluation of contract at instance over an event trace yielding instance is denoted as , which can be obtained by executing all events from consecutively, updating the global blockchain state correspondingly between the steps. Some evaluations may halt abnormally due to a runtime exception, denoted as . We call such invalid traces (at ). Conversely, valid traces evaluate at instance without an exception to a well-formed state , which is implicit when we write . We now define an event ordering bug.

Definition 2 (Event-ordering bug).

For a fixed contract instance , a blockchain state , such that , a pair of valid event traces and constitutes an event-ordering bug iff

  • is a permutation of events in ,

  • if and , then .

Due to the coarse-grained nature of events, targeting multi-transactional executions, our concurrency model does not capture reentrancy bugs [36]. Furthermore, for the sake of tractability, the definition is only concerned with a single contract at a time, even though involved events may modify state of other contracts. This means that our notion of EO bugs only captures the effects on a local state of a contract in focus and won’t distinguish between states of other contracts.777One could generalize Definition 2, allowing to catch bugs resulting in transferring money to one beneficiary instead of another, without any local accounting. In this work, we do not address this class of non-local EO bugs.

Iv-B The Core Algorithm

EthRacer basic algorithm systematically enumerates all traces of up to a bounded length of events (configurable), for a given smart contract. One could employ a purely random dynamic fuzzing approach to generating and testing event traces. However, even for a single entry point, different input values may exercise different code paths. In contrast, symbolic execution is a useful technique to reason about each code path, rather than enumerating input values, efficiently. One could consider a purely symbolic approach that checks properties of code paths encoded fully symbolically, to check for output differences. EthRacer uses an approach that combines symbolic analysis of contract code and randomized fuzzing of event trace combinations in a specific way to find EO bugs.

Symbolic Event Analysis

EthRacer first performs a syntactic analysis to recover the public functions in the bytecode of the contract. These functions are externally callable, and thus, these are entry points for each event. Next, EthRacer employs standard dynamic symbolic execution technique to reason about the outputs of each event separately. For each event , the analysis marks the event inputs as symbolic and gathers constraints down all paths starting from the entry point of . Modulo implementation caveats (as detailed in Section V-B), our analysis creates constraints that over-approximate the set of values that drive down a specified path. Note that since the symbolic analysis aims to over-approximate the feasible paths concretely executable under an event, it does not need to always check the feasibility of each path exactly using SMT solvers in this phase. As a result of this analysis, for each event

, we obtain a vector of symbolic constraints (as SMT formulae)

that encode path constraints for the set of execution paths starting from the entry point of . The pairs is referred to as a symbolic event.

Concretization

Conceptually, symbolic events are concretized in two separate steps. The first concretization step is standard in DSE system, where concretization aids path exploration. Specifically, the feasibility of each path constraints in using an off-the-shelf SMT solver. We eliminate symbolic path constraints for paths that become infeasible. The SMT solver finds satisfying concrete input values for feasible paths. By executing these inputs on the blockchain state given as input to EthRacer, it can find concrete values of global symbolic variables read before being clobbered, call targets, and transaction input contexts. These values can concretize symbolic constraint in . This concretization makes the symbolic constraints less general, but allow pruning away a lot of false positives that due to assuming infeasible paths or values of symbolic inputs. All our prioritization and pruning optimization outlined later in this Section use the outputs of this concretized symbolic execution for analysis.

The second concretization step aims to create one or more concrete value for each path that remains in . The goal of this step is enumerate all the traces concrete events long, and concretely fuzz the contract with these concrete event traces. We use an SMT solver to find value assignments for all symbolic variables in each element of . These concrete values give us a set of concrete events. Concrete events can be directly executed on the Ethereum Virtual Machine (EVM).

Fuzzing with Concrete Events

The fuzzing starts with the random trace of concrete events distinct entry points, and its creates combinations that permute all pairs of events, then all triples, quadruples, and so on. If two traces tested result in differing outputs, we flag the contract as having an EO bug. A pair of concretely tested traces that exhibit diverging outputs and are called a witness pair, and reported by EthRacer for evidence of EO violations. To reduce the effort of the human analyst, EthRacer performs a minimization step on witness pairs. If the removal of a concrete event from both traces in a witness pair does not impact the outputs, such an event is redundant and can be removed. Proceeding this way, a minimized witness pair is finally reported.

Prioritizing and Pruning

We devise three new optimization strategies, which cut down the fuzzing space exponentially:

(a) EthRacer uses its symbolic analysis to infer a relationship between symbolic events after the first concretization step. If an concrete event is before in , it implies that re-ordering them will lead to an invalid trace throwing an exception. Our tool analyzes a set of such concrete events symbolically, which is much faster than learning such relationships through concrete fuzzing. This procedure is described in Section V-C. Note that this optimization can have an exponential reduction in concrete fuzzing phase. Learning an relation between even two events can eliminate generating and testing half of the combinations.888By symmetry, half the combination have (say) event A before B. The second concretization step, therefore, generate concrete events that respect the relations.

(b) During its symbolic analysis, EthRacer identifies the functions that do not read or write any global variables. EthRacer further does not consider events from such functions because they cannot interfere with any other event. While this is a simple observation, in practice, it helps optimization even further by cutting down what requires symbolic analysis.

(c) When handling asynchronous callbacks (which are represented by events in our model), the fuzzer prioritizes generating traces which are linearizable, as explained more precisely in Section IV-D. It then prioritizes traces with events making calls to the off-chain services and generates traces which re-order calling events and its corresponding callback events independently. If a concrete trace tested gives output the same as a linearizable trace, it is not reported as a bug.

Section VI-C explains the exponential reduction of these optimizations on the ERC-20 bug mentioned in Section II with experimental data.

Iv-C Extracting HB Relations

EVM semantics do not feature programming abstractions like Java’s synchronized [15] or a rigorous specification of dynamic event precedence [5, 24], which concurrency analysis techniques targeting other languages or platforms use. That said, smart contract developers impose an intrinsic ordering on events by engineering ad-hoc synchronization via a mechanism EVM supports natively: exceptions999In Solidity, exceptions are raised via throw, require and assert. and global variables. Specifically, the contract may set values for global variables in the processing of one event, and later check / use these before performing critical operations under subsequent event. If the check / use of the global variable results in an exception, the second event will not lead to valid trace and the execution of that event will be nullified. Our observation is that if we can detect these cases, when there is only one valid ordering between a pair of events, we do not need to test for any event orderings where there is an invalid order.

These ordering relations are captured formally by a happens-before relation [21]. In all event traces, if executing an event before leads to an exception, but executing before leads to valid (normal) execution, then we say happens before (denoted ). Finally, if neither from and holds, and can occur in an event trace in any order and are called independent events. The precise definition of the notion of happens-before, as used in this paper, is below.

Definition 3 (Happens-before).

For a fixed contract instance , we say that two events and are in happens-before relation with respect to a set of valid event traces of iff for any trace , if and , then . In other words, always precedes in a trace , in which both and are present.

Recovering the complete relation (as defined above) for a program is difficult even with a powerful symbolic analysis. Consider a simplified version of a real-world  Bounty contract, shown in Figure 4. The way it is implemented, an event will always precede in any valid execution trace, because once is executed, the require clause will trigger an exception if more events of type occur. This achieved by setting the flag bounty_paid to true in line 10. There are 2 paths in and 4 paths in , and thus a total of 8 combination of event-paths to check in order to discover the complete relation between and statically. Our symbolic analysis can encode the path’s logic as SMT formula and check both the orders of a pairs using an SMT solver for differing outputs.

1contract Bounty {
2  bool public bounty_paid = false;
3  address public proposed_beneficiary = 0x0;
4  mapping (address => bool) public has_donated;
5  mapping (address => uint256) public balances;
6
7  function payout(string _password) {
8    require(!bounty_paid);
9    // More requirements on _password etc
10    bounty_paid = true;
11    proposed_beneficiary.transfer(this.balance);
12  }
13
14  function donate() payable {
15    require(!bounty_paid);
16    // more requirements
17    if (!has_donated[msg.sender]) {
18      has_donated[msg.sender] = true;
19    }
20    balances[msg.sender] += msg.value;
21  }
22  // More functions...
23}
Figure 4: Bounty contract with an intrinsic HB relation.

This approach works well for a small number of events (e.g. 2 events in this example). Extracting the full relation for a larger set of events quickly leads to a combinatorial explosion of path orders to search. To address this challenge, our approach infers a “weaker” form of HB-relations, which operates on pairs of events, considering traces with only two events. The following definition makes precise our design choice, and enables a direct implementation strategy.

Definition 4 (Weak happens-before).

For a set of events of a contract instance and a blockchain state , such that , we say that two events are in a weak happens-before relation () iff (a) for some contract state , and (b) .

The implementation strategy for extracting weak happens-before (WHB) is straight-forward. We execute the two differing orderings of each pair of events in a given trace. If we observe that one order leads to exception, and other does not, we inductively learn this relationship. We can also identify which functions are independent as per the natural extension of the definition from HB to WHB.

Notice that WHB implies regular HB for traces of two events, but HB for longer traces can include fewer pairs due to state changes made by other events in a trace. Using WHB leads to an under-approximation of the full relation, as it may render more pairs of events non-independent. This could introduce more false negatives (i.e., will miss some EO-bugs), but at the advantage of an exponential reduction in trace combinations to test.

An important final optimization helps the symbolic analysis of event pairs. EthRacer builds weak hb-relation for events pairs that share at least one global variable and at least of the functions write to the shared variable. Only such functions when re-ordered may lead to different values of global storage variables and thus their corresponding events may be in weak hb-relations. EthRacer analyzes the symbolic write sets of program paths, and uses this information to guide which events to consider during analysis.

Iv-D Optimizing for Linearizability Checking

This optimization is based on the observation that certain orderings of events are well-paired by EVM semantics. When contract execution adheres to these prescribed paired orderings, the results are what the developer likely intended (and must not be flagged). In fact, all orderings which lead to one of these intended outputs do not highlight an unintentional behavior, and need not to be reported. This optimization reduces the witness pairs finally reported to the human analyst, suppressing results that are likely benign.

Figure 5: A call/return event trace in Casino contract.

At present, EthRacer uses such optimization for asynchronous call to Oraclize. For interacting with off-chain resources (e.g., stock exchanges, random number generators), a number of Ethereum contracts use the popular service Oraclize [31]. Oraclize handles the query offline, by retrieving the data from the required URLs, and later sends it back to the asking contract by calling its function __callback in a separate Ethereum transaction.101010A contract calling Oraclize must have a function called __callback. Hence, a call to Oraclize would “return” via an asynchronous callback.

Here, for each Oraclize call, there is matching return or callback. Programmers are expected to match returns to the Oraclize calls, and process accordingly, handling multiple users. The notion that captures intent (or natural program reasoning) is linearizability [17]: each pair of matching call-returns must appear to execute atomically. That is, if two users issue events that trigger Oraclize calls, then either the first user’s call-returns are executed before the second user’s, or the other way around; if their call-return strictly interleave out-of-order, it leads to an unintended (or unnatural) behavior. This is evident in the Casino example (Figure 1).

EthRacer in its fuzzing step recognizes Oraclize calls and asynchronous returns. It first checks all linearizable traces, where matching call-return pairs execute atomically. It memoizes the outputs of these linearizable traces as “canonical” outputs. Then, it generates other possible tracees during its testing and reports a witness pair only if the outputs are not equal to all of the canonical outputs. If a trace is found which does not match any linearized event trace, EthRacer flags the contract as having a EO bug, and reports on the closest linearizable trace in the witness pair. One example of such non-linearizable trace for the Casino contract is presented in Figure 5. For completeness, we provide a definition of linearizable traces, which precisely capture this notion.

Definition 5 (Linearizable event traces).

A valid event trace of a contract instance is linearizable iff a state , such that , can be obtained by executing a trace , which is a permutation of events in , such that

  1. it preserves the order of call/return events from ;

  2. non-overlapping logical transactions in appear in the same order, as they appear in .

V Implementation

EthRacer is implemented in about 6,000 lines of Python. To process a contract, the tool requires as its input either a contract’s bytecode or its Ethereum address. If the latter is given, EthRacer assumes the contract is deployed on the main Ethereum blockchain and gets its bytecode and current global storage from the blockchain, and stores it locally. Otherwise, it assumes the contract is not deployed, and thus starts with empty local storage. An optional input to EthRacer is contract’s Solidity source code. If provided, it improves the readability of the produced reports, allowing EthRacer to use function names instead of EVM signatures.111111A function signature is an 8-digit hexadecimal number that uniquely identifies functions when compiled from Solidity source. The tool reports all pairs of traces that lead to EO bugs.

V-a Preprocessing

From the contract’s bytecode, EthRacer

first collects all the function signatures in it. This is done via a heuristic search that inspects the beginning of the bytecode, where all signatures are defined 

[42], and matches it against predefined patterns. Alternatively, if Solidity source code is provided, then the tool directly extracts the signatures from the source by compiling the code with Solidity compiler solc with the --hashes switch on.

V-B Dynamic Symbolic Execution Engine

EthRacer relies on standard DSE, improving the open-source implementation of a recent work [30]. All inputs to the entry points of contract bytecode are marked symbolic initially. The symbolic execution starts from the first instruction of a contract’s bytecode and interprets sequentially the instructions following the EVM specification [42], with access to running stack, global storage, and local memory. The DSE engine keeps two memory maps: a symbolic map and concrete local map. The local map is initialized with the concrete blockchain state given as input to EthRacer. All global variables of the contract are concretely initialized from the local copy of the global storage. The DSE engine gathers symbolic values, branch, and path constraints in the standard way. We check satisfiability of symbolic constraints using Z3 [9], which can handle operations on numeric and bit-vector domains. Our present implementation supports symbolic analysis of of all EVM opcodes. The unhandled instructions preclude analysis of a small number of paths in of the contracts we analyzed.

The analysis aims to over-approximate values in its symbolic path constraints as a default strategy. For instance, when checking with path feasibility with an SMT solver, if the solver times out, we assume that the path is feasible. We prune away paths only if the SMT solver returns UNSAT. We over-approximate the set of pointer values during the analysis for symbolic memory by not concretizing it eagerly. Similarly, the DSE engine marks return values of CALL instructions (which invokes functions from other contracts) as symbolic. During the DSE analysis (and only during this analysis), we do not concretely execute the externally called contract, but the symbolic return value over-approximates the behavior. External calls cannot modify the caller’s contract state, hence marking only the return value as symbolic is sufficient.

While our symbolic analysis largely over-approximates, it concretizes in a small number cases which help improving path coverage. First, when the value of the symbolic variable is needed for checking path feasibility, we lazily concretize it from the concrete to drive execution down that branch. Second, the DSE engine concretizes lookups for key-value stores (hashmap/hashtable types), which are called “mappings” in Solidity [38] and implemented using SHA3 as the hash function. Our baseline system [30], did not have support for this, and this implementation detail is crucial for finding EO bugs in many contracts, including the ERC-20 example from Figure 2. If the lookup key is symbolic (e.g., it came as a contract call input), our DSE engine concretizes it to the set of values assigned to it in concrete executions observed during DSE. In addition, the first time a symbolic is accessed in a write, we assign it a new random concrete value so that the concrete value set is never empty.

As an example, in Solidity, for a mapping s of type address => int, the value of is located in the global storage at address , where denotes concatenation of words and returns the identifier of a variable s in the contract.121212Each global variable has an identifier that depends on the order of how the global variables have been defined in the contract. For each , the tool maintains a list of all concrete keys seen during the previous executions. When having to execute with a symbolic , it retrieves the list of all seen concrete values of for , branches the execution on all such values, and executes concretely for every . In addition, whenever required, EthRacer generates a random value for , adds it to .

Side-Effects & HB Relations

During the DSE analysis, for each collected function, EthRacer performs conservatively records locations of all global variables of the contract to which the function reads or writes. This analysis helps optimize away functions which do not update global state, as discussed previously. It aid subsequent extraction of relations even further as explained previously. The functions that do not read nor write to any shared global variables need not be considered for analysis.

The analysis of symbolic paths is straight-forward. Paths leading to exceptions are identified by special instructions.131313These include instructions that do not have a valid bytecode, or jumps to unlabelled code indexes (for instance, ). For a pair of symbolic events and corresponding path constraints, we check for path feasibility. The SMT solver returns concrete events that validate if a particular order triggers an exception.

V-C Fuzzing Event Traces

Given the set of all events produced previously, the fuzzer engine creates all possible subsets of up to a certain size.141414The maximal size is parametrized, thus it can be increased or reduced; we tried sizes from 2 to 6 in our experiments. For each subset, all possible traces, which obey the previously discovered HB relation, are generated. Each such trace is executed on a customized EVM instance, which in comparison to the original EVM (of original Ethereum client) is orders of magnitudes faster, as it runs without performing the proof-of-work mining, and does not participate in a message exchange with other nodes on the network. The customized EVM, however, can read from the original Ethereum blockchain by sending queries to Ethereum geth client running on a local machine and participating in the Ethereum network. Hence, if EthRacer processes a contract deployed on the main Ethereum blockchain, the customized EVM initially reads its actual global state, stores it locally, and uses this copy for all sequential reads and writes of the global storage.

After executing each trace, the global storage and the balance of the contract are saved. Once all traces for a particular subset have been executed, the tool finds and outputs a list of pairs of EO-bug traces, i.e., pairs of traces that result either in different global storage or in different balances. The tool then performs minimization by creating a set of pairs of minimal traces of function calls, reproducing the found EO-bugs. The minimization is done by implementing a simple shrinking strategy: same events are removed from the pair of buggy traces, one by one, while checking whether a “smaller” pair of traces still constitute an EO-bug. In our experience, the size of the set of minimal traces is significantly smaller than the length of the full list of buggy traces, so having the minimization procedure provides a better user experience and allows for faster confirmation of witnesses by a human.

To further reduce the number of benign violation of EO, EthRacer implements an additional simple heuristic. EthRacer gives priority to checking all permutations with distinct events. Permutations that reorder events with the same entry point but different input values are checked after all others. The rationale behind this heuristic comes from the fact that a large number of functions change the global output state by writing a value that depends on some input parameter. Thus, calling such functions with different inputs will always result in different global storage of the contract. This is often intended behavior, rather than a bug.

(a) Activity of contracts.
(b) Recency of contracts.
(c) Held balances.
Figure 6: Statistics for the analyzed contracts. The histogram plot 5(a) provides the total transactions handled by the flagged contracts with respect to the block number. From block number to , contracts with source code have seen tremendous increase in the amount of transactions, whereas for contract with only bytecode this number has remained almost constant. Similarly, histogram plot 5(b) shows the number of contracts created with respect to block number. As evident, most of them have been created recently, i.e., after block number . Finally, the plot 5(c) shows the “accumulated” sum of balances of contracts. The highest contracts according to their balance make up for more than of the ether in the flagged contracts.

V-D Checking Linearizability of Event Traces

The linearizability optimizer component of EthRacer is triggered on Oraclize-using contracts, It tailored for detecting a specific kind of EO-bugs, namely linearizability violations (see Definition 5) during fuzzing. For each contract, the linearizability optimizer considers only two functions: one that sends a query to Oraclize and another that receives the “matching” return from Oraclize. The former can be arbitrary, but the latter is always declared as a function with the signature __callback(bytes32 _queryId, string _result,... ), where _queryId is a unique identifier of the query to Oraclize.

Even though the calling function can be arbitrary, the linearizability optimizer finds the matching call/return pair by inspecting the blockchain in the following way. Each call to Oraclize writes a unique _queryId to the blockchain called a log,151515In Solidity, these are called Events, not to be confused with our contract events from Definition 1. while a client contract also stores this value in its persistent state (to use it to identify a matching call from Oraclize). Therefore, for each _queryId, the matching reply from Oraclize can be found by inspecting the incoming calls of the analyzed contract: a call that originates from Oraclize and invokes the __callback function with the required value of _queryId as first argument is the match. Rather than producing new contexts, the linearizability optimizer retrieves them from the blockchain when scanning for all values of the _queryId parameter.

Vi Evaluation

We evaluate EthRacer to measure how many contracts are flagged as having EO bugs, i.e., how many show differing outputs under a different ordering of events. Further, we measure how much effort the analyst has to spend per contract to analyze the flagged traces. We asses how EO bugs compare to existing definitions of transaction re-ordering bugs checked by the existing Oyente tool. Lastly, we present real case studies highlighting efficiency of EthRacer. We highlight only important findings in this section and provide more examples and case studies in Appendix A-A. We urge the readers to refer to the Appendix to gain better insights on our results.

Evaluation subjects

Around of the smart contracts deployed on Ethereum have source code. EthRacer can directly operate on EVM bytecode and does not require source. To test the effectiveness of the tool, we select contracts for which Solidity source code161616We obtain contract source codes from the popular website Etherscan [2], is available and another contracts randomly chosen from the Ethereum blockchain (for which source is unavailable).

EthRacer takes all these contracts in bytecode form and analyzes them for synchronization violations. On the other hand, for analysis of linearizability violations, we find unique contracts on the Ethereum blockchain to which Oraclize callbacks have occurred. We filter out contracts with less than two Oraclize queries, and are left with unique contracts out of which have Solidity source code available. We analyze these contracts for linearizability violations.

Experimental Setup

We run all our experiments on a Linux server, with 64GB RAM and 40 CPUs Intel(R) Xeon(R) E5-2680 v2@2.80GHz. We process the contracts in parallel on cores, with one dedicated core per contract. We set a timeout of minutes per contract. EthRacer is configured to output pairs of events for each pair of functions in the HB relation from its an analysis component; the timeout per pair of functions in the hb relation is minutes. For each function with no event generated in the hb creation phase, EthRacer either generates one event or times out in minute. These events are used for the fuzzing step in EthRacer. The outputs of the EthRacer are a set of pairs of event traces, flagged as having EO bugs. We plan to release a public artifact to enable direct reproducibility of the reported experiments.

Vi-a Efficacy of EthRacer

EthRacer flags a total of ( synchronization and () linearizability violations in the analyzed smart contracts, holding hundreds of millions of dollars worth of Ether over their lifetime. Figure 6 provides statistics on the transaction volumes, recency and ether held by these contracts.

Synchronization violations

Out of the analyzed contracts, are flagged as having synchronization bugs. This shows that a large majority of contracts do not have differing outputs upon the tested orders. Among the contracts, have source code, which we manually analyze, and are from the set of contracts without source.

The maximum Ether held by the contracts over their lifetime sums up to , which is approximately million USD.171717Calculated at 203 USD per one Ether. A large fraction of this amount belongs to contracts that are based on the ERC-20 standard, as the example presented in Section II.

At present, out of contracts are live and currently hold Ether ( USD equivalent); for the distribution of Ether in these contracts). The flagged contracts are transaction intensive as shown in Figure 5(a) and the volume of processed transactions over their lifetime amounts to transactions on the public Ethereum blockchain.

Recent contracts percentage-wise have fewer bugs, presumably due to the rise of automatic analysis tools and manual audit firms. However, shear increase in the number of deployed contracts results in larger number of flagged contracts in recent contracts. This trend can be seen in Figure 5(b).

Figure 7: Number of minimal traces in flagged contracts.
Linearizability violations

Out of the analyzed contracts, the tool flags contracts for linearizability violations. In the past, the flagged contracts have held up to Ether ( million USD equivalent). Currently, of them are live and hold Ether. The contracts are transaction intensive as well, as they have handled transactions in their lifetime.

Vi-B Manual Analysis Effort

EthRacer produces as output a set of pairs of traces exhibiting EO bugs. As shown in Figure 7, for flagged contracts with synchronization bugs the tool produces only a few minimized witness pairs, i.e., on average two, and only a single pair for the majority of the flagged contracts. Similarly, the contracts with linearizability bugs have only one witness pair for all vulnerable contracts. These results highlight that the post-hoc analysis manual effort is minimal and generally requires inspection of only a handful of pairs of traces by a human auditor.

To confirm the simplicity of the post-hoc analysis and to determine whether the EO violations are intentional (benign) or buggy, we manually analyze the flagged cases. We take a randomly chosen contracts flagged for synchronization violations and all contracts (with source code) flagged for linearizability violations. For each of the inspected contracts, we need only a few minutes to confirm or disprove whether the EO violation is true bug or likely benign.

1contract ERC721  {
2  function addPermission(address _addr) public
3    onlyOwner{
4      allowPermission[_addr] = true;
5  }
6  function removePermission(address _addr) public
7    onlyOwner{
8      allowPermission[_addr] = false;
9  }
10  ...
11}
Figure 8: False positive: fragment of an ERC721 contract.
Synchronization Violations

For the 100 contracts we analyzed, are closer to the examples presented in Section and we adjudged them to be true bugs. Apart from the example presented in Section II, we find several other examples of subtle true EO violations. Two additional examples of such contracts named Contest and Escrow, are presented in Appendix A-A.

We deemed that of the contracts we analyzed were violations of the EO definition, but likely benign. These contracts were straight-forward to analyze, as they have a small set of global variables shared between functions of the traces which take different values upon re-ordering. For instance, consider the ERC721 contract shown in Figure 8. Functions addPermission and removePermission are called by the contract owner to update the common variable allowPermission.

EthRacer indicates an EO bug with both orders of a pair of events: (addPermission, removePermission). We adjudge such race conditions to be intentional and benign.

Linearizability Violations

For these violations, among the flagged contracts, have Solidity source code, which we manually analyze. of these contracts have true and subtle EO violations, which follow two predominant patterns:

  • Unprocessed _queryId: The first class occurs in contracts that do not process _queryId in their __callback function. Rather, the contracts assume synchronous nature of Oraclize, i.e., assume that each query to Oraclize is immediately followed by a reply from Oraclize. The contract BlockKing, mentioned in a few previous works [34], is an example of such contract. We have identified one more contract, called Gamble, that suffers from a similar mistake.

  • Improper check on Ether: This second class of bugs is more subtle and has not been identified in prior works. It occurs in contracts similar to our Casino example from Section II. Upon receipt of a bet from a player, the contract checks if it has a sufficient amount Ether for payout in case Oraclize returns value winning for the user, however, it does not keep track of the total amount of Ether required for payout of all awaiting players. Therefore, when several players have open games, and all of them win, the casino may not be able to pay all of them. On the other hand, when each of the players participates and gets the result immediately (i.e., when the case has been linearized), if the casino cannot pay the player, it does not accept the bet, hence the balances of the players will be different. We found 21 contracts that have this type of mistake.

Out of contracts flagged for linearizability violations, contracts have EO violations which seem to be benign (or intended logic) in contracts. Specifically, they have two or more traces that differ in output dependent on the timestamp of the mined block.

Figure 9: Number of functions in flagged contracts.
Summary

EthRacer produces a small number of concrete traces that can be analyzed in a few minutes, and more than half of these correspond to EO violations that we deem to true bugs. EthRacer incurs a practical overhead for diagnosing a dangerous class of errors before deployment.

At this point we would like to mention that among all the flagged contracts with source code for Synchronization violations (i.e., 674), we could find 42 contracts which had contacts of their authors mentioned in their source code. We notified authors of these contracts and gave a week to take a decision and reply to us. However, we received no reply from them in the given time. For the contracts exhibiting Linearizability violations we couldn’t find any author to contact, from reading their source code.

Vi-C Performance

About of the contracts require minutes of analysis time with EthRacer on average per contract, whereas only of them timeout after minutes. Of the minutes, EthRacer spends about minutes to produce events and minutes for fuzzing.

Figure 9 provides a quantitative explanation for this efficiency. It shows the total number of functions in contracts; one can see that a large number of contracts have over callable functions, thus analyzing all permutations would lead to prohibitively large number of traces to test concretely.

A core idea in EthRacer is that of filtering out function pairs before finding their corresponding event pairs in weak hb-relations. This technique removes of all pairs on average in our analyzed contracts. The highest filtration is achieved in a contract with functions where the tool filters out all but pairs. The lowest filtration rate is in contracts where EthRacer does not filter out any of the found functions. Apart from contracts out of , EthRacer provides measurable efficiency gain over the baseline strategy of enumerating all pairs and checking them symbolically.

An illustrative example

EthRacer finds the ERC-20 bug mentioned in Section II in minutes, producing a single minimized pair of traces to inspect. The tool first collects all functions of the contract. The pure function analysis marks functions as pure, leaving only for functions for re-ordering. The pure function analysis substantially improves the power of hb analysis, since only out the functions needed to be checked for relations. Without this optimization, our analysis (Section IV-C) during fuzzing would inspect or pairs instead of . After minutes of symbolic analysis to recover relations, EthRacer creates concrete events as input to the fuzzer. In minute, the fuzzer analyzes traces of length ranging from to and it analyzes a total of traces. This number would be without our hb relation analysis, which has over improvement to concrete fuzzing in this example. After fuzzing, EthRacer outputs traces which have bugs and its witness minimization produces a single pair of traces to inspect. This is precisely the pair presented in Section II. The output produced by EthRacer for this contract is given in Appendix A-B

Figure 10 shows the correlation between the size of the hb relation set and the number of potential traces created by the fuzzer. As shown in the figure, an increase in the number of hb relations leads to a decrease in the number of possible traces the fuzzer needs to create and check. For instance, on average of all analyzed contracts, having a single hb relation reduces the number of possible traces by nearly . Moreover, the average number of HB relations produced by EthRacer for a contract is nearly .

Figure 10: Impact of the number of HB-related event pairs.

Vi-D Comparing EthRacer and Oyente

Oyente is a symbolic execution analyzer which flags contracts with two different traces having different Ether flows (i.e., changes in a contract’s balance) [22, 32]. It tries to produce contexts and such that traces () and () result in two different ether flows where and . Here, and can be the same function. It uses our aforementioned baseline strategy of enumerating all pairs (traces of depth two) and symbolically checking if the two paths lead to two different balances.

Our definitions of event ordering (EO) may appear similar to transaction ordering dependency bugs (TOD) defined and analyzed by the Oyente tool. There is, however, several definitional and technical differences between the two tools:

  1. Oyente does not reason about linearizability violations; transaction ordering dependency bugs are a strict subset of synchronization EO violations.

  2. Oyente detects two paths with different send instructions, which is unlike EO bugs, characterized by observational differences in the final output states. Oyente does not account for global state changes other than account balances interacting in a transaction. TOD detections by Oyente, thus, may include two traces sending to the same recipient, causing no modifications to state other than balances.

  3. Oyente checks for differences between pairs of traces to prevent combinatorial explosion in path space analysis. EthRacer can analyze any combination of traces, and uses a default limit of for presented experiments (but it can be increased). The key reason for its scalability is the use of its HB-relation analysis and analysis optimizations.

  4. Oyente only reports symbolic path constraints responsible for TOD, without giving concrete inputs which exhibit the bug; this is unlike EthRacer which produces concrete and minimized event traces for the analyst to inspect.

Experimental Comparison

To enable a manual analysis of results, we compared EthRacer and Oyente on contracts with available Solidity source code with Oyente (retrieved on April 27, 2018) and EthRacer. First, Oyente has no notion of linearizability and thus it detects none of violating contracts that EthRacer flags in this category. For synchronization violations, EthRacer flags contracts, while Oyente flags cases when its internal default timeout is set to minutes (same as EthRacer) and in online mode.181818In the online mode, Oyente reads the values of the global variables from the actual blockchain, while in offline, it assumes they are symbolic variables. A larger timeout does not advantage Oyente, since it terminates for all tested contracts in this timeout.

Out of the contracts, are flagged by EthRacer as well. We manually inspect all the remaining contracts flagged by Oyente. We find that all these cases are false EO violations — this confirms that EthRacer finds all of true TOD violations flagged by Oyente, and finds more than double of synchronization violations which extend beyond TOD bugs as well. We find that Oyente flags TOD false positives mainly due to two reasons through manual analysis of source code. First, Oyente assumes that different Ether flows always send Ether to different addresses, which is often not the case. Second, in contracts with zero balance, all Ether flows can only send zero Ether, thus they do not differ in output balances although oyente flags them incorrectly.

Vii Related and Future Work

Our work generalizes the class of bugs arising due to non-deterministic scheduling of concurrent transactions. The work by Luu et al. [22] identified a specific kind of synchronization violation related to balance updates, and checked for differences across only a pair of paths. Our work extends this to longer traces and full state differences, finding nearly more true EO violations of previously unknown bugs. Our class of bugs are unrelated to liveness or safety properties identified more recently by symbolic execution techniques [30, 18, 19]. At a technical level, these works provide robust symbolic execution systems which is equivalent to our assumed starting baseline technique. However, in this work, our techniques are complementary to the baseline, directly addressing the combinatorial blowup due to checking traces of large lengths. To show this empirically, we compared directly to a maintained and the open-source Oyente that operates on EVM bytecode in Section VI-D. Alternative systems such as Zeus [18] could be used instead of Oyente, but Zeus is not publicly available and operates on Solidity source rather than EVM bytecode.

It has been later speculated that many of similar issues, stemming from the non-deterministic transactional nature of smart contracts can be thought of in terms of shared-memory concurrency [34]. For instance, TOD could be considered as a notion of an event race [33, 12], while DAO and mishandled responses from linearizability optimizer service would manifest a violation of logical atomicity [17, 16]. While the knowledge of some of those issues has been accumulated by the community [8], we are not aware of any tool that would implement a principled approach to detect them at scale.

The most related work to our approach to finding linearizability violations specifically, is the dynamic analysis by Grossman et al. [16] for detecting of DAO-like re-entrancy vulnerabilities [10, 36]. Their work checks for a specific linearizability property of Effectively Callback Freeness (ECF): a contract is ECF iff for any transaction involving a reentrant call to one can construct an equivalent transaction with without reentrant calls. Their dynamic analysis employs the notion of Invocation Order Constraint (IOC) graph, which only captures the fine-grained shape of a contract execution within a single transaction. Verifying ECF dynamically is drastically simpler than challenge tackled by EthRacer, as ECF entails checking the commutativity reads/writes of functions under a single transaction execution. In this paper, we are interested in handling multi-transactional executions and the associated combinatorial blowup associated. Our notion of hb-relations and our dynamic symbolic analysis are entirely different. Consequently, EthRacer finds linearizability errors and synchronization errors in about ten thousand contracts, none of which are reported in the work by Grossman et al. [16]. Their work finds contracts which are vulnerable to re-entrancy bugs on the Ethereum public blockchain, which fall outside the goals and definition of EO violations.

A concurrent work reports on the Servois [4] tool for automatically generating commutativity conditions from data-structure specifications has been successfully used to confirm the presence of a known linearizability violation in a simple Oraclize-using contract BlockKing [34]. However, Servois can only work with an object encoded in a tailored domain-specific language, thus, it cannot be applied for automatically detecting bugs at scale of an entire blockchain.

Other approaches based on symbolic analysis of Solidity source code or EVM implementations have been employed to detect known bugs from a standard “smart contract vulnerability checklist” [3, 23, 11] in tools, such as Mythrill [27, 26], SmartCheck [37], Securify [41], and Manticore [25], none of which addressed EO bugs.

Our work shares similarity to a vast body of research on checking linearizability and data race detection in traditional programming languages [5, 14, 28, 24, 6, 7]. One might expect that some of those tools could be immediately applicable for the same purpose to smart contracts. The main obstacle to do so is that, unlike existing well-studied concurrency models (sequential consistency [20], Android [5], etc), Ethereum contracts do not come with the formally specified model of explicit synchronization primitives or have explicit programming abstractions. Because of this, our procedure for inferring intrinsic hb relations is considerably different from prior works. We believe our approach lays out useful abstractions for future works investigating concurrency in smart contracts.

Future Work

As noted in Section IV-A, races in a contract can be manifested not only by execution effects on its local state. Other vulnerabilities involve non-determinism in choosing a recipient for transferred funds or logging (aka EVM events). It is possible to extend EthRacer’s model and implementation to account for those bugs while fuzzing wrt. constructed hb relations, and we plan to do so in the future.

Viii Conclusion

We have studied event-ordering bugs in Ethereum smart contracts by exploiting their similarity to two classic notions in concurrent programs: linearizability and synchronization violations. We have provided a formal model for these violations. We have shown how to infer intrinsic happens-before relations from code, and how to use such relations to shrink the search space when searching for bugs.

Ix Acnowledgements

We thank Shweta Shinde and Shiqi Shen for their valuable comments and their help with writing a previous version of this paper. We thank sponsors of the Crystal Center at National University Of Singapore which has supported this work. Further, Ivica Nikolić is supported by the Ministry of Education, Singapore under Grant No. R-252-000-560-112. Aquinas Hobor was partially supported by Yale-NUS College grant R-607-265-322-121.

References

  • [1] “Ethereum github,” https://github.com/ethereum/EIPs/issues/738, 2018, accessed: 2018-05-05.
  • [2] “Etherscan,” https://etherscan.io, 2018, accessed: 2018-05-05.
  • [3] N. Atzei, M. Bartoletti, and T. Cimoli, “A Survey of Attacks on Ethereum Smart Contracts (SoK),” in POST, ser. LNCS, vol. 10204.   Springer, 2017, pp. 164–186.
  • [4] K. Bansal, E. Koskinen, and O. Tripp, “Automatic generation of precise and useful commutativity conditions,” in TACAS (Part I), ser. LNCS, vol. 10805.   Springer, 2018, pp. 115–132.
  • [5] P. Bielik, V. Raychev, and M. T. Vechev, “Scalable race detection for Android applications,” in OOPSLA.   ACM, 2015, pp. 332–348.
  • [6] S. Blackshear, N. Gorogiannis, P. W. O’Hearn, and I. Sergey, “RacerD: Compositional Static Race Detection,” PACMPL, no. OOPSLA, 2018.
  • [7] S. Burckhardt, C. Dern, M. Musuvathi, and R. Tan, “Line-up: a complete and automatic linearizability checker,” in PLDI, 2010, pp. 330–340.
  • [8] ConsenSys Inc., “Ethereum Smart Contract Security Best Practices: Known Attacks,” 2018. [Online]. Available: https://consensys.github.io/smart-contract-best-practices/known_attacks/
  • [9] L. M. de Moura and N. Bjørner, “Z3: an efficient SMT solver,” in TACAS, ser. LNCS, vol. 4963.   Springer, 2008, pp. 337–340.
  • [10] M. del Castillo, “The dao attack,” 2016, 16 June 2016. [Online]. Available: https://www.coindesk.com/dao-attacked-code-issue-leads-60-million-ether-theft/
  • [11] K. Delmolino, M. Arnett, A. E. Kosba, A. Miller, and E. Shi, “Step by step towards creating a safe smart contract: Lessons and insights from a cryptocurrency lab,” in FC 2016 International Workshops, ser. LNCS, vol. 9604.   Springer, 2016, pp. 79–94.
  • [12] D. Dimitrov, V. Raychev, M. T. Vechev, and E. Koskinen, “Commutativity race detection,” in PLDI.   ACM, 2014, pp. 305–315.
  • [13] Ethereum Wiki, “ERC20 Token Standard,” 2018. [Online]. Available: https://theethereum.wiki/w/index.php/ERC20_Token_Standard
  • [14] C. Flanagan and S. N. Freund, “FastTrack: efficient and precise dynamic race detection,” in PLDI.   ACM, 2009, pp. 121–133.
  • [15] B. Goetz, T. Peierls, J. J. Bloch, J. Bowbeer, D. Holmes, and D. Lea, Java Concurrency in Practice.   Addison-Wesley, 2006.
  • [16] S. Grossman, I. Abraham, G. Golan-Gueta, Y. Michalevsky, N. Rinetzky, M. Sagiv, and Y. Zohar, “Online detection of effectively callback free objects with applications to smart contracts,” PACMPL, vol. 2, no. POPL, pp. 48:1–48:28, 2018.
  • [17] M. Herlihy and J. M. Wing, “Linearizability: A correctness condition for concurrent objects,” vol. 12, no. 3, pp. 463–492, 1990.
  • [18] S. Kalra, S. Goel, M. Dhawan, and S. Sharma, “Zeus: Analyzing safety of smart contracts,” in NDSS, 2018.
  • [19] J. Krupp and C. Rossow, “teEther: Gnawing at Ethereum to Automatically Exploit Smart Contracts,” in USENIX Security, 2018.
  • [20] L. Lamport, “The implementation of reliable distributed multiprocess systems,” Computer Networks, vol. 2, pp. 95–114, 1978.
  • [21] ——, “Time, clocks, and the ordering of events in a distributed system,” Commun. ACM, vol. 21, no. 7, pp. 558–565, 1978.
  • [22] L. Luu, D. Chu, H. Olickel, P. Saxena, and A. Hobor, “Making smart contracts smarter,” in CCS.   ACM, 2016, pp. 254–269.
  • [23] R. Ma, S. Stewart, V. Montaghami, E. Zulkoski, and L. Passos, “Quantstamp : The protocol for securing smart contracts,” 2017. [Online]. Available: https://quantstamp.com/
  • [24] P. Maiya, A. Kanade, and R. Majumdar, “Race detection for Android applications,” in PLDI.   ACM, 2014, pp. 316–325.
  • [25] “Manticore: A symbolic execution tool for analysis of binaries and smart contracts,” 2018. [Online]. Available: https://github.com/trailofbits/manticore
  • [26] B. Mueller, “How formal verification can ensure flawless smart contracts,” 2018. [Online]. Available: https://media.consensys.net/how-formal-verification-can-ensure-flawless-smart-contracts-cbda8ad99bd1
  • [27] “Mythril: A security analysis tool for ethereum smart contracts,” 2018. [Online]. Available: https://github.com/b-mueller/mythril
  • [28] M. Naik, A. Aiken, and J. Whaley, “Effective static race detection for Java,” in PLDI.   ACM, 2006, pp. 308–319.
  • [29] S. Nakamoto, “Bitcoin: A peer-to-peer electronic cash system,” 2008. [Online]. Available: http://bitcoin.org/bitcoin.pdf
  • [30] I. Nikolić, A. Kolluri, I. Sergey, P. Saxena, and A. Hobor, “Finding The Greedy, Prodigal, and Suicidal Contracts at Scale,” CoRR, vol. abs/1802.06038, 2018.
  • [31] “Oraclize – Blockchain Oracle service, enabling data-rich smart contracts.” http://www.oraclize.it, 2016.
  • [32] “Oyente: An Analysis Tool for Smart Contracts,” 2018. [Online]. Available: https://github.com/melonproject/oyente
  • [33] V. Raychev, M. T. Vechev, and M. Sridharan, “Effective race detection for event-driven programs,” in OOPSLA.   ACM, 2013, pp. 151–166.
  • [34] I. Sergey and A. Hobor, “A Concurrent Perspective on Smart Contracts,” in 1st Workshop on Trusted Smart Contracts, 2017.
  • [35] O. Shacham, N. G. Bronson, A. Aiken, M. Sagiv, M. T. Vechev, and E. Yahav, “Testing atomicity of composed concurrent operations,” in OOPSLA.   ACM, 2011, pp. 51–64.
  • [36] E. G. Sirer, “Reentrancy Woes in Smart Contracts,” 2016, 13 July 2016. [Online]. Available: http://hackingdistributed.com/2016/07/13/reentrancy-woes/
  • [37] “SmartCheck,” 2018. [Online]. Available: https://tool.smartdec.net/
  • [38] “Solidity: A contract-oriented, high-level language for implementing smart contracts,” 2016. [Online]. Available: http://solidity.readthedocs.io
  • [39] P. Stewart, “Majority concurrence for Jacobellis v. Ohio,” 1964.
  • [40] N. Szabo, “Smart Contracts: Building Blocks for Digital Markets,” 1996.
  • [41] P. Tsankov, A. M. Dan, D. D. Cohen, A. Gervais, F. Buenzli, and M. T. Vechev, “Securify: Practical security analysis of smart contracts,” CoRR, vol. abs/1806.01143, 2018.
  • [42] G. Wood, “Ethereum: A Secure Decentralised Generalised Transaction Ledger,” 2014. [Online]. Available: https://ethereum.github.io/yellowpaper/paper.pdf

Appendix A Appendix

A-a Case Studies: True positives

In the sequel we present two case study examples of true positives of synchronization bugs.

Contest
1function participate()  {
2  participated[msg.sender]=true;
3}
4function vote(address candidate) {
5  if(now < deadlineParticipation || now >= deadlineVoting || voted[msg.sender] || !participated[candidate])
6    throw;
7  else{
8    voters.push(msg.sender);
9    voted[msg.sender] = true;
10    numVotes[candidate]++;
11  }
12}
13function determineLuckyVoters() {
14  for(uint i = 0; i < nLuckyVoters; i++)
15    luckyVoters.push(voters[ random(voters.length) ]);
16}
17function distributePrizes()  {
18  for(uint8 j = 0; j < luckyVoters.length; j++)
19    luckyVoters[j].send(amount);
20  // also sends Ether to the top candidates from numVotes
21  ...
22}
23function close(){
24  if( now >= deadlineVoting ){
25    determineLuckyVoters();
26    distributePrizes();
27  }
28}
29...
Figure 11: A fragment of the contract Contest

The contract from Figure 11 defines a Contest191919This is a simplified version of the original contract available at http://etherscan.io/address/0x325476448021c96c4bf54af304ed502bb7ad0675#code.in which users can participate and subsequently vote for certain participants. The voting happens only after participation deadline (line 5). Once the voting deadline is met, the contract may be closed, resulting in distribution of prizes to the participants with maximal number of votes and to a few lucky voters.

EthRacer run on this contract outputs the minimal EO bug

  • (participate, vote, determineluckyVoters)

  • (participate, determineluckyVoters, vote)

When the function determineLuckyVoters is called, the luckyVoters array is updated with a new lucky voter selected pseudo-randomly from the voters array in line . So, in the first trace after an account votes for a participant the voters array is updated with the new accounts’ address before calling determineLuckyVoters, however, in the second trace, the voters array is not updated when the determineLuckyVoters is called. This leads to different voters array for random selection of luckyVoters for both traces. Hence, the final list of luckyVoters is different for both traces. In order to exploit this bug, the first voter can always call this function and add him to the lucky voters array to get the award. In fact, determineLuckyVoters function should be called only after the voting deadline for the contest to be fair for all the voters.

A simple fix to this bug would be to make the determineLuckyVoter private and only be called inside the close function.

EthRacer starts with functions i.e., possible pairs of functions and narrows it down to potential hb pairs. Finally, it generates only events in and feeds them to the fuzzer. In Fuzzer outputs only minimal traces of depth with one of them capturing this bug. It also outputs a total of traces of depths varying from to which have an EO bug. So, a developer has to analyze only two traces to realize the subtle bug.

Escrow Contract
1contract Escrow  {
2  mapping(address => uint) public escrowFee;
3  EscrowStruct memory currentEscrow;
4  ...
5  function setEscrowFee(uint fee) {
6    require (fee >= 1 && fee <= 100);
7    escrowFee[msg.sender] = fee;
8  }
9
10  function newEscrow(address sellerAddress,
11                 address escrowAddress) payable {
12    require(msg.value > 0 && msg.sender != escrowAddress);
13    //Store escrow details in memory
14    EscrowStruct memory currentEscrow;
15    currentEscrow.buyer = msg.sender;
16    currentEscrow.seller = sellerAddress;
17    currentEscrow.escrow_agent = escrowAddress;
18    currentEscrow.amount = msg.value -
19              escrowFee[escrowAddress] * msg.value / 1000;
20    // More manipulations with currentEscrow
21    ...
22  }
23  ...
24}
Figure 12: A fragment of the contract Escrow.

Consider the fragment of Escrow contract202020The full code of the contract is available at https://etherscan.io/address/0x1c02ce498dc6d0d6ef05a253e021258b07eeba91#code. in Figure 12. The contract facilitate Escrow transactions between a buyer, a seller, and an escrow agent. A small fee (called EscrowFee) is associated with every escrow agent and is paid by a buyer for using the service. The function newEscrow provides the money storage functionality and setEscrowFee is used by an escrow agent to set its escrow fee.

EthRacer indicates the minimal EO bug

  • (newEscrow, setEscrowFee)

  • (setEscrowFee, newEscrow)

Notice that both these functions rely on the mapping escrowFee, by reading and writing to it, which corresponds to the classical notion of an event race. Generally, an escrow agent would set a fee before between a buyer and a seller transact. So, setEscrowFee has to be called by agent before buyer calls newEscrow. However, the contract allows a buyer to call newEscrow before any fee is set to the escrow address, effectively allowing the buyer to forfeit the payment of fee to the escrow agent.

A-B The Output of EthRacer on ERC-20

The following display shows the output of EthRacer on one single buggy instance of the ERC-20 smart contract. Note, this is the list of all pairs of histories that constitute EO bugs (the list of minimized pairs consist of a single pair and it is given in Section II.)

Events
--------------------------------------------------------------------------------
0 : {’tx_value’: ’0’, ’tx_caller’: ’cee827be9b520a485db84d1f09cc0a99ea878686’, ’name’: ’transfer’,
’tx_input’: ’a9059cbb0000000000000000000000006bb8742b27231e22b8f194d4a0737bdeec14c4ca
0000000000000000000000000000000000000000000000000000000000000001’}
1 : {’tx_caller’: ’cee827be9b520a485db84d1f09cc0a99ea878686’, ’name’: ’approve’,
’tx_blocknumber’: ’493e00’, ’tx_value’: ’0’, ’tx_timestamp’: ’5a5c001d’,
’tx_input’: ’095ea7b3000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
0000000000000000000000000000000000000000000000000000000000000001’}
2 : {’tx_caller’: ’cee827be9b520a485db84d1f09cc0a99ea878686’, ’name’: ’transferFrom’,
’tx_blocknumber’: ’493e00’, ’tx_value’: ’0’, ’tx_timestamp’: ’5a5c001d’,
’tx_input’: ’23b872dd000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
0000000000000000000000008bad998271c5560b47917b6fd819fd20cf03e789
0000000000000000000000000000000000000000000000000000000000000001’}
3 : {’tx_caller’: ’cee827be9b520a485db84d1f09cc0a99ea878686’, ’name’: ’approve’,
’tx_blocknumber’: ’493e00’, ’tx_value’: ’0’, ’tx_timestamp’: ’5a5c001d’,
’tx_input’: ’095ea7b3000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
0000000000000000000000000000000000000000000000000000000000000003’}
4 : {’tx_caller’: ’cee827be9b520a485db84d1f09cc0a99ea878686’, ’name’: ’transferFrom’,
’tx_blocknumber’: ’493e00’, ’tx_value’: ’0’, ’tx_timestamp’: ’5a5c001d’,
’tx_input’: ’23b872dd000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
0000000000000000000000008bad998271c5560b47917b6fd819fd20cf03e789
0000000000000000000000000000000000000000000000000000000000000003’}
5 : {’tx_caller’: ’cee827be9b520a485db84d1f09cc0a99ea878686’, ’name’: ’transferFrom’,
’tx_blocknumber’: ’493e00’, ’tx_value’: ’0’, ’tx_timestamp’: ’5a5c001d’,
’tx_input’: ’23b872dd000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
0000000000000000000000000000000000000000000000000000000000000001’}
6 : {’tx_caller’: ’cee827be9b520a485db84d1f09cc0a99ea878686’, ’name’: ’transferFrom’,
’tx_blocknumber’: ’493e00’, ’tx_value’: ’0’, ’tx_timestamp’: ’5a5c001d’,
’tx_input’: ’23b872dd000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
000000000000000000000000cee827be9b520a485db84d1f09cc0a99ea878686
0000000000000000000000000000000000000000000000000000000000000003’}
--------------------------------------------------------------------------------

HB Relations
--------------------------------------------------------------------------------
(1, 2), (3, 4), (1, 5), (3, 6)
--------------------------------------------------------------------------------


Full EO pairs: 43
--------------------------------------------------------------------------------
1 3 2   :  approve approve transferFrom
1 2 3   :  approve transferFrom approve
------------------------------------------------------------
1 0 3 2   :  approve transfer approve transferFrom
1 2 0 3   :  approve transferFrom transfer approve
------------------------------------------------------------
1 0 3 2   :  approve transfer approve transferFrom
1 2 3 0   :  approve transferFrom approve transfer
------------------------------------------------------------
1 0 3 2   :  approve transfer approve transferFrom
0 1 2 3   :  transfer approve transferFrom approve
------------------------------------------------------------
1 0 3 2   :  approve transfer approve transferFrom
1 0 2 3   :  approve transfer transferFrom approve
------------------------------------------------------------
1 2 0 3   :  approve transferFrom transfer approve
1 3 0 2   :  approve approve transfer transferFrom
------------------------------------------------------------
1 2 0 3   :  approve transferFrom transfer approve
1 3 2 0   :  approve approve transferFrom transfer
------------------------------------------------------------
1 2 0 3   :  approve transferFrom transfer approve
0 1 3 2   :  transfer approve approve transferFrom
------------------------------------------------------------
1 3 0 2   :  approve approve transfer transferFrom
1 2 3 0   :  approve transferFrom approve transfer
------------------------------------------------------------
1 3 0 2   :  approve approve transfer transferFrom
0 1 2 3   :  transfer approve transferFrom approve
------------------------------------------------------------
1 3 0 2   :  approve approve transfer transferFrom
1 0 2 3   :  approve transfer transferFrom approve
------------------------------------------------------------
1 2 3 0   :  approve transferFrom approve transfer
1 3 2 0   :  approve approve transferFrom transfer
------------------------------------------------------------
1 2 3 0   :  approve transferFrom approve transfer
0 1 3 2   :  transfer approve approve transferFrom
------------------------------------------------------------
0 1 2 3   :  transfer approve transferFrom approve
1 3 2 0   :  approve approve transferFrom transfer
------------------------------------------------------------
0 1 2 3   :  transfer approve transferFrom approve
0 1 3 2   :  transfer approve approve transferFrom
------------------------------------------------------------
1 3 2 0   :  approve approve transferFrom transfer
1 0 2 3   :  approve transfer transferFrom approve
------------------------------------------------------------
0 1 3 2   :  transfer approve approve transferFrom
1 0 2 3   :  approve transfer transferFrom approve
------------------------------------------------------------
1 3 5 2   :  approve approve transferFrom transferFrom
1 5 3 2   :  approve transferFrom approve transferFrom
------------------------------------------------------------
1 3 2 5 0   :  approve approve transferFrom transferFrom transfer
1 2 3 0 5   :  approve transferFrom approve transfer transferFrom
------------------------------------------------------------
1 3 2 5 0   :  approve approve transferFrom transferFrom transfer
0 1 2 3 5   :  transfer approve transferFrom approve transferFrom
------------------------------------------------------------
1 3 2 5 0   :  approve approve transferFrom transferFrom transfer
1 2 3 5 0   :  approve transferFrom approve transferFrom transfer
------------------------------------------------------------
1 3 2 5 0   :  approve approve transferFrom transferFrom transfer
1 0 2 3 5   :  approve transfer transferFrom approve transferFrom
------------------------------------------------------------
1 3 2 5 0   :  approve approve transferFrom transferFrom transfer
1 2 0 3 5   :  approve transferFrom transfer approve transferFrom
------------------------------------------------------------
1 3 2 0 5   :  approve approve transferFrom transfer transferFrom
1 2 3 0 5   :  approve transferFrom approve transfer transferFrom
------------------------------------------------------------
1 3 2 0 5   :  approve approve transferFrom transfer transferFrom
0 1 2 3 5   :  transfer approve transferFrom approve transferFrom
------------------------------------------------------------
1 3 2 0 5   :  approve approve transferFrom transfer transferFrom
1 2 3 5 0   :  approve transferFrom approve transferFrom transfer
------------------------------------------------------------
1 3 2 0 5   :  approve approve transferFrom transfer transferFrom
1 0 2 3 5   :  approve transfer transferFrom approve transferFrom
------------------------------------------------------------
1 3 2 0 5   :  approve approve transferFrom transfer transferFrom
1 2 0 3 5   :  approve transferFrom transfer approve transferFrom
------------------------------------------------------------
1 5 3 2 0   :  approve transferFrom approve transferFrom transfer
1 0 3 5 2   :  approve transfer approve transferFrom transferFrom
------------------------------------------------------------
1 5 3 2 0   :  approve transferFrom approve transferFrom transfer
1 3 0 5 2   :  approve approve transfer transferFrom transferFrom
------------------------------------------------------------
1 5 3 2 0   :  approve transferFrom approve transferFrom transfer
0 1 3 5 2   :  transfer approve approve transferFrom transferFrom
------------------------------------------------------------
1 2 3 0 5   :  approve transferFrom approve transfer transferFrom
0 1 3 2 5   :  transfer approve approve transferFrom transferFrom
------------------------------------------------------------
1 2 3 0 5   :  approve transferFrom approve transfer transferFrom
1 0 3 2 5   :  approve transfer approve transferFrom transferFrom
------------------------------------------------------------
1 2 3 0 5   :  approve transferFrom approve transfer transferFrom
1 3 0 2 5   :  approve approve transfer transferFrom transferFrom
------------------------------------------------------------
0 1 2 3 5   :  transfer approve transferFrom approve transferFrom
0 1 3 2 5   :  transfer approve approve transferFrom transferFrom
------------------------------------------------------------
0 1 2 3 5   :  transfer approve transferFrom approve transferFrom
1 0 3 2 5   :  approve transfer approve transferFrom transferFrom
------------------------------------------------------------
0 1 2 3 5   :  transfer approve transferFrom approve transferFrom
1 3 0 2 5   :  approve approve transfer transferFrom transferFrom
------------------------------------------------------------
1 0 3 5 2   :  approve transfer approve transferFrom transferFrom
1 5 0 3 2   :  approve transferFrom transfer approve transferFrom
------------------------------------------------------------
1 0 3 5 2   :  approve transfer approve transferFrom transferFrom
1 0 5 3 2   :  approve transfer transferFrom approve transferFrom
------------------------------------------------------------
0 1 3 2 5   :  transfer approve approve transferFrom transferFrom
1 0 2 3 5   :  approve transfer transferFrom approve transferFrom
------------------------------------------------------------
0 1 3 2 5   :  transfer approve approve transferFrom transferFrom
1 2 0 3 5   :  approve transferFrom transfer approve transferFrom
------------------------------------------------------------
1 3 0 5 2   :  approve approve transfer transferFrom transferFrom
1 5 0 3 2   :  approve transferFrom transfer approve transferFrom
------------------------------------------------------------
1 3 0 5 2   :  approve approve transfer transferFrom transferFrom
1 0 5 3 2   :  approve transfer transferFrom approve transferFrom
------------------------------------------------------------