Smart contracts are a focal point of modern blockchain environments. Such contracts were firstly popularized by Ethereum [Buterin2014], but soon thereafter other networks developed their own smart contract languages, enabling the implementation of blockchain-based decentralized applications between untrusted parties.
Smart contracts usually operate over user owned assets, thus, vulnerabilities in programs and in the underlying programming languages can lead to considerable losses. The famous attack on the DAO resulted in a theft of approximately 60 million USD worth of Ether [Atzei2017, DAOExplained, Chen2020]. Due to recent exploitations of vulnerabilities in smart contracts, blockchain providers turned their attention to the development of robust programming languages, often relying on formal verification, including Liquidity111http://www.liquidity-lang.org/doc/index.html by Tezos, Plutus by IOHK [Brunjes], Move by Facebook [blackshear2019move], and Rholang222https://github.com/rchain/rchain/tree/master/rholang-tutorial by RChain. Such languages aim at offering flexible and complex smart contracts while assuring that developers may fully trust contract behaviour. Unfortunately, for Plutus, the last objective has not been completely achieved yet. As we show in the next section, in Plutus, assets can be easily lost forever to the ledger with a simple unintended interaction.
To counter unplanned interactions with smart contracts endpoints while automating the development of boilerplate code, we propose SmartScribble, a protocol specification language for smart contracts. Its syntax is adapted from the Scribble protocol language [Yoshida2014] to the smart contract trait and features primitives for sequential composition, choice, recursion, and interrupts. Protocols in SmartScribble specify interactions between participants and the ledger, as well as triggers to interrupt protocol execution. The business logic underlying the contract can be added by the programmer after the automatic generation of the smart contract boilerplate code. The generated code relies on a finite state machine to validate all interactions, precluding unexpected behaviours.
SmartScribble currently targets Plutus, a native smart contract programming language for the Cardano blockchain [Kiayias2017], based on the Extended Unspent Transaction Output model [DBLP:conf/fc/Chakravarty0MMJ20], a solution that expands the limited expressiveness of the Unspent Transaction Output model. In UTxO, transactions consist of a list of inputs and outputs. Outputs correspond to the quantity available to be spent by inputs of subsequent transactions. Extended UTxO expands UTxO’s expressiveness without switching to an account-based model, that introduces a notion of shared mutable state, ravelling contract semantics [DBLP:conf/fc/Chakravarty0MMJ20]. Nevertheless, the framework we propose can be integrated with other smart contract languages and blockchain infrastructures expressive enough to support state machines.
Several works have been adopting state machines to control the interaction of participants with smart contracts. FSolidM [Mavridou2017]—the closest proposal to SmartScribble—introduces a model for smart contracts based on finite state machines. FSolidM relies on the explicit construction of finite state machines for contract specification; instead, we automatically generate all state machine code. On a different fashion, the model checker Cubicle [DBLP:conf/fm/ConchonKZ19] encodes smart contracts and the transactional model of the blockchain as a state machine.
SmartScribble distinguishes itself from other domain-specific languages—BitML, integrated with the Bitcoin blockchain [Bartoletti2018], Obsidian [coblenz2020obsidian], a typestate-oriented language, and Nomos [nomos], a functional (session-typed) language—by abstracting the interactive behaviour and details of the target programming language through a protocol specification, only relying on the smart contract language to implement the business logic and thus flattening the learning curve.
The next section motivates SmartScribble via an example where assets are lost to the ledger; section 3 presents the protocol language and section 4 contract generation from protocols. Section 5 describes some preliminary results of our evaluation of SmartScribble, and section 6 concludes the paper and points to future work. Appendix 0.A contains the source code for the vulnerable contract we explore in our motivation, appendix 0.B presents input and logs for playground simulations, appendix 0.C contains the definition of SmartScribble protocols used in section 5, and appendix 0.D the source code for the business logic of our running example.
2 Smart contracts can go wrong
This section identifies a weakness of the Plutus smart contract programming language. Although Plutus is developed with a clear focus on reliability, it lacks mechanisms to enforce correct patterns of interactions.
As a running example we consider the popular guessing game (contract in appendix 0.A), the paradigm for secret-based contracts where participants try to guess a secret and get rewarded if successful. Another example that falls in this category is a lottery. Figure 1 represents a correct sequence of events in the guessing game:
The owner of the contract locks a secret and deposits a prize in ADA333ADA is the digital currency of the Cardano blockchain. 1 ADA = 1,000,000 Lovelace. to be retrieved by the first player who correctly guesses the secret.
The player tries to guess the secret.
If the guess matches the secret, the player retrieves the prize and the game ends; otherwise, the player is warned that the guess did not succeed and the game continues.
In Plutus, the parties involved in the protocol are not required to follow valid patterns of interaction. We explore a scenario where one of the parties deviates from the (implicitly) expected flow and show that this leads to a faulty behaviour that is silenced by the blockchain. Figure 2 represents a scenario where the owner incorrectly executes two consecutive lock operations and the player provides a correct guess. A simulation of this scenario in the Plutus Playground444https://prod.playground.plutus.iohkdev.io/ is illustrated in Figure 3. Starting with 10 Lovelace in both the owner’s and player’s wallets, we reach a situation where the log identifies a validation failure for a guess that coincides with that of the first lock (see Figure 8 in Appendix 0.B). The final balances in Figure 4 show that the player did not collect the reward despite having guessed the first secret and the owner lost their money to the contract.
This behaviour is certainly unexpected: rather than overriding the first lock or having the second lock fail, both secrets are stored in the ledger as outputs. When a guess is performed, both stored outputs are compared to the guess and, as a consequence, no guess will ever validate against two different secrets. Furthermore, the prize is irrevocably lost to the contract without any possibility of retrieval. This is an unexpected band of silent behaviour that we want to prevent. Even in this simple scenario users lose assets to the ledger. Similar situations are very likely to occur in complex contracts, with devastating results.
We propose specifying the interaction behaviour of smart contracts through protocols that describe the valid patterns of interactions between different classes of users and the contract. Our approach prevents unexpected contract behaviours by having contracts automatically validating interactions. The protocol for the guessing game described at the end of next section, detects and avoids further attempts to lock secrets, among other unintended interactions. Note that this type of vulnerability is different from Transaction Ordering Dependence [DBLP:journals/access/SayeedMC20] that is related to corrupt miners maliciously changing the order of transactions, and not the order in which the endpoints are called.
3 Specifying smart contract protocols in SmartScribble
Scribble [Yoshida2014] is a language to describe application-level protocols for communicating systems. It comes with tools to generate Java or Python APIs on which developers can base correct-by-construction implementations of protocols.
SmartScribble is based as much as possible on Scribble, even if it covers only a fragment of the language and includes support for smart contract specific features. The base types of SmartScribble include String, HashedString (strings stored in the ledger), PubKeyHash (wallet public key identifiers) and Value (an amount in ADA). To watch SmartScribble in action we start with a very simple version of the guessing game protocol and gradually make it more robust. Our first version is the straight line guessing game, featuring a sequential composition of three endpoints: lock, guess, closeGame.
The StraightLineGuessingGame protocol introduces, in the first line, the roles users are expected to take when interacting with the smart contract: the Owner owns the game; the Player makes guesses. Stateful protocols require state to be kept in the contract. The field declaration introduces the types of the fields that are stored within the state machine. In this case, we need an HashedString for the secret. Along with the declared fields, SmartScribble creates an extra Value field by default, this field is used to manage the funds in the contract, in this instance, we use it to store the prize. The fields are stored in the state machine in the form of tuples, with each element of the tuple corresponding to one of the declared fields. Users may declare repeated types. The tuple with stored contents can be used by the programmer when implementing the business logic. Protocol StraightLineGuessingGame makes use of interaction constructs to describe interactions between an user and the endpoints lock, guess and closeGame. In this protocol, the three endpoints must be exercised once, in the order by which they appear in the protocol, and by users of the appropriate role. Endpoint signatures comprise the endpoint name followed by the types of the parameters.
It should be stressed that the guessing nature of the contract is nowhere present in the protocol. Nothing in the protocol associates the HashedString in endpoint lock to a secret, or Value to the prize. Nowhere it is said that guessing the secret entails the transfer of the prize to the Player’s account. Instead, the protocol governs interaction only: which endpoints are available to which roles, at which time. The business logic associated with the contract is programmed later, in the contract language of the blockchain.
Our next version allows the owner to cancel the game after locking the secret (perhaps the secret was too easy or the prize was set too low). The choice operator denotes a choice made by an user, featuring different alternative branches.
After locking the secret, the Owner is given two choices: to cancel the game (cancelGame) or to allow a player to make a guess (proceedWithGame). The two branches represent two different endpoints in the contract. The choice is in the hands of a single role, Owner in this case. This role should exercise one endpoint or the other (but not both). Protocols for the two branches are distinct. In the case of proceedWithGame, endpoint guess is to be called by Player. The cancelGame branch is empty. In either case, the Owner is supposed to close the game after making the choice.
The third version allows one or more players to continue guessing while the game is kept open by the owner. We make use of rec-loops for the effect.
The rec constructor introduces a labelled recursion point. In this case the protocol may continue at the recursion point by means of the Loop label. In any iteration of the loop the owner is called to decide whether the game continues or not (perhaps the secret was found or the owner got tired of playing). If she decides proceedWithGame, a player is given the chance of guessing (by calling endpoint guess) and the owner is called again to decide the faith of the game.
Version three requires a lot of Owner intervention: the continuation of the game depends on her choice—proceedWithGame or cancelGame—after each guess. We want protocols able to terminate automatically, based on guess validation or on the passage of time. Our fourth version takes advantage of the do-interrupt constructor and the trigger declaration:
In the last version of the protocol, the Owner does not have any further involvement after locking the secret. The game ends when one of the triggers is activated. Declarations slot trigger closeGame and funds trigger closeGame contain the keywords slot and funds, that instruct the compiler to generate functions in the business logic module where the programmer may define the conditions for these triggers. The primitive role Contract signs the closeGame operation. This is not an endpoint, but an interaction that is executed automatically.
Constructors rec, choice, do-interrupt and protocol definitions share Scribble’s syntax entirely. Interactions are simplified: we remove to <recipient> present in Scribble syntax because in our setting the recipient is always the contract. The declarations field, funds trigger, slot trigger are exclusive to SmartScribble.
4 Smart contract generation from SmartScribble
This section details the smart contracts generated from SmartScribble protocols and explain how developers can add custom business logic to complete contract code. To ensure the validation of participants’ interactions with the contract we construct a finite state machine from each SmartScribble protocol, whose implementation is automatically generated by our compiler.
Protocols are governed by finite state machines. Figure 5 depicts the automata for the recursive and the do-interrupt guessing game in section 3. The correspondence is such that endpoints in the automaton correspond to edges, and pre- and post-interaction points in a protocol correspond to nodes. Sequential composition, choice and rec generate appropriate wiring. Interrupts call the associated function generating new edges, as in the case of closeGame where an edge links state #2 to the terminal state #3 (Figure 4(b)).
The SmartScribble compiler generates code divided in three different modules: Domain and Library Module, Smart Contract Module and Business Logic Module. In this section we give a brief overview of the three modules.
The Domain and Library Module includes declaration of errors, the interface for the contract, the definition of state machine inputs, states and the functions to interact with the state machine.
The Smart Contract Module contains code activated when interacting with endpoints. For example, lock registers the two triggers and calls the corresponding function in the Business Logic Module. The latter function returns either an error or the fields to be stored at the new state. If an error is received, no state transition is performed. Otherwise, the machine advances to the next state and sets its new contents. Changes to the value field stored in the state results in the transfer of funds between the node interacting with the endpoint and the contract. This module also contains code for state transition that is used to define the state machine and boilerplate code specific to Plutus’ contracts. To implement the state machine we use the Plutus State Machine library555Language.Plutus.Contract.StateMachine, part of the standard Plutus package.
Finally, the Business Logic Module contains signatures for each of the endpoints in the contract. The actual code is meant to be written by the contract developer. The interaction
in the protocol requires a function
(signature simplified) in the Smart Contract Module that returns either a new StateContents (a tuple composed of the fields stored in the state) or an error. If the value is non-positive, lock returns an error including an error message; otherwise returns a StateContents, that is a pair, composed of the hashed string corresponding to the input and the value. These are the two fields to store in the new state of the state machine (state#2 in Figure 4(b), reached via the guess-labelled edge). The triggers: funds trigger and slot trigger associated with lock, generate functions with signatures:
for the respective triggers. In lockFundTrigger, the developer should add an expression with the condition to activate the trigger, e.g., (\funds -> funds ‘V.leq‘ 0). In lockSlotTrigger we specify the Slot that activates the trigger.
Corresponding to the
in the protocol, a function with the following signature must be written.
Function guess reads the secret from the machine state (an HashedString) compares with the hashed version of the input string. If they match, it returns a pair whose second component is zero ADA, otherwise it returns an appropriate error message. The caller to guess (in module Smart Contract Module) detects the difference in the value field of the state and credits the difference in the client’s account. Finally, the closeGame () from Contract interaction point needs a function with the same name that, in this case, returns a state with HashedString ”Game over” and zero ADA as its fields.
The complete code of the module is in appendix 0.D; the code for the three functions and two triggers amounts to 13 lines.
We now revisit the scenario in Figure 2 using the input from Figure 3, that previously resulted in an error. Thanks to the integration with a state machine, the contract now impedes the second lock from taking effect, as can be seen in Figure 9 (available in appendix 0.B). In Figure 6, we see that now the player is able to retrieve the prize for guessing correctly: the player terminates with 13 Lovelace, and the owner ends with 7 Lovelace, resulting from the deposit of 3 Lovelace with the first lock.
This section compares the amount of lines of code (LOC), those written by the developer and those generated by our compiler. To carry out the comparison, we use the guessing game together with three protocols representative of simple smart contracts. The new protocols are in appendix 0.C.
- Ping Pong
A simple protocol that alternates between ping and pong operations, ad eternum. No business logic is required for this protocol.
A crowdfunding where an owner starts a campaign with a goal (in ADA), and contributors donate to the campaign. When the owner decides to close the campaign, all the donations stored in the contract are collected.
A protocol where a seller starts an auction over some token, setting the time limit and the maximum number of bids. Buyers bid for the token. When the auction is over, the seller collects the funds of the highest bid and the corresponding bidder gets the token.
Table 1 summarizes the analysis. Depending on the protocol, the amount of generated code varies from 150 to 187 lines. In all our examples, the generated code is at least larger than the source written in SmartScribble. The business logic varies a lot from contract to contract; nevertheless, it is important to note that it is extremely likely to be a small portion of the complete contract due to the amount of necessary boilerplate that Plutus requires. We see that the ratio between all the code written by the programmer (that is, the protocol and the business logic code) and the Plutus code that would otherwise be manually written is less than 1/4 in all analysed contracts. When we compare LOC for suggested implementations666Implementations available on Plutus’ GitHub and SmartScribble, we conclude that with SmartScribble, the code manually written is once again 1/5 or less for every scenario. Even in implementations developed by experts, the boilerplate portion of the contract is significant.
6 Conclusion and future work
We present SmartScribble—a protocol language for smart contracts—and a compiler that automatically generates all contract code, except the business logic. The generated code relies on state machines to prevent unexpected interactions with the contract. We claim that SmartScribble improves the reliability of contracts by reducing the likelihood of programmers introducing faults in smart contract code. Our language also flattens the learning curve, allowing developers to focus on the business logic rather than on the boilerplate required to setup a contract, namely in Plutus. Preliminary results point to a 1/4 ratio between the number of lines of code written by the programmer and those in the final contract. This paper constitutes an initial report on using protocol descriptions to generate contract code. Much remains to be done. SmartScribble
protocols classify participants under different roles, but we currently do not enforce any form of association of participants to roles. We plan to look into different forms of enforcing the association. Business logic is currently manually written in the contract language (Plutus) and added to the code generated from the protocol. We plan to look into ways of adding more business logic to protocols, thus minimising the Plutus code that must be hand written. Some features ofSmartScribble are strongly linked with Plutus. The trigger generation is one of those features: it depends on Plutus libraries for the effect. Nevertheless, we believe that SmartScribble can be adapted to target other languages with minimal changes to the syntax and semantics. Generating Solidity code might be an interesting option for the future, as it also supports state machines. Lastly, evaluation needs to be elaborated. In ongoing work, we are comparing the usage of computational resources between contracts implemented with SmartScribble and the corresponding suggested implementations.
Appendix 0.A Plutus code for vulnerable guessing game
Appendix 0.B Plutus playground simulation for the guessing game scenario
Appendix 0.C SmartScribble protocols for the evaluation section
The ping-pong protocol
The crowdfunding protocol
The auction protocol
Appendix 0.D Plutus code for business logic
Guessing game logic module (SmartScribble)