Helmholtz: A Verifier for Tezos Smart Contracts Based on Refinement Types

A smart contract is a program executed on a blockchain, based on which many cryptocurrencies are implemented, and is being used for automating transactions. Due to the large amount of money that smart contracts deal with, there is a surging demand for a method that can statically and formally verify them. This tool paper describes our type-based static verification tool Helmholtz for Michelson, which is a statically typed stack-based language for writing smart contracts that are executed on the blockchain platform Tezos. Helmholtz is designed on top of our extension of Michelson’s type system with refinement types. Helmholtz takes a Michelson program annotated with a user-defined specification written in the form of a refinement type as input; it then typechecks the program against the specification based on the refinement type system, discharging the generated verification conditions with the SMT solver Z3. We briefly introduce our refinement type system for the core calculus Mini-Michelson of Michelson, which incorporates the characteristic features such as compound datatypes (e.g., lists and pairs), higher-order functions, and invocation of another contract. Helmholtz successfully verifies several practical Michelson programs, including one that transfers money to an account and that checks a digital signature.


Introduction
A blockchain is a data structure to implement a distributed ledger in a trustless yet secure way.The idea of blockchains is initially devised for the Bitcoin cryptocur-rency [12] platform.Many cryptocurrencies are implemented using blockchains, in which value equivalent to a significant amount of money is exchanged.
Recently, many cryptocurrency platforms allow programs to be executed on a blockchain.Such programs are called smart contracts [19] (or, simply contracts in this article) since they work as a device to enable automated execution of a contract.In general, a smart contract is a program P a associated with an account a on a blockchain.When the account a receives money from another account b with a parameter v, the computation defined in P a is conducted, during which the state of the account a (e.g., the balance of the account and values that are stored by the previous invocations of P a ) which is recorded on the blockchain may be updated.The contract P a may execute money transactions to another account (say c), which results in invocations of other contracts (say P c ) during or after the computation; therefore, contract invocations may be chained.
Although smart contracts' original motivation was handling simple transactions (e.g., money transfer) among the accounts on a blockchain, recent contracts are being used for more complicated purposes (e.g., establishing a fund involving multiple accounts).Following this trend, the languages for writing smart contracts also evolve from those that allow a contract to execute relatively simple transactions (e.g., Script for Bitcoin) to those that allow a program that is as complex as one written in standard programming languages (e.g., EVM for Ethereum and Michelson [13] for Tezos [4]).
Due to a large amount of money they deal with, verification of smart contracts is imperative.Static verification is especially needed since a smart contract cannot be fixed once deployed on a blockchain.Attack on a vulnerable contract indeed happened.For example, the DAO attack, in which the vulnerability of a fundraising contract was exploited, resulted in the loss of cryptocurrency equivalent to approximately 150M USD [18].
In this article, we describe our type-based static verifier HELMHOLTZ 1 for smart contracts written in Michelson.The Michelson language is a statically and simply typed stack-based language equipped with rich data types (e.g., lists, maps, and higher-order functions) and primitives to manipulate them.Although several highlevel languages that compile to Michelson are being developed, Michelson is most widely used to write a smart contract for Tezos as of writing.
A Michelson program expresses the above computation in a purely functional style, in which the Michelson program corresponding to P a is defined as a function.The function takes a pair of the parameter v and a value s that represents the current state of the account (called storage) and returns a pair of a list of operations and the updated storage s .Here, an operation is a Michelson value that expresses the computation (e.g., transferring money to an account and invoking the contract associated with the account) that is to be conducted after the current computation (i.e., P a ) terminates.After the computation specified by P a finishes with a pair of a storage value and an operation list, a blockchain system invokes the computation specified in the operation list.This purely functional style admits static verification methods for Michelson programs similar to those for standard functional languages.
As the theoretical foundation of HELMHOLTZ, we design a refinement type system for Michelson as an extension of the original simple type system.In contrast to standard refinement types that refine the types of values, our type system refines the type of stacks.
We show that our tool can verify several practical smart contracts.In addition to the contracts we wrote ourselves, we apply our tool to the sample Michelson programs used in Mi-cho-coq [2], a formalization of Michelson in Coq proof assistant [21].These contracts consist of practical contracts such as one that checks a digital signature and one that transfers money.
We note that HELMHOLTZ currently supports approximately 80% of the whole instructions of the Michelson language.Another limitation of the current HELMHOLTZ is that it can verify only a single contract, although one often uses multiple contracts for an application, in which a contract may call another by a money transfer operation, and their behavior as a whole is of interest.We are currently extending HELMHOLTZ so that it can deal with more programs.
Our contribution is summarized as follows: (1) Definition of the core calculus Mini-Michelson and its refinement type system; (2) Automated verification tool HELMHOLTZ for Michelson contracts implemented based on the type system of Mini-Michelson; the interface to the implementation can be found at https://www.fos.kuis.kyoto-u.ac.jp/trylang/Helmholtz; and (3) Evaluation of HELMHOLTZ with various Michelson contracts, including practical ones.A preliminary version of this article was presented at International Conference on Tools and Algorithms for the Construction and Analysis of Systems (TACAS) in 2021.We have given detailed proofs of properties of Mini-Michelson and a more detailed description about the verifier implementation, in addition to revision of the text.
The rest of this article is organized as follows.Before introducing the technical details, we present an overview of the verifier HELMHOLTZ in Section 2 using a simple example of a Michelson contract.Section 3 introduces the core calculus Mini-Michelson with its refinement type system and states soundness of the refinement type system.(Detailed proofs are deferred to Appendix A.) We also discuss a few extensions implemented in the verifier.Section 4 describes the verifier HELMHOLTZ, a case study, and experimental results.After discussing related work in Section 5, we conclude in Section 6.

Overview of HELMHOLTZ and Michelson
We give an overview of our tool HELMHOLTZ in this section before presenting its technical details.We also explain Michelson by example (Section 2.2) and user-written annotation added to a Michelson program for verification purposes (Section 2.3).

HELMHOLTZ
As input, HELMHOLTZ takes a Michelson program annotated with (1) its specification expressed in a refinement type and (2) additional user annotations such as loop invariants.It typechecks the annotated program against the specification using our refinement type system; the verification conditions generated during the typechecking is discharged by the SMT solver Z3 [11].If the code successfully typechecks, then the program is guaranteed to satisfy the specification.HELMHOLTZ is implemented as a subcommand of tezos-client, the client program of the Tezos blockchain.For example, to verify boomerang.tz in Figure 1, we run tezos-client refinement boomerang.tz.If the verification succeeds, the command outputs VERIFIED to the terminal screen (with a few log messages); otherwise, it outputs UNVERIFIED.

An Example Contract in Michelson
Figure 1 shows an example of a Michelson program called boomerang.A Michelson program is associated with an account on the Tezos blockchain; the program is invoked by transferring money to this account.This artificial program in Figure 1, when it is invoked, is supposed to transfer the received money back to the account that initiated the transaction.
A Michelson program starts with type declarations of its parameter, whose value is given by contract invocation, and storage, which is the state that the contract account stores.Lines 1-2 declare that the types of both are unit, the type inhabited by the only value Unit.Lines 3-8 surrounded by << and >> are a user-written annotation used by HELMHOLTZ for verification; we will explain this annotation later.The code section in Lines 10-29 is the body of this program.
Let us take a look at the code section of the program.In the following explanation of each instruction, we describe the state of the stack after each instruction as comments; stack elements are delimited by .
-Execution of a Michelson program starts with a stack with one value, which is a pair (param, st) of a parameter param and a storage value storage.-CDR pops the pair at the top of the stack and pushes the second value of the popped pair; thus, after executing the instruction, the stack contains the single value st.-NIL pushes the empty list [] to the stack; the instruction is accompanied by the type operation of the list elements for typechecking purposes.-AMOUNT pushes the nonnegative amount of the money sent to the account to which this program is associated.-PUSH mutez 0 pushes the value 0. The type mutez represents a unit of money used in Tezos.-IFCMPEQ b1 b2, if the state of the stack before executing the instruction is v1 v2 tl, (1) pops v1 and v2 and (2) executes the then-branch b1 (resp., the else-branch b2) if v2 = v1 (resp., v2 = v1).In boomerang, this instruction does nothing if amount = 0; otherwise, the instructions in the else-branch are executed.
-SOURCE at the beginning of the else-branch pushes the address src of the source account, which initiated the chain of contract invocations that the current contract belongs to, resulting in the stack src [] st.-CONTRACT T pops an address addr from the stack and typechecks whether the contract associated with addr takes an argument of type T .If the typechecking succeeds, then Some (Contract addr) is pushed; otherwise, None is pushed.
The constructor Contract creates an object that represents a typechecked contract at the given address.In Tezos, the source account is always a contract that takes the value Unit as a parameter; thus, Some (Contract src) will always be pushed onto the stack.-ASSERT_SOME pops a value v from the stack and pushes v' if v is Some v'; otherwise, it raises an exception.-UNIT pushes the unit value Unit to the stack.
-TRANSFER_TOKENS, if the stack is of the shape varg vamt vcontr tl, pops varg, vamt, and vcontr from the stack and pushes (Transfer varg vamt vcontr) onto tl.The value Transfer varg vamt vcontr is an operation object expressing that money (of amount vamt) shall be sent to the account vcontr with the argument varg after this program finishes without raising an exception.Therefore, the program associated with vcontr is invoked after this program finishes.Otherwise, an operation object is an opaque tuple and no instruction can extract its elements.
-CONS with the stack v1 v2 tl pops v1 and v2, and pushes a cons list v1::v2 onto the stack.(We use the list notation in OCaml here.)-After executing one of the branches associated with IFCMPEQ in this program, the shape of the stack should be ops storage, where ops is [] if amount = 0 or [Transfer varg vamt vcontr] if amount > 0. The instruction PAIR pops ops and storage, and pushes (ops,storage).
A Michelson program is supposed to finish its execution with a singleton stack whose unique element is a pair of (1) a list of operations to be executed after the current execution of the contract finishes and (2) the new value for the storage.
Michelson is a statically typed language.Each instruction is associated with a typing rule that specifies the shapes of stacks before and after it by a sequence of simple types such as int and int list.For example, CONS requires the type of top element to be T and that of the second to be T list (for any T ); it ensures the top element after it has type T list.
Other notable features of Michelson include first-class functions, hashing, instructions related to cryptography such as signature verification, and manipulation of a blockchain using operations.

Specification
A user can specify the behavior of a program by a ContractAnnot annotation, which is a part of the augmented syntax of our verification tool.A ContractAnnot annotation gives a specification of a Michelson program by the following notation inspired by the refinement types: {(param,st) | pre} -> {(ops,st') | post} & {exc | abpost} where pre, post, and abpost are predicates.This specification reads as follows: if this program is invoked with a parameter param and storage st that satisfies the property pre, then (1) if the execution of this program succeeds, then it returns a list of operations ops and new storage storage' that satisfy the property post; (2) if this program raises an exception with value exc, then exc satisfies abpost.The specification language, which is ML-like, is expressive enough to cover the specifications for practical contracts, including the ones we used in the experiments in Section 4.5.In the predicates, one can use several keywords such as amount for the amount of the money sent to this program when it is invoked and source for the source account's address.
The ContractAnnot annotation in Figure 1 (Lines 3-8) formalizes this program's specification as follows.This program can take any parameter and storage (Line 3).Successful execution of this program results in a pair (ops,st') that satisfies the condition in Lines 4-7 that expresses (1) if amount = 0, then ops is empty, that is, no operation will be issued; (2) if amount > 0, then ops is a list of a single element Transfer Unit amount c, where c is bound for Contract source2 , which expresses transfer of money of the amount amount to the account at source with the unit argument. 3In the specification language, source and amount are keywords that stand for the source account and the amount of money sent to this program, respectively.The part & { _ | False } expresses that this program does not raise an exception.This specification correctly formalizes the intended behavior of this program.

Refinement Type System for Mini-Michelson
In this section, we formalize Mini-Michelson, a core subset of Michelson with its syntax, operational semantics, and refinement type system.We omit many features from the full language in favor of conciseness but includes language constructs-such as higher-order functions and iterations-that make verification difficult.

Syntax
Figure 2 shows the syntax of Mini-Michelson.Values, ranged over by V , consist of integers i; addresses a; operation objects Transfer(V, i, a) to invoke a contract at a by sending money of amount i and an argument V ; pairs (V 1 ,V 2 ) of values; the empty list []; cons V 1 :: V 2 ; and code IS of first-class functions. 4 LAMBDA pushes a function (described by its operand IS) onto the stack and EXEC calls a function.Perhaps unfamiliar is DIP IS, which pops and saves the stack top somewhere else, execute IS, and then push back the saved value.
We also use a few kinds of stacks in the following definitions: operand stacks, ranged over by S, type stacks, ranged over by T , and type binding stacks, ranged over by ϒ .The empty stack is denoted by ‡ and push is by .We often omit the empty stack and write, for example, V 1 V 2 for V 1 V 2 ‡.Intuitively, T 1 .. T n and x 1 :T 1 .. x n : T n describe stacks V 1 .. V n where each value V i is of type T i .We will use variables to name stack elements in the refinement type system.
We summarize main differences from Michelson proper: -Michelson has the notion of type attributes, which classify types, according to which generic operations such as PUSH can be applied.For example, values of pushable types can be put on the stack by PUSH.Since type operation is not pushable, an instruction such as PUSH operation Transfer(V, i, a) is not valid in Michelson-all operations have to be created by designated instructions.We ignore type attributes for simplicity here, but the implementation of HELMHOLTZ, which calls the typechecker of Michelson, does not.-As we saw in Section 2, an operation is created from an address in two steps via a contract value.Since we model only one kind of operations, i.e., Transfer(V, i, a), we simplify the process to let instruction TRANSFER_TOKENS directly creates an operation from an address in one step.We also omit the typecheck of the contract associated with an address.-In Michelson, each execution of a smart contract is assigned a gas to control how long the contract can run to prevent contracts from running too long.-We do not formally model exceptions for simplicity and, thus, the refinement type system do not (have to) capture exceptional behavior.Our verifier, however, does handle exceptions; we will informally discuss how we extend the type system with exceptions in Section 3.6.

Operational Semantics
Figure 3 defines the operational semantics of Mini-Michelson.A judgment of the form S I ⇓ S (or S IS ⇓ S , resp.) means that evaluating the instruction I (or the instruction sequence IS, resp.)under the stack S results in the stack S .Although the defining rules are straightforward, we will make a few remarks about them.The rule (E-DIP) means that DIP IS pops and saves the stack top somewhere else, execute IS, and then push back the saved value, as explained above.This instruction implicitly gives Mini-Michelson (and Michelson) a secondary stack.(E-PUSH) means that PUSH T V does not check if the pushed value is well formed at run time: the check is the job of the simple type system, discussed soon.

Fig. 3 Operational Semantics of Mini-Michelson
The rules (E-IFT) and (E-IFF) define the behavior of the branching instruction IF IS 1 IS 2 , which executes IS 1 or IS 2 , depending on the top of the operand stack.As we have mentioned, nonzero integers mean True.Thus, (E-IFT) is used for the case in which IS 1 is executed, and otherwise, (E-IFF) is used.There is another branching instruction IF_CONS IS 1 IS 2 , which executes either instruction sequence depending on whether the list at the top of the stack is empty or not (cf.(E-IFCONST) and (E-IFCONSF)).
The rules (E-LOOPT) and (E-LOOPF) define the behavior of the looping instruction LOOP IS.This instruction executes IS repeatedly until the top of the stack becomes false.(E-LOOPT) means that, if the condition is True, IS is executed, and then LOOP IS is executed again.(E-LOOPF) means that, if the condition is false, the loop is finished after dropping the stack top.A similar looping instruction is ITER IS, which iterates over a list (see (E-ITERNIL) and (E-ITERCONS)).
The rule (E-LAMBDA) means that LAMBDA T 1 T 2 IS pushes the instruction sequence to the stack and (E-EXEC) means that EXEC pops the instruction sequence IS and the stack top V , saves the rest of the stack S elsewhere, runs IS with V as the sole value in the stack, pushes the result V back to the restored stack S.
The rule (E-TRANSFERTOKENS) means that TRANSFER_TOKENS T creates an operation object and pushes onto the stack.(As we have discussed, we omit a run-time check to see if T is really the argument type of the contract that the address a stores.)

Simple Type System
Mini-Michelson (as well as Michelson) is equipped with a simple type system.The type judgment for instructions is written T I ⇒ T , which means that instruction I transforms a stack of type T into another stack of type T .The type judgment for values is written V : T , which means that V is given simple type T .The typing rules, which are shown in Figure 4, are fairly straightforward.Note that these two judgment forms depend on each other-see (RTV-FUN) and (T-PUSH).

Refinement Type System
Now we extend the simple type system to a refinement type system.In the refinement type system, a simple stack type T 1 .. T n is augmented with a formula ϕ in an assertion language to describe the relationship among stack elements.More concretely, we introduce refinement stack types, ranged over by Φ, of the form {x 1 :T 1 ... x n : and refine the type judgment form, accordingly.We start with an assertion language, which is many-sorted first-order logic and proceed to the refinement type system.

Assertion Language
The assertion language is many-sorted first-order logic, where sorts are simple types.We show the syntax of terms, ranged over by t, and formulae, ranged over by ϕ, in Figure 5.As usual, x is bound in ∃ x:T.ϕ.Most of them are straightforward but the formulae of the form call(t 1 ,t 2 ) = t 3 deserves an explanation.It means that, if instruction sequence denoted by t 1 is called with (a singleton stack that stores) a value denoted by t 2 (and terminates), it yields the value denoted by t 3 .The term constructor Value Typing V : T 6 Well-Sorted Terms and Formulae.
Transfer(t 1 ,t 2 ,t 3 ) allows us to refer to the elements in an operation object, which is opaque.Conjunction ϕ 1 ∧ ϕ, implication ϕ 1 =⇒ ϕ 2 , and universal quantification ∀ x:T.ϕ are defined as abbreviations as usual.We use several common abbreviations such as t 1 = t 2 for ¬ (t 1 = t 2 ), ∃ x 1 :T 1 , .. , x n :T n .ϕfor ∃x 1 :T 1 . . . .∃x n :T n .ϕ,etc.A typing environment, ranged over by Γ , is a sequence of type binding.We assume all variables in Γ are distinct.We abuse a comma to concatenate typing environments, e.g., Γ 1 ,Γ 2 .We also use a type binding stack ϒ as a typing environment, explicitly denoted by ϒ , which is defined as ‡ = empty and x:T ϒ = x:T, ϒ .
Well-sorted terms and formulae are defined by the judgments Γ t : T and Γ ϕ : * , respectively.The former means that the term t is a well-sorted term of the sort T under the typing environment Γ and the latter that the formula ϕ is well-sorted under the typing environment Γ , respectively.The derivation rules for each judgment, shown in Figure 6, are straightforward.Note that well-sortedness depends on the simple type system via (WT-VAL).
Let a value assignment σ be a mapping from variables to values.We write σ [x → V ] to denote the value assignment which maps x to V and otherwise is identical to σ .As we are interested in well-sorted formulae, we consider a value assignment that respects a typing environment, as follows.
Definition 1.A value assignment σ is typed under a typing environment Γ , denoted by σ : Γ , iff σ (x) : T for every x:T ∈ Γ .Now we define the semantics of the well-sorted terms and formulae in a standard manner as follows.

Typing Rules
The type system is defined by subtyping and typing: a subtyping judgment is of the form Γ Φ 1 <: Φ 2 , which means stack type Φ 1 is a subtype of Φ 2 under Γ , and a type judgment for instructions (resp.instruction sequences) is of the form Γ Φ 1 I Φ 2 (resp.Γ Φ 1 IS Φ 2 ), which means that if I (resp.IS) is executed under a stack satisfying Φ 1 , the resulting stack (if terminates) satisfies Φ 2 .We often call Φ 1 pre-condition and Φ 2 post-condition, following the terminology of Hoare logic.Note that the scopes of variables declared in Γ include Φ 1 and Φ 2 but those bound in Φ 1 do not include Φ 2 .To express the relationship between the initial and final stacks, we use the type environment Γ .In writing down concrete specifications, it is sometimes convenient to allow the scopes of variables bound in Φ 1 to include Φ 2 , but we find that it would clutter the presentation of typing rules.In our type system, subtyping is defined semantically as follows.

Definition 4 (Subtyping relation). A refinement stack type
We show the typing rules in Figure 7 and Figure 8.It is easy to observe that the type binding stack parts in the pre-and post-conditions follows the simple type system.We will focus on predicate parts below.tions that the top of the input stack is True (x = 0) and False (x = 0).The variable x is existentially quantified because the top element will be removed before the execution of either branch.

Refinement Instruction Sequence Typing
-(RT-LOOP) is similar to the proof rule for while-loops in Hoare logic.The formula ϕ is a loop invariant.Since the body of LOOP is executed while the stack top is nonzero, the pre-condition for the body IS is strengthened by x = 0, whereas the post-condition of LOOP IS is strengthened by x = 0. -(RT-LAMBDA) is for the instruction to push a first-class function onto the operand stack.The premise of the rule means that the body IS takes a value (named y 1 ) of type T 1 that satisfies ϕ 1 and outputs a value (named y 2 ) of type T 2 that satisfies ϕ 2 (if it terminates).The post-condition in the conclusion expresses, by using call, that the function x has the property above.The extra variable y 1 in the type environment of the premise is an alias of y 1 ; being a variable declared in the type Stack Typing S : T 9 Simple and refinement stack typing environment y 1 can appear in both ϕ 1 and ϕ 2 5 and can describe the relationship between the input and output of the function.
-(RT-EXEC) just adds to the post-condition call(x 2 , x 1 ) = x 3 , meaning the result of a call to the function x 2 with x 1 as an argument yields x 3 .It may look simpler than expected; the crux here is that ϕ is expected to imply ∀ x 1 :T 1 , where ϕ 1 and ϕ 2 represent the pre-and post-conditions, respectively, of function x 2 .If x 1 satisfies ϕ 1 , then we can derive that ϕ 2 holds.-(RT-SUB) is the rule for subsumption to strengthening the pre-condition and weakening the post-condition.

Properties
In this section, we show soundness of our type system.Informally, what we show is that, for a well-typed program, if we execute it under a stack which satisfies the pre-condition of the typing, then (if the evaluation halts) the resulting stack satisfies the post-condition of the typing.We only sketch proofs with important lemmas.The detailed proofs are found in Appendix A.
To state the soundness formally, we give additional definitions.
Definition 5 (Free variables).The set of free variables in ϕ is denoted by fvars(ϕ).
Definition 6 (Erasure).We define Φ , which is the simple stack type obtained by erasing predicates from Φ, as follows.
Definition 7 (Stack typing).Stack typing S : T and refinement stack typing σ : Γ |= S : Φ are defined by the rules in Figure 9.
Note that the definition of refinement stack typing follows the informal explanation of the refinement stack types in Section 3.4.
We start from soundness of simple type system as follows, that is, for a simply well-typed instruction sequence T1 IS ⇒ T2 , if evaluation starts from correct stack S 1 , that is S 1 : T1 , and results in a stack S 2 ; then S 2 respects T2 , that is S 2 : T2 .This lemma is not only just a desirable property but also one we use for proving the soundness of the refinement type system in the case EXEC.We state the main theorem as follows.
Theorem 9 (Soundness of the Refinement Type System).
The proof is close to a proof of soundness of Hoare logic, with a few extra complications due to the presence of first-class functions.One of the key lemmas is the following one, which states that a value assignment can be represented by a logical formula or a stack element: Lemma 10.The following statements are equivalent: Then, we prove a few lemmas related to LOOP (Lemma 11), ITER (Lemma 12), predicate call (Lemmas 13 and 14), and subtyping (Lemma 15).
Proof.By the definition of the semantics of call.

Extension with Exceptions
The type system implemented in HELMHOLTZ is extended to handle instruction FAILWITH, which immediately aborts the execution, discarding all the stack elements but the top element.The type judgment form is extended to which means that, if IS is executed under a stack satisfying Φ 1 , then the resulting stack satisfies Φ 2 (if normally terminates), or Φ 3 (if aborted by FAILWITH).The typing rule for instruction FAILWITH, which raises an exception with the value at the stack top, is given as follows: The rule expresses that, if FAILWITH is executed under a non-empty stack that satisfies ϕ, then the program point just after the instruction is not reachable (hence, {ϒ | ⊥}).
The refinement ∃ x:T, ϒ .ϕ∧ x = err for the exception case states that ϕ in the precondition with the top element x is equal to the raised value err; since x is not in the scope in the exception refinement, x is bound by an existential quantifier.Most of the other typing rules can be extended with the "&" part easily.For (RT-LAMBDA) and (RT-EXEC), we first extend the the assertion language with a new predicate call_err(t 1 ,t 2 ) = t 3 meaning the call of t 1 with t 2 aborts with the value t 3 .(The semantics of call is unchanged.)Using the new predicate, (RT-LAMBDA) and (RT-EXEC) are modified as in Figure 10.

Tool Implementation
In this section, we present HELMHOLTZ, the verification tool based on the refinement type system.We first discuss how Michelson code can be annotated.Then, we give an overview of the verification algorithm, which reduces the verification problem to SMT solving, and discuss how Michelson-specific features are encoded.Finally, we show a case study of contract verification and present verification experiments.

Annotations
HELMHOLTZ supports several forms of annotations (surrounded by << and >> in the source code), other than ContractAnnot explained in Section 2. As we have already mentioned, ML-like notations can be used to describe formulae, which have to be quantifier free (mainly because state-of-the-art SMT solvers do not handle quentifiers very well).We explain several constructs for an annotation in the following.
Assert Φ and Assume Φ can appear before or after an instruction.The former asserts that the stack at the annotated program location satisfies the type Φ; the assertion is verified by HELMHOLTZ.If there is an annotation Assume Φ, HELM-HOLTZ assumes that the stack satisfies the type Φ at the annotated program location.A user can give a hint to HELMHOLTZ by using Assume Φ.The user has to make sure that it is correct; if an Assume annotation is incorrect, the verification result may also be incorrect.
An annotation LoopInv Φ may appear before a loop instruction (e.g., LOOP and ITER).It asserts that Φ is a loop invariant of the loop instruction.In the current implementation, annotating a loop invariant using LoopInv Φ is mandatory for a loop instruction.HELMHOLTZ checks that Φ is indeed a loop invariant and uses it to verify the rest of the program.
In the current implementation, a LAMBDA instruction, which pushes a function on the top of the stack, must be accompanied by a LambdaAnnot annotation.LambdaAnnot comes with a specification of the pushed function written in the same way as ContractAnnot.Concretely, the specification of the form Φ pre → Φ post & Φ abpost (x 1 : T 1 , . . ., x n : T n ) specifies the precondition Φ pre , the (normal) postcondition Φ post , and the (abnormal) postcondition Φ abpost as refinement stack types.The binding (x 1 : T 1 , . . ., x n : T n ) introduces the ghost variables that can be used in the annotations in the body of the annotated LAMBDA instruction; 6 ; one can omit if it is empty.
The first contract in Figure 11, which pushes a function that takes a pair of integers and returns the sum of them, exemplifies LambdaAnnot.The annotated type of the function (Line 6) expresses that it returns 4 if it is fed with a pair (3, 1).The ghost variables a and b are used in the annotations Assume (Line 10) and Assert (Line 12) in the body to denote the first and the second arguments of the pair passed to this function.To describe a property for recursive data structures, HELMHOLTZ supports measure functions introduced by Kawaguchi et al. [9] and also supported in Liquid Haskell [23].A measure function is a (recursive) function over a recursive data structure that can be used in assertions.The annotation Measure x : T 1 → T 2 where p 1 = e 1 | • • • | p n = e n defines a measure function x over the type T 1 .The measure function x takes a value of type T 1 , destructs it by the pattern matching, and returns a value of type T 2 .Metavariables p and e represent ML-like patterns and expressions.The second contract in Figure 11, which computes the length of the list passed as a parameter, exemplifies the usage of the Measure annotation.This contract defines a measure function len that takes a list of integers and returns its type; it is used in ContractAnnot and LoopInv.

Overview of the Verification Algorithm
HELMHOLTZ takes an annotated Michelson program and conducts typechecking based on the refinement type system in Section 3.4.The typechecking procedure (1) computes the verification conditions (VCs) for the program to be well-typed and (2) discharges it using an SMT solver.The latter is standard: We decide the validity of the generated VCs using an SMT solver (Z3 in the current implementation.)We explain the VC-generation step in detail.
For an annotated contract, HELMHOLTZ conducts forward reasoning starting from the precondition and generates VCs if necessary.During the forward reasoning, HELMHOLTZ keeps track of the Γ -and-ϒ part of the type judgment.
The typing rules are designed so that they enable the forward reasoning if a program is simply typed.For example, consider the rule (RT-ADD) in Figure 7.This rule can be read as a rule to compute a postcondition ∃ x 1 :int, x 2 :int.ϕ∧ x 1 + x 2 = x 3 from a precondition ϕ if the first two elements in ϒ are x 1 and x 2 .The other rules can also be read as postcondition-generation rules in the same way.
There are three places where HELMHOLTZ generates a verification condition.
-At the end of the program: HELMHOLTZ generates a condition that ensures that the computed postcondition of the entire program implies the postcondition annotated to the program.-Before and after instruction LAMBDA: HELMHOLTZ generates conditions that ensure that the pre-and post-conditions of the instruction LAMBDA is as annotated in LambdaAnnot.
-At a loop instruction: HELMHOLTZ generates verification conditions that ensure the condition annotated by LoopInv is indeed a loop invariant of this instruction.
A VC generated by HELMHOLTZ at these places is of the form ∀ x : T .ϕ 1 ⇒ ϕ 2 , where x : T is a sequence of bindings.
To discharge each VC, as many verification condition discharging procedures do, HELMHOLTZ checks whether its negation, ∃ x : T .ϕ 1 ∧ ¬ϕ 2 , is satisfiable; if it is unsatisfiable, then the original VC is successfully discharged.We remark that our type system is designed so that ϕ 1 and ϕ 2 are quantifier free for a program that does not use LAMBDA and EXEC.Indeed, ϕ 2 comes only from the annotations, which are quantifier-free.ϕ 1 comes from the postcondition-computation procedure, which is of the form ∃ x : T .ϕ 1 for quantifier-free ϕ 1 for the instructions other than LAMBDA and EXEC; the formula ∃ x : T .ϕ 1 ∧ ¬ϕ 2 is equivalent to ∃ x : T , x : T .ϕ 1 ∧ ¬ϕ 2 .

Encoding Micheslon-Specific Features
Since our assertion language includes several specific features that originates from Michelson and our assertion language, HELMHOLTZ needs to encode them in discharging VCs so that Z3 can handle them.We explain how this encoding is conducted.

Michelson-Specific Functions and Predicates
We use encode several Michelson-specific values using uninterpreted functions.For example, HELMHOLTZ assumes the following typing rule for instruction SHA256, which converts the top element to its SHA256 hash.
In the post condition, we use an uninterpreted function sha256 to express the SHA256 hash of x.In Z3, this uninterpreted function is declared as follows.The first line declares the signature of sha256.The second line is the axiom for sha256 that the length of a hash is always 32.
Notice that, we cannot assert that a hash is equal to a specific constant since sha256 is an uninterpreted function.Therefore, HELMHOLTZ cannot prove:

Implementation of Measure Functions
A measure function is encoded as an uninterpreted function accompanied with axioms that specify the behavior of the function, which is defined by the Measure annotation.For example, for the following form of a measure function definition for lists: Theoretically, one could insert the following declarations and assertions when it generates an input to Z3 to encode this definition: However, Z3 tends to timeout if we naively insert the above axioms to Z3 input which contains a universal quantifier in the encoded definition.
To address this problem, HELMHOLTZ rewrites VCs so that heuristically instantiated conditions on a measure function are available where necessary.Consider the above definition of f as an example.Suppose HELMHOLTZ obtains a VC of the form ∃ x : T .ϕ 1 ∧ ¬ϕ 2 mentioned in Section 4.2 and ϕ 1 and ϕ 2 contains (cons e h,i e t,i ) for i ∈ {1, . . ., N}.Then, HELMHOLTZ constructs a formula ϕ meas := i∈{1,...,N} (= (f (cons e h,i e t,i )) e2) and rewrites the VC to ∃ x : T .ϕmeas ∧ ϕ 1 ∧ ¬ϕ 2 .
We remark that, in LiquidHaskell, measure functions are treated as a part of the type system [9]: the asserted axioms are systematically (instantiated and) embedded into the typing rules.In HELMHOLTZ, measure functions are treated as an ingredient that is orthogonal to the type system; the type system is oblivious of measure functions until its definition is inserted to Z3 input.

Overloaded Functions
Due to the polymorphically-typed instructions in Michelson, our assertion language incorporates polymorphic uninterpreted functions.For example, Michelson has an instruction PACK, which pops a value (of any type) from the stack, serializes it to a binary representation, and pushes the serialized value.HELMHOLTZ typechecks this instruction based on the following rule.
The term pack(x) in the postcondition represents a serialized value created from x. Since x may be of any simple type T , pack must be polymorphic.
Having a polymorphic uninterpreted function in assertions is tricky because Z3 does not support a polymorphic value.HELMHOLTZ encodes polymorphic uninterpreted functions to a monomorphic function whose name is generated by mangling the name of its instantiated parameter type.For example, the above pack(x) is encoded as a Z3 function pack!int(x) whose type is int → bytes.Although there are infinitely many types, the number of the encoded functions is finite since only finitely many types appear in a single contract.

Michelson-Specific Types
In encoding a VC as Z3 constraints, HELMHOLTZ maps types in Michelson into sorts in Z3, e.g., the Michelson type nat for nonnegative integers to the Z3 sort Int.A naive mapping from Michelson types to Z3 sorts is problematic; for example, ∀x : nat.x ≥ 0 in HELMHOLTZ is valid, but a naively encoded formula (forall ((x Int)) (>= x 0)) is invalid in Z3.This naive encoding ignores that a value of sort nat is nonnegative.
To address the problem, we adapt the method encoding a many-sorted logic formula into a single-sorted logic formula [3].Concretely, we define a sort predicate P T (x) for each sort T , which characterizes the property of the sort T .For example, P nat (x) := x ≥ 0. We also define sort predicates for compound data types.
Using the sort predicates, we can encode a VC into a Z3 constraint as follows: ∀x : T.φ is encoded into ∀x : ] = Int).Furthermore, we also add axioms about co-domain of uninterpreted functions as ∀ x i : T i .P T ( f ( x i )) for the function f of T i → T .

Case Study: Contract with Signature Verification
Figure 12 presents the code of the contract checksig.tz,which verifies that a sender indeed signed certain data using her private key.This contract uses instruction CHECK_SIGNATURE, which is supposed to be executed under a stack of the form key sig bytes tl, where key is a public key, sig is a signature, and bytes is some data.CHECK_SIGNATURE pops these three values from the stack and pushes True if sig is the valid signature for bytes with the private key corresponding to key.The instruction ASSERT after CHECK_SIGNATURE checks if the signature checking has succeeded; it aborts the execution of the contract if the stack top is False; otherwise, it pops the stack top (True) and proceeds the next instruction.The intended behavior of checksig.tz is as follows.It stores a pair of an address addr, which is the address of a contract that takes a string parameter, and a public key pubkey in its storage.It takes a pair (sign,data) of type (pair signature string) as a parameter; here, signature is the primitive Michelson type for signatures.This contract terminates without exception if sign is created from the serialized (packed) representation of data and signed by the private key corresponding to pubkey.In a normal termination, this contract transfers 1 mutez to the contract with address addr.If this signature verification fails, then an exception is raised.
This behavior is expressed as a specification in the ContractAnnot annotation in checksig.tzas follows.
-The refinement of its pre-condition part expresses that the address stored in the first element addr of the storage is an address of a contract that takes a value of type string as a parameter.This is expressed by the pattern-matching on contract_opt addr, which checks if there is an intended parameter type of contract stored at the address addr and returns the contract (wrapped by Some) if there is.The intended parameter type is given by the pattern expression Contract<string> _, which matches a contract that takes a string.
-The refinement of the post-condition forces the following three conditions: (1) the store is not updated by this contract ((addr, pubkey) = new_store); (2) sign is the signature created from the packed bytes pack data of the string in the second element of the parameter and signed by the private key corresponding to the second element pubkey of the store (sig pubkey sign (pack data)); and (3) the operations ops returned by this contract is [ Transfer data 1 c ], which represents an operation of transferring 1 mutez to the contract c, which is bound to Contract addr, with the parameter data.The predicate sig and the function pack are primitives of the assertion language of HELMHOLTZ.-The refinement in the exception part expresses that if an exception is raised, then the signature verification should have failed (not (sig pubkey sign (pack data))).
HELMHOLTZ successfully verifies checksig.tzwithout any additional annotation in the code section.If we change the instruction ASSERT in Line 15 to DROP to let the contract drop the result of the signature verification (hence, an exception is not raised even if the signature verification fails), the verification fails as intended.

Experiments
We applied HELMHOLTZ to various contracts; Table 1 is an excerpt of the result, in which we show (1) the number of the instructions in each contract (column #instr.)and ( 2) time (ms) spent to verify each contract.The experiments are conducted on MacOS Big Sur 11.4 with Quad-Core Intel Core i7 (2.3 GHz), 32 GB RAM.We used Z3 version 4.8.10.The contracts boomerang.tz,deposit.tz,manager.tz,vote.tz, and reservoir.tzare taken from the benchmark of Mi-cho-coq [2].checksig.tz,discussed above, is derived from weather_insurance.tz of the official Tezos test suite. 7vote_for_delegate.tzand xcat.tz are taken from the official test suite; xcat.tz is simplified from the original.tzip.tz is taken from Tezos Improvement proposals. 8triangular_num.tz is a simple test case that we made as an example of using LOOP.The source code of these contracts can be found at the Web interface of HELMHOLTZ.Each contract is supposed to work as follows.
boomerang.tz:Transfers the received amount of money to the source account.
deposit.tz: Transfers money to the sender if the address of the sender is identical to that is stored in the storage.manager.tz:Calls the passed function if the address of the caller matches the address stored in the storage.vote.tz:Accepts a vote to a candidate if the voter transfers enough voting fee, and stores the tally.tzip.tz:One of the components implementing Tezos smart contracts API.We verify one entrypoint of the contract.checksig.tz:The one explained in Section 4.4.
-vote_for_delegate.tz:Delegates one's ballot in voting by stakeholders, which is one of the fundamental features of Tezos, to another using a primitive operation of Tezos.xcat.tz:Transfers all stored money to one of the two accounts specified beforehand if called with the correct password.The account that gets money is decided based on whether the contract is called before or after a deadline.reservoir.tz:Sends a certain amount of money to either a contract or another depending on whether the contract is executed before or after the deadline.-triangular_num.tz:Calculates the sum from 1 to n, which is the passed parameter.
In the experiments, we verified that each contract indeed works according to the intention explained above.triangular_num.tzwas the only contract that required a manual annotation for verification in the code section; we needed to specify a loop invariant in this contract.
Although the numbers of instructions in these contracts are not large, they capture essential features of smart contracts; every contract except triangular_num.tzexecutes transactions; deposit.tzand manager.tzcheck the identity of the caller; and checksig.tzconducts signature verification.The time spent on verification is small.

Related Work
There are several publications on the formalization of programming languages for writing smart contracts.Hirai [7] formalizes EVM, a low-level smart contract language of Ethereum and its implementation, using Lem [14], a language to specify semantic definitions; definitions written in Lem can be compiled into definitions in Coq, HOL4, and Isabelle/HOL.Based on the generated definition, he verifies several properties of Ethereum smart contracts using Isabelle/HOL.Bernardo et al. [2] implemented Mi-Cho-Coq, a formalization of the semantics of Michelson using the Coq proof assistant.They also verified several Michelson contracts.Compared to their approach, we aim to develop an automated verification tool for smart contracts.Park et al. [15] developed a formal verification tool for EVM by using the K-framework [17], which can be used to derive a symbolic model checker from a formally specified language semantics (in this case, formalized EVM semantics [6]), and successfully applied the derived model checker to a few EVM contracts.It would be interesting to formalize the semantics of Michelson in the K-framework to compare HELMHOLTZ with the derived model checker.
The DAO attack [18], mentioned in Section 1, is one of the notorious attacks on a smart contract.It exploits a vulnerability of a smart contract that is related to a callback.Grossman et al. [5] proposed a type-based technique to verify that execution of a smart contract that may contain callbacks is equivalent to another execution without any callback.This property, called effectively callback freedom, can be seen as one of the criteria for execution of a smart contract not to be vulnerable to the DAO-like attack.Their type system focuses on verifying the ECF property of the execution of a smart contract, whereas ours concerns the verification of generic functional properties of a smart contract.
Benton proposes a program logic for a minimal stack-based programming language [1].His program logic can give an assertion to a stack as our stack refinement types do.However, his language does not support first-class functions nor instructions for dealing with smart contracts (e.g., signature verification).
Our type system is an extension of the Michelson type system with refinement types, which have been successfully applied to various programming languages [16,22,9,10,20,26,23,24,25].DTAL [25] is a notable example of an application of refinement types to an assembly language, a low-level language like Michelson.A DTAL program defines a computation using registers; we are not aware of refinement types for stackbased languages like Michelson.
We notice the resemblance between our type system and a program logic for PCF proposed by Honda and Yoshida [8], although the targets of verification are different.Their logic supports a judgment of the form A e : u B, where e is a PCF program, A is a pre-condition assertion, B is a post-condition assertion, and u represents the value that e evaluates to and can be used in B, which resembles our type judgment in the formalization in Section 3. Their assertion language also incorporates a term expression f • x, which expresses the value resulting from the application of f to x; this expression resembles the formula call(t 1 ,t 2 ) = t 3 used in a refinement predicate.We have not noticed an automated verifier implemented based on their logic.Further comparison is interesting future work.

Conclusion
We described our automated verification tool HELMHOLTZ for the smart contract language Michelson based on the refinement type system for Mini-Michelson.HELM-HOLTZ verifies whether a Michelson program follows a specification given in the form of a refinement type.We also demonstrated that HELMHOLTZ successfully verifies various practical Michelson contracts.
Currently, HELMHOLTZ supports approximately 80% of the whole instructions of the Michelson language.The definition of a measure function is limited in the sense that, for example, it can define only a function with one argument.We are currently extending HELMHOLTZ so that it can deal with more programs.
HELMHOLTZ currently verifies the behavior of a single contract, although a blockchain application often consists of multiple contracts in which contract calls are chained.To verify such an application as a whole, we plan to extend HELMHOLTZ so that it can verify an inter-contract behavior compositionally by combining the verification results of each contract.

A Proof of Soundness
This Appendix is only for reviewing.If this is accepted, we will remove it, upload a preprint version with the proof to arXiv.The article will refer to the preprint version.
We prove our main theorem (Theorem 16), whose first item is Theorem 9.The proof is close to a proof of soundness of Hoare logic, with a few extra complications due to the presence of first-class functions.We first prove a few lemmas related to LOOP (Lemma 11), ITER (Lemma 12), predicate call (Lemmas 13 and 14), and subtyping (Lemma 15).We often use Theorem 8 implicitly.Proof.By induction on the derivation of (H.2).We conduct the last rule that derives (H.2), which is either (E-LOOPT) or (E-LOOPF).
Case (E-LOOPT).We have Proof.By induction on the derivation of (H.4).We conduct case analysis on the last rule that derives (H.4), which is either (E-ITERNIL) or (E-ITERCONS).
Proof.By the definition of the semantics of call.
Theorem 16 (Soundness).The following two statements hold. ( Proof.The proof is done by mutual induction on the given derivation of (H.1) and (H.4).
Case (RT-SEQ).We have for some I , IS , and Φ .The last rule that derives (H.2) is (E-SEQ).Therefore, we have Case (RT-DIP).We have for some IS, x, T , ϒ , ϒ , ϕ, and ϕ .The last rule that derives (H.5) is (E-DIP).Therefore, we have Case (RT-DROP).We have for some x, T , ϒ , and ϕ.The last rule that derives (H.5) is (E-DROP).Therefore, we have for some V .Case (RT-DUP).We have for some x, x , T , ϒ , and ϕ.The last rule that derives (H.5) is (E-DUP).Therefore, we have for some V and S. By (H.6), we have σ Case (RT-SWAP).We have for some x, x , T , ϒ , and ϕ.The last rule that derives (H.5) is (E-SWAP).Therefore, we have for some V , V , and S. By (H.6), we have σ Case (RT-PUSH).We have for some x, T , ϒ , ϕ, and V .The last rule that derives (H.6) is (E-PUSH).Therefore, we have Case (RT-NOT).We have for some x, ϒ , and ϕ.The last rule that derives (H.5) is either (E-NOTT) or (E-NOTF).We only show the case of (E-NOTT); (E-NOTF) is similar.In this case, we have ))} as required.
Case (RT-IF).We have Then, the goal follows From IH and (H.74).
Evaluation for Instruction Sequence S IS ⇓ S (E-NOP) S {} ⇓ S S I ⇓ S S IS ⇓ S (E-SEQ) S {I; IS} ⇓ S Evaluation for Instruction S I ⇓ S S IS ⇓ S

Fig. 10
Fig. 10 Modified Typing Rules for LAMBDA and EXEC.
NOT and ADD for manipulating integers; PAIR, CAR, and CDR for pairs; NIL and CONS for constructing lists; LAMBDA for a first-class function; EXEC for calling a function; and TRANSFER_TOKENS to create an operation.Instructions for control structures are IF and IF_CONS, which are for branching on integers (whether the stack top is True or not) and lists (whether the stack top is a cons or not), respectively, and LOOP and ITER, which are for iteration on integers and lists, respectively.