Deductive Proof of Ethereum Smart Contracts Using Why3

04/25/2019 ∙ by Zeinab Nehai, et al. ∙ ENS Paris-Saclay CEA 0

A bug or error is a common problem that any software or computer program may encounter. It can occur from badly writing the program, a typing error or bad memory management. However, errors can become a significant issue if the unsafe program is used for critical systems. Therefore, formal methods for these kinds of systems are greatly required. In this paper, we use a formal language that performs deductive verification on an Ethereum Blockchain application based on smart contracts, which are self-executing digital contracts. Blockchain systems manipulate cryptocurrency and transaction information. Therefore , if a bug occurs in the blockchain, serious consequences such as a loss of money can happen. Thus, the aim of this paper is to propose a language dedicated to deductive verification, called Why3, as a new language for writing formal and verified smart contracts, thereby avoiding attacks exploiting such contract execution vulnerabilities. We first write a Why3 smart contracts program; next we formulate specifications to be proved as absence of RunTime Error properties and functional properties, then we verify the behavior of the program using the Why3 system. Finally we compile the Why3 contracts to the Ethereum Virtual Machine (EVM). Moreover, we give a set of generic mathematical statements that allows verifying functional properties suited to any type of smart contracts holding cryptocurrency, showing that Why3 can be a suitable language to write smart contracts. To illustrate our approach, we describe its application to a realistic industrial use case.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 1

page 2

page 3

page 4

This week in AI

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

1 Introduction

In this paper we use formal methods on a technology that has received increasing attention this recent years, namely Blockchains [nakamoto2008bitcoin]. A blockchain system is a distributed ledger that maintains a continuously-growing history of unmodifiable ordered information recorded in blocks that are themselves linked to previous blocks. The most well-known and used blockchains are Bitcoin [nakamoto2008bitcoin] and Ethereum [buterin2014next]. This popular technology has been applied to finance, medical records [ekblaw2016case], and even politics with digital voting [foroglou2015further]. In addition, a feature that has given rise to a stronger interest in blockchains is the ability to write Smart Contracts [wood2014ethereum]. Smart Contracts are executable programs that run on blockchains. They permit trusted transactions and agreements to be carried out among parties without the need for a central authority while keeping transactions traceable, transparent, and irreversible. A contract can define any set of rules represented in its programming language, thus enabling the implementation of decentralized applications. But on the down side, we are confronted with increasing various attacks exploiting smart contract execution vulnerabilities leading to significant malicious scenarios, such as the infamous The DAO attack [atzei2017survey], resulting in a loss of $60M. Our motivation is to ensure safe and correct smart contracts, avoiding the presence of computer bugs, by using a deductive verification language able to write, check and compile such programs and defining their properties. Specifically, the desired behaviors of the program are defined by pre-conditions that characterize a condition that must be true before the beginning of a program, and post-conditions that say what is true at the end of the program. We choose as a formal and verifier language an automated tool called Why3 [filliatre2013why3], which is a complete tool (including theorem, predicate, lemma, or axiom) to perform deductive program verification. It has a logic language in which we express the specifications and check the proofs, and a programming language in which we define the program. We applied the described method to a use case that has already been the subject of a previous work, namely the Blockchain Energy Market Place (BEMP) application [nehai].

The contributions of this paper are as follows:

  1. We propose Why3 as a formal language for writing, checking and compiling smart contracts. This approach gives us a correct-by-construction programs.

  2. We report the comparison results between existing smart contracts, written in Solidity [buterin2014next], the Ethereum smart contracts language, and the same existing contracts encoded by hand in Why3. Showing that Why3 can properly encode Solidity language.

  3. We detail a formal and verified Trading contract, an exemple of a more complicated contract than the majority of the existing Solidity contracts.

  4. We provide a way to prove the quantity of gas used by a smart contract.

Related work.

Since the DAO attack, the introduction of formal methods at the level of smart contracts has increased. Raziel is a framework to prove the validity of smart contracts to third parties before their execution in a private way [sanchez2017raziel]. In that paper, the authors also use a deductive proof approach, but their concept is based on Proof-Carrying Code (PCC) infrastructure, which consists of annotating the source code, thus proofs can be checked before contract execution to verify their validity. Our method does not consist in annotating the Solidity source code but in writing the contract program and thus getting a correct-by-construction program. Another tool, called Oyente, has been developed to analyze Ethereum smart contracts to detect bugs. In the corresponding paper [luu2016making], the authors were able to run Oyente on 19,366 existing Ethereum contracts, and as the result, the tool flagged 8,833 of them as vulnerable. Although that work provides interesting conclusions, it uses symbolic execution, analyzing execution paths, so it does not allow to prove functional properties of the entire application. The initiative of the current paper is directly related to a previous work [nehai], which dealt with formally verifying the smart contracts application by using model-checking. The paper established a methodology to construct a three-fold model of an Ethereum application, with properties formalized in temporal logic CTL. However, because of the limitation of the model-checker used, ambitious verification could not be achieved (e.g., a model for consumers and producers). This present work aims to surpass the limits encountered with model-checking, by using a deductive proof approach on an Ethereum application using the Why3 tool.

The paper is organized as follows. Basic definitions of the main concepts of Blockchain and smart contracts, along with discussion of their vulnerabilities, are stated in section 2. Section 3 describes the approach from a theoretical and formal point of view by explaining the choices made in this study, and section 4 is the application of the approach to a real case. A proof-of-concept of compiling Why3 contracts is described in section 5. Finally, section 6 states some conclusions and future perspectives to study.

2 Background on Blockchain and Smart Contracts

Bitcoin & Ethereum.

The Blockchain as defined in [nakamoto2008bitcoin] is a peer-to-peer network, allowing time-stamped transactions broadcast to all nodes of the network, called Miners. Transactions are thus collected and recorded in a block, where they cannot be modified. A block can be validated if a miner resolves the Proof-of-Work (PoW) calculation. The block is then broadcast to all miners. A Blockchain can be Public, that means anyone can join, read, write, or participate with the blockchain (permissionless blockchain), or can be Private, that means restrictions are placed on who is allowed to participate in the network and in what transactions (permissioned blockchain). This paper uses the latter blockchain type. Initially, the blockchain environment — Bitcoin — was intended for online payments using a decentralised cryptocurrency — bitcoins — in order to send money directly from one party to another without going through a financial institution. But since then, this technology has spread and several types of blockchains have emerged, including Ethereum [buterin2014next]; Ethereum and Bitcoin are the two most well-known and used blockchains. Ethereum is a decentralized virtual machine (EVM) characterizing the runtime environment for executed programs —smart contracts— which are replicated on each node of the blockchain. Every Ethereum node in the network runs an EVM implementation and executes the same instructions. They allow features such as transferring Ethereum cryptocurrency — ether— to users or contracts but also creating new contracts or calling functions of contracts [atzei2017survey].

Smart Contracts and their vulnerabilities.

A contract is a promise or set of promises that recognizes and governs duties and pre-specifies transaction rules arising from agreements between non-trusting participants, which are enforced by the consensus of the blockchain [clack2016smart]. This term appeared in the Ethereum framework as a digital protocol written in a high-level programming language, e.g. Solidity111Ethereum foundation:Solidity, the contract-oriented programming language. https://github.com/ethereum/solidity, which is then compiled into EVM code, and recorded in the blockchain. Each contract is identified by an address, and holds an amount of ether. A contract is an imperative sequential program, which consists of a set of instructions performing specific actions. It can manipulate functions and variables and it can invoke other contracts by sending a transaction to the target contract address. Basically, a transaction contains two parts: the execution payment (in gas, for miners) and the input data for the invocation. Indeed, each execution performed by a contract is invoiced by a pre-defined amount of gas, in order to ensure the fair behavior of the miners. This execution fee is specified by the user within the transaction.

Figure 1 gives a simple Solidity contract example “contract Recording” that assigns an amount (an unsigned integer) to an address each time the function recordData is called.

1contract Recording { 2  address owner; 3  mapping (address => uint) public dataBalance; 4 5  modifier onlyOwner () { 6      if (msg.sender != owner){throw;} 7           _;} 8 9  function recordData(address _userID, uint _amount) onlyOwner returns (bool) { 10      if (_userID == address(0x0)) return false; 11      if (_amount == 0) return false; 12      dataBalance[_userID] = _amount; 13      return true;}}
Figure 1: A simple contract that records a certain unsigned integer amount

In the Solidity language, there are primitive features such as the modifier function (Line 5), whose role is to restrict access to a function; it can be used to model the states and guard against incorrect usage of the contract. Moreover, information included in a transaction is affected by default variables, such as msg.sender for the sender address, msg.value for the amount of ether, and msg.data for the invocation data. Accordingly, recordData is restricted to being executed by the owner address (i.e. msg.sender, the calling contract), otherwise a throw is raised (Line 6) which is reduced to an exception. The function returns false in the cases where the address user is invalid or there is no amount to record (Lines 10-11), otherwise it returns true.

Since its creation in 2014 [wood2014ethereum], the Solidity language provided a suitable area for malicious users desiring to take advantage of vulnerabilities that smart contracts encounter [luu2016making] [atzei2017survey], e.g., to earn an amount of money through a flaw found in a contract. Hence, the language has been in constant evolution and development in order to create a secure environment against potential attacks. Atzei et al. in [atzei2017survey] have established a summary of common programming pitfalls and identify two major causes of vulnerabilities: problems in the Solidity language and poor documentation of weaknesses. Moreover, they give a taxonomy of vulnerabilities in Ethereum smart contracts. We are interested in the category of vulnerability related to the Solidity language, presented in [atzei2017survey]. Table 1 summarizes some causes of vulnerabilities that we are interested in this study. As explained before, a contract can invoke a function of another contract and send money to a user or to another contract using primitive functions such as send() and call(), or by direct call (i.e. as a traditional function call of an imperative language). However, the primitives can be a source of bugs. Due to a lack of space, please refer to the corresponding paper for more details regarding the definition of vulnerabilities.

Level Cause of vulnerability
Solidity Call to the unknown
Gasless send
Exception disorders
Reentrancy
Table 1: An extract from [atzei2017survey] on taxonomy of vulnerabilities in Ethereum contracts

3 A New Approach to Verifying an Ethereum Blockchain Application Using Why3

3.1 Background of the study

Deductive approach.

Our previous work aimed to verify smart contracts of an Ethereum application using an abstraction method, model-checking [nehai]. Despite interesting results from this modeling method, the approach to property verification was not satisfactory. Indeed, it is well known that the model-checking approach confronts us either with limitation on combinatorial explosion, or limitation with invariant generation. Thus, proving properties involving a large number of states was impossible to achieve because of these limitations. This conclusion led us to consider applying another formal methods technique, deductive verification, which has the advantage of being less dependent on the size of the state space. In this approach, the user is asked to write the invariants.

Why3 tool.

We chose the automated Why3 tool [filliatre2013why3] as our platform for deductive program verification. It provides a rich language for specification and programming, called WhyML (it can be used as an intermediate language), and relies on well known external theorem provers such as Alt-ergo [altergo], Z3 [z3smt], and CVC4 [BCD+11]. Why3 comes with a standard library222http://why3.lri.fr/ of logical theories and programming data structures. The logic of Why3 is a first-order logic with polymorphic types and several extensions: recursive definitions, algebraic data types and inductive predicates. In this approach, we define properties as pre-conditions, post-conditions and invariants. The development of Why3 is mainly motivated by the necessity to model the behavior of programs (both purely applicative and imperative) and formally prove their properties.

Solidity contracts.

We focus on Solidity because it seems to be the most well-known and used language for smart contracts, thus drawing the parallel between the Solidity contracts and the Why3 contracts seems interesting to study. In addition, the case study in our possession consists of contracts written in this language. Moreover, we chose to switch from the Solidity program to the Why3 because Solidity changes very frequently to address attacks; therefore its semantics is not clear enough to directly prove the source code. We believe Why3 could be a language for writing smart contracts while proving their correctness and absence of bugs.

3.2 Encoding of Solidity language to Why3 language

Library modeling.

Solidity is an imperative object-oriented programming language, characterized by static typing. It provides several elementary types that can be combined to form complex types such as booleans, signed, unsigned, and fixed-width integers, settings, and domain-specific types like addresses. Moreover, the address type has primitive functions able to transfer ether (send(), transfer()) or manipulate cryptocurrency balances (.balance).

Solidity contains elements that are not part of the Why3 language. One could model these as additional types or primitive features. Here is a simple example that transfers amount units, from the address “owner” stated in Figure 1 to “x”:

1function _transfer (address x, int amount) onlyOwner {
2    if (owner.balance >= amount) x.send(amount); }

To verify the transfer function into Why3, we have to translate the Solidity program into a Why3 program. In Why3, uint256 and address do not exist, thus we introduce new Why3 types to model those Solidity types:
type uint256 and type address. For machine integers, we have to set the range of values : !type uint256 = ¡range 0 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFF… ¿!

The reason is that specifications use only mathematical integers, e.g., int type, we cannot introduce partial functions in the logic, such as an uint256 addition or subtraction. If an addition is to appear within a specification, it should be the usual addition over mathematical integers. Thus Why3 implicitly maps values of type uint256 to the corresponding value in type int.

Figure 2: Communication process between on-chain and off-chain

Based on the same reasoning, we have modeled the type Int160, Uint160 (which characterizes type uint in Solidity). We also model the address type and its members. We choose to encode the private storage (balance) by a Hashtable having as a key value an address, and the associated value a uint256 value. The current value of the balance of addresses is balance[address]. In addition, the send member is translated by a val function, which performs operations on the balance hashtable. In Solidity, the send() function can fail (return False) due to an out-of-gas, e.g. an overrun of 2300 units of gas, because in certain cases the transfer of ether to a contract involves the execution of the contract fallback, therefore the function might consume more gas than expected (more than 2300). Because Why3 does not solve every problem, we chose to modify the Solidity send() function. The Why3 send() function does not allow fallback execution, it only transfers ether from one address to another. As we made the choice of a Private blockchain type, we avoid an unexpected raise of “Exception Disorder”. Thus, the Why3 send() function does not return a boolean, because we assume that if the function is executed, the transfer must take place. Finally, we give a model of gas, in order to specify the maximum amount of gas needed in any case. We introduce a new type: type gas = int. The quantity of gas is modeled as a mathematical integer, in order to simplify the proof of total consumption, and because it is only used in proof. This part is detailed later (3.3).

Private functions & public functions.

Implementing smart contracts can be presented in Ethereum through the concept of Oracles [EthOracle]. An oracle can be seen as the link between the blockchain and the “real world”. Some smart contracts functions have arguments that are external to the blockchain. However, the blockchain does not have access to the real world, it cannot retrieve information from an off-chain data source which is untrusted. Accordingly, the oracle provides a service responsible for entering external data into the blockchain, having the role of a trusted third party. However, questions arise about reliability of such oracles and accuracy of information. Oracles can have unpredictable behavior, e.g. a sensor that measures the temperature might be an oracle, but might be faulty; thus one must account for invalid information from oracles. Figure 2 illustrates the communication between various system in the real world with the blockchain. The figure demonstrate that the collection of the data is done in three stages. Off-chain, we have raw data of various different natures such as weather temperature or energy consumption or human input. This data is collected by oracles. Finally, oracles can provide information to the blockchain.

Based on this distinction, we defined two types of functions involved in contracts, namely Private functions and Public functions. We noted that some functions are called internally, by other Solidity functions, while others are called externally by oracles. Functions that interact with oracles are defined as public functions. The proof approach of the two types is different. For the private functions one defines pre-conditions and post-conditions, and then we prove that no error can occur and that the function behaves as it should. It is thus not necessary to define exceptions to be raised throughout the program; they are proved to never occur. Conversely, the public functions are called by oracles, the behavior of the function must therefore take into account any input values and it is not possible to require conditions upstream of the call. So in contrast, the exceptions are necessary; we use a so-called defensive proof in order to protect ourselves from the errors that can be generated by oracles. No constraints are applied on post-conditions. Let’s take a Solidity send_token() example to illustrate the approach.

1function send_token(address receiver, uint amount) onlyOwner {
2    if (balance[owner] < amount) return;
3    balance[owner] -= amount;
4    balance[receiver] += amount; }

If we take the previous example of the send_token() function, as a private function, we obtain the result in Figure 3.

1let private_send_token (amount: uint256)(receiver: address): uint 2    requires {amount > 0 /\ balance[owner] >= amount} 3    requires {msg_sender <> receiver /\ msg_sender == owner} 4    requires {balance[receiver] + amount < max_uint256} 5    ensures {balance[msg_sender] = old (balance[msg_sender]) - amount} 6    ensures {balance[receiver] = old (balance[receiver]) + amount } 7= 8    balance[owner]<- balance[owner] - amount; 9    balance[receiver]<- balance[receiver] + amount;
Figure 3: Translation of send_token private function into Why3

The specification states that we require a positive amount to send and that the owner own at least amount (Line 2) and that the address that will receive the amount cannot be the caller of the function, i.e. msg_sender (Line 3). We appreciate from the post-conditions (Lines 5-6) that at the end of the function execution an amount is transferred from one address to another (Lines 8-9). Line 4 states an absence of integer overflow, this part is detailed in section (3.3). A complete set of specifications must provide sufficient information to know the role of a function without even looking at the program. The properties of private_send_token() are sufficient to prove the function and to interpret it without seeing the program. We carry out the same approach for a public function; the result is shown in Figure 4.

1exception InvalidAmount, InvalidAddress, OnlyOwner, NoAmount, Overflow 2 3let public_send_token (amount: uint256)(receiver: address): uint 4    raises {InvalidAmount -> amount <= 0} 5    raises {InvalidAddress -> msg_sender == receiver} 6    raises {OnlyOwner -> msg_sender != owner} 7    raises {NoAmount -> balance[owner] < amount} 8    raises {Overflow -> balance[receiver] + amount > max_uint256} 9    ensures {balance[msg_sender] = old (balance[msg_sender]) - amount} 10    ensures {balance[receiver] = old (balance[receiver]) + amount } 11= 12    if msg_sender != owner then raise OnlyOwner; 13    if amount <= 0 then raise InvalidAmount; 14    if msg_sender == receiver then raise InvalidAddress; 15    if balance[owner] < amount then raise NoAmount; 16    if balance[receiver] + amount > max_uint256 then raise Overflow; 17      balance[owner]<- balance[owner] - amount; 18      balance[receiver]<- balance[receiver] + amount;
Figure 4: Translation of send_token public function into Why3

Following the modeling rules, we defined exceptions (Lines 1, 4-8) to be raised and no pre-conditions. What we could have as requirements in Figure 3, we have as exceptions in the public case. We raise an exception in the two cases where we have no amount to send (Lines 4, 13), and if the receiver is also the sender (Lines 5, 14). In addition, in our approach, the modifier primitive can be modeled in two ways. In Figure 3 it is modeled as a pre-condition (Line 3), and in Figure 4 it is modeled as an exception to handle (Lines 6, 12). Lines (8-16) refer to the integer overflow property discussed in the next subsection.

Remark:

Note that another aspect of proof is modular specification. When a function calls another, it must be able to verify the pre-conditions of the function called, prior to the call, in order to satisfy the pre-condition.

3.3 Defining specifications to be proved

RTE properties.

Runtime Errors (RTE) are an annoying and frustrating experience for users. RTEs are errors that are only detected when running the program. The cost of such bugs can be very high and many methods have been proposed to try to reduce these failures [mauborgne2004astree] [long2014automatic]. There are so many different types of runtime errors that they are not always easy to diagnose. We focus on the principal RTEs, namely : (1) Positive values; In the blockchain environment, most of the exchanged data express an amount of cryptocurrency, hence a transferred negative amount can cause damage. The receiver could lose money instead of receiving. (2) Integer overflow; In many situations, performing proof of absence of integer overflows is extremely difficult and invasive. In our programs, we manipulate machine integers, thus we fix a maximum and a minimum bound. We must show the absence of arithmetic overflow by defining bounding pre-conditions. Hence, such bounds invade specifications throughout the program, resulting in an impractical annotation/proof burden. When manipulate counter we use Peano number from [clochard2015avoid], so that we avoid overflow proof. (3) Index out of array bounds. (4) Division by zero.

Functional properties.

Proving that a program does not cause RTEs does not mean that the program behaves as it should. We also want to prove its correctness and termination. A correct program is one that does exactly what its designers and users intend it to do. Furthermore, a formally correct program is one whose correctness can be proved mathematically, by specifying mathematically what the program is intended to do, for all possible values of its input. We have defined mathematical formulas, useful for functional properties as predicates, axioms, or invariants. For each mathematical formula useful to the proof, we refer to the vulnerabilities stated in section 2, resulting from the paper [atzei2017survey].

  • Pre-conditioning an acceptable transfer. Sending a quantity of ether via the primitive send() or transfer() can cause various vulnerabilities. Hence, securing the process of such primitives is fundamental. Therefore, we introduce a predicate that states an acceptable transaction: We note that h is a hashtable that records the token balance of users (identifyed by their address)

    1predicate acceptableEtherTransaction (h: t uint256) (sender : address)
    2    (receiver: address) (amount: uint256)
    3    = h[sender] >= amount /\ h[receiver] + amount <= max_uint256

    This predicate can be used in a pre-condition specification of a function that performs a transfer of ether such as the send() primitive of an address. Thus, a user cannot send an amount of ether that he does not have. To note that the predicate also defines the RTE property . In addition, the Why3 send() function does not implement a fallback function, which allows us to avoid Call to the unknown and Reentrancy vulnerabilities.

  • acceptableEtherTransaction can be specified with the predicate etherTransactionCompletedSuccessfully, to ensure that a transaction is successfully executed.
    1predicate etherTransactionCompletedSuccessfully (hBefore : t uint256) 2    (hAfter : t uint256) (sender, receiver: address) 3    = hBefore[sender] + hBefore[receiver] = hAfter[sender] + hAfter[receiver]
    A transfer completes correctly if the sum of the sender and the receiver balance before and after does not change. That is, there is no loss of money during the process of sending. This predicate can prove absence of the occurrence Exception disorder and Reentrancy errors.

  • Ensuring a maximum consumption of gas. Finally, the vulnerability Gasless send can be avoided by the gas model. Instructions in Solidity consume an amount of gas, and they are categorized by level of difficulty; e.g., for the set , the amount to pay is , and for a create operation the amount to pay is  [wood2014ethereum]. The price of an operation is proportional to its difficulty. Accordingly, we fix for each Why3 function, the appropriate amount of gas needed to execute it. Thus, at the end of the function instructions, a variable gas expresses the total quantity of gas, consumed during the process. It is a ghost variable, hence it can be used only in logical statements. We introduce a val ghost function that adds to the variable gas the amount of gas consumed by each function calling add_gas (see the section 5 for more details on gas allocation).

    1val ghost add_gas (used : gas) (allocation: int): unit
    2    requires { 0 <= used /\ 0 <= allocation }
    3    ensures  { !gas = (old !gas) + used }
    4    ensures  { !alloc = (old !alloc) + allocation }
    5    writes   { gas, alloc}

    The specifications of the function above require that the quantity of gas to add to gas is necessarily positive. Moreover, at the end of the function we ensure that there is no extra gas consumption. The writes { gas } statement specifies that the modified variable is gas.

4 Use Case : BEMP Decentralized Application

This section demonstrates our approach on a case study “the Blockchain Energy Market Place application” (BEMP). For more details about the application, please refer to [nehai]. Briefly, this Blockchain application makes it possible to manage energy exchanges in a peer-to-peer way between the inhabitants of a district as shown in Figure 5. The figure illustrates:

  • (1) & (1’) Energy production (Alice) and energy consumption (Bob).

  • (2) & (2’) Smart meters provide production/consumption data to Ethereum blockchain.

  • (3) Bob pays Alice in ether for his energy consumption.

A first attempt of the approach.

In our initial work, we applied our method on a simplified version of the application, that is, a one-to-one exchange (1 producer and 1 consumer). This first test allowed us to identify and prove RTE properties such as overflow or index out of array bounds. The simplicity of the unidirectional exchange model did not allow the definition of complex functional properties to show the importance and utility of the Why3 tool. An energy trade application is a suitable case study in order to show the potential of the formal deductive verification provided by Why3. Moreover, in addition to transferring ether, users transfer crypto-Kilowatthours to reward consumers consuming locally produced energy. Hence, the system needs to formulate and prove predicates and functional properties of functions handling various data other than cryptocurrency. Therefore, we define the same predicates as for ether, namely acceptableAmountTransaction and amountTransactionCompletedSuccessfully, by replacing the quantity of ether by the amount of crypto-Kilowatthours.

Figure 5: BEMP Process

Examples of private function from the BEMP application.

In this part, we apply the rules of proof and modeling according to the private function type. First of all, we identify the contracts to be proved, then for each contract we classify each function. BEMP consists of 5 different contracts, some only communicate internally, e.g., contract

ETPAccount allowing to create an account, while others only serve to communicate with oracles; e.g., contract ETPAccounts allowing to register an account. But for reasons of a lack of space, we will limit ourselves to showing an example of a private function.

1let transferFromMarket (_to : address) (cryptokwh : uint) : bool 2    requires {msg_sender == market /\ cryptokwh > 0 } 3    requires {acceptableAmountTransaction marketBalanceOf       importBalanceOf market _to cryptokwh} 4    ensures {amountTransactionCompletedSuccessfully (old marketBalanceOf) marketBalanceOf (old importBalanceOf) importBalanceOf market _to} 5    = (* The program *)
Figure 6: Example of a private function from BEMP

Figure 6 illustrates a proof example of a private function, resulting from the BEMP application. The function allows transferring cryptokwh from the market address to the _to address. This process is internal to the blockchain, there is no external exchange, hence the function is qualified as private. According to the modeling approach, we define complete pre-conditions and post-conditions. The pre-condition in Line 2 expresses the modifier function discussed in section 3, thus tranferFromMarket() function can only be executed by the market. Note that marketBalanceOf is the hashtable that records crypto-Kilowatthours balances associated with market addresses, and importBalanceOf is the hashtable that records the amount of crypto-Kilowatthours intended for the buyer adresses. From the specification set in Figure 6, we understand the behavior of the function without referencing to the program. To be executed, transferFromMarket must respect RTE and functional properties:

  • RTE properties: Positive values; before the execution of the function, it is necessary to require (Line 2) that there is a valid amount of crypto-Kilowatthours to transfer. Integer overflow; before the execution of the function, it is necessary to require that no overflow will occur when _to will receive cryptokwh (Line 3, see Predicate acceptableAmountTransaction).

  • Functional properties: (1) Acceptable transfer; before the execution of the function, it is necessary to require (Line 3) that the transfer can be done, hence the market has enough crypto-Kilowatthours to send. (2) Successful transfer; after the execution of the function, we ensure (Line 4) that the transaction is completed successfully. (3) modifier function; the function can be executed only by the market, thus we require that the caller of the function may be solely the market (Line 2)

The set of specifications is necessary and sufficient to prove the expected behavior of the function.

Verifying the updated BEMP application.

In a second step, we extended the application under study to an indefinite number of users, and then enriched our specifications. The use of the deductive approach is quite suitable for this order of magnitude. The major aspect that differs between the two versions comes from the choice that consumers have to make between the various offers of sale at their disposal on the market. Indeed, in the first version we had only two actors, so no choice was made. Alice supplies Bob with electricity, and the price of a kilowatt hour was fixed in advance. Now, we want to introduce a marketplace environment with sale and purchase offers based on a simple trading algorithm. We introduce additional types such as order and order_trading. The order type is a record that contains orderAddress which can be a seller or a buyer, tokens that express the crypto-Kilowatthours, and price_order. The order_trading type is a record that contains seller information; seller_t:order, seller_index:int, buyer information; buyer_t:order, buyer_index:int, the transferred amount amount_t, and the trading price price_t. For a first approach to trading, we adapted an order book matching algorithm with the limit orders algorithm to our case study [domowitz1993taxonomy].

Properties to prove.

The trading algorithm allows matching a potential consumer with a potential seller, recorded in two arrays buy_order and sell_order taken as parameters of the algorithm. In order to obtain an expected result at the end of the algorithm, properties must be respected. We define specifications that make it possible throughout the trading process. The algorithm is a private function type, because it runs on-chain. Thus no exceptions are defined but pre-conditions are:

  • RTE properties: we define properties of the type positive values; the parameters of the functions must not be empty (empty array), in which case the trading can not take place, and the type Index out of array bounds.

  • Functional properties: (1) Successful price matching; The function is intended to match consumers and producers. We encourage consumers, so sellers can only provide energy to consumers who make an offer to buy at a price greater than or equal to the selling offer. This rule can not be violated, we gathered these rules in the form of a predicate matching. (2) Successful amount matching; just like payment, a seller can not transfer a larger quantity than the buyer needs (predicate matching).

1predicate matching (ord: list order) (b_ord : array order) (s_ord : array order)= 2 forall k :int. 0 <= k < length ord -> ord[k].buyer_t.orderAddress = 3  b_ord[(ord[k].buyer_index)].orderAddress /\ 4  ord[k].seller_t.orderAddress = s_ord[(ord[k].seller_index)].orderAddress /\ 5  ord[k].buyer_t.price_order = b_ord[(ord[k].buyer_index)].price_order /\ 6  ord[k].seller_t.price_order = s_ord[(ord[k].seller_index)].price_order /\ 7  ord[k].price_t <=  b_ord[(ord[k].buyer_index)].price_order  /\ 8  0 <= ord[k].amount_t <= b_ord[(ord[k].buyer_index)].tokens

5 Compiling Why3 Contracts and Proving Gas Consumption

The final step of the deductive verification approach is the deployment of Why3 contracts. EVM is designed to be the runtime environment for the smart contracts on the Ethereum blockchain [wood2014ethereum]. The EVM is a stack-based machine (word of 256 bits) and uses a set of instructions (called opcodes)333https://ethervm.io to execute specific tasks. The EVM features two memory, one volatile which does not survive the current transaction and a second for storage which survive but a lot more expensive to modify. The goal of this section is to describe the approach of compiling Why3 contracts into EVM code and proving cost of functions. The compilation444The implementation can be found at http://francois.bobot.eu/fm2019/ in itself is straighforward, it is done in three phases. The first by compiling to an EVM which uses symbolic labels for jump destination and macro instructions. The second phases compute the absolute address of the labels, it must be done inside a fixpoint because the size of the jump addresses has an impact on the size of the instruction. Finally the assembly code is translated to pure EVM assembly and printed. Most of Why3

can be translated, the proof-of-concept compiler allows to use algebraic datatypes, not nested pattern-matching, mutable records, recursive functions, while loop, integer bounded arithmetic (32, 64,128, 256 bits). Global variables are restricted to mutable records with field of integers. It could be extended to hashtable using the hashing technique of the keys used in

Solidity. Without using specific instructions, like for C, Why3 is extracted to garbage collected langage, here all the allocations are done in the volatile memory, so the memory is reclaimed only at the end of the transaction. The execution of each bytecode instruction has an associated cost. One must pay some gas when sending a transaction, if there is not enough gas to execute the transaction, the execution stopped and the state is rolled back. So it is important to be sure that at any later date the execution of a smart contract will not require an unreasonable quantity of gas. The computation of WCET is facilitated in EVM by the absence of cache. So we could use techniques of [cerco] which annotates in the source code the quantity of gas used, here using a function add_gas used allocations. The number of allocations is important because the real gas consumption of EVM integrates the maximum quantity of volatile memory used. The compilation checks that all the paths of the function have a cost smaller than the sum of the add_gas g a on it.

1let rec mk_list42 [@ evm:gas_checking] (i:int32) : list int32 2 requires { 0 <= i }  ensures { i = length result }  variant { i } 3 ensures { !gas - old !gas <= i * 185 + 113 } 4 ensures { !alloc - old !alloc <= i * 96 + 32 } = 5   if i <= 0 then (add_gas 113 32; Nil) 6   else (let l = mk_list42 (i-1) in add_gas 185 96; Cons (0x42:int32) l)

Currently the cost of the modification of storage is over-approximated, using specific contract for the functions that modify it we could specify that it is less expansive to use a memory cell already used.

6 Conclusion & Perspectives

In this paper, we applied concepts of deductive verification to a computer protocol intended to enforce some transaction rules within an Ethereum blockchain application. The aim is to avoid errors that could have serious consequences. Reproduce, with Why3, the behavior of Solidity functions showed that Why3 is suitable for writing and verifying smart contracts programs. The presented method was applied to a use case that describes an energy market place allowing local energy trading among inhabitants of a neighbourhood. The resulting modeling allows establishing a trading contract, in order to match consumers with producers willing to make a transaction. In addition, this last point demonstrates that with a deductive approach it is possible to model and prove the operation of the BEMP application at realistic scale (e.g. matching consumers with producers), contrary to model-checking in [nehai], thus allowing the verifying of more realistic functional properties.

Future work will focus on verifying and proving blockchain properties involving communication between users and the impact that a lack of ether can have on the entire eco-system of the blockchain.

References

Appendix A : Trading Algorithme

1let trading (buy_order : array order) (sell_order : array order)
2 : list order_trading =
3  let order_list : ref (list order_trading) = ref Nil in
4  let i = ref 0 in
5  let j = ref 0 in
6
7  sorted_decreasing buy_order;
8  sorted_decreasing sell_order;
9
10  while !i < (Arr.length buy_order)  && !j < (Arr.length sell_order)  do
11    if buy_order[!i].price_order >= sell_order[!j].price_order then begin
12      let price_to_pay = sell_order[!j].price_order in
13      if buy_order[!i].tokens <= sell_order[!j].tokens then begin
14      let amount_transfered = buy_order[!i].tokens in
15        let tokens = sell_order[!j].tokens - buy_order[!i].tokens in
16        sell_order[!j] <- { sell_order[!j] with tokens = tokens };
17        let registered_order = {
18          seller_t = sell_order[!j].orderAddress;
19          seller_index = !j;
20          buyer_t = buy_order[!i].orderAddress;
21          buyer_index = !i;
22          price_t = price_to_pay;
23          amount_t = amount_transfered;
24        } in
25        order_list := registered_order :: !order_list;
26        if sell_order[!j].tokens = zero_unsigned then incr j;
27        incr i
28      end else begin
29        let amount_transfered = sell_order[!j].tokens in
30        let tokens = buy_order[!i].tokens - sell_order[!j].tokens in
31        buy_order[!i] <- { buy_order[!i] with tokens = tokens };
32        let registered_order = {
33          seller_t = sell_order[!j].orderAddress;
34          seller_index = !j;
35          buyer_t = buy_order[!i].orderAddress;
36          buyer_index = !i;
37          price_t = price_to_pay;
38          amount_t = amount_transfered;
39        } in
40        order_list := registered_order :: !order_list;
41        incr j
42      end
43    end else incr j
44  done;
45  !order_list
46end

Appendix B : WCET of function with allocation

1type list ’a = Nil | Cons a (list ’a)
2
3function length (l: list a) : int =
4  match l with
5  | Nil      -> 0
6  | Cons _ r -> 1 + length r
7  end
8
9let rec length_ [@ evm:gas_checking] (l:list ’a) : int32
10 requires { (length l) <= max_int32 }
11 ensures { !gas - old !gas <= (length l) * 128 + 71 }
12 ensures { !alloc - old !alloc <= 0 }
13 ensures { result = length l }
14 variant { l } =
15  match l with
16  | Nil -> add_gas 71 0; 0
17  | Cons _ l -> add_gas 128 0; 1 + length_ l
18  end
19
20let rec mk_list42 [@ evm:gas_checking] (i:int32) : list int32
21 requires { 0 <= i }
22 ensures { !gas - old !gas <= i * 185 + 113 }
23 ensures { !alloc - old !alloc <= i * 96 + 32 }
24 ensures { i = length result }
25 variant { i } =
26  if i <= 0 then (add_gas 113 32; Nil) else
27  let l = mk_list42 (i-1) in
28  add_gas 185 96;
29  Cons (0x42:int32) l
30
31let g_ [@ evm:gas_checking] (i:int32) : int32
32 requires { 0 <= i }
33 ensures { !gas - old !gas <= i * 313 + 242 }
34 ensures { !alloc - old !alloc <= i * 96 + 32 } =
35  add_gas 58 0;
36  let l = mk_list42 i in
37  length_ l’