\Section{Move and the Prover} Move was developed for the Diem blockchain \cite{DIEM}, but its design is not specific to blockchains. A Move execution consists of a sequence of updates evolving a \emph{global persistent memory state}, which we just call the \emph{(global) memory}. Similar to other blockchains, updates are a series of atomic transactions. All runtime errors result in a transaction abort, which does not change the blockchain state except to transfer some currency (``gas'') from the account that sent the transaction to pay for cost of executing the transaction. The global memory is organized as a collection of resources, described by Move structures (data types). A resource in memory is indexed by a pair of a type and an address (for example the address of a user account). For instance, the expression |exists>(addr)| will be true if there is a value of type |Coin| stored at |addr|. As seen in this example, Move uses type generics, and working with generic functions and types is rather idiomatic for Move. %Notice that account addresses are not just arbitrary values but have a specific %role in Move's programming methodology related to access control via the builtin %type of \emph{signers}, as will be discussed later. A Move application consists of a set of \emph{transaction scripts}. Each script defines a Move function with input parameters but no output parameters. This function updates the global memory and may emit events. The execution of this function can abort because of an abort instruction or implicitly because of a runtime error such as an out-of-bounds vector index. \Paragraph{Programming in Move} \begin{Figure} \caption{\label{fig:AccountDef} Account Example Program} \begin{MoveBox} module Account { struct Account has key { balance: u64, } fun withdraw(account: address, amount: u64) acquires Account { let balance = &mut borrow_global_mut(account).balance; assert(*balance >= amount, Errors::limit_exceeded()); *balance = *balance - amount; } fun deposit(account: address, amount: u64) acquires Account { let balance = &mut borrow_global_mut(account).balance; assert(*balance <= Limits::max_u64() - amount, Errors::limit_exceeded()); *balance = *balance + amount; } public(script) fun transfer(from: &signer, to: address, amount: u64) acquires Account { assert(Signer::address_of(from) != to, Errors::invalid_argument()); withdraw(Signer::address_of(from), amount); deposit(to, amount); } } \end{MoveBox} \end{Figure} \noindent In Move, one defines transactions via \emph{script functions} which take a set of parameters. Those functions can call other functions. Script and regular functions are encapsulated in \emph{modules}. Move modules are also the place where structs are defined. An illustration of a Move contract is given in Fig.~\ref{fig:AccountDef} (for a more complete description see the Move Book~\cite{MOVE_LANG}). The example is a simple account which holds a balance in the struct |Account|, and offers the script function |transfer| to manipulate this resource. Scripts generally have \emph{signer} arguments, which are tokens which represent an account address that has been authenticated by a cryptographic signature. The |assert| statement in the example causes a Move transaction to abort execution if the condition is not met. Notice that Move, similar as Rust, supports references (as in |&signer|) and mutable references (as in |&mut T|). However, references cannot be part of structs stored in global memory. %Abortion can also happen implicitly; for example, the %expression |borrow_global_mut(addr)| will abort if no resource |T| exists at %|addr|. \Paragraph{Specifying in Move} \begin{Figure} \caption{\label{fig:AccountSpec} Account Example Specification} \begin{MoveBox} module Account { spec transfer { let from_addr = Signer::address_of(from); aborts_if from_addr == to; aborts_if bal(from_addr) < amount; aborts_if bal(to) + amount > Limits::max_u64(); ensures bal(from_addr) == old(bal(from_addr)) - amount; ensures bal(to) == old(bal(to)) + amount; } spec fun bal(acc: address): u64 { global(acc).balance } invariant forall acc: address where exists(acc): bal(acc) >= AccountLimits::min_balance(); invariant update forall acc: address where exists(acc): old(bal(acc)) - bal(acc) <= AccountLimits::max_decrease(); } \end{MoveBox} \end{Figure} \noindent The specification language supports {\em Design By Contract} \cite{DESIGN_BY_CONTRACT}. Developers can provide pre and post conditions for functions, which include conditions over parameters and global memory. Developers can also provide invariants over data structures, as well as the contents of the global memory. Universal and existential quantification over bounded domains, such as like the indices of a vector, as well as effectively unbounded domains, such as memory addresses and integers, are supported. Quantifiers make the verification problem undecidable and cause difficulties with timeouts. However, in practice, we notice that quantifiers have the advantage of allowing more direct formalization of many properties, which increases the clarity of specifications. %% The latter makes the specification language very expressive, but also %% renders the verification problem in theory undecidable (and in practice %% dependent on heuristic decision procedures). Fig.~\ref{fig:AccountSpec} illustrates the specification language by extending the account example in Fig.~\ref{fig:AccountDef} (for the definition of the specification language see \cite{MOVE_SPEC_LANG_DEF}). This adds the specification of the |transfer| function, a helper function |bal| for use in specs, and two global memory invariants. The first invariant states that a balance can never drop underneath a certain minimum. The second invariant refers to an update of global memory with pre and post state: the balance on an account can never decrease in one step more than a certain amount. Note that while the Move programming language has only unsigned integers, the specification language uses arbitrary precision signed integers, making it convenient to specify something like |x + y <= limit|, without the complication of arithmetic overflow. Specifications for the |withdraw| and |deposit| functions have been omitted in this example. \MVP supports omitting specs for non-recursive functions, in which case they are treated as being inlined at caller site. %% A discerning reader may have noted that the program in Fig.~\ref{fig:AccountDef} %% does not actually satisfy the specification in Fig.~\ref{fig:AccountSpec}. This %% will be discussed in the next section. % The constructs we have seen so far are only a subset of the available features % of the Move specification language. Notably, the language supports the following % additional features: % \begin{itemize} % \item Function preconditions via the |requires|-clause. % \item Data invariants for |struct| types, as a predicate over the field values. % \item Means to abstract commonly used specification fragments in so-called % \emph{specification schemas} which can then be included in other specification % blocks. % \end{itemize} \Paragraph{Running the Prover} \label{sec:RunningProver} \MVP is fully automatic, like a type checker or linter, and is expected to finish in a reasonable time, so it can be integrated in the regular development workflow. Running \MVP on the |module Account| produces multiple errors. The first is this one: \begin{MoveDiag} error: abort not covered by any of the `aborts_if` clauses -- account.move:24:3 | 13 | let balance = &mut borrow_global_mut(account).balance; | ----------------- abort happened here | = at account.move:18: transfer = from = signer{0x18be} = to = 0x18bf = amount = 147u8 = at ... \end{MoveDiag} \noindent \MVP detected that an implicit abort condition is missing in the specification of the |withdraw| function. It prints the context of the error, as well as an \emph{execution trace} which leads to the error. Values of variable assignments from the counterexample found by the SMT solver are printed together with the execution trace. Logically, the counterexample presents an assignment to variables where the program fails to meet the specification. In general, \MVP attempts to produce readable diagnostics for Move developers without the need of understanding any internals of the prover. There are more verification errors in this example, related to the global invariants: the code makes no attempt to respect the limits in |min_balance()| and |max_decrease()|. The problem can be fixed by adding more |assert| statements to check that the limits are met (see Appendix~\ref{sec:CorrectedAccount}). The programs and specifications \MVP deals with are much larger than this example. The conditions under which a transaction in the Diem framework can abort typically involve dozens of individual predicates, stemming from other functions called by this transaction. Moreover, there are hundreds of memory invariants specified, encoding access control and other requirements for the Diem blockchain. %%% Local Variables: %%% mode: latex %%% TeX-master: "main" %%% End: