A minimal core calculus for Solidity contracts
aa r X i v : . [ c s . P L ] A ug A minimal core calculus for Solidity contracts
Massimo Bartoletti , Letterio Galletta , and Maurizio Murgia , Universit`a degli Studi di Cagliari, Cagliari, Italy IMT Lucca, Lucca, Italy Universit`a di Trento, Trento, Italy
Abstract.
The Ethereum platform supports the decentralized executionof smart contracts , i.e. computer programs that transfer digital assets be-tween users. The most common language used to develop these contractsis Solidity, a Javascript-like language which compiles into EVM bytecode,the language actually executed by Ethereum nodes. While much researchhas addressed the formalisation of the semantics of EVM bytecode, rela-tively little attention has been devoted to that of Solidity. In this paperwe propose a minimal calculus for Solidity contracts, which extends animperative core with a single primitive to transfer currency and invokecontract procedures. We build upon this formalisation to give semanticsto the Ethereum blockchain. We show our calculus expressive enough toreason about some typical quirks of Solidity, like e.g. re-entrancy.
Keywords:
Ethereum; smart contracts; Solidity
A paradigmatic feature of blockchain platforms is the ability to execute “smart”contracts, i.e. computer programs that transfer digital assets between users, with-out relying on a trusted authority. In Ethereum [5] — the most prominent smartcontracts 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 contractcontrols an amount of crypto-currency, that it can exchange with other users andcontracts. Users interact with contracts by sending transactions, which representprocedure calls, and may possibly involve transfers of crypto-currency from thecaller to the callee. The sequence of transactions on the blockchain determinesthe state of each contract, and the balance of each user.Ethereum supports contracts written in a Turing-complete language, calledEVM bytecode [8]. Since programming at the bytecode level is inconvenient,developers seldom use EVM bytecode directly, but instead write contracts inhigher-level languages which compile into bytecode. The most common of theselanguages is Solidity [1], a Javascript-like language supported by the EthereumFoundation. There is a growing literature on the formalization of Solidity, whichroughly can be partitioned in two approaches, according to the distance fromthe formal model to the actual language. One approach is to include as large aubset of Solidity as possible, while the other is to devise a core calculus thatis as small as possible, capturing just the features of Solidity that are relevantto some specific task. In general, the first approach has more direct practicalapplications: for instance, a formal semantics very close to that of the actualSolidity can be the basis for developing precise analysis and verification tools.Although diverse in nature, the motivations underlying the second approach arenot less strong. The main benefit of omitting almost all the features of the fulllanguage is that by doing so we simplify rigorous reasoning. This simplification isessential for the development of new proof techniques (e.g., axiomatic semantics),static analysis techniques (e.g., data and control flow analysis, type systems), aswell as for the study of language expressiveness (e.g., rigorous comparisons andencodings to/from other languages). The co-existence of these two approachesis common in computer science: for instance, the formalization of Java gave riseof a lot of research since the mid 90s, producing Featherweight Java [9] as themost notable witness of the “minimalistic” approach.
Contribution
In this paper we pursue the minimalistic approach, by introduc-ing a core calculus for smart contracts. Our calculus, called
TinySol (for “TinySolidity”), features an imperative core, which we extend with a single constructto call contracts and transfer currency. This construct, inspired by Solidity “ex-ternal” calls, captures the most paradigmatic aspect of smart contracts, i.e. theexchange of digital assets according to programmable rules. Slightly divergingfrom canonical presentations of imperative languages, we use key-value storesto represent the contract state, so abstracting and generalising Solidity statevariables. We formalise the semantics of
TinySol in Section 2, using a big-stepoperational style. In Section 3 we show
TinySol expressive enough to reproduce reentrancy attacks , one of the typical quirks of Solidity; the succinctness of theseproofs witnesses an advantage of our minimalistic approach. In Section 4 werefine our formalization, by giving semantics to transactions and blockchains.In Section 5 we exemplify
TinySol through a variety of complex contracts, rang-ing from wallets, to escrow services, lotteries, and Ponzi schemes. Aiming atminimality,
TinySol makes several simplifications w.r.t. Solidity: in Section 6 wediscuss the main differences between the two languages.
Related work
Besides ours, the only other Solidity-inspired minimal core cal-culus 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 cryptocurrencyand external calls, while omitting other language features, like e.g. internal anddelegate calls, function modifiers, and the gas mechanism. The main differencebetween
TinySol and FS is stylistic: while our design choice was to start from abasic imperative language, and extend it with a single contract-oriented primitive(external calls), FS follows the style of Featherweight Java, modelling functionbodies as expressions. Compared to our calculus, FS also includes the dynamiccreation of contracts, and a type system which detects some run-time errors. Afurther difference is that FS models blockchains as functions from contract iden-tifiers to states; instead, we represent a blockchain as a sequence of transactions,nd then we reconstruct the state by giving a semantics to this sequence. In thisway 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 con-structs, including e.g. access to memory and storage, inheritance, internal andexternal calls, and function modifiers. The formalization also deals with com-plex data types, like structs, arrays and mappings. The works [10,18] propose tour-de-force formalizations of larger fragments of Solidity, also including a gasmechanism. Both [19] and [18] mechanize their semantics in the Coq proof assis-tant, while [10] uses the K-Framework [14]. The work [13] extends the semanticsof [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 , . . . , par-titioned into account addresses A , B , . . . and contract addresses C , D , . . . . Wewrite 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 contractprocedure, where f is the procedure name, x are its formal parameters (omittedwhen 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 bythe expression E ; – X : f ( v ) $ n calls the procedure f (with actual parameters v ) of the contractat 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 areprovided (since their definition is standard, we will not detail them). The expres-sion ! k evaluates to true if the key k is bound in the contract store, otherwiseit 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 theaddress 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 treatmentof account and contract addresses: indeed, calling a procedure on an account ad-dress A can only result in a pure currency transfer to A , since the procedure canonly perform a skip . We further postulate that: (i) expressions and statementsare well-typed: e.g., guards in conditionals and in loops have type bool; (ii) the ::= statement skip skip | throw exception | E := E ′ store update | S ; S ′ sequence | if E then S else S ′ conditional | while E do S loop | E : f ( E ) $ E call E ::= expression v value | x const name | X address | op E operator | ? E key lookup | ! E key bound? | X : E context Fig. 1: Syntax of
TinySol .procedures in Γ ( C ) have distinct names; (iii) the key balance cannot stay at theleft of an assignment; (iv) the constant names sender and value cannot stay inthe formal parameters of a procedure.We use the following syntactic sugar. For a call X : f ( v ) $ n , when there is nomoney transfer (i.e., n = 0) we just write it as X : f ( v ); when the target is anaccount address A (so, the call is to the procedure f skip ), we write it as A $ n .We write if E then S for if E then S else skip .The semantics of contracts is given in terms of a function from states tostates. A state σ : Addr → ( Val ⇀ Val ) maps each address to a key-valuestore, i.e. a partial function from values (keys) to values. We use the standardbrackets notation for representing finite maps: for instance, { v / x , · · · , v n / x n } maps x i to v i , for i ∈ ..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 accountaddresses A , and dom σ C ⊇ { balance } for all contract addresses C . A qualifiedkey is a term of the form X .k . We write σ ( X .k ) for σ X k .A state update π : Addr ⇀ ( Val ⇀ Val ) is a substitution from qualifiedkeys 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:( σπ ) X = δ X where δ X k = ( π X k if X .k ∈ keys( π ) σ X k otherwiseWe define the auxiliary operators σ + X : n and σ − X : n on states, which,respectively, increase/decrease the balance of X of n currency units: σ ◦ X : n = σ { ( σ X balance ) ◦ n / X . balance } ( ◦ ∈ { + , −} ) Example 1.
Let σ be a state which maps X to the store δ X = { / k , / k } . Let π = { / X .k } and π ′ = { / X .k } be state updates. We have ( σπ ) X = { / k , / k } ,( σπ ′ ) X = { / k , / k , / k } , and ( σπ ) Y = ( σπ ′ ) Y = σ Y for all Y = X .We give the operational semantics of statements in a big-step style. Thesemantics of a statement S is parameterised over a state σ , an address X (the v K X σ,ρ = v J x K X σ,ρ = ρ x J Y K X σ,ρ = Y J op E K X σ,ρ = op J E K X σ,ρ J Y : E K X σ,ρ = J E K Y σ,ρ J ? E K X σ,ρ = σ X ( J E K X σ,ρ ) J ! E K X σ,ρ = ( true if J E K X σ,ρ = ⊥ and σ X ( J E K X σ,ρ ) = ⊥ false if J E K X σ,ρ = ⊥ and σ X ( J E K X σ,ρ ) = ⊥ J skip K X σ,ρ = σ J E K X σ,ρ = k J E ′ K X σ,ρ = v J E := E ′ K X σ,ρ = σ { v / X .k } J E K X σ,ρ = b ∈ { true , false } J if E then S true else S false K X σ,ρ = J S b K X σ,ρ J S K X σ,ρ = σ ′ J S ; S K X σ,ρ = J S K X σ ′ ,ρ J E K X σ,ρ = false J while E do S K X σ,ρ = σ J E K X σ,ρ = true J S K X σ,ρ = σ ′ J while E do S K X σ,ρ = J while E do S K X σ ′ ,ρ J E K X σ,ρ = Y J E K X σ,ρ = v · · · v h J E K X σ,ρ = n ≤ σ X balancef ( x · · · x h ) { S } ∈ Γ ( Y ) σ ′ = σ − X : n + Y : nρ ′ = { X / sender , n / value , v / x , · · · , v h / x h } J E : f ( E ) $ E K X σ,ρ = J S K Y σ ′ , ρ ′ Fig. 2: Semantics of statements and expressions.contract wherein S is evaluated), and an environment ρ : Const ⇀ Val , usedto 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 avalue; so, expressions have no side effects. We assume that all the semanticoperators are strict , i.e. their result is ⊥ if some operand is ⊥ . We denote by J S K X σ,ρ the semantics of a statement S in a given state σ , environment ρ , andaddress X , where the partial function J · K X σ,ρ is defined by the inference rulesin Figure 2. We write J S K X σ,ρ = ⊥ when the semantics of S is not defined.The semantics of expressions is straightforward; note that we use op to denotesyntactic operators, and op for their semantic counterpart. The environment ρ isused 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 : f ( E ) $ E within X has a defined semantics iff: (i) E evaluates to an address Y ; (ii) E evaluates to a non-negative number n , notexceeding the balance of X ; (iii) the contract at Y has a procedure named f withformal parameters x · · · x h ; (iv) E evaluates to a sequence of values of length h . If all these conditions hold, then the procedure body S is executed in a statewhere X ’s balance is decreased by n , Y ’s balance is increased by n , and in anenvironment where the formal parameters are bound to the actual ones, and thespecial 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 σ C k = ⊥ : ? k :=1 k := ? k if !? k then k :=0 else k :=1 throw ? k :=1; skip while true do skip e have that: (a) ? k :=1 evaluates to ⊥ because the first premise of the assign-ment rule is not satisfied, as the lhs of the assignment evaluates to ⊥ ; (b) simi-larly, k := ? k evaluates to ⊥ because the second premise is not satisfied, as the rhsevaluates to ⊥ ; (c) if !? k then k :=0 else k :=1 evaluates to ⊥ , because the se-mantics of the guard is ⊥ ; (d) since there are no semantic rules for throw , implic-itly this means that its semantics is undefined; (e) ? k :=1; skip is a sequence oftwo commands, where the first command evaluates to ⊥ . The rule for sequencesrequires that the first command evaluates to some state σ ′ , while this is not thecase for ? k :=1. Therefore, the premise of the rule does not hold, and so the overallcommand evaluates to ⊥ ; (f) finally, while true do skip evaluates to ⊥ , becausethere exists no (finite) derivation tree which infers J while true do skip K 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 anytransaction whose statement evaluates to ⊥ will be reverted (see e.g. Example 6). Example 3 (Wallet).
Consider the following procedures of the contract at C : f () { if sender = A then skip else throw } g ( x, y ) { if sender = A && value = 0 && ? balance ≥ x then y $ x else throw } 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 currencytransfer from sender to C ). The procedure g also checks that no currency istransferred 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 σ C balance = 3, and let ρ = { A / sender , / value , / x , B / y } . We have: J S g K C σ,ρ = J y $ x K C σ,ρ = J y : f skip () $ x K C σ,ρ = J skip K B σ − C :2+ B :2 , { C / sender , / value } = σ − C : 2 + B : 2Note that J S g K C σ,ρ = ⊥ if σ C balance <
2, or ρ sender = A , or ρ value = 0. We now show how to express in
TinySol re-entrancy , a subtle features of Soliditywhich was exploited in the famous “DAO Attack” [3,12].
Example 4 (Harmless re-entrancy).
Consider the following procedures: f ( x, b ) { if b then { D : g (); x $ value }} ∈ Γ ( C ) g () { sender : f ( B , false ) } ∈ Γ ( D )Intuitively, f first calls g , and then transfers value units of currency to theaddress x . The procedure g attempts to change the currency recipient by callingack 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 σ C balance = 1, we have: J S K X σ,ρ = J if b then { D : g (); x $ value } K C σ,ρ ′ ( ρ ′ = { C / sender , / value , true / b , A / x } )= J D : g (); x $ value K C σ,ρ ′ = J x $ value K C σ ′ ,ρ ′ ( σ ′ = J D : g () K C σ,ρ ′ )= σ ′ − C : 1 + A : 1where σ ′ = J D : g () K C σ,ρ ′ = J sender : f ( B , false ) K D σ, { C / sender , / value } = J if b then { D : g (); x $ value } K C σ,ρ ′′ ( ρ ′′ = { D / sender , / value , false / b , B / x } )= J skip K C σ,ρ ′′ = σ Since σ ′ = σ , we conclude that J S K C σ,ρ = σ − C : 1 + A : 1. So, g has failed itsattempt to divert the currency transfer to B . ⊓⊔ Example 5 (Vicious re-entrancy).
Consider the following procedures: f () { if not ! k && ? balance ≥ then { D : g () $ k := true } } ∈ Γ ( C ) g () { C : f () } ∈ Γ ( D )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 suchthat σ C balance = n ≥ σ C k = ⊥ , and let ρ = { D / sender , / value } , ρ ′ = { C / sender , / value } . Let S f and S g be the bodies of f and g . We have: J S f K C σ,ρ = J D : g () $ k := true K C σ,ρ = J k := true K C σ ,ρ σ = J D : g () $ K C σ,ρ = J S g K D σ − C :1+ D :1 ,ρ ′ = J S f K C σ − C :1+ D :1 ,ρ = J D : g () $ k := true K C σ − C :1+ D :1 ,ρ = J k := true K C σ ,ρ σ = J D : g () $ K C σ − C :1+ D :1 ,ρ = J S g K D σ − C :2+ D :2 ,ρ ′ = J k := true K C σ ,ρ σ i = J k := true K C σ i +1 ,ρ (for i ∈ . . . n − σ n = J skip K C σ − C : n + D : n,ρ = σ − C : n + D : n Summing up, J S f K C σ,ρ = ( σ − C : n + D : n ) { true / k } , i.e. D has drained all thecurrency from C . ⊓⊔ A transaction T is a term of the form A n −→ C : f ( v ), where A is the address ofthe caller, C is the address of the called contract, f is the called procedure, n isthe value transferred from A to C , and v is the sequence of actual parameters.he semantics of T in a given state σ , is a new state σ ′ = J T K σ . The function J · K σ is defined by the following rules: f ( x ) { S } ∈ Γ ( C ) σ A balance ≥ n J S K C σ − A : n + C : n, { A / sender , n / value , v / x } = σ ′ J A n −→ C : f ( v ) K σ = σ ′ [Tx1] f ( x ) { S } ∈ Γ ( C ) (cid:0) σ A balance < n or J S K C σ − A : n + C : n, { A / sender , n / value , v / x } = ⊥ (cid:1) J A n −→ C : f ( v ) K σ = σ [Tx2] Rule [Tx1] handles the case where the transaction is successful: this happenswhen A ’s balance is at least n , and the procedure call terminates in a non-error state. Note that n units of currency are transferred to C before starting toexecute 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 executionof 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 isobtained by folding the semantics of its transactions: J ǫ K σ = σ J T B K σ = J B K J T K σ Note that erroneous transactions occuring in a blockchain have no effect on itssemantics (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 T T , where: T = A −→ C : f () T = A −→ C : g (2 , B )Let S f and S g be the bodies of f and g , respectively. σ A balance = 5 and σ C balance = 0. By rule [Tx1] we have that: J T K σ = J S f K C σ − A :3+ C :3 , { A / sender , / value } = J skip K C σ − A :3+ C :3 , { A / sender , / value } = σ − A : 3 + C : 3Now, let σ ′ = σ − A : 3 + C : 3. By rule [Tx1] we have that: J T K σ ′ = J S g K C σ ′ , { A / sender , / value , / x , B / y } = J y $ x K C σ ′ , { A / sender , / value , / x , B / y } = σ ′ − C : 2 + B : 2Let σ ′′ = σ ′ − C : 2 + B : 2. By rule [Tx2] , we obtain J B K σ = J T K σ ′′ = σ ′′ . In this section we illustrate the expressiveness of
TinySol through a series ofexamples. .1 An extended wallet
In Figure 3 we refine the wallet contract in Example 3, by keeping track in thestore of the amount of money transferred to each user.The contract
TinyWallet has two procedures: init , which initializes thecontract owner , and pay , which transfers amount units of currency from thecontract to the account dst .The procedure init checks at line 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 that (i) the caller is the contractowner, (ii) the caller does not transfer any currency along with the call, and (iii) thecontract balance is enough. If any of these conditions does not hold, the proce-dure throws an exception. At line , if dst is not bound yet in the store, thenit is set to amount . Otherwise, at line the old value is incremented by amount .Finally, line transfers amount units of currency to the recipient. contract TinyWallet { init () { if !owner then throw else owner := sender } pay ( amount , dst ) { if ( sender /= ?owner || value /=0 || amount > ? balance ) then throw else { if not ! dst then dst := amount else dst := ? dst + amount ; dst $ amount } } } Fig. 3: An extended wallet 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 casethere is some problem with the purchase. If buyer and seller do not find anagreement, they can resort to an external authority, which decides how the initialdeposit is split among them (retaining a fee).The procedure init initializes three keys: buyer (the sender of the transac-tion), seller and oracle (passed as parameters). The guard !buyer ensuresthat init can be called at most once. The procedures pay and refund autho-rize, respectively, the fund transfer to the seller or to the buyer ; their guards contract
TinyEscrow { init (x,y) { if !buyer then throw else { buyer := sender ; seller := x; oracle := y } } pay () { if sender /= ?buyer then throw else ?seller $ ? balance } refund () { if sender /= ?seller then throw else ?buyer $ ? balance } dispute () { if ( sender /= ?buyer && sender /= ?seller) then throw else ?oracle. openDispute () } } contract Oracle { init () { isOpen := false } openDispute () { if not ?isOpen then { isOpen := true; escrow := sender } } closeDispute (z) { if sender /= AOracle then throw else if ?isOpen { fee := ?escrow:? balance * 0.01; ?escrow:?buyer $ (?escrow:? balance - ?fee) * z; ?escrow:?seller $ ?escrow:? balance ; ?fee $ AOracle; isOpen := false } } } Fig. 4: An escrow contract using an oracle.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, theprocedure openDispute just binds the key escrow to the address of the contractcaller (
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 thefirst 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. 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 contract TinyLottery { init () { nPlayers := 0 } join (h) { if (?nPlayers = 2 || value /= 3) then throw else if ?nPlayers = 0 then { p1 := sender ; h1 := h, nPlayers := 1; t0 := Clock:time+1000 } else if (h = ?h1) then throw else { p2 := sender ; h2 := h, nPlayers := 2 } } leave () { if ( sender = ?p1 && ?nPlayers = 1 && Clock:time > t0) then { ?p1 $ ? balance ; nPlayers := 0; } else throw } reveal (s) { if (?nPlayers /= 2) then throw else if ( sender = ?p1 && hash(s) = ?h1 && not !s1) then {s1 := s; ?p1 $ else if ( sender = ?p2 && hash(s) = ?h2 && not !s2) then {s2 := s; ?p2 $ else throw } win () { if (!s1 && !s2) then if ((?s1 + ?s2) %2 = 0) then ?p1 $ else ?p2 $ else if (!s1 && Clock:time > t0) then ?p1 $ else if (!s2 && Clock:time > t0) then ?p2 $ else throw } } Fig. 5: A two-players lottery.2 units of currency as collateral, which are used as compensation in case ofdishonest 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 commitmentprotocol, similarly to [2]. The check h = ?h1 at line serves to avoid an attackwhere 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 beforetime 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 revealtheir secrets: when this happens, the player redeems her collateral. Finally, theprocedure win determines the winner of the lottery, who will collect the bets. Ifboth players have revealed their secrets, then the winner is p1 or p2 , dependingon the parity of the sum of the secrets. Otherwise, one player can redeem thebets if she has revealed her secret and the deadline t0 has passed. In Figure 6 we implement a
Ponzi scheme , i.e. a contract where users investmoney, and can redeem their investment (plus interests) if enough users investenough money in the contract afterwards. In particular, we consider a schemewhich pays back users in order of arrival; this kind of Ponzi schemes gained somepopularity in the early stage of Ethereum, with dozens of different instances. contract
TinyPonzi { init () { if !owner then throw else { owner := sender ; n := 0; // total number of investors p := 0 // number of paid investors } } join () { if ( value < 1 || not !owner) then throw else { ?n := ( sender , value ); n := ?n + 1; value /10 $ ?owner; while (?p < ?n && ? balance >= 2*snd(??p)) do { $ fst(??p); p := ?p + 1 } } } } Fig. 6: A Ponzi scheme.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 numberof investors who have been paid. The procedure join allows users to investmoney, and distributes the new investment among all the other users who havenot been paid so far. The procedure exploits the key-value store to maintain anarray of investors. At line , the key ?n (i.e., the current value bound to n ) isbound to a pair, which contains the address of the new investor, and the investedamount. 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 ). At lines , the procedurescans the array of unpaid users, starting from the oldest entry. As long as thebalance 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. We have introduced
TinySol , a minimal core contract calculus inspired by Solid-ity. While our calculus is focussed on a single new construct to call contracts andtransfer currency, other languages have been proposed to capture other peculiaraspects of smart contracts. Some of these language are domain-specific, e.g. forfinancial contracts [4,7] and for business processes [11,17], while some others aremore abstract, modelling contracts as automata with guarded transitions [13,16].Establishing the correctness of the compilation from these languages to Soliditywould be one of the possible applications of a bare bone formal model, like our
TinySol . Another possible application of a minimal calculus is the investigationf different styles of semantics, like e.g. denotational and axiomatic semantics.Further, the study of analysis and optimization techniques for smart contractsmay 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 donot model a gas mechanism . In Ethereum, when sending a transaction, usersdeposit into it some crypto-currency, to be paid to the miner which appends thetransaction to the blockchain. Each computation step performed by the minerconsumes part of this deposit; when the deposit reaches zero, the miner stopsexecuting the transaction. At this point, all the effects of the transaction (exceptthe payment to the miner) are rolled back. Although in
TinySol we do not modelthe gas mechanism, we still ensure that non-terminating calls have an undefinedsemantics (see e.g. Example 2), so that they are rolled back by rule [Tx2] . Thesemantics of
TinySol could be easily extended with an “abstract” gas model,by associating a cost to instructions and recording the gas consumption in theenvironment. However, note that any gas mechanism formalized at the levelof abstraction of Solidity would not faithfully reflect the actual Ethereum gasmechanism, where the cost of instructions are defined at the EVM bytecode level.Indeed, compiler optimizations would make it hard to establish a correspondencebetween 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 upperbounds 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 withspecial transactions which extends the mapping Γ with the contracts generatedat run-time. Once this is done, adding constructors is standard.In Ethereum, contracts can implement time constraints by using the blockpublication time, accessible via the variable block.timestamp . In TinySol wedo not record timestamps in the blockchain. Still, time constraints can be im-plemented by using oracles, i.e. contracts which allow certain trusted parties toset 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 doesnot match any of the procedures in the contract, a special unnamed “fallback”procedure (with no arguments) is implicitly invoked. Extending
TinySol withthis mechanism would be straightforward. Delegate and internal calls, which wehave omitted in
TinySol , would be simple to model as well.
Acknowledgements
Massimo Bartoletti and Maurizio Murgia are partiallysupported by Autonomous Region of Sardinia projects
Sardcoin and
Smart col-laborative engineering . Letterio Galletta is partially supported by IMT Schoolfor Advanced Studies Lucca project
PAI VeriOSS . Maurizio Murgia is partiallysupported by MIUR PON
Distributed Ledgers for Secure Open Communities . eferences
1. Solidity documentation. https://solidity.readthedocs.io/en/v0.5.4/ (2019)2. Andrychowicz, M., Dziembowski, S., Malinowski, D., Mazurek, L.: Secure multi-party computations on Bitcoin. Commun. ACM 59(4), 76–84 (2016)3. Atzei, N., Bartoletti, M., Cimoli, T.: A survey of attacks on Ethereum smart con-tracts (SoK). In: POST. LNCS, vol. 10204, pp. 164–186. Springer (2017)4. Biryukov, A., Khovratovich, D., Tikhomirov, S.: Findel: Secure derivative contractsfor Ethereum. In: Financial Cryptography Workshops. LNCS, vol. 10323, pp. 453–467. Springer (2017)5. Buterin, V.: Ethereum: a next generation smart contract and decentralized applica-tion platform. https://github.com/ethereum/wiki/wiki/White-Paperhttps://github.com/ethereum/wiki/wiki/White-Paper