{'fraction_non_alphanumeric': 0.04215610510046368, 'fraction_numerical': 0.0052936630602782074, 'mean_word_length': 4.357275926309253, 'pattern_counts': {'":': 0, '<': 0, '<?xml version=': 0, '>': 0, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
A minimal core calculus for Solidity contracts

The Ethereum platform supports the decentralized execution of smart contracts, i.e. computer programs that transfer digital assets between users. The most common language used to develop these contracts is Solidity, a Javascript-like language which compiles into EVM bytecode, the language actually executed by Ethereum nodes. While much research has addressed the formalisation of the semantics of EVM bytecode, relatively little attention has been devoted to that of Solidity. In this paper we propose a minimal calculus for Solidity contracts, which extends an imperative core with a single primitive to transfer currency and invoke contract procedures. We build upon this formalisation to give semantics to the Ethereum blockchain. We show our calculus expressive enough to reason about some typical quirks of Solidity, like e.g. re-entrancy.


Introduction
A paradigmatic feature of blockchain platforms is the ability to execute "smart" contracts, i.e. computer programs that transfer digital assets between users, without relying on a trusted authority. In Ethereum [5] -the most prominent smart contracts platform so far -contracts can be seen as concurrent objects [15]: they have an internal mutable state, and a set of procedures to manipulate it, which can be concurrently called by multiple users. Additionally, each contract controls an amount of crypto-currency, that it can exchange with other users and contracts. Users interact with contracts by sending transactions, which represent procedure calls, and may possibly involve transfers of crypto-currency from the caller to the callee. The sequence of transactions on the blockchain determines the state of each contract, and the balance of each user.
Ethereum supports contracts written in a Turing-complete language, called EVM bytecode [8]. Since programming at the bytecode level is inconvenient, developers seldom use EVM bytecode directly, but instead write contracts in higher-level languages which compile into bytecode. The most common of these languages is Solidity [1], a Javascript-like language supported by the Ethereum Foundation. There is a growing literature on the formalization of Solidity, which roughly can be partitioned in two approaches, according to the distance from the formal model to the actual language. One approach is to include as large a subset of Solidity as possible, while the other is to devise a core calculus that is as small as possible, capturing just the features of Solidity that are relevant to some specific task. In general, the first approach has more direct practical applications: for instance, a formal semantics very close to that of the actual Solidity can be the basis for developing precise analysis and verification tools. Although diverse in nature, the motivations underlying the second approach are not less strong. The main benefit of omitting almost all the features of the full language is that by doing so we simplify rigorous reasoning. This simplification is essential for the development of new proof techniques (e.g., axiomatic semantics), static analysis techniques (e.g., data and control flow analysis, type systems), as well as for the study of language expressiveness (e.g., rigorous comparisons and encodings to/from other languages). The co-existence of these two approaches is common in computer science: for instance, the formalization of Java gave rise of a lot of research since the mid 90s, producing Featherweight Java [9] as the most notable witness of the "minimalistic" approach.
Contribution In this paper we pursue the minimalistic approach, by introducing a core calculus for smart contracts. Our calculus, called TinySol (for "Tiny Solidity"), features an imperative core, which we extend with a single construct to call contracts and transfer currency. This construct, inspired by Solidity "external" calls, captures the most paradigmatic aspect of smart contracts, i.e. the exchange of digital assets according to programmable rules. Slightly diverging from canonical presentations of imperative languages, we use key-value stores to represent the contract state, so abstracting and generalising Solidity state variables. We formalise the semantics of TinySol in Section 2, using a big-step operational style. In Section 3 we show TinySol expressive enough to reproduce reentrancy attacks, one of the typical quirks of Solidity; the succinctness of these proofs witnesses an advantage of our minimalistic approach. In Section 4 we refine our formalization, by giving semantics to transactions and blockchains. In Section 5 we exemplify TinySol through a variety of complex contracts, ranging from wallets, to escrow services, lotteries, and Ponzi schemes. Aiming at minimality, TinySol makes several simplifications w.r.t. Solidity: in Section 6 we discuss the main differences between the two languages.
Related work Besides ours, the only other Solidity-inspired minimal core calculus we are aware of is Featherweight Solidity (FS) [6]. Similarly to our TinySol, FS focusses on the most basic features of Solidity, i.e. transfers of cryptocurrency and external calls, while omitting other language features, like e.g. internal and delegate calls, function modifiers, and the gas mechanism. The main difference between TinySol and FS is stylistic: while our design choice was to start from a basic imperative language, and extend it with a single contract-oriented primitive (external calls), FS follows the style of Featherweight Java, modelling function bodies as expressions. Compared to our calculus, FS also includes the dynamic creation of contracts, and a type system which detects some run-time errors. A further difference is that FS models blockchains as functions from contract identifiers to states; instead, we represent a blockchain as a sequence of transactions, and then we reconstruct the state by giving a semantics to this sequence. In this way we can reason e.g. about re-orderings of transactions, forks, etc.
A few papers pursue the approach of formalising large fragments of Solidity. The work [19] proposes a big-step operational semantics for several Solidity constructs, including e.g. access to memory and storage, inheritance, internal and external calls, and function modifiers. The formalization also deals with complex data types, like structs, arrays and mappings. The works [10,18] propose tour-de-force formalizations of larger fragments of Solidity, also including a gas mechanism. Both [19] and [18] mechanize their semantics in the Coq proof assistant, while [10] uses the K-Framework [14]. The work [13] extends the semantics of [10] to encompass also exceptions and return values.

TinySol syntax and semantics
We assume a set Val of values v, k, . . ., a set Const of constant names x, y, . . ., a set of procedure names f, g, . . .. and a set Addr of addresses X, Y, . . ., partitioned into account addresses A, B, . . . and contract addresses C, D, . . .. We write sequences in bold, e.g. v is a sequence of values; ǫ is the empty sequence. We use n, n ′ , . . . to range over N, and b, b ′ , . . . to range over boolean values.
A contract is a finite set of terms of the form f(x){S }, where S is a statement, with syntax in Figure 1. Intuitively, each term f(x){S } represents a contract procedure, where f is the procedure name, x are its formal parameters (omitted when empty), and S is the procedure body. Each contract has a key-value store, which we model as a partial function from keys k ∈ Val to values v ∈ Val.
Statements extend those of a basic imperative language with three constructs: throw raises an uncatchable exception, rolling-back the state; -k:=E updates the key-value store, binding the key k to the value denoted by the expression E ; -X : f(v)$n calls the procedure f (with actual parameters v) of the contract at address X, transferring n units of currency to X.
The expressions used within statements (Figure 1, right) can be constants (e.g., integers, booleans, strings), addresses, and operations between expressions. We assume that all the usual arithmetic, logic and cryptographic operators are provided (since their definition is standard, we will not detail them). The expression !k evaluates to true if the key k is bound in the contract store, otherwise it evaluates to false. The expression ?k denotes the value bound to the key k in the contract store. The expression X : E evaluates E in the context of the address X. For instance, X : ?k denotes the value bound to k in the store of X.
We assume a mapping Γ from addresses to contracts, such that Γ (A) = {f skip (){skip}} for all account addresses A. This allows for a uniform treatment of account and contract addresses: indeed, calling a procedure on an account address A can only result in a pure currency transfer to A, since the procedure can only perform a skip. We further postulate that: (i) expressions and statements are well-typed: e.g., guards in conditionals and in loops have type bool; (ii) the procedures in Γ (C) have distinct names; (iii) the key balance cannot stay at the left of an assignment; (iv) the constant names sender and value cannot stay in the formal parameters of a procedure.
We use the following syntactic sugar. For a call X : f(v)$n, when there is no money transfer (i.e., n = 0) we just write it as X : f(v); when the target is an account address A (so, the call is to the procedure f skip ), we write it as A$n.
The semantics of contracts is given in terms of a function from states to states. A state σ : Addr → (Val ⇀ Val) maps each address to a key-value store, i.e. a partial function from values (keys) to values. We use the standard brackets notation for representing finite maps: for instance, { v1 /x1, · · · , vn /xn} maps x i to v i , for i ∈ 1..n. When a key k is not bound to any value in σ X, we write σ X k = ⊥. We postulate that dom σA = {balance} for all account addresses A, and dom σC ⊇ {balance} for all contract addresses C. A qualified key is a term of the form X.k. We write σ(X.k) for σXk.
A state update π : Addr ⇀ (Val ⇀ Val) is a substitution from qualified keys to values; we denote with { v /X.k} the state update which maps X.k to v. We define keys(π) as the set of qualified keys X.k such that X ∈ dom π and k ∈ dom πX. We apply updates to states as follows: We define the auxiliary operators σ + X : n and σ − X : n on states, which, respectively, increase/decrease the balance of X of n currency units: Example 1. Let σ be a state which maps X to the store We give the operational semantics of statements in a big-step style. The semantics of a statement S is parameterised over a state σ, an address contract wherein S is evaluated), and an environment ρ : Const ⇀ Val, used to evaluate the formal parameters and the special names sender and value.
Executing S may affect both the store of X and, in case of procedure calls, also the store of other contracts. Instead, the semantics of an expression is a value; so, expressions have no side effects. We assume that all the semantic operators are strict, i.e. their result is ⊥ if some operand is ⊥. We denote by S X σ,ρ the semantics of a statement S in a given state σ, environment ρ, and address X, where the partial function · X σ,ρ is defined by the inference rules in Figure 2. We write S X σ,ρ = ⊥ when the semantics of S is not defined. The semantics of expressions is straightforward; note that we use op to denote syntactic operators, and op for their semantic counterpart. The environment ρ is used to evaluate constant names x, while the state σ is used to evaluate !E and ?E . The semantics of statements is mostly standard, except for the last rule. A procedure call E 0 : f(E 1 )$E 2 within X has a defined semantics iff: (i) E 0 evaluates to an address Y; (ii) E 2 evaluates to a non-negative number n, not exceeding the balance of X; (iii) the contract at Y has a procedure named f with formal parameters x 1 · · · x h ; (iv) E 1 evaluates to a sequence of values of length h. If all these conditions hold, then the procedure body S is executed in a state where X's balance is decreased by n, Y's balance is increased by n, and in an environment where the formal parameters are bound to the actual ones, and the special names sender and value are bound, respectively, to X (the caller) and n (the value transferred to Y).
Example 2. Consider the following statements, to be evaluated within a contract C in a store σ where σCk = ⊥: We have that: (a) ?k:=1 evaluates to ⊥ because the first premise of the assignment rule is not satisfied, as the lhs of the assignment evaluates to ⊥; (b) similarly, k:=?k evaluates to ⊥ because the second premise is not satisfied, as the rhs evaluates to ⊥; (c) if !?k then k:=0 else k:=1 evaluates to ⊥, because the semantics of the guard is ⊥; (d) since there are no semantic rules for throw, implicitly this means that its semantics is undefined; (e) ?k:=1; skip is a sequence of two commands, where the first command evaluates to ⊥. The rule for sequences requires that the first command evaluates to some state σ ′ , while this is not the case for ?k:=1. Therefore, the premise of the rule does not hold, and so the overall command evaluates to ⊥; (f) finally, while true do skip evaluates to ⊥, because there exists no (finite) derivation tree which infers while true do skip C σ,ρ = σ. Summing up, all the statements above have an undefined semantics. In practice, the semantic rules for transactions (see Section 4) ensure that the effects of any transaction whose statement evaluates to ⊥ will be reverted (see e.g. Example 6). The procedure f allows A to deposit funds to the contract; dually, g allows A to transfer funds to other addresses. The guard sender = A ensures that only A can invoke the procedures of C; calls from other addresses result in a throw, which leaves the state of C unchanged (in particular, throw reverts the currency transfer from sender to C). The procedure g also checks that no currency is transferred along with the contract call (value = 0), and that the balance of C is enough (?balance ≥ x). Let S g be the body of g, let σ be such that σCbalance = 3, and let ρ = { A /sender, 0 /value, 2 /x, B /y}. We have: 3 Digression: modelling re-entrancy We now show how to express in TinySol re-entrancy, a subtle features of Solidity which was exploited in the famous "DAO Attack" [3,12].
Example 4 (Harmless re-entrancy). Consider the following procedures: Intuitively, f first calls g, and then transfers value units of currency to the address x. The procedure g attempts to change the currency recipient by calling back f, setting the parameter x to B. We prove that this attack fails. Let S = C : f(A, true)$1. For all σ and ρ such that σCbalance = 1, we have: Since σ ′ = σ, we conclude that S C σ,ρ = σ − C : 1 + A : 1. So, g has failed its attempt to divert the currency transfer to B.
⊓ ⊔ Example 5 (Vicious re-entrancy). Consider the following procedures: Intuitively, f would like to transfer 1 ether to D, by calling g. The guard not!k is intended to ensure that the transfer happens at most once. Let σ be such that σCbalance = n ≥ 1 and σCk = ⊥, and let ρ = { D /sender, 0 /value}, ρ ′ = { C /sender, 1 /value}. Let S f and S g be the bodies of f and g. We have:

Transactions and blockchains
A transaction T is a term of the form A n − → C : f(v), where A is the address of the caller, C is the address of the called contract, f is the called procedure, n is the value transferred from A to C, and v is the sequence of actual parameters.
The semantics of T in a given state σ, is a new state σ ′ = T σ . The function · σ is defined by the following rules: Rule [Tx1] handles the case where the transaction is successful: this happens when A's balance is at least n, and the procedure call terminates in a nonerror state. Note that n units of currency are transferred to C before starting to execute f, and that the names sender and value are set, respectively, to A and n. Instead, [Tx2] applies either when A's balance is not enough, or the execution of f fails (this also covers the case when f does not terminate). In these cases, T does not alter the state, i.e. σ ′ = σ.
A blockchain B is a finite sequence of transactions. The semantics of B is obtained by folding the semantics of its transactions: Note that erroneous transactions occuring in a blockchain have no effect on its semantics (as rule [Tx2] makes them identities w.r.t. the append operation).

Example 6.
Recall the contract C from Example 3, and let B = T 0 T 1 T 0 , where: Let S f and S g be the bodies of f and g, respectively. σAbalance = 5 and σCbalance = 0. By rule [Tx1] we have that: Now, let σ ′ = σ − A : 3 + C : 3. By rule [Tx1] we have that:

Additional examples
In this section we illustrate the expressiveness of TinySol through a series of examples.

An extended wallet
In Figure 3 we refine the wallet contract in Example 3, by keeping track in the store of the amount of money transferred to each user. The contract TinyWallet has two procedures: init, which initializes the contract owner, and pay, which transfers amount units of currency from the contract to the account dst.
The procedure init checks at line 4 if the key owner is defined; if not, it means that the contract is still in the initial state where all keys (except balance) are undefined, and in this case it binds the key owner to the sender of the transaction.
The procedure pay requires at line 8 that (i) the caller is the contract owner, (ii) the caller does not transfer any currency along with the call, and (iii) the contract balance is enough. If any of these conditions does not hold, the procedure throws an exception. At line 10, if dst is not bound yet in the store, then it is set to amount. Otherwise, at line 11 the old value is incremented by amount.

An escrow contract
In Figure 4 we specify in TinySol a simple escrow contract, which allows a buyer to deposit some funds to the contract and later authorize their transfer to a seller. Further, the seller can authorize a full refund to the buyer, in case there is some problem with the purchase. If buyer and seller do not find an agreement, they can resort to an external authority, which decides how the initial deposit is split among them (retaining a fee).
The procedure init initializes three keys: buyer (the sender of the transaction), seller and oracle (passed as parameters). The guard !buyer ensures that init can be called at most once. The procedures pay and refund authorize, respectively, the fund transfer to the seller or to the buyer; their guards ensure that a participant cannot authorize a transfer to herself. Either buyer and seller can call dispute, which in turns calls the procedure openDispute of the contract at address oracle.
A possible contract with this procedure is Oracle in Figure 4: there, the procedure openDispute just binds the key escrow to the address of the contract caller (TinyEscrow). The oracle resolves the dispute by calling the procedure closeDispute: its parameter z is the fraction of the deposit which goes to the buyer; 1% of the deposit goes to the oracle as fee. Note that, if buyer or seller call pay or refund before the oracle calls closeDispute, then the effect of the first four instructions within the else branch of closeDispute is null (since balance is zero), and the invocation just results in the closure of the dispute.

A two-players lottery
In Figure 5 we code in TinySol a two-players lottery, inspired by the one in [2]. The players p1 and p2 bet 1 unit of currency each; additionally, they deposit 2 units of currency as collateral, which are used as compensation in case of dishonest behaviour. The procedure join allows the players to join the lottery; the parameter h is the hash of a secret, used to implement a timed commitment protocol, similarly to [2]. The check h = ?h1 at line 7 serves to avoid an attack where the second player replays the same hash of the first one. The procedure leave allows the first player to leave the lottery, if no other player joins before time t0. Note that time is provided by an oracle, modelled by the contract Clock (not displayed in the figure). The procedure reveal allows both players to reveal their secrets: when this happens, the player redeems her collateral. Finally, the procedure win determines the winner of the lottery, who will collect the bets. If both players have revealed their secrets, then the winner is p1 or p2, depending on the parity of the sum of the secrets. Otherwise, one player can redeem the bets if she has revealed her secret and the deadline t0 has passed.

A Ponzi scheme
In Figure 6 we implement a Ponzi scheme, i.e. a contract where users invest money, and can redeem their investment (plus interests) if enough users invest enough money in the contract afterwards. In particular, we consider a scheme which pays back users in order of arrival; this kind of Ponzi schemes gained some popularity in the early stage of Ethereum, with dozens of different instances. The procedure init sets the contract owner, and initializes to 0 the key n, which counts the total number of investors, and p, which counts the number of investors who have been paid. The procedure join allows users to invest money, and distributes the new investment among all the other users who have not been paid so far. The procedure exploits the key-value store to maintain an array of investors. At line 14, the key ?n (i.e., the current value bound to n) is bound to a pair, which contains the address of the new investor, and the invested amount. We use fst and snd to access the first and second element of a pair, respectively. When a new user joins the scheme, the owner receives 1/10 of the value transferred along with the call (line 16). At lines 17-19, the procedure scans the array of unpaid users, starting from the oldest entry. As long as the balance is enough, each user receives twice the amount she invested. Note that ?p denotes the value bound to p (i.e., the index of the first unpaid user), while ??p denotes the pair (sender, value) associated to that user.

Conclusions
We have introduced TinySol, a minimal core contract calculus inspired by Solidity. While our calculus is focussed on a single new construct to call contracts and transfer currency, other languages have been proposed to capture other peculiar aspects of smart contracts. Some of these language are domain-specific, e.g. for financial contracts [4,7] and for business processes [11,17], while some others are more abstract, modelling contracts as automata with guarded transitions [13,16]. Establishing the correctness of the compilation from these languages to Solidity would be one of the possible applications of a bare bone formal model, like our TinySol. Another possible application of a minimal calculus is the investigation of different styles of semantics, like e.g. denotational and axiomatic semantics. Further, the study of analysis and optimization techniques for smart contracts may take advantage of a succinct formalization like ours.
Differences between TinySol and Solidity Aiming at minimality, TinySol simplifies or neglects several features of Solidity. A first difference is that we do not model a gas mechanism. In Ethereum, when sending a transaction, users deposit into it some crypto-currency, to be paid to the miner which appends the transaction to the blockchain. Each computation step performed by the miner consumes part of this deposit; when the deposit reaches zero, the miner stops executing the transaction. At this point, all the effects of the transaction (except the payment to the miner) are rolled back. Although in TinySol we do not model the gas mechanism, we still ensure that non-terminating calls have an undefined semantics (see e.g. Example 2), so that they are rolled back by rule [Tx2]. The semantics of TinySol could be easily extended with an "abstract" gas model, by associating a cost to instructions and recording the gas consumption in the environment. However, note that any gas mechanism formalized at the level of abstraction of Solidity would not faithfully reflect the actual Ethereum gas mechanism, where the cost of instructions are defined at the EVM bytecode level. Indeed, compiler optimizations would make it hard to establish a correspondence between the cost of a piece of Solidity code and the cost of its compiled bytecode. Still, an abstract gas model could be useful in practice, e.g. to establish upper bounds to the gas consumption of a piece of Solidity code.
A second difference is that our model assumes the set of contracts to be fixed, while in Ethereum new contracts can be created at run-time. As a consequence, TinySol does not feature constructors that are called when the contract is created. Dynamic contract creation could be formalized by extending our model with special transactions which extends the mapping Γ with the contracts generated at run-time. Once this is done, adding constructors is standard.
In Ethereum, contracts can implement time constraints by using the block publication time, accessible via the variable block.timestamp. In TinySol we do not record timestamps in the blockchain. Still, time constraints can be implemented by using oracles, i.e. contracts which allow certain trusted parties to set their keys (e.g., timestamps), and make them accessible to other contracts (see e.g. the lottery contract in Section 5).
In Ethereum, when the procedure name specified in the transaction does not match any of the procedures in the contract, a special unnamed "fallback" procedure (with no arguments) is implicitly invoked. Extending TinySol with this mechanism would be straightforward. Delegate and internal calls, which we have omitted in TinySol, would be simple to model as well.