File ‹Invariant.ML›

(*  
    Title:      Invariant.ML
    Author:     Diego Marmsoler
*)

signature INVARIANT =
sig

end

structure Invariant: INVARIANT =
struct

open Solidity_Util;
open Solidity_Data;

fun mk_conj (SOME y) x =
  HOLogic.mk_conj (x,y)
| mk_conj NONE x = x

fun list_ex xs x =
  fold_rev (fn (nm,tp) => fn tr => HOLogic.mk_exists (nm,tp,tr)) xs x

fun mk_inv_term ctxt termSType.TValue TBool =
    let
      val bl = (singleton (Name.variants (Variable.names_of ctxt)) "bl", typbool);
      val t = Conststorage_data.Value Typevaltype a for ConstBool a for Free bl;
      val ctxt' = Variable.declare_names (Free bl) ctxt;
    in
      (((t, bl), NONE), ctxt')
    end
  | mk_inv_term ctxt termSType.TValue TSint =
    let
      val si = (singleton (Name.variants (Variable.names_of ctxt)) "si", typ256 word);
      val t = Conststorage_data.Value Typevaltype a for ConstUint a for Free si;
      val ctxt' = Variable.declare_names (Free si) ctxt;
    in
      (((t, si), NONE), ctxt')
    end
  | mk_inv_term ctxt termSType.TValue TAddress =
    let
      val ad = (singleton (Name.variants (Variable.names_of ctxt)) "ad", a);
      val t = Conststorage_data.Value Typevaltype a for ConstAddress a for Free ad;
      val ctxt' = Variable.declare_names (Free ad) ctxt;
    in
      (((t, ad), NONE), ctxt')
    end
  | mk_inv_term ctxt (Const (const_nameSType.TValue, _) $ (Const (const_nameVType.TBytes, _) $ _)) =
    let
      val bt = (singleton (Name.variants (Variable.names_of ctxt)) "bt", typbytes);
      val t = Conststorage_data.Value Typevaltype a $ (ConstBytes a $ Free bt);
      val ctxt' = Variable.declare_names (Free bt) ctxt;
    in
      (((t, bt), NONE), ctxt')
    end
  | mk_inv_term ctxt (Const (const_nameSType.TMap, _) $ _ $ dtype) = 
    let
      val mp = (singleton (Name.variants (Variable.names_of ctxt)) "mp", TypeState.valtype a --> TypeStores.storage_data Typevaltype a);
      val ctxt' = Variable.declare_names (Free mp) ctxt;
      val y = (singleton (Name.variants (Variable.names_of ctxt')) "y", TypeState.valtype a);
      val ctxt'' = Variable.declare_names (Free y) ctxt';
      val (((t, (fname, ftyp)), cnj), ctxt''') = mk_inv_term ctxt'' dtype;
      val t' = Conststorage_data.Map Typevaltype a for Free mp
      val cnj' =
        (HOLogic.mk_all (fst y, snd y,
          HOLogic.mk_exists (fname, ftyp,
           mk_conj cnj (HOLogic.mk_eq (Free mp $ Free y ,t)))))
    in
      (((t', mp), SOME cnj'), ctxt''')
    end
  | mk_inv_term ctxt (Const (const_nameSType.TEnum, _) $ dtypes) = 
    let
      val em = (singleton (Name.variants (Variable.names_of ctxt)) "em", HOLogic.listT TypeStores.storage_data Typevaltype a);
      val ctxt' = Variable.declare_names (Free em) ctxt;    
      val (((ts,vs),cnjs),ctxt'') = fold_map (fn x => fn ctxt => mk_inv_term ctxt x) (HOLogic.dest_list dtypes) ctxt'
                                |>> ListPair.unzip
                                |>> apfst ListPair.unzip
      val t = Conststorage_data.Array Typevaltype a for Free em

      val cnj' = HOLogic.mk_list TypeStores.storage_data Typevaltype a ts
            |> curry HOLogic.mk_eq (Free em);

      val cnj = fold mk_conj cnjs cnj'
              |> list_ex vs;
    in
      (((t, em), SOME cnj), ctxt'')
    end
  | mk_inv_term ctxt (Const (const_nameSType.TArray, _) $ l $ dtype) = 
    let
      val ar = (singleton (Name.variants (Variable.names_of ctxt)) "ar", HOLogic.listT TypeStores.storage_data Typevaltype a);
      val ctxt' = Variable.declare_names (Free ar) ctxt;
      val y0' = List.tabulate (HOLogic.dest_number l |> snd, K (dtype));
      val (((ts,vs),cnjs),ctxt'') = fold_map (fn x => fn ctxt => mk_inv_term ctxt x) y0' ctxt'
                                  |>> ListPair.unzip
                                  |>> apfst ListPair.unzip

      val t = Conststorage_data.Array Typevaltype a for Free ar

      val cnj' = HOLogic.mk_list TypeStores.storage_data Typevaltype a ts
               |> curry HOLogic.mk_eq (Free ar);

      val cnj = fold mk_conj cnjs cnj'
              |> list_ex vs;
    in
      (((t, ar), SOME cnj), ctxt'')
    end
  | mk_inv_term ctxt (Const (const_nameSType.DArray, _) $ dtype) = 
    let
      val da = (singleton (Name.variants (Variable.names_of ctxt)) "da", HOLogic.listT TypeStores.storage_data Typevaltype a);
      val ctxt' = Variable.declare_names (Free da) ctxt;
      val (((t, (fname, ftyp)), cnj), ctxt'') = mk_inv_term ctxt' dtype;
      val t' = Conststorage_data.Array Typevaltype a for Free da
      val y = (singleton (Name.variants (Variable.names_of ctxt'')) "y", typnat);
      val t'' =
        (HOLogic.mk_exists (fname, ftyp,
         mk_conj cnj (HOLogic.mk_eq (ConstList.nth TypeStores.storage_data Typevaltype a for Free da Free y, t))))
      val cnj' =
        HOLogic.mk_all (fst y, snd y,
          (HOLogic.mk_imp
            (ConstOrderings.ord_class.less typnat for Free y Constsize HOLogic.listT TypeStores.storage_data Typevaltype a for Free da,
            t'')));
    in
      (((t', da), SOME cnj'), ctxt'')
    end
  | mk_inv_term _ t = error ("unsupported type" ^ @{make_string} t)

val _ = let
  val specparser =
    let
      fun mk_invariant (((name, var), inv), contract_name) thy =
        let
          val name_lc = decapitalizeFirst contract_name;
          val lthy = Named_Target.init [] (Context.theory_base_name thy ^ "." ^ name_lc) thy
          val var =
            let
              val tp = HOLogic.mk_prodT (typString.literal --> Typestorage_data Typevaltype a, typnat)
            in
              readAs lthy tp var
            end;
          val (trms', ctxt) =
            let
              val data = the (mlookupN (Proof_Context.theory_of lthy) name_lc);
              val members = map (fn (t1,t2) => (HOLogic.mk_fst var $ t1, t2)) (#members data);
              fun go mname (((trm, (fname, ftyp)), cnj), ctxt) = ((mname, ((trm, (fname, ftyp)), cnj)), ctxt);
            in
              lthy |> Variable.declare_constraints var
                   |> fold_map (fn(x,y) => (fn ctxt => go x (mk_inv_term ctxt y))) members
            end;
          val trms =
            let
              fun go (x, ((trm, (fname, ftyp)), cnj)) =
                (HOLogic.mk_exists (fname, ftyp,
                  (mk_conj cnj
                    (HOLogic.mk_eq (x, trm)))))
            in
              map go trms'
            end;

          val inv = Syntax.read_term ctxt inv |> change_types;

          val (vars, prems) =
            let
              fun go (x, ((trm, (fname, ftype)), cnj)) =
                ((fname, ftype), HOLogic.mk_Trueprop (mk_conj cnj (HOLogic.mk_eq (x, trm))))
            in
              map go trms' |> ListPair.unzip
            end;

          val varsI = fst (Term.dest_Free var) :: map fst vars
          val premsI = HOLogic.mk_Trueprop inv :: prems
          fun mytac {context, prems} def =
            rewrite_goal_tac context [def] THEN' blast_tac (context addSIs prems)

          val (tm, thm, lthy) =
            let
              val idef = lambda var (fold (curry HOLogic.mk_conj) trms inv)
              fun export ((term, (_, thm)), (lthy_new, lthy_old)) =
                let
                  val pi = Proof_Context.export_morphism lthy_old lthy_new;
                  val thms_new = (Morphism.thm pi) thm;
                  val term_new = (Morphism.term pi) term;
                in
                  (term_new, thms_new, lthy_new)
                end;
            in
              tap (K (writeln "Generating invariant definition ..."))
              Local_Theory.begin_nested lthy
              |> snd
              |> Local_Theory.define ((name, NoSyn), (Binding.empty_atts, idef)) ||> `Local_Theory.end_nested
              |> export
            end;

          val varsE = fst (Term.dest_Free var) :: ["thesis"]
          val elim =
            let
              fun go x y = Logic.all (Free x) y;
              val thesis = Free ("thesis", typbool)
              val prems = fold go vars (Logic.list_implies (premsI, HOLogic.mk_Trueprop thesis));
            in
              Logic.mk_implies (HOLogic.mk_Trueprop (tm $ var), Logic.mk_implies (prems, HOLogic.mk_Trueprop thesis))
            end;

        in
          tap (K (writeln "Generating introduction rule ..."))
          Goal.prove lthy varsI premsI (HOLogic.mk_Trueprop (tm $ var)) (fn x => mytac x thm 1)
           |> (fn thm => Local_Theory.note ((Binding.map_name (fn x => x ^ "I") name, @{attributes [wprules]}), [thm]) lthy)
           |> snd
           |> tap (K (writeln "Generating elimination rule ..."))
           |> (fn lthy => Goal.prove lthy varsE [] elim (fn x => mytac x thm 1)
            |> (fn thm => Local_Theory.note ((Binding.map_name (fn x => x ^ "E") name, @{attributes [wperules]}), [Object_Logic.rulify lthy thm]) lthy))
           |> snd
           |> Local_Theory.exit_global
        end;
    in
      (Parse.binding -- Parse.term -- (Parse.$$$ "where" |-- Parse.term) -- (Parse.$$$ "for" |-- Parse.string)) >> mk_invariant
    end
in
  solidity_command Toplevel.theory @{command_keyword "invariant"} "creates a contract" specparser
end

end