Introduction
nonce
used to prevent replay attack; 2) balance
standing for account’s holding of Ether (or ETH
), Ethereum’s native cryptocurrency; 3) storageRoot
representing account-owned storage data (structured as a Merkle tree); and 4) codeHash
referring to self-governance code. Here, the last two fields (i.e., storageRoot
and codeHash
) are key for smart contracts, which set them apart from ordinary accounts.codeHash
). In Fig.1, we show a simple contract named EtherBank
in Solidity. Solidity is the statically typed object-oriented high-level programming language dedicated to smart contract programming, and the most popular and widely used such languages in Ethereum. Solidity supports a rich group of features, such as native big integer (uint256/int256
) type, dynamic array, user-defined struct
, multiple inheritance, and important blockchain primitives (e.g., msg.sender
, block.number
).
EtherBank
contract defines a storage variable named balances
(line 4), which is persisted on blockchain within consecutive transactions. This variable represents a balance record of relevant accounts, modelled as a mapping from account address to its corresponding Ether savings. Besides, there are two publicly available functions in EtherBank
, i.e., deposit()
(line 6) and withdraw(uint256 amount)
(line 11). These functions define the processing logic for corresponding requests, and will thus get executed if called by other accounts, respectively. For instance, the withdraw(uint256 amount)
function states, if called, it first ensures there are no potential integer overflows (line 12), then updates account’s balance (line 13), and at last begins to transfer requested Ether accordingly (line 14). To facilitate this kind of contract execution, Ethereum implements a Turing-complete stack-based virtual machine called Ethereum Virtual Machine, or EVM in abbreviation (see Section 2). And every smart contract (just like EtherBank
) has to be compiled into a corresponding EVM bytecode (i.e., a sequence of instructions) before ever deployed and executed in Ethereum.tx.sender
2) on a transaction basis, and are further be factorized into two related parameters in concept of gas, i.e., tx.gasLimit
and tx.gasPrice
. Here, tx.gasLimit
specifies maximal amount of gas available to the transaction, whereas tx.gasPrice
converts gas units into ETH
value (the exact fee paid by transaction sender). In principal, every transaction sender is required to specify both tx.gasLimit
and tx.gasPrice
, and will have to pay the amount of Ether (i.e., tx.gasLimit ·tx.gasPrice) before execution. If, after transaction execution, there are unused gas left, the remaining part will be refunded back to transaction sender in ETH
in the same rate as tx.gasPrice
.OG
) exception [14], or in short, gas exception.-
RQ1 How do out of gas exceptions exist in Ethereum? To what extent does it affect external users, network peers, as well as the blockchain as a whole?
-
RQ2 What are the main factors or reasons for out of gas exceptions? Are there lessons developers, researchers, and users can learn from?
-
RQ3 How effectively do existing tools or methods can help in preventing out of gas exceptions? What are the limitations?
-
We give a comprehensive taxonomy of EVM runtime exceptions, and find that the two most commonly seen exception types are out of gas and explicit revert, which combinedly account for around 95% of all exception instances, w.r.t. both external transactions as well as internal message calls.
-
To the best of our knowledge, we are the first to conduct large scale empirical analysis on out of gas exceptions in blockchain-based cloud applications on Ethereum. Our study shows that this kind of exceptions is very prevalence in the world of smart contracts, and has already caused significant amount of losses.
-
We have investigated the reasons behind out of gas exceptions. More specifically, we identify four possible factors, i.e., misunderstanding of transaction mechanism, conservative gas limit, compiler derived bug, and unbounded mass operation.
-
We have studied existing tools and methods in use of preventing out of gas exceptions. The result suggests room for further research and investigations.
Background
Blockchain-based cloud application
Frontend
, Middleware
, and Backend
. Besides, there may be a separate storage service, or Database
, providing necessary persistent storage functionalities. What makes blockchain-based cloud applications unique is the additional capability to communicate with Blockchain
. Usually, this capability of interacting with blockchain is provided by the Blockchain Endpoint
, which might be a dedicated blockchain client (e.g., Geth
and Parity
for Ethereum blockchain), or through cloudalized blockchain endpoint service (e.g., Infura for Ethereum).
Ethereum, smart contract, and EVM
ETH
to transfer as well as optional input data. If the transaction target (denoted by tx.to
) is another EOA, nothing special will happen (other than ETH
transfer). However, if tx.to
points to an existing smart contract, Ethereum will load contract’s code as well as transaction input data, and send them to EVM (Ethereum Virtual Machine) for further execution. As long as no exception occurs during execution, the result will be persisted and synchronized across the whole network. Besides interacting with an existing smart contract, users can also deploy new contracts by leaving tx.to
to empty, and filling in the transaction input (i.e., tx.input
) with appropriately encoded init code [28].Keccak256
hash function, EVM adopts a large word size of 256 bits. While the stack and storage are accessible in slots of words, the memory is byte-addressable, so as to be read and written at any preferable byte position. In default, all newly accessed memory and storage locations in EVM are initialized to zero value.ADD
, EXP
; (2) cryptographic primitive, e.g., SHA3
; (3) blockchain and environmental information, e.g., BALANCE
, EXTCODESIZE
; (4) storage manipulation, e.g., MLOAD
, SSTORE
; (5) control flow, e.g., JUMP
, JUMPI
; (6) logging, e.g., LOG0
, LOG4
; and (7) system operation, e.g., CALL
, SELFDESTRUCT
.CALL
instructions, i.e., CALL
, CALLCODE
, DELEGATECALL
, and STATICCALL
. They both expect parameters like ETH
value to transfer, message call data, return data position, as well as gas limit for the internal call. Sometime, these contract-generated message calls are also known as internal transactions, as opposite to external transactions fired directly by EOAs. As far as EVM concerns, internal and external transactions are of little difference, since both are processed and executed in exactly the same way. However, for analysis purpose, the internal transactions are much more difficult to capture than external ones since they may only reside during runtime execution.v0.5.11
released in August 13th, 2019), it is still impossible for smart contracts to conduct common try
/catch
operations w.r.t. runtime exceptions. Thus, the only safe and possible way for exception handling is to fully revert current call, as well as all its sub-calls. In default, runtime exceptions will automatically “bubble up” or be re-thrown, causing the whole external transaction to revert. A few exceptions are message calls triggered by low-level functions like call
, delegatecall
, and staticcall
of the target contract.The gas mechanism of Ethereum
tx.gasLimit
, which restricts the maximal amount of gas that can be used by the transaction. The transaction gas limit, together with a gas price, i.e., tx.gasPrice
, combinedly decide how much ETH
a transaction sender has to pre-pay (plus additional ETH
transferred directly to the receiver) before his or her transaction accepted as valid for further processing4. Besides, every valid block also has to set its own gas limit, i.e., block.gasLimit
, which corresponds to the maximal accumulated gas cost that are allowed for all the transactions in that block.tx
) consists of three parts: 1) intrinsic gas cost; 2) execution gas cost; and 3) deploy gas cost.
tx
), its intrinsic gas cost can be divided into: 1) input data cost; 2) contract creation cost; and 3) basic cost.
tx
) is the sum of individual gas cost for every executed instruction (denoted by INS
).
tx.to
is empty). For a specific contract creation transaction (denoted by tx
), it charges for every byte of data that are returned (i.e., the newly created contract’s code, denoted by o) by the execution.
SLOAD
/SSTORE
, will cause a storage cost. What’s more, both SLOAD
and SSTORE
consumes a significantly larger amount of gas than other instructions (since storage access is much slower than computation), and that the cost of SSTORE
is even higher than SLOAD
so as to account for the “harder” task of writing than merely reading. Even the same SSTORE
instruction itself may consume different amount of gas (20000 or 5000), depending on different context. As for memory execution cost, EVM follows the just-in-time fashion, i.e., every instruction only pays for the additional active memory footprint resulted from its execution. This is also known as memory expansion cost (see Definition 5).INS
) corresponds to the difference between active runtime memory cost Cactive(μi) before and after its execution, where μi is the current active runtime memory size in words (i.e., 32 bytes or 256 bits). Here, we use m to represent the current EVM runtime memory.
Methodology
Geth
and Parity
with different settings), and scraping from blockchain explorer like Etherscan. The collected data are stored into a dedicated offline database for further analysis. Secondly, we use automatic script and manual inspection to investigate the overall status of out of gas exceptions, with a focus on their causing factors or behind reasons (RQ1 and RQ2). At last, we investigate the effectiveness of existing tools in helping prevent out of gas exceptions (RQ3) using historical transactions as reference.
Geth
client and one Parity
client. Both nodes are set to sync to the latest block height, i.e., 8,547,396 as of September 14th, 2019. We instrumented the Geth
by adding code to identify and extract transactions triggering at least one instance of any runtime exceptions (including out of gas). The Geth
node is running in full
syncmode with state pruning on
for about one month, on a machine with 2 Intel(R) Xeon(R) E5-2680 v4 CPUs (28 cores, 56 threads), 378 GB RAM, and 2 TB SSD. After successfully synced to block #8,547,396 (i.e., as of September 14th, 2019) in about 1 month, the datadir
directory takes up about 416 GB of disk space. Besides, we also maintain a Parity
node in archive
pruning mode with tracing on
. While this may consume more than 7× SSD space depending on specific setting 5, Archive nodes are special as they also provide the unmatched ability to replay past transactions, retrieve execution traces, as well as send simulated transactions at any point of time in history, which normal full nodes (with state pruning on
) cannot offer. The Parity
node we use in this work is based on QuikNode’s dedicated Ethereum node service, which exposes standard Web3 JSON-RPC APIs through both HTTP and WebSocket protocols. It takes about 2 days for this node to fully synchronize.ETH
price as well as known contract source code from Etherscan. And in the tool evaluation phase, we investigate the effectiveness of using native Solidity compiler in helping prevent these exceptions.Results
RQ1: status quo
Exception taxonomy
EOG
) and those after within code deployment (i.e., deploy out of gas, DOG
). In other words, EOG
happens because of short of execution gas cost, whereas DOG
results from lacking of deploy gas cost (see Definition 4 in “The gas mechanism of Ethereum” section).
Exception Type | Occurence | Percentage | |||
---|---|---|---|---|---|
All | External | Ratio | All | External | |
1) Explicit Revert | |||||
Require-Style Revert ( RR ) | 14,000,856 | 11,456,103 | 1.22 | 8.12% | 64.91% |
Assert-Style Revert ( AR ) | 990,183 | 925,701 | 1.07 | 0.57% | 5.24% |
2) Out of Gas | |||||
Deploy Out of Gas ( DOG ) | 10,963 | 10,963 | 1 | 0% | 0.06% |
Execute Out of Gas ( EOG ) | 155,373,273 | 4,281,071 | 36.29 | 90.09% | 24.25% |
3) Stack Overflow/Underflow | |||||
Call-Stack Overflow ( CSO ) | 10,032 | 1,113 | 9.01 | 0% | 0.01% |
Data-Stack Underflow ( DSU ) | 153,445 | 53,501 | 2.87 | 0.09% | 0.30% |
Data-Stack Overflow ( DSO ) | 152 | 152 | 1 | 0% | 0.001% |
4) Illegal Instruction | |||||
Invalid Jump Destination ( IJD ) | 1,341,130 | 1,306,785 | 1.03 | 0.78% | 7.40% |
Invalid Opcode ( IO ) | 232,226 | 189,518 | 1.23 | 0.13% | 1.07% |
5) Not Enough Ether | |||||
Insufficient Balance ( IB ) | 359,822 | 356,517 | 1.01 | 0.21% | 2.02% |
6) Miscellanea | |||||
Client Decision, Illegal Write, etc. | 1,693 | 1,692 | 1.00 | 0% | 0.01% |
♢Summary | |||||
Out of Gas ( DOG + EOG ) | 155,384,236 | 4,291,945 | 36.20 | 90.09% | 24.32% |
Explicit Revert ( RR + AR ) | 14,991,039 | 12,137,417 | 1.24 | 8.69% | 68.77% |
Other Exception Types | 2,098,500 | 1,684,217 | 1.25 | 1.22% | 9.54% |
♢Summery (excluding DoS attacks) | |||||
Out of Gas ( DOG + EOG ) | 4,666,508 | 4,233,428 | 1.10 | 21.86% | 24.10% |
Explicit Revert ( RR + AR ) | 14,987,686 | 12,134,192 | 1.24 | 70.20% | 69.07% |
Other Exception Types | 1,696,017 | 1,654,643 | 1.03 | 7.94% | 9.42% |
EOG
type in Table 2, where every external transaction tends to trigger above 36 instances of exceptions during its execution.Occurrence
column, we show results for three related concepts: 1) number of exception instances, including external and internal transactions; 2) number of external transactions; and 3) average number of exception instances per external transaction. In the Percentage
column, we show numbers for exception instances and external transactions.-
1) out of gas and explicit revert are two most commonly seen types of exception in Ethereum, which combinedly account for more than 90% of the occurrences in terms of both exception instances (i.e., external transactions plus internal message calls) as well as external transactions.
-
2) When considering all the transactions, out of gas alone accounts for more than 90% of all exceptions, with explicit revert only takes another 8%. However, after excluding the notorious DoS attacks [29, 30] by eliminating transactions from block #2,250,000 till block #2,750,000 (both inclusive), the result swaps, where gas exception now takes up slightly more than 20%, while explicit revert accounts for another 70%. A closer look at the DoS interval reveals that only 58,517 external transactions in this section contribute to a total number of 150,717,728 exception instances, that’s nearly 2,576 instances per external transaction. The finding suggests a gigantic influence of the DoS attacks on our study of gas exception. Hence, in the following of this work, we always exclude transactions (and their exception instances) from block #2,250,000 to block #2,750,000, in hope of minimizing unfavourable effects from these attacks.
-
3) On average, all the exception types in Table 2 take place more than once in a single external transaction. In other words, there are at least an external transaction that has witnessed more than one exception. Or, some contracts tend to ignore or not fully revert in case of deep runtime exceptions. Besides, transactions may also trigger more than one type of exceptions. This can be checked by adding all the relative percentage of external transactions for each exception type, which yields around 105%, exceeding the normal 100%.
Accumulative consequences
EOG
are shown in full lines, and indices for DOG
are in dashed lines. The results in Fig. 4 are collected and presented in intervals of 1 million blocks, from block #0 till block #8,547,396, and we show each value in their logarithmic scale. To evaluate losses, we choose three related indices: 1) number of affected external transactions (shown as Txs
); 2) accumulated affected gas units (shown as Gas
in units of 109 gas, or giga gas); and 3) corresponding affected ETH
values (shown as ETH
).
ETH
values) 6, it is the simplest and most easily accessible estimator we can get, and we have found some evidences showing the two indices do not differ very significantly, e.g., in orders of magnitude. Besides, we also calculate the corresponding affected ETH
values with respect to accumulated affected gas by taking each transaction individually with its designated transaction price, i.e., tx.gasPrice
. Last, the number of accumulated affected gas does not include intrinsic gas cost (see Definition 2 in “The gas mechanism of Ethereum” section) as well as mandatory CALL
execution cost in some cases, i.e., when outer transaction does not trigger an out of gas exception itself. This kind of simplification is reasonable since we’re more interested in comparing the pure wasted gas for gas exceptions, whereas the aforementioned costs always exist regardless of any exception.-
1) The losses resulted from gas exceptions are huge. In segments of 1 million blocks, as large as some hundred thousand external transactions are affected (that’s slightly less than 1 transaction per block), causing a number of several hundreds
ETH
values wasted, or in US dollars, tens of thousands with a fairly low average exchange rate of $150/ETH. As for specific type,EOG
dominatesDOG
in each of the considered indices (i.e., number of external transactions, accumulated affected gas units, and corresponding affectedETH
values), and the differences are often in orders of magnitude large (i.e., ten times or above). -
2) In terms of every index, both
EOG
andDOG
experience similar trends throughout entire transaction history. For example, consider the number of external transactions involved in gas exception, i.e.,EOG
Txs
andDOG
Txs
shown in Fig. 4. Both lines begin with a small number, then quickly climb to reach their maxima, and at last stay relatively stable 7, i.e., about 106 external transactions forEOG
and 103 forDOG
. What’s more, bothEOG
Txs
andDOG
Txs
reach their maximal value (i.e., 1,254,650 forEOG
and 2,796 forDOG
) in between block #5,000,000 (Jan, 2018) and #6,000,000 (July, 2018), when blockchain and cryptocurrency industry experience their latest hype. -
3) The lines for accumulated affected gas units and corresponding
ETH
values match very well in shapes, a trend that can be tested by bothEOG
andDOG
exception types. This suggests a relatively stable long term gas price across large section of blocks. -
4) In the interval of #2,000,000 to #3,000,000, we see a salient rise of both affected gas units and
ETH
values forEOG
exception, then the same indices soon descend to around 1/10 of the original level in interval #3,000,000 to #4,000,000. Finally, in interval #4,000,000 to #5,000,000, the exact two indices, i.e.,EOG
Gas
andEOG
ETH
, jump again to reach their previous high levels. During these three intervals, we findEOG
Gas
andEOG
ETH
are the only pair of indices showing this kind of trend, i.e., swing up and down in order of magnitude (i.e., ten times or above) in consecutive intervals. By comparing with other indices, we think this results from a sudden dump of affected gas units andETH
value forEOG
during blocks #3,000,000 to #4,000,000, instead of an opposite situation where quick jump between blocks #2,000,000 to #3,000,000 happens before.
Smart contracts
sender
and receiver
addresses, we manage to figure out the most “popular” accounts related to out of gas exceptions. More specifically, we are interested in finding accounts sending and receiving most gas exception transactions (both EOG
and DOG
) whether through external transactions or internal message calls.Instance
), accumulated affected gas units (denoted as Gas
, see “Accumulative consequences” sections), and corresponding affected ETH
values (denoted as Ether
). Besides, we also estimate the monetary losses of ETH
using an exchange rate of $150/ETH.
EOG
exceptions. Whereas the highest ranked accounts with both EOG
and DOG
exceptions ranks #54 (i.e., 0x6090 ∼78Ef
) as transaction sender and #13 (i.e., _
, the placeholder address for contract creation transaction) as receiver.-
1) Smart contracts tend to see more gas exceptions than plain EOAs. This can be easily checked by observing that all the accounts in Table 3 are actually smart contracts, and recall that we mention before the highest ranking EOAs for each direction only take up #54 (as transaction sender) and #13 (as transaction receiver) respectively. There are at least two explanations for this phenomenon. On one hand, smart contracts are more vulnerable to out of gas exceptions. A smart contract, once deployed on Ethereum, can never change its execution code during entire lifetime. This means existing bugs or inappropriate gas limit settings are hard to be fixed then. Thus, if a contract sets a too conservative gas limit for internal message calls, it should have seen more gas exceptions compared to one with a much loose gas limit. On the other hand, smart contracts tend to communicate more frequently between each other than EOAs, creating a large base for unexpected gas exceptions. As a rule of thumb, developers tend to reuse well-tested and verified code libraries while building new applications, where in Ethereum the libraries may be previously deployed contracts which expose the same addresses. Besides, to mitigate the risk of unknown bugs and facilitate better maintainability, it is even widely recommended to build smart contracts using proxy patterns, which again increase the interactions between these contracts. All in all, the communications between smart contracts are much more common than between EOAs, contributing to a much larger surface for runtime exceptions, including gas exceptions.
-
2) All the contracts in Table 3 has experienced a large number of gas exceptions during lifetime, where the top 1 accounts both see above 1 million exceptions as transaction sender and receiver respectively. However, the contracts causing most gas units and
ETH
losses, i.e.,0x0601 ∼266d
and0xd0a6 ∼7ccf
, are not the most frequently involved. In fact, the underlined contract0xd0a6 ∼7ccf
(EOSSale) has caused more than 164ETH
losses with only 44,721 transaction calls, which is much smaller than contract0x04
of 1,412,148 invocations instead. -
3) Contracts at the bottom half of Table 3 (i.e., receiving the most gas exceptions) has caused far more losses than the top half (i.e., sending the most exceptions). Notice that the total amount of losses (both gas and
ETH
) are always identical counting from both directions, which suggests an imbalance or asymmetry between transaction senders and receivers. In other words, a large number of ordinary accounts (both EOAs and smart contracts) tend to interact with a small set of popular accounts (mostly smart contracts) which act as celebrities in the world of smart contracts. For example, EOAs may need to transfer well-known ERC-20 tokens between each other by calling the same token transfer function, and famous contract libraries are often shared for reuse by a large number of smart contracts. -
4) Consider the contract with address
0x04
which ranks first as the most out of gas exception receiver. According to Ethereum yellow paper [14], this contract is among a set of 8 special “precompiled” contracts that are proposed to facilitate common and preliminary functionalities to the platform, e.g., the elliptic curve public key recovery function, the SHA2 256-bit hash scheme, the RIPEMD 160-bit hash scheme, and so forth. As for0x04
, it acts as an identity function for its input, i.e., by returning the same input data as its output value. While not figuring out the point to call this contract, an even more appealing fact emerges when we look at the huge amount of exception instances (i.e., 1,412,148) versus a nearly negligible affected gas units (i.e., 4,236,441). Further investigation reveals that all exceptions result from internal message calls, and all but one invocations have set a small gas limit of 3 units. Even mysteriously, theParity
trace module seems unable to identify these internal invocations, as well as the resulting gas exceptions. After a careful inspection of relevant traces and contract bytecode, we are confident to confirm the existence of both transactions and exceptions. We guess the leading factor for these large number of small gas limit calls to contract0x04
is a subtle compiler bug, however, we do not know the intention and mechanism behind currently. -
5) There are two contracts showing up in both lists that send and receive most gas exception transactions, i.e., the underlined contract
0xd0a6 ∼7ccf
(EOSSale) and the tilded contract0x0601 ∼266d
(KittyCore), which happens to be implementations of two most popular token standards in Ethereum, i.e., ERC-20, ERC-7219.
Transactions
OG
), number of message calls (including the outmost external transaction, denoted as Call
), accumulated affected gas units (denoted as Gas
), available execution gas limit (excluding intrinsic gas cost for the outmost external transaction, denoted as Limit
), and whether this external transaction runs out of gas itself (denoted as Ext
). As before, we intentionally exclude transactions between block #2,250,000 and #2,750,000 to mitigate the influence of historical DoS attacks on Ethereum, and that we present wasted gas units as accumulated gas limits of exception transactions.
-
1) While most transactions in Table 4 have triggered an impressive number of
OG
exceptions, more than half of them are not externally out of gas themselves. In other words, only looking at the external transaction may lead to serious under-estimation of the frequency for gas exceptions. -
2) The underlined transaction
0xeffd ∼5725
in block #3,271,486 is caught with an extremely large number of gas exceptions, i.e., 1,562 in a single external transaction. By further investigation, we find it a contract creation transaction (withtx.to
set to empty) with 1,561 delegatecalls to the same contract0x7f6E ∼86F3
, each time with 0 gas limit (and thus doomed to failed asEOG
). Further study on the transaction input data, here act as contract initiation code, reveals that the code performs nothing meaningful but only continuously generating out of gas delegatecalls through an infinite loop, and that the bytecode seems not have been produced by standardsolc
compiler, but instead coded manually to perform the instructed tasks. While we do not have access to the source code of this init code or of the delegated contract0x7f6E ∼86F3
, we believe it is not intended to do something good, and may be linked to previous DoS attacks. -
3) The last three transactions in the bottom half of Table 4 (i.e.,
0xd0f8 ∼7458
,0x0180 ∼b2d8
, and0xf52c ∼aa55
) each causes a tiny amount of gas losses, i.e., 3 units per message call in average, whereas Etherscan seems not reporting any gas exception in them. However, by carefully inspection of the data, as well as using online debugger of Etherscan, we are quite sure about their existence, and that we find all these exceptions are direct results of invoking the identity contract (i.e.,0x04
) with inadequate gas limit, as described in “Smart contracts” section. What’s more, all these exceptions happens to be triggered by the contract0x60bf ∼18EC
, which appears in the top half of Table 3 as the contract sending most out of gas exceptions.
Blockchain-based cloud applications
Contract
), contract name (denoted as Name
), and statistics of both sending and receiving out of gas transactions, denoted as Sending OG
and Receiving OG
respectively. For the last two indices, we further divide them into three sub-indices for each: 1) number of EOG
exceptions; 2) accumulated affected gas units; and 3) corresponding affected ETH
values.
Contract | Name | Sending OG | Receiving OG | ||||
---|---|---|---|---|---|---|---|
EOG | Gas | Ether | EOG | Gas | Ether | ||
0x06012c8cf97BEaD5deAe237070F9587f8E7A266d | KittyCore | 22,847 | 1,486,219,348 | 40.37 | 38,118 | 792,031,653 | 24.70 |
0xb77FeddB7e627a78140a2a32CAC65A49eD1DBa8E | GeneScience | 0 | 0 | 0 | 23 | 1,242,399 | 0.12 |
0x57831A0C76Ba6b4FDcbadd6cb48cB26e8fc15e93 | Offers | 0 | 0 | 0 | 4 | 111,456 | 0.0004 |
0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C | SaleClockAuction | 6,813 | 133,555,374 | 4.40 | 33,486 | 1,286,027,958 | 37.60 |
0xC7af99Fe5513eB6710e6D5f44F9989dA40F27F26 | SiringClockAuction | 2,770 | 49,120,889 | 1.35 | 7,566 | 385,080,223 | 10.28 |
DOG
exceptions for both transaction direction, i.e., sending and receiving, since we find no smart contract in Table 5 has ever triggered such an exception. In fact, there are even no contract creation instructions inside any of these contract’s source code.-
1) The distribution of gas exceptions among contracts is asymmetric. In other words, different contracts in the same application seem trigger an uneven number of gas exceptions. Among these 5 smart contracts of CryptoKitties,
KittyCore
andSaleClockAuction
have triggered the most gas exceptions, both in terms of exception numbers as well as affected gas units. In particular,KittyCore
is the most vulnerable contract for sending and receiving gas exceptions in terms of number, i.e., 22,847 and 38,118. WhereasKittyCore
andSaleClockAuction
ranks first in sending and receiving most gas affectedOG
transactions, respectively. -
2) For a single smart contract, the gas exception distribution between transaction directions (i.e., sending or receiving) is also often imbalanced. For example, the
SaleClockAuction
contract has seen 4 times more gas exceptions for receiving transaction calls than sending out. -
3)
GeneScience
andOffers
have seen much less out of gas exceptions during lifetime than the rest of contracts in Table 5. Besides, they are not recorded as sending out a single gas exception transaction. The reason for this lie in the function decomposition of different contracts, whereGeneScience
andOffers
are never expected to invoke other contract’s functions during course of execution, so will never trigger exceptions in the outward direction (i.e., as transaction sender). What’s more, the two contracts also have relatively fixed behaviour, so it is much easier to predict or even bound the maximal gas consumptions before transactions.
RQ2: causing factor
Common causing factors
-
1) Misunderstanding Transaction Mechanism This is a commonly seen and most trivial causing factor for out of gas exceptions, especially w.r.t. to external transactions. In particular, according to the transaction processing mechanism, if the transaction target/destination (
tx.to
) is a smart contract, Ethereum will load that contract’s code and starting running along with transaction input (tx.input
) in EVM. Note, this process is automatically triggered by Ethereum without user intervention. Thus if the user overlooks or ignores the aforementioned contract execution mechanism, and sets transaction gas limit to its minimal viable value (i.e., the very basic intrinsic gas cost for a valid external transaction, 21,000 for normal transfer and 53,000 for contract creation), there will always be out of gas exception since not a single gas unit is available for further contract execution. In our data set, we have found a total number of 542,193 external transactions having this kind of problem, accounting for nearly one fifths of such transactions. Besides, the problem does not see a clearly decreasing in terms of transaction numbers as time passes by. In particular, we have found 41,820 external transactions suffering from the problem from block #8,000,000 to #8,547,396, whereas the highest number per one million blocks is just 175,204 for interval #4,000,000 to #5,000,000. -
2) Conservative Gas Limit This kind of problem stems from the fact that the transactions can terminate without any exception but are otherwise set with a lower gas limit than needed. For example, the transaction
0xf31d ∼9557
in block #8,547,387 happens to run out of gas with a relative small gas limit 30,000. By setting a much higher gas limit, we find the actual gas needed for the transaction is only 37,112, or 7,112 more units compared to original gas limit. In other words, the user could have saved a gas loss of 30,000 units by merely paying 7,112 units more, that’s a 22,888 units net earning. -
3) Compiler Derived Bug Sometimes, the problem for out of gas exception may stem from hidden bugs or flaws of the contract compiler (in most cases the
solc
Solidity compiler.) An example of this kind is the under-gas call to precompiled identity contract0x04
[14], where the message call only gets 3 units of gas for execution. This accounts for about 2% of all the exception instances found in our data set. According to [14], the gas cost for identity contract is 15 units plus 3 per input word. In other words, the cost is always large or equal to 15, where a gas limit of 3 is doomed out of gas. In fact, we have seen a large number of such instances during our investigation. (Table 3), like the transaction0xd0f8 ∼7458
shown in “Transactions” section (Table 4). While we do not know the cause of this problem, and it may not be a big problem for users, it at least reflects the fact that Solidity compilers are not mature right now, and should be carefully checked in production environment. -
4) Unbounded Mass Operation The authors of [21] have revealed several gas-related contract vulnerabilities which may trigger unexpected behaviours, e.g., locking specific functions forever, or running into a doomed out of gas loop. This phenomenon is confirmed in our investigation by transaction
0x448b49f72d23ecdb281bf1a92d94ab63ef3
efc58937d80f51fa2dadd02591bdb
, where two contracts mutually call each other recursive, lead to out of gas. -
5) Others Due to the large size of our dataset, i.e., more than 56 million transaction traces from nearly 150,000 unique smart contracts, we are unable to cover every contract and its execution traces. The factors shown above are discovered by manual inspection of top accounts, transactions, and contracts involved in out of gas exceptions found in our data set. We plan to check the rest of our data in further, looking for both transactions and smart contracts. We believe there are more hidden factors waiting for discovery.
RQ3: tool evaluation
Data set
NULL
representing contract creation and the 0x04
precompiled contract with no source code available). Since some evaluated tools only accept source code as input, we further checked and retrieved source code of these contracts using Etherscan getsourcecode
API, making sure all these contracts have corresponding verified source code available. In Table 6, we show a list of 10 example contracts from the data set, offering information like contract address, contract name, number of exception instances, and important compiler parameters.
Contract | Name | Instance | Compiler | ||
---|---|---|---|---|---|
Version | Optimize | Run | |||
0x744d70FDBE2Ba4CF95131626614a1763DF805B9E | SNT | 81,830 | v0.4.11 | YES | 200 |
0xd0a6E6C54DbC68Db5db3A091B171A77407Ff7ccf | EOSSale | 44,721 | v0.4.11 | NO | 200 |
0x06012c8cf97BEaD5deAe237070F9587f8E7A266d | KittyCore | 38,118 | v0.4.18 | YES | 200 |
0x8d12A197cB00D4747a1fe03395095ce2A5CC6819 | EtherDelta | 35,300 | v0.4.9 | YES | 200 |
0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C | SaleClockAuction | 33,486 | v0.4.18 | YES | 200 |
0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b | Token | 30,622 | v0.4.11 | YES | 200 |
0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0 | DSToken | 26,523 | v0.4.11 | NO | 200 |
0x5EdC1a266E8b2c5E8086d373725dF0690af7e3Ea | YottaCoin | 23,943 | v0.4.24 | NO | 0 |
0xB68042de5B3dA08a80C20d29aEFab999D0848385 | IDAGToken | 20,165 | v0.4.23 | NO | 200 |
0x331d077518216c07C87f4f18bA64cd384c411F84 | EToken2 | 20,079 | v0.4.8 | YES | 200 |
v0.4.x
. Besides, 6 of the contracts turn gas optimization option on
, and all with an expected execution run (by –optimize-runs
option) of 200 times.Gas estimator
eth_estimateGas
JSON-RPC API exposed by Geth
) can return more accurate gas cost estimations as compared to offline gas estimators. After all, the “estimations” returned by online gas estimators are actually real gas costs of the transactions, based on the current world state seen by the tools. If we believe users will always stick to using online gas estimators before proposing transactions, the possibility that these transactions running out of gas will be negligibly low, and thus we should not have found so many gas exceptions as in our study. The point is that, it suggests Ethereum users are not always using online gas estimators before submitting their transactions. The reasons behind are manifold, perhaps they just do not know of these tools, or maybe users are unable to get accessible to these tools because they do not have direct control over their accounts (e.g., users host their accounts on third-party platforms like cryptocurrency exchanges and do not possess their own Ethereum clients). In any case, we are sure there is some room for offline gas estimators.solc
native gas estimator (–gas
) on a data set of 10 contracts. We leave other similar tools to further studies.Function
); and 2) with respect to message calls (Instance
).
solc
native gas estimator in use of preventing out of gas exceptionsAddress | Contract | Function | Instance | ||||
---|---|---|---|---|---|---|---|
All | Solve | Ratio | All | Solve | Ratio | ||
0x744d70FDBE2Ba4CF95131626614a1763DF805B9E | SNT | 26 | 10 | 38.5% | 81,830 | 0 | 0% |
0xd0a6E6C54DbC68Db5db3A091B171A77407Ff7ccf | EOSSale | 30 | 17 | 56.7% | 44,651 | 0 | 0% |
0x06012c8cf97BEaD5deAe237070F9587f8E7A266d | KittyCore | 60 | 46 | 76.7% | 38,118 | 1,899 | 5.0% |
0x8d12A197cB00D4747a1fe03395095ce2A5CC6819 | EtherDelta | 27 | 17 | 63.0% | 33,738 | 2,187 | 6.5% |
0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C | SaleClockAuction | 19 | 11 | 57.9% | 33,041 | 0 | 0% |
0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b | Token | 28 | 9 | 32.1% | 30,622 | 0 | 0% |
0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0 | DSToken | 22 | 9 | 40.9% | 26,011 | 24 | 0.1% |
0x5EdC1a266E8b2c5E8086d373725dF0690af7e3Ea | YottaCoin | 23 | 10 | 43.5% | 23,943 | 0 | 0% |
0xB68042de5B3dA08a80C20d29aEFab999D0848385 | IDAGToken | 20 | 12 | 60.0% | 20,165 | 0 | 0% |
0x1F0480a66883De97d2b054929252aaE8F664c15c | NePay | 22 | 12 | 54.5% | 16,333 | 0 | 0% |
solc
helps to prevent (Solve); 3) the extent solc
can help (Ratio). In this experiment, we use a v0.4.25
version solc
compiler since both contracts in Table 7 only accepts compiler version v0.4.x
. In doing this test, we assume the contract code is fixed and we want to refer to solc
gas estimator to properly set transaction gas limits.-
1) As for public functions,
solc
can help in preventing nearly half of the gas exceptions. In other words, considering an average contract,solc
gives meaningful estimations for about half of the public functions. Note, thesolc
gas estimator is so conservative that it rejects any function with any kind of loops (e.g., reading from a dynamic array) or unbounded calls. Thus the results it returns should be always exact upper bound for certian functions11. -
2) When considering transaction distribution,
solc
seems do not have any applaudable effects. In particular, as shown by contract0xd0a6 ∼7ccf
, not a single of the exception instance can be saved with helpsolc
. The reason for this is these instances all calls to functions that are not covered bysolc
(so it cannot give any useful information w.r.t. gas cost). Note, the test instances are all collected from our previously found out of gas transactions, so the result shown here is skewed towards hard cases where loops and unbounded calls exist, and may not be fair tosolc
. However, what is clear is that if we want to solve those real-world out of gas problems,solc
estimator alone is far from useful, and we need more powerful tools for this purpose.
Code optimizer
solc
compiler also provides a native code optimizer which could be turned on with –optimize
option. This optimizer is designed to work on the assembly level, trying to reduce redundancies and rearrange bytecode in hope that the output code could be lighter and more gas efficient. In general, this smart contract optimization problem is a multi-objective optimization, so that the result bytecode is both small in size as well as cheap in execution (i.e., consumes less gas units when called). To help make the right balance between these two targets, users can provide an additional parameter to the optimizer with –optimize-runs
option (which defaults to 200), representing the expected average number of invocation for each function. Thus, by setting larger –optimize-runs
parameters, users expect more frequent function executions, and the optimizer should produce code more suitable for these high-frequency use cases. In contrast, smaller –optimize-runs
parameters represent less active invocations, and should produce code optimized to initial deployments (which cost gas units when the contract is deployed).solc
native optimizer to help prevent out of gas exceptions. In general, when configured correstly, the optimizer should provide a more gas efficient bytecode that consumes less gas when executed, thus lowering the risk of out of gas exceptions. We plan to evaluate other similar contract code optimizers in future work.–optimize
option for the same data set of 10 contracts as in “Gas estimator” section. The compiler we use is of version v0.4.25
and the –optimize-runs
parameter is set to 200. Note, in doing this test, we assume the gas limit of each transaction is fixed, and to see if we can avoid gas exceptions by using optimized contract code.
solc
built-in code optimizer in use of preventing out of gas exceptions, with –optimize
option turned on and the –optimize-runs
parameter set to 200Address | Contract | Function | Instance | ||||
---|---|---|---|---|---|---|---|
All | Up | Down | All | Solve | Ratio | ||
0x744d70FDBE2Ba4CF95131626614a1763DF805B9E | SNT | 26 | 0 | 0 | 81,830 | 0 | 0% |
0xd0a6E6C54DbC68Db5db3A091B171A77407Ff7ccf | EOSSale | 30 | 15↑ | 2↓ | 44,651 | 0 | 0% |
0x06012c8cf97BEaD5deAe237070F9587f8E7A266d | KittyCore | 60 | 0 | 0 | 38,118 | 375 | 1.0% |
0x8d12A197cB00D4747a1fe03395095ce2A5CC6819 | EtherDelta | 27 | 0 | 0 | 33,738 | 0 | 0% |
0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C | SaleClockAuction | 19 | 0 | 0 | 33,041 | 0 | 0% |
0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b | Token | 28 | 0 | 0 | 30,622 | 0 | 0% |
0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0 | DSToken | 22 | 4↑ | 5↓ | 26,011 | 0 | 0% |
0x5EdC1a266E8b2c5E8086d373725dF0690af7e3Ea | YottaCoin | 23 | 8↑ | 2↓ | 23,943 | 0 | 0% |
0xB68042de5B3dA08a80C20d29aEFab999D0848385 | IDAGToken | 20 | 1↑ | 11↓ | 20,165 | 0 | 0% |
0x1F0480a66883De97d2b054929252aaE8F664c15c | NePay | 22 | 0 | 0 | 16,333 | 0 | 0% |
Function
); and 2) improvements with respect to individual transactions (Instance
). In the Function
part, we present three values, i.e., total number of public functions (All), number of public functions with increasing gas consumptions (Up), and number of public functions with decreasing gas consumptions (Down). Whereas in the Instance
part, we choose the same format as in Table 7, reporting total number of exception transactions (All), number of transactions that can be fixed (Solve), and the relative scale of fixed transactions (Ratio). Note, as can be seen in Table 6, six out of the ten contracts in this test have already turned on –optimize
option when deployed and that the –optimize-runs
parameters are all set to 200 just as in our experiment.-
1) All four smart contract with
–optimize
option previously turned off have seen changes of each public function’s gas cost. For example, the0xd0a6 ∼7ccf
contract experience a rise of costs in half of the public functions, whereas it only sees cost reduction in two functions. Oppositely, the0xB680 ∼8385
contract will have half of the functions cutting down gas costs, and with one exception to increase cost. This suggest that code optimizer can at least modify gas costs for different functions, and this may lead to a trade-off between adding costs to some functions and at the same time reducing to some others. -
2) While the code optimizer do have some effects in changing function’s gas cost, it seems have little effect to really prevent gas exceptions. Again, look the four underlined contracts, these contracts all have seen some reduction of gas costs (at least for some functions), but it turns out that not a single exception transaction can be fixed just because of gas cost reduction. The reasons may be that previous transactions have set a too lower gas limit that out optimization can not manage to save, or that the functions with significant cost reduction are just not those hotspots for out of gas exceptions.
-
3) When look at the
Instance
part, we find that only one contract (the0x06012 ∼266d
) seems to be sensitive to the use of code optimization techniques. In fact, not a single function in this contract has seen changes in gas cost, and the appearance of these transactions is just a byproduct of the underestimate ofsolc
gas estimator. In Table 8, we calculate the Solve ofInstance
by comparingsolc
gas estimations with actual gas limits. Since the estimator may return underestimated reading in certain cases, these transactions show up as false positives.
Other approaches
solc
provides an option to perform code optimization, i.e., the –optimize
option, accompany with a modifiable empirical optimization parameter, i.e., the –optimize-runs
which specifies the expected number of invocation for each contract function. Other useful tools are proposed to detect and rectify under-optimized code fragments [23], or to generate gas-optimization-centric code from exists bytecode [25]. Last but not least, another approach for defending out of gas exceptions is to find ill-coded contracts before the deployment, so that deployed contracts will not contain any potential vulnerabilities that may trigger out of gas exceptions [21]. We propose to investigate these tools in further studies.Summaries and implications
Findings | Implications | Relevant Parties |
---|---|---|
I. Out of gas and explicit revert are most commonly seen exceptions in Ethereum, which together account for 90% of all occurrences both in terms of exception instances as well as external transactions. | Insufficient gas limit is very common to encounter, as well as both require and assert failures. Developers and end users should pay special attention to potential gas exceptions. | BCP developers BCP end users |
II. During the infamous DoS attacks between block #2,250,000 and #2,750,000, an exploit of 58,517 transactions has triggered 150,717,728 gas exceptions (or 2,576 per transaction), which has significantly skewed the ordinary distribution of different exception types. | One transactions may trigger multiple number of gas exceptions, with use of restricted gas limit per internal call. Concentration of gas exceptions may be evidence to deliberate attacks against the platform or smart contracts. Investigation on gas exceptions should intentionally distinguish between attack related instances and other cases. | Blockchain Researchers Security Researchers |
III. Since inception, out of gas exceptions alone have caused more than 3,000 ETH losses, or approximately several hundred thousand US dollars in worth. On average, every block sees an instance of gas exception. In other words, precious transaction slots are wasted in a one-slot-per-block manner. | The accumulated negative effects of gas exceptions are huge enough that developers, end users, and operators cannot ignore. By following appropriate guidance, it is possible to save money and time for blockchain-based cloud application participants. | BCP developers BCP end users Blockchain Researchers |
IV. Even until very recently (block #8,547,396, or Spet. 14th, 2019), the frequency of gas exceptions do not see significant changes, especially in the most recent times. In other words, gas exceptions do appear in a relatively steady rate regardless of new methods or best-practices proposed for out of gas exception mitigation. | There may be several explanations. First of all, new tools or practices in gas exception mitigation are not applied broadly among relevant participants, which may results from lack of acceptance or delayed adoption. Second, smart contract code is not frequently updated, so that existing gas issues take action again and again. Thus, improve the acceptance of new approaches as well as regularly updates of contract code should be very important. | BCP developers BCP end users Blockchain Researchers |
V. By comparing smart contracts with externally owned accounts, we find the former are more susceptible to out of gas exceptions, in the sense that gas exceptions are more concentrated on smart contracts than externally owned accounts. Besides, the receivers of gas exception transactions are more concentrated on small set of contracts, whereas the senders tend to be more diverse. | A few popular smart contracts tend to send and receive large number of gas exception transactions, suggesting developers to pay more attention to gas exception related issues during contract development, such as set a larger gas limit to inter-contract invocations or add additional safeguards to unexpected gas exceptions, especially when integrating with popular established libraries. | BCP developers |
VI. The precompiled smart contract with address 0x04 (which act as identity function for inputs) is responsible for a large number of gas exceptions, although each with very little gas units, typically 3 units per (internal) transaction. Considering the mass scale and small influence, we believe this is linked to some issue of the Solidity compiler. | While we do not know the overall mechanism of this finding, it still suggests the critical role of smart contract compilers and other development tools in the cause and prevention of gas exceptions. Specifically, the developers of these tools should pay more attention to the potentially negative effect of their decisions on gas consumption issues. | Dev-tool developers |
VII. There are transactions which trigger a large number of gas exceptions during execution, whereas the external transactions themselves do not run out of gas. In other words, gas exceptions happened deep in the call stack may not cause a cascading of exceptions in certain cases, e.g., the calling contract has set a fixed small gas limit to internal transactions. | Hidden gas exceptions are of particular interest to developers and researchers. On one hand, developers should be careful when calling other contract’s functions, by setting appropriate gas limits and adding relative safeguards. On the other hand, hidden gas exceptions may be byproduct of critical vulnerabilities or attacks (like in the infamous Ethereum DoS attacks [29, 30]). | BCP developers Blockchain researchers |
VIII. A recurring reason of gas exceptions is that the transactions are given too few gas units. This can further be divided into two categories: 1) leaving no gas units for any code execution; 2) setting conservative gas limits than actual needs. | When calling smart contracts (whether from EOA or other smart contract), try to provide more gas units than it seems to consume. For example, always add an additional 5,000 units to the gas consumption result of transaction simulations, or use a sophisticated gas estimator that is proven to return a strict overestimate reading for gas consumption. | BCP developers BCP end users |
IX. According to experiment, the native gas estimator of solc tend to provide estimations of limited use in gas exception mitigation. The tool fails to produce meaningful output when encounters loops or unbounded calls, which however are the exact causes for many real-world out of gas transactions. On the other hand, online estimators should provide satisfactory results if used before each transaction, which is unfortunately not strictly followed, as shown by our results. | Always use Ethereum client’s online gas estimation functionality before submitting new transaction, and if possible, consult more tools in providing gas cost estimations. Besides, there is a need for developing and promoting new tools for gas exception mitigation, like gas-oriented code optimization as well as sophisticated gas cost estimators. | BCP developers BCP end users |
Related work
Decentralized application
Ethereum gas mechanism and out of gas exception
solc
) failed to optimize. These patterns are further classified into two groups: useless-code related patterns and loop-related patterns. They then built a tool called GASPER
which can find three of these seven patterns using contract bytecode. In [25], the same author reported 24 bytecode level anti-patterns, and then built a contract optimizer named GasReducer
baed on these anti-patterns. Unlike [23] and [25], our work focuses on out of gas exceptions, and we use an empirical analysis oriented methodology to find their consequences, their reasons, as well as challenges to existing tools or methods. While gas-costly patterns or anti-patterns may lead to out of gas exceptions, they are neither decisive nor complete.MadMax
to help find these gas-related vulnerabilities. Compared with [21], our work is more focused on out of gas exceptions themselves and the causing factors, whereas their work deal with identifying and preventing vulnerabilities stemming from out of gas exceptions. Besides, we also show that failing to specify an appropriate tx.gasLimit
can also contribute to gas exceptions.GASTAP
, which can infer an upper bound for each function’s gas cost. Experiments showed that GASTAP
outperforms solc
’s native gas estimators as it can deal with more complex situations where solc
lacks support of. At the same time, Marescotti et al. [44] proposed a worst-case gas consumption estimation technique inspired by bounded model-checking techniques. Their method was built on top of the so-called gas consumption paths (GCPs), then they used SMT solver and EVM’s gas consumption capabilities to retrieve concrete gas limits. However, since [44] lacks a tool implementation as well as subsequent experiments, we do not know its effectiveness on real world smart contracts.solc
in gas cost estimation by reducing the risk of underestimation and out of gas exception. We are interested in GasFuzz as well as other fuzzing-based approaches to gas exception mitigation problem, and plan to compare them in the following work.