Skip to content

Latest commit

 

History

History
1170 lines (901 loc) · 34.5 KB

slides.rst

File metadata and controls

1170 lines (901 loc) · 34.5 KB
title:Provably Correct Smart Contracts using DeepSEA
css:talk.css
css:alectryon.css
css:tango_subtle.css
js-body:alectryon.js
slide-numbers:true
data-transition-duration:0
alectryon/serapi/args:-R /home/daniel/Documents/Code/research/Eth-Eng-Grp-Talk-2023/contracts/result DeepSpec -R /home/daniel/Documents/Code/research/Eth-Eng-Grp-Talk-2023/contracts/Crowdfunding Crowdfunding -R /home/daniel/Documents/Code/research/Eth-Eng-Grp-Talk-2023/contracts/trivial trivial -R /home/daniel/Documents/Code/research/Eth-Eng-Grp-Talk-2023/contracts/proofs SmartContract

Provably Correct Smart Contracts using DeepSEA

Daniel Britten

Ethereum Engineering Group Meetup 2023

Note

It's great to be here...

The talks are from 40 to 90 minutes long, including question time.


Overall Goal: Demonstate a technique for showing the correctness of smart contracts


Overview:

  • Theorem-proving example in the proof assistant Coq
  • DeepSEA example: compilation
  • DeepSEA example: proof
  • Correctness property of a Crowdfunding contract
  • Paper overviews

Introduction to theorem-proving in Coq

Example: Even and odd numbers


.. coq:: fold

  Definition Even (n : nat) := exists k, n = 2 * k.
  Definition Odd  (m : nat) := exists l, m = 2 * l + 1.

  Lemma EvenToOdd : forall (n : nat), Even n -> Odd (n + 1).
  Proof.
    intros.
    unfold Even in H.
    destruct H as [k].
    unfold Odd.
    exists k.
    rewrite <- H.
    reflexivity.
  Qed.


DeepSEA


Introduction to creating a DeepSEA smart contract

Example: Trivial contract converting bool to int


object signature trivial = {
    const boolToInt : bool -> int
}

object Trivial : trivial {
    let boolToInt b =
      if b then 1 else 0
}

layer CONTRACT = { o = Trivial }

object signature trivial = {
    const boolToInt : bool -> int;
    boolToIntTracker : bool -> int
}

object Trivial : trivial {
    let seenTrueYet : bool := false

    let boolToInt b =
      if b then 1 else 0

    let boolToIntTracker b =
      if b then
        begin
            seenTrueYet := true;
            1
        end
      else 0
}

layer CONTRACT = { o = Trivial }

$ dsc trivial.ds bytecode
5b60005b60206109205101610920525b61022660006020610920510301525b60006020
610920510301516101005260206101002060006020610920510301525b600060006020
61092051030151555b60206109205103610920525b60005b9050386300000073600039
386000f35b60006000fd5b610940610920527c01000000000000000000000000000000
000000000000000000000000006000350480635192f3c01463000000495780631e01e7
071463000000965760006000fd5b6004355b60006109205101610920525b8063000000
67576300000085565b600190505b60006109205103610920525b805b90506000526020
6000f35b60009050630000006c565b60006000fd5b6004355b60206109205101610920
525b8063000000b4576300000111565b61022660006020610920510301525b60006020
610920510301516101005260206101002060006020610920510301525b600160006020
61092051030151555b600190505b60206109205103610920525b805b90506000526020
6000f35b6000905063000000f8565b60006000fd

$ dsc trivial.ds abi

[ {"type":"constructor",
  "name":"constructor",
  "inputs":[], "outputs":[], "payable":false,
  "constant":false, "stateMutability":"nonpayable"},
{"type":"function",
  "name":"boolToInt",
  "inputs":[{"name":"b", "type":"bool"}],
  "outputs":[{"name":"", "type":"uint256"}],
  "payable":false,
  "constant":true,
  "stateMutability":"view"},
{"type":"function",
  "name":"boolToIntTracker",
  "inputs":[{"name":"b", "type":"bool"}],
  "outputs":[{"name":"", "type":"uint256"}],
  "payable":true,
  "constant":false,
  "stateMutability":"payable"}]

Note

Next slide is a reminder of the contract definition.


object signature trivial = {
    const boolToInt : bool -> int;
    boolToIntTracker : bool -> int
}

object Trivial : trivial {
    let seenTrueYet : bool := false

    let boolToInt b =
      if b then 1 else 0

    let boolToIntTracker b =
      if b then
        begin
            seenTrueYet := true;
            1
        end
      else 0
}

layer CONTRACT = { o = Trivial }

.. coq:: none

  Require Import String.
  Require Import trivial.DataTypeOps.
  Require Import trivial.LayerCONTRACT.

  Require Import DeepSpec.lib.Monad.StateMonadOption.
  Require Import DeepSpec.lib.Monad.RunStateTInv.
  Require Import lib.ArithInv.
  Import DeepSpec.lib.Monad.Monad.MonadNotation.

  Require Import Lia.
  Require Import List.
  Require Import Bool.
  Require Import ZArith.
  Require Import cclib.Maps.
  Require Import cclib.Integers.

  Require Import DataTypes.
  Require Import backend.MachineModel.

  Require Import DataTypes.
  Import ListNotations.

  Require Import core.MemoryModel.
  Require Import HyperTypeInst.

  Require Import Maps.
  Import Maps.Int256Tree_Properties.
  Import Maps.Int256Tree.

  Require Import trivial.ContractModel.
  Import trivial.ContractModel.ContractModel.

  Require Import Syntax.

  Open Scope Z.

  Section Proof.
  Context (contract_address : addr).
  Context {memModelOps : MemoryModelOps mem}.


$ dsc trivial.ds coq ...

if f then ret 1 else ret 0
.. coq:: fold

  Require Import Syntax. (* .none *)
  Print Trivial_boolToInt_opt.
  Print Trivial_boolToInt.


$ dsc trivial.ds coq ...

if f then
  MonadState.modify (update_Trivial_seenTrueYet true) ;;
  ret 1
else
  ret 0
.. coq:: fold

  Print Trivial_boolToIntTracker_opt.
  Print Trivial_boolToIntTracker.


A proof about our Trivial contract


.. coq:: fold

  Lemma boolToInt_proof : forall input context before result after,
    let machine_environment :=
      (make_machine_env contract_address before context (fun _ _ _ _ => true)) in

    runStateT (Trivial_boolToInt_opt input machine_environment) (contract_state before)
      = Some (result, after)

    ->

    result = 1 <-> input = true.


Main goal:

result = 1 <-> input = true
.. coq:: fold

  Proof. (* .all -.h#memModelOps *)
    intros. (* .all -.h#machine_environment -.h#memModelOps *)
    Transparent Trivial_boolToInt_opt. (* .all -.h#* .h#H *)
    unfold Trivial_boolToInt_opt in H. (* .all -.h#* .h#H *)
    split; intros. (* .all -.h#* *)
      - (* "->" result is 1 ∴ input is true. *) (* .all -.h#* .h#H .h#H0 *)
        inv_runStateT_branching. (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
        + (* Go down true branch of if statement. *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
          reflexivity.
        + (* Go down false branch of if statement, gives a contradiction. *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
          subst. (* .all -.h#* .h#H1 *) discriminate.
      - (* "<-" input is true ∴ result is 1. *)  (* .all -.h#* .h#H .h#H0 *)
        inv_runStateT_branching. (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
        + (* Go down true branch of if statement *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
          subst. (* .all -.h#* .h#H0 *)  reflexivity.
        + (* Go down false branch of if statement, gives a contradiction. *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
          discriminate.
  Qed.

Note

Remember to click the extra button to show hypotheses for both goals when they are there.

Note that some hypothesis are hidden in the visualisations for clarity.

Next slide has a copy of the contract definition.


object signature trivial = {
    const boolToInt : bool -> int;
    boolToIntTracker : bool -> int
}

object Trivial : trivial {
    let seenTrueYet : bool := false

    let boolToInt b =
      if b then 1 else 0

    let boolToIntTracker b =
      if b then
        begin
            seenTrueYet := true;
            1
        end
      else 0
}

layer CONTRACT = { o = Trivial }

.. coq:: fold

  Lemma boolToIntTracker_proof : forall input context before result after,
    let machine_environment :=
      (make_machine_env contract_address before context (fun _ _ _ _ => true)) in
    runStateT (Trivial_boolToIntTracker_opt input machine_environment) (contract_state before)
      = Some (result, after)
    -> result = 1 <-> input = true. (* .all -.h#memModelOps *)
  Proof. (* .all -.h#memModelOps *)
    intros. (* .all -.h#machine_environment -.h#memModelOps *)
    Transparent Trivial_boolToIntTracker_opt. (* .all -.h#* .h#H *)
    unfold Trivial_boolToIntTracker_opt in H. (* .all -.h#* .h#H *)
    split; intros. (* .all -.h#* *)
      - (* "->" result is 1 ∴ input is true. *) (* .all -.h#* .h#H .h#H0 *)
        inv_runStateT_branching. (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H3 .h#H4 *)
        + (* Go down true branch of if statement. *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H3 .h#H4 *)
          reflexivity.
        + (* Go down false branch of if statement, gives a contradiction. *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
          subst. (* .all -.h#* .h#H1 *) discriminate.
      - (* "<-" input is true ∴ result is 1. *)  (* .all -.h#* .h#H .h#H0 *)
        inv_runStateT_branching. (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H3 .h#H4 *)
        + (* Go down true branch of if statement *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H3 .h#H4 *)
          subst. (* .all -.h#* .h#H0 *)  reflexivity.
        + (* Go down false branch of if statement, gives a contradiction. *) (* .all -.h#* .h#Heqb .h#H0 .h#H1 .h#H2 *)
          discriminate.
  Qed.


.. coq:: none

  End Proof.
  Open Scope nat.

.. coq:: none

  Require Import Crowdfunding.DataTypeOps.
  Require Import Crowdfunding.LayerCONTRACT.
  Require Import Crowdfunding.ContractModel.
  Import Crowdfunding.ContractModel.ContractModel.

  Section ProofCrowdfunding.

.. coq:: none

  Definition wei := int256. (* TODO consider whether this should be a tagged type instead. *)
  Delimit Scope int256_scope with int256.
  Infix "+" := Int256.add : int256_scope.
  Infix "-" := Int256.sub : int256_scope.
  Infix "=?" := Int256.eq (at level 70, no associativity) : int256_scope.

  Ltac me_transfer_cases :=
    try match goal with
      H : (Int256.one =? Int256.one)%int256 = false |- _ =>
        rewrite Int256.eq_true in H; discriminate
        end;
    try match goal with
      H : runStateT mzero _ = ret _ |- _ =>
      simpl in H; discriminate
    end.

  (* TODO this will probably need updating based on the definition of me_transfer *)
  Ltac ds_inv :=
        repeat (
          try inv_runStateT_branching;
          let Case := fresh "NoOverflowOrUnderflowInTransferCase" in
          try match goal with
            | H : context[me_transfer _  _ _] |- _ =>
            unfold me_transfer, make_machine_env in H;
            destruct (noOverflowOrUnderflowInTransfer _ _ _ _
                      && (_ _ _ _ _)) eqn:Case
          end
        );
        me_transfer_cases.



  (*
  The goal here is to, in a sense, quantify over an arbitrary snapshot of the Blockchain and then model all possible interactions after that point. In particular, modelling most precisely the smart contract.
  *)

  Section Blockchain_Model.

  (* begin snippet snapshot_variables *)
  Context
    (snapshot_timestamp : int256)
    (snapshot_number : int256)
    (snapshot_blockhash : int256 -> int256)
    (snapshot_balances : addr -> wei).
  (* end snippet snapshot_variables *)

  Definition ContractState := global_abstract_data_type.

  (* begin snippet initial_state *)
  Definition initial_state : BlockchainState :=
    mkBlockchainState
      snapshot_timestamp
      snapshot_number
      snapshot_balances
      snapshot_blockhash
      init_global_abstract_data
  .
  (* end snippet initial_state *)

  Context {HmemOps: MemoryModelOps mem}.
  Context {memModelOps : MemoryModelOps mem}.

  Context
    (contract_address : addr).

  (* begin snippet assumption_example *)
  Context
    (address_accepts_funds :
      option ContractState -> addr -> addr -> wei -> bool).
  Open Scope int256. (* .none *)
  Definition address_accepts_funds_assumed_for_from_contract
    d sender recipient amount :=
    if sender =? contract_address then true else
    address_accepts_funds d sender recipient amount.
  Check address_accepts_funds_assumed_for_from_contract.
  Close Scope int256. (* .none *)
  Definition address_accepts_funds_assumption :=
    address_accepts_funds_assumed_for_from_contract.
  (* end snippet assumption_example *)

  (* The current model also has the implicit assumption that the transfers to a smart contract during a function call via callvalue are always accepted by the contract.
    This could be changed by editing callvalue_prf in the definition of Action, similarly to how it is done for `externalBalanceTransfer` *)

  Definition updateTimeAndBlock before block_count time_passing : BlockchainState :=
  mkBlockchainState
    (time_passing + (timestamp before))%int256
    (block_count + (block_number before))%int256
    (balance before)
    (blockhash before)
    (contract_state before)
  .

  Definition validTimeChange block_count time_passing current_block_number current_timestamp : bool :=
    (* Note, testing for positive block_count and time_passing is unnecessary while they are Int256 values.
      It would be necessary to add positivity checks if using Z instead of course. *)
    ((Int256.intval block_count) + (Int256.intval current_block_number) <=? Int256.max_unsigned)%Z
    && ((Int256.intval time_passing) + (Int256.intval current_timestamp) <=? Int256.max_unsigned)%Z.

  Open Scope int256.
  Definition update_balances sender recipient amount balances : (addr -> wei) :=
    (* Here the balances are updated without checking for overflows. Overflow checks must be done elsewhere. *)
    fun a =>
    if sender =? recipient then balances a else
      if a =? sender then (balances sender) - amount else
      if a =? recipient then (balances recipient) + amount
        else balances a.
  Close Scope int256.

  Definition update_balance before latest_balances : BlockchainState :=
    mkBlockchainState
    (timestamp before)
    (block_number before)
    latest_balances
    (blockhash before)
    (contract_state before)
  .

  Definition noOverflowOrUnderflowInTransfer (sender recipient : addr) (amount : wei) (balances : addr -> wei) : bool :=
    ((Int256.intval (balances sender)) - (Int256.intval amount) >=? 0)%Z
    && ((Int256.intval (balances recipient)) + (Int256.intval amount) <=? Int256.max_unsigned)%Z.

  (* TODO-Review - This defines how balances are updated in the model after transferEth *)
  Open Scope int256.
  Definition current_balances
    (* Note on where insufficient balance-checking takes place:
      Overflow and underflow of balances must already have been checked before this function.
      (i.e. before a transfer is placed in Outgoing_transfer_recipient_and_amount it should
            have been checked to ensure no overflow/underflow.)
      Currently this check is expected to be implemented by the me_transfer definition.
      !! Ensure you are using an appropriate me_transfer definition. !! *)
    (successful_transfer : option (addr * wei))
    (initial_balances : addr -> wei)
    : (addr -> wei) :=
      match successful_transfer with
        | None => initial_balances
        | Some (recipient, amount) =>
            update_balances contract_address recipient amount initial_balances
      end.
  Close Scope int256.

  Definition new_balance_after_contract_call (before : BlockchainState) (d : ContractState) : (addr -> wei) :=
      (current_balances
        (Outgoing_transfer_recipient_and_amount d)
        (balance before)).

  Definition next_blockchain_state (before : BlockchainState) (d : ContractState) : BlockchainState :=
    mkBlockchainState
      (timestamp before)
      (block_number before)
      (new_balance_after_contract_call before d)
      (blockhash before)
      d.

  (* This approach to defining Action requires all calls to a contract
    function to succeed, i.e. return (Some _ _), failure cases are
    amalgamated into the revert case. This means only needing to prove
    the (typically) trivial revert case once, without losing generality. *)
  Inductive Action (before : BlockchainState) :=
    | call_Crowdfunding_donate (context : CallContext)
        (callvalue_prf : noOverflowOrUnderflowInTransfer (caller context) contract_address (callvalue context) (balance before) = true)
        r (* The return value of calling donate successfully given the context (and arguments, if applicable) *)
        contract_state_after (* The contract state after calling donate successfully given the context (and arguments, if applicable) *)
        (case_donate_prf :
            runStateT (Crowdfunding_donate_opt (make_machine_env contract_address before context address_accepts_funds_assumption)) (contract_state before)
            = Some (r, contract_state_after))
    | call_Crowdfunding_getFunds (context : CallContext)
        (callvalue_prf : noOverflowOrUnderflowInTransfer (caller context) contract_address (callvalue context) (balance before) = true)
        r (* The return value of calling getFunds successfully given the context (and arguments, if applicable) *)
        contract_state_after (* The contract state after calling getFunds successfully given the context (and arguments, if applicable) *)
        (case_getFunds_prf :
            runStateT (Crowdfunding_getFunds_opt (make_machine_env contract_address before context address_accepts_funds_assumption)) (contract_state before)
            = Some (r, contract_state_after))
    | call_Crowdfunding_claim (context : CallContext)
        (callvalue_prf : noOverflowOrUnderflowInTransfer (caller context) contract_address (callvalue context) (balance before) = true)
        r (* The return value of calling claim successfully given the context (and arguments, if applicable) *)
        contract_state_after (* The contract state after calling claim successfully given the context (and arguments, if applicable) *)
        (case_claim_prf :
            runStateT (Crowdfunding_claim_opt (make_machine_env contract_address before context address_accepts_funds_assumption)) (contract_state before)
            = Some (r, contract_state_after))
    | externalBalanceTransfer (sender recipient : addr) (amount : wei) (* Note that if wei is currently an int256, so it is guaranteed to be non-negative. If ever changed to using Z again an appropriate check would be needed in this definition. *)
        (prf : sender <> contract_address /\
          ((noOverflowOrUnderflowInTransfer sender recipient amount (balance before))
          && (address_accepts_funds_assumption None sender recipient amount) = true))
    | timePassing (block_count time_passing : int256)
                  (prf : validTimeChange block_count time_passing (block_number before) (timestamp before) = true)
    | revert (* A no-op, or a call with some error resulting in no state change, such as a contract reverting during its code execution, or such as calling an invalid function when there is no fallback defined. TODO check that DeepSEA does not have any fallback function in generated bytecode. *).

.. coq:: none

  Fixpoint step
    (before : BlockchainState) (action : Action before) : BlockchainState :=
  match action with
  | call_Crowdfunding_donate context
      callvalue_prf r d_after case_donate_prf =>
        next_blockchain_state before d_after
  | call_Crowdfunding_claim context
      callvalue_prf r d_after case_claim_prf =>
        next_blockchain_state before d_after
  | call_Crowdfunding_getFunds context
      callvalue_prf r d_after case_getFunds_prf =>
        next_blockchain_state before d_after
  | timePassing block_count time_passing prf =>
      updateTimeAndBlock before block_count time_passing
  | externalBalanceTransfer sender recipient amount prf =>
      update_balance before (update_balances sender recipient amount (balance before))
  | revert => before
  end.

.. coq:: none

  Definition resetTransfers (d : ContractState) : ContractState :=
    {|
    Crowdfunding_owner := Crowdfunding_owner d;
    Crowdfunding_max_block := Crowdfunding_max_block d;
    Crowdfunding_goal := Crowdfunding_goal d;
    Crowdfunding_backers := Crowdfunding_backers d;
    Crowdfunding_funded := Crowdfunding_funded d;
    Crowdfunding_deadlinePassed_msg := Crowdfunding_deadlinePassed_msg d;
    Crowdfunding_successfullyDonated_msg := Crowdfunding_successfullyDonated_msg d;
    Crowdfunding_alreadyDonated_msg := Crowdfunding_alreadyDonated_msg d;
    Crowdfunding_funded_msg := Crowdfunding_funded_msg d;
    Crowdfunding_failed_msg := Crowdfunding_failed_msg d;
    Crowdfunding_too_early_to_claim_funds_msg := Crowdfunding_too_early_to_claim_funds_msg d;
    Crowdfunding_too_early_to_reclaim_msg := Crowdfunding_too_early_to_reclaim_msg d;
    Crowdfunding_cannot_refund_msg := Crowdfunding_cannot_refund_msg d;
    Crowdfunding_here_is_your_money_msg := Crowdfunding_here_is_your_money_msg d;
    Outgoing_transfer_recipient_and_amount := None
  |}.

.. coq:: none

  Record Step := mkStep
    {
      Step_state : BlockchainState;
      Step_action : Action Step_state
    }.

.. coq:: none

  Record StepSpace := mkStepSpace
    {
      StepSpace_state : BlockchainState;
      StepSpace_actions : Type
    }.

  Class Next (b : BlockchainState) : Type :=
  {
      next : Action b -> BlockchainState
  }.

  Instance : Next initial_state :=
  {
    next := step initial_state
  }.


  Definition InSecond (st : BlockchainState) :=
    exists (a : Action initial_state), st = step initial_state a.

  Definition InThird (st : BlockchainState) :=
      exists (two : BlockchainState) (a : Action two) ,
        InSecond two /\ st = step two a.

  Definition InFourth (st : BlockchainState) :=
    exists (three : BlockchainState) (a : Action three) ,
      InThird three /\ st = step three a.

  Open Scope nat.
  Inductive InPossible (st : BlockchainState) (n:nat) :=
    | inzero (H : exists (a : Action initial_state), st = step initial_state a) (Hn : n = 0) : InPossible st n
    | inSn (current : BlockchainState) (Hs : InPossible current (n - 1))

    (
      H : exists (a : Action current),
      st = step current a
    )
    : InPossible st n

    .
  Close Scope nat.

  Definition stepOnce prev := (step (Step_state prev) (Step_action prev)).
  Definition stepOnceAndWrap prev next_action := (mkStep (stepOnce prev) next_action).
  Hint Unfold stepOnce stepOnceAndWrap.

  (* begin snippet ReachableVia *)


.. coq:: none

  Inductive ReachableVia from :
    BlockchainState -> Step-> list Step -> Prop :=
  | initial_case (next_action : Action from)
      : ReachableVia from from
                        (mkStep from next_action)
                        [mkStep from next_action]
  | step_case
      (prevSt : BlockchainState) (prev : Step)
      (prevList : list Step)
      (Hprev : ReachableVia from prevSt
                              prev prevList)
      (next_action : Action (stepOnce prev))
      : ReachableVia from  (stepOnce prev)
      (stepOnceAndWrap prev next_action)
      (stepOnceAndWrap prev next_action
          :: prevList).

.. coq:: none

  (* end snippet ReachableVia *)
  (* begin snippet ReachableViaLinkStateToStep *)
  Lemma ReachableViaLinkStateToStep : forall st st' s l,
    ReachableVia st st' s l -> st' = Step_state s.
  Proof.
    intros.
    destruct H; reflexivity.
  Qed.
  (* end snippet ReachableViaLinkStateToStep *)

  Lemma ReachableViaLinkStepToList : forall st st' s l,
    ReachableVia st st' s l -> exists tl, s :: tl = l.
  Proof.
    intros.
    destruct H.
    - exists []. reflexivity.
    - exists prevList. reflexivity.
  Qed.

  Ltac reachableFromByLinks :=
    match goal with
    | H : ReachableVia _ _ _ _ |- _ =>
      let StateToStepName := fresh "HReachableViaLinkStateToStep" in
      let StepToListName := fresh "HReachableViaLinkStepToList" in
      epose proof (ReachableViaLinkStateToStep _ _ _ _ H) as StateToStepName;
      epose proof (ReachableViaLinkStepToList _ _ _ _ H) as StepToListName
    end.


  (* Ugh *)
  (* Inductive ReachableVia from (s : BlockchainState) (next_action : Action s) : list Step -> Prop :=
  | initial_case (first_action : Action from)
      : ReachableVia from from first_action [mkStep from first_action]
  | step_case (prevList : list Step) (Hprev : ReachableVia from s next_action prevList)
      (next_step_action : Action (step s next_action))
      : ReachableVia from (step s next_action) next_step_action
      (stepOnce s next_action next_step_action :: prevList)
  . *)

  Definition ReachableFrom from state := exists l step', ReachableVia from state step' l.

  Definition Reachable := ReachableFrom initial_state.

  (* begin snippet since_as_long:: no-out *)

A Crowdfunding Correctness Property


.. coq:: fold

  Definition since_as_long (Property1 : BlockchainState -> Prop)
      (Property2 : BlockchainState -> Prop) (PropertyAboutAction : Step -> Prop) :=
    forall actions start finish helper,
      ReachableVia start finish helper actions ->
      Property1 start
      -> (forall act, List.In act actions -> PropertyAboutAction act)
      -> Property2 finish.

  Notation "Property2 `since` Property1 `as-long-as` PropertyAboutAction" :=
    (since_as_long Property1 Property2 PropertyAboutAction) (at level 1).

.. coq:: none

  (* end snippet since_as_long *)

  (* begin snippet donation_recorded *)
  Definition donation_recorded (a : addr)
    (amount : Z) (s : BlockchainState) :=
      Int256Tree.get_default 0 a
        (Crowdfunding_backers (contract_state s))
          > 0.
  (* end snippet donation_recorded *)

  Definition no_claims_from (a : addr) (s : Step) :=
    match Step_action s with
    | (call_Crowdfunding_claim _ a _ _ _) => False
    | _ => True
    end.

  Ltac destruct_if_H :=
    let caseName := fresh "IfCase" in
    match goal with
      | [ _ : context[if ?X then _ else _] |- _ ] => destruct X eqn:caseName
    end.

  Ltac destruct_beq256_H :=
    let caseName := fresh "IfCaseBeq" in
      match goal with
        | [ _ : context[(?X =? ?Y)%int256] |- _ ] => destruct (X =? Y)%int256 eqn:caseName
      end.

  Ltac destruct_geq256_H :=
    let caseName := fresh "IfCaseGeq" in
      match goal with
        | [ _ : context[(?X >=? ?Y)%int256] |- _ ] => destruct (X >=? Y)%int256 eqn:caseName
      end.

  Hint Unfold Z_bounded. (*Causes annoying issues, use autounfold in *. *)


  Ltac destruct_and :=
    match goal with
      | [ H : (_ /\ _) |- _ ] => destruct H
    end.


.. coq:: none

  Ltac Hlinks :=
  match goal with
  | H : ReachableVia _ _ _ _ |- _ =>
    let StateToStepName := fresh "HS" in
    let StepToListName := fresh "HL" in
    epose proof (ReachableViaLinkStateToStep _ _ _ _ H) as StateToStepName;
    epose proof (ReachableViaLinkStepToList _ _ _ _ H) as StepToListName
  end.

  Ltac inv_FT :=
    try match goal with H : false = true |- _ => inversion H end.

  Hint Unfold stepOnceAndWrap step stepOnce make_machine_env.

  Definition donate_fun := Crowdfunding_donate_opt.


.. coq:: fold

  Open Scope int256. (* .none *)

  Theorem donation_preserved :
    forall (user : addr) (amount : Z),
                   (donation_recorded user amount)
      `since`      (donation_recorded user amount)
      `as-long-as` (no_claims_from user).


.. coq:: fold

  Proof.
  unfold since_as_long.
  intros.

  induction H; [assumption|].

  assert(donation_recorded user amount prevSt).
  apply IHReachableVia.
  intros.
  apply H1.
  apply in_cons; assumption.

  clear H0.
  clear IHReachableVia.
  unfold donation_recorded in *.

  reachableFromByLinks.

  assert (no_claims_from user prev).
  apply H1.
  destruct HReachableViaLinkStepToList.
  subst.
  right. left. reflexivity.

  destruct prev; simpl in *; unfold stepOnceAndWrap, step in *; simpl in *.
  clear H1. (* no_claims_one, not needed this time? *)
  clear next_action.
  clear H. (* ReachableVia, no longer needed? *)
  clear HReachableViaLinkStepToList.
  unfold no_claims_from in H0.
  unfold stepOnce. simpl.
  unfold donation_recorded in *.
  destruct Step_action0; simpl in *;
    rewrite <- HReachableViaLinkStateToStep in *;
    clear HReachableViaLinkStateToStep Step_state0.
    - Transparent Crowdfunding_donate_opt. unfold Crowdfunding_donate_opt in case_donate_prf.
      ds_inv; subst; simpl.
      + match goal with H : false = true |- _ => inversion H end.
      + destruct (user =? (caller context))%int256 eqn:Case.
        * apply Int256eq_true in Case. rewrite <- Case.
          rewrite get_default_ss.
          exfalso.
          subst.
          unfold make_machine_env in Heqb0; simpl in Heqb0.
          apply Z.eqb_eq in Heqb0.
          rewrite Heqb0 in H2.
          lia.
        * apply Int256eq_false in Case.
          rewrite get_default_so; assumption.
      + match goal with H : false = true |- _ => inversion H end.
    - Transparent Crowdfunding_getFunds_opt. unfold Crowdfunding_getFunds_opt in case_getFunds_prf.
      ds_inv; subst; simpl; try lia.
    - contradiction.
    - assumption.
    - assumption.
    - assumption.
  Qed.


  Close Scope int256. (* .none *)

Note

If showing a part of this, show the very last Transparent and unfold of getFunds_opt. This shows something familiar that looks complex but is resolved via automation, because getFunds is what is called by the owner, so it has no bearing on the user donation record which is the focus of this lemma.

Also can show the first section, in particular after "Proof."

.. coq:: none

  End Blockchain_Model.
  End ProofCrowdfunding.


Paper overviews


modelling-a-blockchain-paper.png
provably-correct-paper.png
reentrancy-paper.png
amm-paper.png
deepsea-paper.png

References


Thank you!

I would like to thank my supervisor Professor Steve Reeves at the University of Waikato and Vilhelm Sjöberg at CertiK for their valuable insights and input.

I would also like to thank Associate Professor Jing Sun and the University of Auckland for kindly hosting me during this research.


Additional Slides


Example: a property of a list membership function


.. coq:: none

  Require Import Nat.
  Require Import PeanoNat.
  Require Import Bool.
  Require Import List.
  Import ListNotations.

.. coq:: fold

  Module MyList. (* .none *)
  Inductive list (A : Type) : Type :=
  | nil : list A
  | cons : A -> list A -> list A.
  End MyList. (* .none *)

  Fixpoint contains (n:nat) (l:list nat) : bool :=
    match l with
    | [] => false
    | h :: tl => (n =? h) || contains n tl
  end.


.. coq:: fold

  Lemma contains_property :
    forall n list1, contains n list1 = true
      -> forall list2, contains n (list1 ++ list2) = true.
  Proof.
    intros n.
    induction list1.
    - simpl. intros. discriminate.
    - intros. simpl in *.
      apply orb_prop in H.
      destruct H.
      + apply orb_true_intro.
        left. assumption.
      + apply orb_true_intro.
        right.
        eapply IHlist1 in H.
        eassumption.
  Qed.


.. coq:: fold

  Lemma contains_correctness : forall n l, contains n l = true <-> In n l.
  Proof.
    Print In.
    split.
    - induction l as [|l'].
      + simpl. discriminate.
      + simpl. intros.
        apply orb_prop in H.
        destruct H.
        * left. rewrite Nat.eqb_eq in H. auto.
        * right. apply IHl in H. assumption.
    - induction l as [|l'].
      + simpl. contradiction.
      + simpl. intros.
        destruct H.
        * apply orb_true_intro.
          left.
          subst.
          apply Nat.eqb_refl.
        * apply orb_true_intro.
          right.
          auto.
  Qed.


Example: Simple state machine


example state machine diagram


.. coq:: none

  Require Import Coq.Program.Tactics.
  Local Obligation Tactic := idtac.
  Module fsm.

.. coq:: fold

  Inductive State :=
    | initial
    | middle
    | extra
    | final
  .

  Inductive Transition (before : State) :=
    | advance (prf : before <> final)
    | sidetrack (prf : before = initial).


.. coq:: fold

  Local Obligation Tactic := try discriminate. (* .none *)
  Program Definition step (s : State) (t : Transition s) :=
    match t with
    | advance _ =>
      match s with
      | initial => middle
      | middle => final
      | extra => middle
      | final => _
      end
    | sidetrack _ =>
      match s with
      | initial => extra
      | _ => _
      end
  end.


.. coq:: fold

  Next Obligation.
  intros.
  exfalso.
  subst.
  contradiction.
  Defined.
  Next Obligation.
  intros.
  exfalso.
  subst.
  contradiction.
  Defined.

Local Obligation Tactic := try discriminate. (* Used for the above. *)

.. coq:: fold

  Lemma three_transitions_gives_final :
  forall t1 t2 t3, let s1 := step initial t1 in let s2 := step s1 t2 in
    step s2 t3 = final.
  Proof.
  intros.
  destruct t1. simpl in *.
    - destruct t2. simpl in *.
      + destruct t3.
        * contradiction.
        * discriminate.
      + discriminate.
    - destruct t2. simpl in *.
      + destruct t3. simpl in *.
        * reflexivity.
        * discriminate.
      + discriminate.
  Qed.

  End fsm. (* .none *)