[{"data":1,"prerenderedAt":20},["ShallowReactive",2],{"blog-detail:move-prover-tutorial-part-1":3},{"category":4,"article":5},null,{"slug":6,"category":7,"categoryLabel":8,"subcategoryLabel":9,"authorName":10,"title":11,"excerpt":12,"contentHtml":13,"coverUrl":4,"seoTitle":11,"seoDescription":12,"seoImageUrl":14,"publishedAt":15,"updatedAt":16,"readTime":17,"cover":4,"hasCover":18,"coverAlt":19},"move-prover-tutorial-part-1","research","Research","Industry Research","MoveBit","Verify Smart Contracts in Aptos with the Move Prover Pt.1","It has been widely acclaimed that the Move Prover makes it very simple to formally verify Move contracts, offering very strong safety guarantees. How does it work? Does it provide the ultimate safety guarantee for smart contracts?","\u003Cp>\u003Cem>Xudong Wang  &amp; 0xyi\u003C\u002Fem> 09-14-2022\u003C\u002Fp>\n\u003Cp>It has been widely acclaimed that the Move Prover makes it very simple to formally verify Move contracts, offering very strong safety guarantees.  How does it work? Does it provide the ultimate safety guarantee for smart contracts?\u003C\u002Fp>\n\u003Cp>We will write an article series on how to verify smart contracts with the Move Prover in Aptos, what are the tips and best practices, a more complicated case study using the Move Prover, and what are the current limitation and its future outlook. \u003C\u002Fp>\n\u003Cp>For this part, we will first give a tutorial on how to verify smart contracts with the Move Prover in Aptos.\u003C\u002Fp>\n\u003Ch2 id=\"What-is-the-Move-Prover\">What is the Move Prover\u003C\u002Fh2>\n\u003Cp>Formal verification is a technique that uses rigorous mathematical methods to describe the behavior and reason about the correctness of computer systems.\u003Cbr>Now it has certain applications in the fields of operating systems, compilers, and other fields that require high correctness.\u003C\u002Fp>\n\u003Cp>Smart contracts deployed on the blockchain manipulate various digital assets, and their correctness is also critical.\u003Cbr>\u003Cstrong>Move Prover (MVP)\u003C\u002Fstrong> is designed to prevent bugs in smart contracts written in the Move language.\u003C\u002Fp>\n\u003Cp>In Aptos ( and Starcoin\u002FSui ), Users can specify functional properties of smart contracts using the \u003Cstrong>Move Specification Language (MSL)\u003C\u002Fstrong>, and then use the Move Prover to automatically and statically inspect them.\u003C\u002Fp>\n\u003Cp>Simply put, there can be two components in a Move file:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>Part of it is program code, which is the part most of us are most familiar with. It is written in the Move programming language (sometimes just called the Move language). We use it to define data types, and functions.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>The other part is the Formal specification. It is optional and written in the Move specification language. We use it to describe what properties a program code should satisfy. Such as describing the behavior of a function.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>When we write the formal specification, after calling the Move Prover, it will verify whether the Move program meets these requirements according to the written specification, helping developers to find potential problems as early as possible in the development stage, and giving other users confidence in the properties of the program that has been verified.\u003C\u002Fp>\n\u003Ch2 id=\"Install-Prover-dependencies-in-Aptos\">Install Prover dependencies in Aptos\u003C\u002Fh2>\n\u003Cp>Before using the Move Prover, let’s install some of its external dependencies.\u003Cbr>It is assumed that you already have \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Faptos.dev\u002Fcli-tools\u002Faptos-cli-tool\u002Finstall-aptos-cli\">installed Aptos CLI\u003C\u002Fa>.\u003Cbr>Make sure you did “Step 3” and installed the dependencies of the Move Prover:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-bash\" data-language=\"bash\">\u003Ccode class=\"language-bash\">.\u002Fscripts\u002Fdev_setup.sh -yp\n. ~\u002F.profile\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>When the above command is executed, enter \u003Ccode>boogie \u002Fversion\u003C\u002Fcode>, if the output is similar to “Boogie program verifier version X.X.X”, then the installation has been successful.\u003C\u002Fp>\n\u003Cp>Note that currently the Move Prover can only run under UNIX-based operating systems (such as Linux, and macOS).\u003Cbr>Windows users can run it by installing \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fwindows\u002Fwsl\u002Finstall\">WSL\u003C\u002Fa>.\u003C\u002Fp>\n\u003Ch2 id=\"Prepare-an-example-for-verification-in\">Prepare an example for verification in\u003C\u002Fh2>\n\u003Ch3 id=\"Project-creation\">Project creation\u003C\u002Fh3>\n\u003Cp>First, let’s create a new empty Move package:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-bash\" data-language=\"bash\">\u003Ccode class=\"language-bash\">mkdir basic_coin\ncd basic_coin\naptos move init --name BasicCoin\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>You can see that its directory structure is as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-none\">\u003Ccode class=\"language-none\">basic_coin\n    |\n    |---- Move.toml (text file)\n    |\n    `---- sources   (Directory)\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch3 id=\"Module-code\">Module code\u003C\u002Fh3>\n\u003Cp>Now create \u003Ccode>basic_coin\u002Fsources\u002FBasicCoin.move\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cdetails>\n\u003Csummary> BasicCoin.move content\u003C\u002Fsummary>\n\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">\u002F\u002F\u002F This module defines a minimal and generic Coin and Balance.\nmodule BasicCoin::basic_coin {\n    use std::error;\n    use std::signer;\n\n    \u002F\u002F\u002F Error codes\n    const ENOT_MODULE_OWNER: u64 = 0;\n    const EINSUFFICIENT_BALANCE: u64 = 1;\n    const EALREADY_HAS_BALANCE: u64 = 2;\n\n    struct Coin&lt;phantom CoinType&gt; has store {\n        value: u64\n    }\n\n    struct Balance&lt;phantom CoinType&gt; has key {\n        coin: Coin&lt;CoinType&gt;\n    }\n\n    \u002F\u002F\u002F Publish an empty balance resource under `account`'s address. This function must be called before\n    \u002F\u002F\u002F minting or transferring to the account.\n    public fun publish_balance&lt;CoinType&gt;(account: &amp;signer) {\n        let empty_coin = Coin&lt;CoinType&gt; { value: 0 };\n        assert!(!exists&lt;Balance&lt;CoinType&gt;&gt;(signer::address_of(account)), error::already_exists(EALREADY_HAS_BALANCE));\n        move_to(account, Balance&lt;CoinType&gt; { coin:  empty_coin });\n    }\n\n    \u002F\u002F\u002F Mint `amount` tokens to `mint_addr`. This method requires a witness with `CoinType` so that the\n    \u002F\u002F\u002F module that owns `CoinType` can decide the minting policy.\n    public fun mint&lt;CoinType: drop&gt;(mint_addr: address, amount: u64, _witness: CoinType) acquires Balance {\n        \u002F\u002F Deposit `total_value` amount of tokens to mint_addr's balance\n        deposit(mint_addr, Coin&lt;CoinType&gt; { value: amount });\n    }\n\n    public fun balance_of&lt;CoinType&gt;(owner: address): u64 acquires Balance {\n        borrow_global&lt;Balance&lt;CoinType&gt;&gt;(owner).coin.value\n    }\n\n    \u002F\u002F\u002F Transfers `amount` of tokens from `from` to `to`. This method requires a witness with `CoinType` so that the\n    \u002F\u002F\u002F module that owns `CoinType` can  decide the transferring policy.\n    public fun transfer&lt;CoinType: drop&gt;(from: &amp;signer, to: address, amount: u64, _witness: CoinType) acquires Balance {\n        let addr_from = signer::address_of(from);\n        let check = withdraw&lt;CoinType&gt;(addr_from, amount);\n        deposit&lt;CoinType&gt;(to, check);\n    }\n\n    fun withdraw&lt;CoinType&gt;(addr: address, amount: u64) : Coin&lt;CoinType&gt; acquires Balance {\n        let balance = balance_of&lt;CoinType&gt;(addr);\n        assert!(balance &gt;= amount, EINSUFFICIENT_BALANCE);\n        let balance_ref = &amp;mut borrow_global_mut&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n        *balance_ref = balance - amount;\n        Coin&lt;CoinType&gt; { value: amount }\n    }\n    \n    fun deposit&lt;CoinType&gt;(addr: address, check: Coin&lt;CoinType&gt;) acquires Balance{\n        let balance = balance_of&lt;CoinType&gt;(addr);\n        let balance_ref = &amp;mut borrow_global_mut&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n        let Coin { value } = check;\n        *balance_ref = balance + value;\n    }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003C\u002Fdetails>\n\n\u003Cp>Here we assume that you have a certain grasp of the Move language, and can understand the source code of \u003Ccode>BasicCoin.move\u003C\u002Fcode> above and know the function of each part.\u003C\u002Fp>\n\u003Ch3 id=\"TOML-configuration\">TOML configuration\u003C\u002Fh3>\n\u003Cp>BasicCoin uses some facilities of the Aptos standard library, and also needs to add \u003Ccode>aptos-framework\u003C\u002Fcode> to the dependencies. We also need to specify what numerical address it should be replaced with. \u003C\u002Fp>\n\u003Cp>Therefore, we modify Move.toml as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-toml\" data-language=\"toml\">\u003Ccode class=\"language-toml\">[package]\nname = \"BasicCoin\"\nversion = \"0.0.0\"\n\n[dependencies]\nAptosFramework = { git = \"https:\u002F\u002Fgithub.com\u002Faptos-labs\u002Faptos-core.git\", subdir = \"aptos-move\u002Fframework\u002Faptos-framework\u002F\", rev = \"main\" }\n\n[addresses]\nBasicCoin=\"Replace_It_With_Your_Numerical_Address\"  \n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2 id=\"The-first-verification-code\">The first verification code\u003C\u002Fh2>\n\u003Cp>To give us a first impression of the use of the Move Prover, add the following code snippet to BasicCoin.move:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec balance_of {\n    pragma aborts_if_is_strict;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Syntactically, this code can be added anywhere in the BasicCoin module, but it is recommended to place it after the definition of the \u003Ccode>balance_of\u003C\u002Fcode> function in order to clearly see the correspondence between the definition and the specification when reading the code.\u003C\u002Fp>\n\u003Cp>Simply put, the \u003Ccode>spec balance_of {...}\u003C\u002Fcode> block will contain our \u003Cstrong>property specification\u003C\u002Fstrong> for the \u003Ccode>balance_of\u003C\u002Fcode> function.\u003Cbr>There are many types of property specifications, some common examples are:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Will this function abort? Under what circumstances does it abort?\u003C\u002Fli>\n\u003Cli>What conditions must be met for the parameters to call this function?\u003C\u002Fli>\n\u003Cli>What is the return value of this function?\u003C\u002Fli>\n\u003Cli>After the function is executed, how will the state of the virtual machine be changed?\u003C\u002Fli>\n\u003Cli>What invariants does this function maintain?\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>For example, the Move Prover allows all possible aborts by default when we don’t give any abort conditions.\u003Cbr>And in the simple snippet above, we tell Prover with the directive \u003Ccode>aborts_if_is_strict\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>I would like to strictly check the possibility of aborting this function. Report an error if there is any abort not listed by the programmer.\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>Now, we run the \u003Ccode>prove\u003C\u002Fcode> command in the \u003Ccode>basic_coin\u003C\u002Fcode> directory:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-bash\" data-language=\"bash\">\u003Ccode class=\"language-bash\">aptos move prove\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>it will call the Move Prover to check the code in the package.\u003Cbr>Then we can see the Prover reporting the following error message:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-none\">\u003Ccode class=\"language-none\">error: abort not covered by any of the `aborts_if` clauses\n   ┌─ .\u002Fsources\u002FBasicCoin.move:38:5\n   │\n35 │           borrow_global&lt;Balance&lt;CoinType&gt;&gt;(owner).coin.value\n   │           ------------- abort happened here with execution failure\n   ·\n38 │ ╭     spec balance_of {\n39 │ │       pragma aborts_if_is_strict;\n40 │ │     }\n   │ ╰─────^\n   │\n   =     at .\u002Fsources\u002FBasicCoin.move:34: balance_of\n   =         owner = 0x29\n   =     at .\u002Fsources\u002FBasicCoin.move:35: balance_of\n   =         ABORTED\n\nFAILURE proving 1 modules from package `basic_coin` in 1.794s\n{\n  \"Error\": \"Move Prover failed: exiting with verification errors\"\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Prover’s output tells us that it found a situation where the \u003Ccode>balance_of\u003C\u002Fcode> function aborts, but we don’t explicitly point out the possibility of such aborts.\u003Cbr>Looking at the code that triggers the abort, we can see that the exception is caused by calling the built-in \u003Ccode>borrow_global\u003C\u002Fcode> function when the \u003Ccode>owner\u003C\u002Fcode> does not own a resource of type \u003Ccode>Balance&lt;CoinType&gt;\u003C\u002Fcode>.\u003Cbr>Following the guidance of the error message, we can add the following \u003Ccode>aborts_if\u003C\u002Fcode> condition:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec balance_of {\n    pragma aborts_if_is_strict;\n    aborts_if !exists&lt;Balance&lt;CoinType&gt;&gt;(owner);\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>After adding this condition, try calling Prover again and see that there are no more validation errors.\u003Cbr>Now we can confidently confirm that the \u003Ccode>balance_of\u003C\u002Fcode> function has one and only one possibility of abnormal termination, that is, the parameter \u003Ccode>owner\u003C\u002Fcode> does not own a resource of type \u003Ccode>Balance&lt;CoinType&gt;\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"Verify-withdraw-function\">Verify withdraw function\u003C\u002Fh2>\n\u003Cp>The signature of the function \u003Ccode>withdraw\u003C\u002Fcode> is as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">fun withdraw&lt;CoinType&gt;(addr: address, amount: u64) : Coin&lt;CoinType&gt; acquires Balance\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Its role is to withdraw the \u003Ccode>amount\u003C\u002Fcode> of coins from the address \u003Ccode>addr\u003C\u002Fcode> and return it.\u003C\u002Fp>\n\u003Ch3 id=\"Specify-the-abort-condition-for-withdraw\">Specify the abort condition for \u003Ccode>withdraw\u003C\u002Fcode>\n\u003C\u002Fh3>\n\u003Cp>There are two possibilities for \u003Ccode>withdraw\u003C\u002Fcode> to abort:\u003C\u002Fp>\n\u003Col>\n\u003Cli>No resource of type \u003Ccode>Balance&lt;CoinType&gt;\u003C\u002Fcode> in \u003Ccode>addr\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>The balance in \u003Ccode>addr\u003C\u002Fcode> is less than \u003Ccode>amount\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>From these, we can define the abort condition like this:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec withdraw {\n    let balance = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n    aborts_if !exists&lt;Balance&lt;CoinType&gt;&gt;(addr);\n    aborts_if balance &lt; amount;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cul>\n\u003Cli>A spec block can contain let bindings, which bind a long expression with a name that can be used repeatedly.\u003Cbr>\u003Ccode>global&lt;T&gt;(addr): T\u003C\u002Fcode> is a built-in function that returns a resource of type \u003Ccode>T\u003C\u002Fcode> at address \u003Ccode>addr\u003C\u002Fcode>.\u003Cbr>Here, we set balance to the number of tokens owned by addr via the let binding;\u003C\u002Fli>\n\u003Cli>\n\u003Ccode>exists&lt;T&gt;(address): bool\u003C\u002Fcode> is a built-in function that returns true if resource \u003Ccode>T\u003C\u002Fcode> exists at address \u003Ccode>addr\u003C\u002Fcode>; otherwise returns false.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The two lines of \u003Ccode>aborts_if\u003C\u002Fcode> statements correspond to the two conditions mentioned above.\u003Cbr>In general, if a function has multiple \u003Ccode>aborts_if\u003C\u002Fcode> conditions, the conditions are ORed together.\u003C\u002Fp>\n\u003Cp>As mentioned earlier, if we don’t specify any abort conditions, Prover will not impose any restrictions on aborts.\u003Cbr>But once we give any kind of abort conditions, Prover defaults that we want to strictly check all possible aborts, so we need to list all possible conditions, which is equivalent to implicitly adding the instruction \u003Ccode>pragma aborts_if_is_strict\u003C\u002Fcode>.\u003Cbr>If only some of the conditions for abnormal exit are listed, the Prover will report a validation error.\u003Cbr>However, if the \u003Ccode>pragma aborts_if_is_partial\u003C\u002Fcode> is defined in the spec block, this is equivalent to telling the Prover:\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>I just want to list some of the conditions that will cause an abort, please just verify that it will abort under those conditions.\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>If you are interested, you can do such a set of experiments to verify:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>When deleting any of the above two \u003Ccode>aborts_if\u003C\u002Fcode> conditions, Prover will report an error;\u003C\u002Fli>\n\u003Cli>When all \u003Ccode>aborts_if\u003C\u002Fcode> conditions are deleted at the same time, Prover will not report an error;\u003C\u002Fli>\n\u003Cli>When adding \u003Ccode>pragma aborts_if_is_partial\u003C\u002Fcode>, no matter how many \u003Ccode>aborts_if\u003C\u002Fcode> conditions are kept, Prover will not report an error (of course, the conditions themselves must be correct).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Some readers may be curious about the order of the three statements in the spec block:\u003Cbr>Why the definition of balance can be written after \u003Ccode>aborts_if !exists&lt;Balance&lt;CoinType&gt;&gt;(addr)\u003C\u002Fcode>.\u003Cbr>Because, if the latter holds true, \u003Ccode>balance\u003C\u002Fcode> does not actually exist.\u003Cbr>Wouldn’t this order cause the Prover to fail?\u003Cbr>Simply put: no, the statements in the spec block are declarative and the order doesn’t matter.\u003C\u002Fp>\n\u003Cp>For a more detailed understanding, you can refer to the MSL documentation for more information.\u003C\u002Fp>\n\u003Ch3 id=\"Specify-the-functional-nature-of-withdraw\">Specify the functional nature of \u003Ccode>withdraw\u003C\u002Fcode>\n\u003C\u002Fh3>\n\u003Cp>Next, we define functional properties.\u003Cbr>The two \u003Ccode>ensures\u003C\u002Fcode> statements in the following spec block give us what we expect from the \u003Ccode>withdraw\u003C\u002Fcode> functionality:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec withdraw {\n    let balance = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n    aborts_if !exists&lt;Balance&lt;CoinType&gt;&gt;(addr);\n    aborts_if balance &lt; amount;\n\n    let post balance_post = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n    ensures balance_post == balance - amount;\n    ensures result == Coin&lt;CoinType&gt; { value: amount };\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>In this code, first by using \u003Ccode>let post\u003C\u002Fcode> binding, define \u003Ccode>balance_post\u003C\u002Fcode> as the balance of \u003Ccode>addr\u003C\u002Fcode> after the function is executed, it should be equal to \u003Ccode>balance - amount\u003C\u002Fcode>. Then, \u003Ccode>result\u003C\u002Fcode> is a special name that represents the return value, which should be the \u003Ccode>amount\u003C\u002Fcode> of tokens.\u003C\u002Fp>\n\u003Ch2 id=\"Verify-the-deposit-function\">Verify the \u003Ccode>deposit\u003C\u002Fcode> function\u003C\u002Fh2>\n\u003Cp>The signature of the function deposit is as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">fun deposit&lt;CoinType&gt;(addr: address, check: Coin&lt;CoinType&gt;) acquires Balance\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>It deposits the token funds indicated by \u003Ccode>check\u003C\u002Fcode> into the address \u003Ccode>addr\u003C\u002Fcode>. Its canonical definition is as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec deposit {\n    let balance = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n    let check_value = check.value;\n\n    aborts_if !exists&lt;Balance&lt;CoinType&gt;&gt;(addr);\n    aborts_if balance + check_value &gt; MAX_U64;\n\n    let post balance_post = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n    ensures balance_post == balance + check_value;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Here, \u003Ccode>balance\u003C\u002Fcode> is defined as the balance in \u003Ccode>addr\u003C\u002Fcode> before the function is executed, and \u003Ccode>check_value\u003C\u002Fcode> is defined as the amount of tokens to be deposited. It will abort in two cases:\u003C\u002Fp>\n\u003Col>\n\u003Cli>There is no resource of type \u003Ccode>Balance&lt;CoinType&gt;\u003C\u002Fcode> in \u003Ccode>addr\u003C\u002Fcode>;\u003C\u002Fli>\n\u003Cli>Or the sum of \u003Ccode>balance\u003C\u002Fcode> and \u003Ccode>check_value\u003C\u002Fcode> is greater than the maximum value of type \u003Ccode>u64\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>The \u003Ccode>ensures\u003C\u002Fcode> statement is used to let the Prover make sure that in any case, the balance in \u003Ccode>addr\u003C\u002Fcode> can be updated correctly after the function is executed.\u003C\u002Fp>\n\u003Cp>The syntax mentioned earlier will not be repeated here.\u003Cbr>Astute readers may have noticed that it is worth noting that the expression \u003Ccode>balance + check_value &gt; MAX_U64\u003C\u002Fcode> is problematic in the Move program.\u003Cbr>Because the addition on the left may cause an overflow exception.\u003Cbr>If we want to write a similar check in the Move program, we should use an expression like \u003Ccode>balance &gt; MAX_U64 - check_value\u003C\u002Fcode> to avoid the overflow problem.\u003C\u002Fp>\n\u003Cp>However, this expression is perfectly fine in the Move Specification Language (MSL).\u003Cbr>Since the spec block uses the MSL language, its type system is different from that of Move.\u003Cbr>In MSL, all integers are of type \u003Ccode>num\u003C\u002Fcode>, which is an integer in the mathematical sense. That is, it is signed and has no size limit.\u003Cbr>All built-in integer types (\u003Ccode>u8\u003C\u002Fcode>, \u003Ccode>u64\u003C\u002Fcode>, etc.) are automatically converted to type \u003Ccode>num\u003C\u002Fcode> when referencing data in a Move program in MSL.\u003Cbr>A more detailed description of the type system can be found in the MSL documentation.\u003C\u002Fp>\n\u003Ch2 id=\"Verify-the-transfer-function\">Verify the \u003Ccode>transfer\u003C\u002Fcode> function\u003C\u002Fh2>\n\u003Cp>The signature of the function \u003Ccode>transfer\u003C\u002Fcode> is as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">public fun transfer&lt;CoinType: drop&gt;(from: &amp;signer, to: address, amount: u64, _witness: CoinType) acquires Balance\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>It is responsible for the transfer \u003Ccode>from\u003C\u002Fcode> the account from to the address \u003Ccode>to\u003C\u002Fcode>, and the transfer amount is \u003Ccode>amount\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Let’s ignore the abort condition for now and only consider its functional nature, and try to write its validation specification:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec transfer {\n    let addr_from = signer::address_of(from);\n\n    let balance_from = global&lt;Balance&lt;CoinType&gt;&gt;(addr_from).coin.value;\n    let balance_to = global&lt;Balance&lt;CoinType&gt;&gt;(to).coin.value;\n    let post balance_from_post = global&lt;Balance&lt;CoinType&gt;&gt;(addr_from).coin.value;\n    let post balance_to_post = global&lt;Balance&lt;CoinType&gt;&gt;(to).coin.value;\n\n    ensures balance_from_post == balance_from - amount;\n    ensures balance_to_post == balance_to + amount;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Here \u003Ccode>from\u003C\u002Fcode> is of type \u003Ccode>signer\u003C\u002Fcode>, not a direct address.\u003Cbr>Although in the program we have created a local variable called \u003Ccode>addr_from\u003C\u002Fcode>, we cannot directly reference it in the spec block.\u003Cbr>At the same time, the expression of this address needs to be repeated several times, and it is very cumbersome to write repeatedly. We bind it to \u003Ccode>addr_from\u003C\u002Fcode> again.\u003Cbr>Then use \u003Ccode>let\u003C\u002Fcode> and \u003Ccode>let post\u003C\u002Fcode> to define several variables, corresponding to the balances in the two addresses \u003Ccode>addr_from\u003C\u002Fcode> and \u003Ccode>to\u003C\u002Fcode> before and after the function is executed.\u003Cbr>Finally, use the \u003Ccode>ensures\u003C\u002Fcode> statement to tell Prover that the balance in \u003Ccode>from\u003C\u002Fcode> should be subtracted by \u003Ccode>amount\u003C\u002Fcode>; the balance in \u003Ccode>to\u003C\u002Fcode> should be increased by \u003Ccode>amount\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>At first glance, there seems to be no problem at all. But is it really so?\u003Cbr>Let’s see if Prover thinks this is “the correct description of the behavior of this function”.\u003Cbr>After typing \u003Ccode>aptos move prove\u003C\u002Fcode> you can see:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-none\">\u003Ccode class=\"language-none\">error: post-condition does not hold\n   ┌─ .\u002Fsources\u002FBasicCoin.move:58:9\n   │\n58 │         ensures balance_from_post == balance_from - amount;\n   │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n   │\n   =     at .\u002Fsources\u002FBasicCoin.move:45: transfer\n   =     at .\u002Fsources\u002FBasicCoin.move:51: transfer (spec)\n   =     at .\u002Fsources\u002FBasicCoin.move:53: transfer (spec)\n   =     at .\u002Fsources\u002FBasicCoin.move:54: transfer (spec)\n   =     at .\u002Fsources\u002FBasicCoin.move:45: transfer\n   =         from = signer{0x0}\n   =         to = 0x0\n   =         amount = 1\n   =         _witness = &lt;generic&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>It is somewhat out of our expectations. Prover prompts that the postconditions are not satisfied, indicating that the behavior described in the previous spec block is not exactly the same as the \u003Ccode>transfer\u003C\u002Fcode> function.\u003Cbr>Why is this so? Let’s look down again: the parameters that make the postconditions not satisfied are \u003Ccode>from = signer{0x0}\u003C\u002Fcode> and \u003Ccode>to = 0x0\u003C\u002Fcode>. We should know the reason: when the account transfers money to itself, both \u003Ccode>to\u003C\u002Fcode> and \u003Ccode>from\u003C\u002Fcode> point to the same address, so the balance does not change.\u003C\u002Fp>\n\u003Cp>There are two solutions now:\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Plan A\u003C\u002Fstrong> does not modify the function definition, but changes the specification.\u003Cbr>In the spec block, consider whether the two accounts for the transfer and receiving are the same address.\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">let post eq_post = balance_to == balance_to_post;\nlet post ne_post = balance_from_post == balance_from - amount\n                &amp;&amp; balance_to_post   == balance_to   + amount;\nensures (addr_from == to &amp;&amp; eq_post) || (addr_from != to &amp;&amp; ne_post);\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Or use another slightly more intuitive if syntax:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">let post eq_post = balance_to == balance_to_post;\nlet post ne_post = balance_from_post == balance_from - amount\n                &amp;&amp; balance_to_post   == balance_to   + amount;\nensures if (addr_from == to) eq_post else ne_post;\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Note that \u003Ccode>if (P) E1 else E2\u003C\u002Fcode> is not the same as conditional execution in program logic – it’s actually a syntactic sugar equivalent to \u003Ccode>ensures\u003C\u002Fcode> both \u003Ccode>P ==&gt; E1\u003C\u002Fcode> and \u003Ccode>!P ==&gt; E2\u003C\u002Fcode>.\u003Cbr>And \u003Ccode>p ==&gt; q\u003C\u002Fcode> is actually \u003Ccode>!p || q\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>That is to say, the end of the second way of writing actually represents this logic:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">ensures (addr_from == to  ===&gt;  eq_post) &amp;&amp; (addr_from != to  ===&gt; ne_post);\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>that is:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">ensures (addr_from != to || eq_post) &amp;&amp; (addr_from == to  || ne_post);\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Interested readers can verify it by themselves through the direct value table or by simplifying to normal form, the former \u003Ccode>(addr_from == to &amp;&amp; eq_post) || (addr_from != to &amp;&amp; ne_post)\u003C\u002Fcode> and the latter \u003Ccode>(addr_from != to || eq_post) &amp;&amp; (addr_from == to  || ne_post)\u003C\u002Fcode> are actually exactly equivalent expressions.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Plan B\u003C\u002Fstrong> does not modify the spec, but directly adds \u003Ccode>assert!(addr_from != to, EEQUAL_ADDR);\u003C\u002Fcode> in the function body, and adds the definition of the error code \u003Ccode>EEQUAL_ADDR\u003C\u002Fcode> in front, so that the self-transfer transaction cannot be completed.\u003C\u002Fp>\n\u003Cp>Obviously, it is not meaningful to transfer money to yourself, so it is better to directly prohibit this kind of transaction.\u003Cbr>So plan B is a better practice.\u003Cbr>It directly guarantees that the two are definitely not the same address when successfully executed, and the code is more concise.\u003C\u002Fp>\n\u003Ch3 id=\"Practice\">Practice\u003C\u002Fh3>\n\u003Cp>Currently, we have only completed functional verification of the \u003Ccode>transfer\u003C\u002Fcode> function.\u003Cbr>But it doesn’t say under what circumstances it will abort.\u003Cbr>As an exercise, give it an appropriate \u003Ccode>aborts_if\u003C\u002Fcode> condition. The answer can be found in our next article.\u003C\u002Fp>\n\u003Ch2 id=\"Verify-mint-function\">Verify mint function\u003C\u002Fh2>\n\u003Cp>The signature of the function \u003Ccode>mint\u003C\u002Fcode> is as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">public fun mint&lt;CoinType: drop&gt;(mint_addr: address, amount: u64, _witness: CoinType) acquires Balance\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>It is responsible for minting the \u003Ccode>amount\u003C\u002Fcode> of tokens and depositing them in the address \u003Ccode>mint_addr\u003C\u002Fcode>.\u003Cbr>More interesting is \u003Ccode>_witness\u003C\u002Fcode>, which is of type \u003Ccode>CoinType\u003C\u002Fcode>.\u003Cbr>Because only the module that defines the \u003Ccode>CoinType\u003C\u002Fcode> can construct a value of this type, this guarantees the identity of the caller.\u003C\u002Fp>\n\u003Cp>There is actually only one call to \u003Ccode>deposit\u003C\u002Fcode> in the \u003Ccode>mint\u003C\u002Fcode> function.\u003Cbr>It is not difficult to imagine that there should be many similarities in the specifications to be satisfied by the two. Drawing a tiger according to a cat, it is not difficult to write:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec mint {\n    let balance = global&lt;Balance&lt;CoinType&gt;&gt;(mint_addr).coin.value;\n\n    aborts_if !exists&lt;Balance&lt;CoinType&gt;&gt;(mint_addr);\n    aborts_if balance + amount &gt; MAX_U64;\n\n    let post balance_post = global&lt;Balance&lt;CoinType&gt;&gt;(mint_addr).coin.value;\n    ensures balance_post == balance + amount;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2 id=\"Verify-the-publish-balance-function\">Verify the \u003Ccode>publish_balance\u003C\u002Fcode> function\u003C\u002Fh2>\n\u003Cp>The signature of the function \u003Ccode>publish_balance\u003C\u002Fcode> is as follows:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">public fun publish_balance&lt;CoinType&gt;(account: &amp;signer)\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>It publishes an empty \u003Ccode>Balance&lt;CoinType&gt;\u003C\u002Fcode> resource under \u003Ccode>account\u003C\u002Fcode>.\u003Cbr>So if the resource already exists it should exit abnormally, and end normally the balance should be zero:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec publish_balance {\n    let addr = signer::address_of(account);\n    aborts_if exists&lt;Balance&lt;CoinType&gt;&gt;(addr);\n\n    ensures exists&lt;Balance&lt;CoinType&gt;&gt;(addr);\n    let post balance_post = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n    ensures balance_post == 0;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2 id=\"Simplify-redundant-specifications-with-Schema\">Simplify redundant specifications with Schema\u003C\u002Fh2>\n\u003Cp>Congratulations! So far, we have completed the verification of all the functions of BasicCoin step by step.\u003Cbr>However, if you look closely at the code, many of the spec blocks look very similar, and the file structure would be clearer if they could be shortened a bit.\u003C\u002Fp>\n\u003Cp>Schema is a means of building a specification by grouping properties.\u003Cbr>Semantically, they are also syntactic sugar, and their use in a spec block is equivalent to expanding the conditions they contain into functions, structs, or modules.\u003C\u002Fp>\n\u003Ch3 id=\"Eliminate-simple-repetitions\">Eliminate simple repetitions\u003C\u002Fh3>\n\u003Cp>As a most obvious example, the spec blocks of \u003Ccode>mint\u003C\u002Fcode> and \u003Ccode>deposit\u003C\u002Fcode> are a little different except for the variable names (in terms, they are \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLambda_calculus#%CE%B1-conversion\">alpha convertible\u003C\u002Fa>), and the overall structure can be said to be exactly the same.\u003Cbr>To simplify them, let’s create a Schema:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec schema DepositSchema&lt;CoinType&gt; {\n    addr: address;\n    amount: u64;\n\n    let balance = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n\n    aborts_if !exists&lt;Balance&lt;CoinType&gt;&gt;(addr);\n    aborts_if balance + amount &gt; MAX_U64;\n\n    let post balance_post = global&lt;Balance&lt;CoinType&gt;&gt;(addr).coin.value;\n    ensures balance_post == balance + amount;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>This Schema declares two typed variables, and some conditions about what those variables should satisfy.\u003Cbr>When other places want to use this Schema, use \u003Ccode>include DepositSchema {addr: XX, amount: YY}\u003C\u002Fcode> to import it.\u003Cbr>where \u003Ccode>XX\u003C\u002Fcode> and \u003Ccode>YY\u003C\u002Fcode> are expressions used to replace \u003Ccode>addr\u003C\u002Fcode> and \u003Ccode>amount\u003C\u002Fcode>.\u003Cbr>If the expression is exactly the same as the corresponding variable name, you can just write the variable name, or simply omit it.\u003C\u002Fp>\n\u003Cp>With the above Schema definition, we can now simplify the previous spec:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-rust\" data-language=\"rust\">\u003Ccode class=\"language-rust\">spec mint {\n  include DepositSchema&lt;CoinType&gt; {addr: mint_addr};\n}\n\u002F\u002F ....\nspec deposit {\n    include DepositSchema&lt;CoinType&gt; {amount: check.value};\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch3 id=\"Practice-1\">Practice\u003C\u002Fh3>\n\u003Cp>In addition to the above example, find another spec block (such as \u003Ccode>publish_balance\u003C\u002Fcode>), and split it into a Schema declaration and a spec block that uses the corresponding Schema.\u003Cbr>As an exercise, the Schema you created might not be available in this code, so it doesn’t feel like there’s a benefit to it.\u003Cbr>But if in the later development, there are other functions that call \u003Ccode>publish_balance\u003C\u002Fcode>, it will be more convenient.\u003C\u002Fp>\n\u003Ch2 id=\"Conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>In our article so far, we’ve shown you how the Move Prover works and it could potentially provide the ultimate safety guarantee for smart contracts. In the upcoming series, we will explore \u003C\u002Fp>\n\u003Cul>\n\u003Cli>the tips and best practices; \u003C\u002Fli>\n\u003Cli>a more complicated case study using the Move Prover; \u003C\u002Fli>\n\u003Cli>the limitation of the current version of the Move Prover, and its future outlook;\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>So stay tuned!\u003C\u002Fp>\n\u003Cp>More and more developers are developing or deploying smart contracts on Move Ecosystem,  it is recommended to ship the Move contracts with a comprehensive security assessment using the Move Prover and other industry-leading audit methodology. If you need help evaluating Move smart contracts for vulnerabilities and certifying their behavior.  Please \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fwww.movebit.xyz\u002F#contact\">fill out our contact form\u003C\u002Fa>. We’d be happy to help.\u003C\u002Fp>\n\u003Ch2 id=\"About-Us\">About Us\u003C\u002Fh2>\n\u003Cp>\u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fwww.movebit.xyz\u002F\">MoveBit\u003C\u002Fa> is a security audit company for the Move ecosystem. Our team is comprised of security leaders from academia and enterprise, with 10 years of cybersecurity experience, and has presented security research papers at top international security academic conferences such as NDSS and CS. \u003C\u002Fp>\n\u003Ch3 id=\"References\">References\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\n\u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fgithub.com\u002Fmove-language\u002Fmove\u002Ftree\u002Fmain\u002Flanguage\u002Fdocumentation\u002Ftutorial\u002F\">Move tutorial\u003C\u002Fa> by the Move team\u003C\u002Fli>\n\u003Cli>\n\u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fcookbook.starcoin.org\u002Fdocs\u002Fmove\u002Fmove-prover\u002Fmvp-tutorial\u002F\">Starocin Move Prover tutorial\u003C\u002Fa> by Xudong Wang\u003C\u002Fli>\n\u003C\u002Ful>","\u002Fmedia\u002Fmovebit\u002F2022\u002F09\u002F2b36c3a7da22b02f60e79524b16e5664.png","2022-09-27T03:15:00Z","2026-08-11T18:05:56Z","19 min read",false,"Verify Smart Contracts in Aptos with the Move Prover Pt.1 cover image",1792635795142]