Require Import String ZArith.
Require Import syntax macros semantics comparable util.
Import error.
Require List.
Require tez.
Require map.
Require List.
Import List.ListNotations.
Open Scope list.
Require Import spending_limit_contract_definition.
Module Spending_limit_contract_verification (C:ContractContext).
Module semantics := Semantics C. Import semantics.
Module Tactics.
(*
* Solves some fuel-related goals on the form
* Hfuel : Y <= fuel |- X <= fuel by showing X <= Y
*)
Hint Rewrite List.rev_length : fuel.
Ltac simple_fuel :=
match goal with
| Hfuel:_ <= ?fuel |- _ =>
autorewrite with fuel in *;
apply (le_trans_rev _ _ _ Hfuel); simpl; omega
end.
(* Tactics for rewriting under existential binders *)
Ltac ex_under_aux' tac := etransitivity; [eapply forall_ex; intro; tac; apply iff_refl|].
Ltac ex_under_aux tac := ex_under_aux' tac || (rewrite iff_comm; ex_under_aux' tac; rewrite iff_comm).
Tactic Notation "ex_under" tactic(T) := ex_under_aux ltac:(T).
Ltac ex_rewrite rw := ex_under_aux ltac:(rewrite rw).
Ltac S_fuel_to_add_fuel :=
match goal with
| [ fuel : Datatypes.nat |- _ ] =>
repeat match goal with
| [ |- context[S fuel] ] => change (S fuel) with (1 + fuel)
| [ |- context[S (?n + fuel)] ] => change (S (n + fuel)) with ((S n) + fuel)
end
end.
Ltac simple_fuel_add_n_fuel :=
match goal with
| [ fuel : Datatypes.nat |- _ ] =>
match goal with
| [ |- ?n <= ?m + fuel ] => apply Nat.le_sub_le_add_l
end
end.
Ltac my_simple_fuel :=
intros; try S_fuel_to_add_fuel; try simple_fuel_add_n_fuel;
(omega || simple_fuel).
Lemma my_simple_fuel_test_1:
forall (A : type) (xs : Datatypes.list (data A)) (fuel : Datatypes.nat),
Datatypes.length xs <= fuel ->
1 + Datatypes.length xs <= S fuel.
Proof. my_simple_fuel. Qed.
Lemma my_simple_fuel_test_2:
forall (fuel : Datatypes.nat) (L : Datatypes.list (comparable_data timestamp * comparable_data mutez)),
1 + Datatypes.length L <= fuel -> 1 + Datatypes.length L <= fuel.
Proof.
intros fuel L H.
my_simple_fuel.
Qed.
End Tactics.
Import Tactics.
Fixpoint total_amount_transaction_list1
(transaction_list : (data (list (pair mutez (contract unit)))))
(acc : data mutez)
(result : data mutez) : Prop :=
match transaction_list with
| nil => result = acc
| cons (tz, _) transaction_list =>
exists new_acc,
(add _ _ _ Add_variant_tez_tez acc tz) = Return new_acc /\
(total_amount_transaction_list1 transaction_list new_acc result)
end.
Definition total_amount_transaction_list
(transaction_list : data (list (pair mutez (contract unit))))
(result : data mutez) :=
total_amount_transaction_list1 transaction_list (0 ~Mutez) result.
Definition slc_ep_transfer2_transaction_iter_body_spec {A} :
(stack (list operation ::: mutez ::: A) -> Prop) ->
(data (pair mutez (contract unit))) ->
stack (list operation ::: mutez ::: A) -> Prop :=
fun psi '(transferred_amount, destination) '(oplist, (threshold, st)) =>
exists new_threshold,
add _ _ _ Add_variant_tez_tez threshold transferred_amount = Return new_threshold /\
psi ((transfer_tokens unit I tt transferred_amount destination) :: oplist,
(new_threshold, st)).
Lemma slc_ep_transfer2_transaction_iter_body_spec_precond_ex A psi transferred_amount destination oplist threshold st :
slc_ep_transfer2_transaction_iter_body_spec (A := A) psi (transferred_amount, destination) (oplist, (threshold, st)) =
precond_ex (add _ _ _ Add_variant_tez_tez threshold transferred_amount)
(fun new_threshold => psi ((transfer_tokens unit I tt transferred_amount destination) :: oplist,
(new_threshold, st))).
Proof.
reflexivity.
Qed.
Opaque add.
Opaque sub.
Lemma slc_ep_transfer2_transaction_iter_body_correct {A} env fuel :
forall (psi : stack (list operation ::: mutez ::: A) -> Prop)
(element : data (pair mutez (contract unit)))
(st : stack (list operation ::: mutez ::: A)),
3 <= fuel ->
eval_seq_precond fuel env slc_ep_transfer2_transaction_iter_body
psi
(element, st) <->
slc_ep_transfer2_transaction_iter_body_spec
psi element st.
Proof.
intros psi (transferred_amount, destination) (oplist, (threshold, st)) Hfuel.
rewrite slc_ep_transfer2_transaction_iter_body_spec_precond_ex.
rewrite <- precond_exists.
unfold slc_ep_transfer2_transaction_iter_body.
unfold eval_seq_precond.
simpl eval_seq_precond_body.
more_fuel.
simpl.
more_fuel.
simpl.
more_fuel.
simpl.
reflexivity.
Qed.
Lemma slc_ep_transfer2_transaction_iter_correct
{A}
env
psi
(transaction_list : data (list (pair mutez (contract unit))))
(st : stack A) :
forall
(oplist : data (list operation))
fuel
(threshold : data mutez),
4 + Datatypes.length transaction_list <= fuel ->
eval_precond fuel env (ITER (i := iter_list _) slc_ep_transfer2_transaction_iter_body) psi
(transaction_list, (oplist, (threshold, st))) <->
exists final_threshold,
total_amount_transaction_list1 transaction_list threshold final_threshold /\
psi
(List.rev
(List.map
(fun '(transfered_amount, destination) =>
transfer_tokens unit I tt transfered_amount destination) transaction_list) ++
oplist,
(final_threshold, st)).
Proof.
intros oplist fuel threshold H.
rewrite <- eval_precond_correct.
rewrite precond_iter_bounded
with (body_spec := slc_ep_transfer2_transaction_iter_body_spec)
(fuel_bound := 3).
- generalize dependent oplist.
generalize dependent threshold.
induction transaction_list as [| (head_mut, ct) transaction_list_tail ]; intros threshold oplist.
+ rewrite iff_comm. apply ex_eq_simpl.
+ simpl.
rewrite ex_2. rewrite ex_order.
apply forall_ex.
intro acc.
rewrite underex_and_assoc.
rewrite <- ex_and_comm.
apply and_both.
rewrite IHtransaction_list_tail; try my_simple_fuel.
rewrite <- List.app_assoc.
reflexivity.
- intros fuel0 (transferred_amount, destination) (oplist', (threshold', st')) psi0 Hfuel.
rewrite eval_seq_precond_correct.
apply slc_ep_transfer2_transaction_iter_body_correct.
assumption.
- intros psi1 psi2 (transferred_amount, destination) (oplist', (threshold', st')) Hpsi.
unfold slc_ep_transfer2_transaction_iter_body_spec.
apply forall_ex. intro new_threshold. rewrite Hpsi. reflexivity.
- simple_fuel.
Qed.
Definition reverse {sty A B} :
instruction
sty false
(list A ::: list A ::: B)
(list A ::: B)
:= ITER (i := iter_list _) { CONS }.
Definition reverse_body_spec
{A : type} {B : Datatypes.list type}
(psi : stack (list A ::: B) -> Prop)
(x : data A)
(st : stack (list A ::: B)) : Prop :=
match st with
(xs, st') => psi (cons x xs, st')
end.
Lemma reverse_correct
fuel self_type env (A : type) (B : Datatypes.list type)
(xs ys : data (list A))
(st : stack B)
(psi : stack (list A ::: B) -> Prop) :
2 + Datatypes.length xs <= fuel ->
eval_precond (self_type := self_type) fuel env reverse psi (xs, (ys, st))
<-> psi (List.app (List.rev xs) ys, st).
Proof.
intros Hfuel.
extract_fuel (Datatypes.length xs + 1).
rewrite <- Nat.add_assoc.
rewrite <- eval_precond_correct.
unfold reverse.
rewrite precond_iter_bounded with
(body_spec := reverse_body_spec)
(fuel_bound := 1).
rewrite fold_right_psi_simpl with
(f := reverse_body_spec)
(f' := fun x '(ys, st) => (cons x ys, st)).
- apply eq_iff_refl.
f_equal.
unfold data.
fold data_aux.
generalize (@List.rev (data_aux (op (data_aux Empty_set)) A) xs) ys.
induction l; intro ys'; simpl.
+ reflexivity.
+ now rewrite IHl.
- destruct st0. reflexivity.
- intros fuel0 x (xs', st') psi0 H.
more_fuel. simpl. reflexivity.
- intros psi1 psi2 x (xs', st') H.
apply H.
- omega.
Qed.
Definition slc_ep_transfer_loop_body_spec self_type (env : @proto_env self_type)
(input : stack (list (pair timestamp mutez) ::: list (pair timestamp mutez) ::: mutez ::: storage_auth_ty ::: nil))
(*
* (output : stack (bool ::: list (pair timestamp mutez) ::: list (pair timestamp mutez) ::: mutez ::: storage_auth_ty ::: nil))
*)
psi : Prop :=
let '(q1, (q2, (amount, st))) := input in
match q1, q2 with
| hd :: q1', q2 =>
if ((fst hd) <=? (now env))%Z
then exists sum, add _ _ _ Add_variant_tez_tez amount (snd hd) = Return sum /\
psi (true, (q1', (q2, (sum, st))))
else psi (false, (q1, (q2, (amount, st))))
| [], hd :: q2' => psi (true, (List.rev (hd :: q2'), ([], (amount, st))))
| [], [] => psi (false, ([], ([], (amount, st))))
end.
Lemma slc_ep_transfer_loop_body_correct env fuel psi (q1 q2 : data (list (pair timestamp mutez))) amount st :
(* sum (queue) + amount = init_thresh *)
6 + Datatypes.length q2 <= fuel ->
eval_precond fuel env slc_ep_transfer_loop_body psi (q1, (q2, (amount, st)))
<-> slc_ep_transfer_loop_body_spec _ env (q1, (q2, (amount, st))) psi.
Proof.
intros Hfuel.
unfold slc_ep_transfer_loop_body.
cbn.
destruct q1.
- destruct q2.
+ now (do 5 (more_fuel; cbn)).
+ simpl in Hfuel.
do 3 (more_fuel; cbn).
destruct q2; [reflexivity|].
match goal with |- context[eval_precond fuel env (@ITER ?tff ?st ?c ?i ?A ?code) ?psi] =>
remember (eval_precond fuel env (@ITER tff st c i A code) psi) as eval_itercode
end.
more_fuel.
cbn.
subst eval_itercode.
rewrite reverse_correct; [|my_simple_fuel].
rewrite <- List.app_assoc.
reflexivity.
- destruct d as (ts, tz).
do 6 (more_fuel; cbn).
rewrite <- comparison_to_int_leb.
rewrite <- comparison_to_int_opp.
destruct (_ >=? _)%Z.
+ rewrite precond_exists. unfold precond_ex.
apply forall_ex.
intro x.
apply and_both_0; [|reflexivity].
rewrite add_comm; reflexivity.
+ reflexivity.
Qed.
(* Start defining specification *)
Fixpoint allowed_amount l rem now result :=
match l with
| nil => result = rem
| cons (frozen_timestamp, frozen_amount) l =>
match compare timestamp frozen_timestamp now with
(* Modified Arvid & Zaynah *)
(* frozen_timestamp <= now *)
| Lt | Eq =>
exists new_rem,
(* new_rem = rem + frozen_amount *)
add _ _ _ Add_variant_tez_tez rem frozen_amount = Return new_rem /\
allowed_amount l new_rem now result
| Gt => result = rem
end
end.
(* true iff exists t1 \in l1, t1 > now *)
Fixpoint queue_cut_before_now (l1 : data (list (pair timestamp mutez))) (now : data timestamp) : Datatypes.bool :=
match l1 with
| nil => false
| cons (t, _) l1 =>
match compare timestamp t now with
(* t <= now *)
| Lt | Eq => queue_cut_before_now l1 now
| Gt => true
end
end.
(* filter (fun ts => ts > now) l *)
Fixpoint remove_past (l : data (list (pair timestamp mutez))) (now : data timestamp) :=
match l with
| nil => nil
| cons (t, m) l =>
match compare timestamp t now with
| Lt | Eq => remove_past l now
| Gt => (t,m)::l
end
end.
Definition update_queue
(l1 l2 : data (list (pair timestamp mutez)))
(now : data timestamp) :=
if queue_cut_before_now l1 now then
(remove_past l1 now, l2)
else
(* l1 can be removed completely *)
(remove_past (List.rev l2) now, nil).
Lemma slc_ep_transfer_loop_correct1
env
fuel
psi
(q1 q2 : data (list (pair timestamp mutez)))
(amount : data mutez)
(st : stack (storage_auth_ty ::: nil)) :
Datatypes.length q1 + Datatypes.length q2 + 6 <= fuel ->
queue_cut_before_now q1 (now env) = true ->
eval_precond fuel env slc_ep_transfer_loop psi (true, (q1, (q2, (amount, st)))) <->
(exists amount' ,
allowed_amount q1 amount (now env) amount' /\
psi (remove_past q1 (now env), (q2, (amount', st)))).
Proof.
intros Hfuel Hqueue_cut.
unfold slc_ep_transfer_loop.
generalize dependent amount.
generalize dependent fuel.
induction q1 as [ | (tz, ts) q1 ]; intros fuel Hfuel amount.
- inversion Hqueue_cut.
- rewrite plus_comm in Hfuel.
more_fuel; simpl.
rewrite slc_ep_transfer_loop_body_correct by my_simple_fuel.
unfold slc_ep_transfer_loop_body_spec.
unfold queue_cut_before_now in Hqueue_cut.
fold queue_cut_before_now in Hqueue_cut.
simpl compare in Hqueue_cut.
rewrite Z.leb_compare.
simpl fst.
unfold allowed_amount. simpl compare. fold allowed_amount.
unfold remove_past. simpl compare. fold remove_past.
repeat rewrite Z_match_compare_gt in *.
ex_rewrite Z_match_compare_gt.
destruct (now env <? tz)%Z eqn:HtzCompNow.
+ more_fuel. simpl. rewrite iff_comm. apply ex_eq_simpl.
+ rewrite ex_2. rewrite ex_order. apply forall_ex. intro sum.
rewrite underex_and_assoc. rewrite <- ex_and_comm. apply and_both.
now rewrite IHq1; try assumption; try my_simple_fuel.
Qed.
Lemma queue_cut_before_false_tl:
forall q a t x,
queue_cut_before_now ((a,t) :: q) x = false ->
queue_cut_before_now q x = false /\ (a <=? x)%Z = true.
Proof.
simpl; intros; simpl in H.
rewrite Z.leb_compare.
(*Set Printing All.*)
case_eq ((a ?= x)%Z ) ; intros; try rewrite H0 in H; simpl in H; try congruence; intuition.
Qed.
Remark add_mul: forall a b, 2* (S a) + b = a + (S (S a)) + b.
Proof.
intros;omega.
Qed.
Lemma eval_precond_loop fuel self_ty (env : @proto_env self_ty) A (code : instruction _ false A (bool ::: A)) st psi :
eval_precond (S fuel) env (LOOP {code})%michelson psi (true, st) =
eval_precond fuel env code (eval_precond fuel env (LOOP {code})%michelson psi) st.
Proof.
reflexivity.
Qed.
Lemma slc_ep_transfer_loop_correct2':
forall
env
psi
(q1 : data (list (pair timestamp mutez)))
fuel (x: data (pair timestamp mutez))
(q2 : data (list (pair timestamp mutez)))
( Hfuel: 6 + Datatypes.length q2 <= fuel)
(Hqueue_cut : queue_cut_before_now (x::q1) (now env) = false)
(amount : data mutez)
(st : stack (storage_auth_ty ::: nil)),
eval_precond ((S (Datatypes.length q1)) + fuel) env slc_ep_transfer_loop psi (true, ((x::q1), (q2, (amount, st)))) <->
exists amount',
allowed_amount (x::q1) amount (now env) amount' /\
eval_precond fuel env slc_ep_transfer_loop
psi ( true, ([],(q2, (amount',st)))).
Proof.
intros env psi ; unfold slc_ep_transfer_loop. induction q1 as [ | (tz, ts) q1 ]; intros.
(* case q1 == nil *)
- destruct x.
simpl.
rewrite slc_ep_transfer_loop_body_correct by my_simple_fuel. simpl.
simpl in Hqueue_cut.
case_eq ( (d ?= now env)%Z); intros K; rewrite K in Hqueue_cut; inversion Hqueue_cut.
+ rewrite Z.leb_compare. rewrite K. intuition.
* destruct H as [sum [Heq Prd]].
exists sum. split;auto.
exists sum. split;auto.
* destruct H as [am [A Prd]].
destruct A as [sum' [Heq1 Heq2]].
subst. exists sum'. split;auto.
+ rewrite Z.leb_compare. rewrite K. intuition.
* destruct H as [sum [Heq Prd]].
exists sum. split;auto.
exists sum. split;auto.
* destruct H as [am [A Prd]].
destruct A as [sum' [Heq1 Heq2]].
subst. exists sum'. split;auto.
(* case q1 <> nil *)
- assert (Hqueue_ind: queue_cut_before_now ((tz, ts) :: q1) (now env) = false).
simpl in Hqueue_cut.
destruct x. case_eq ((d ?= now env)%Z ); intro K; rewrite K in Hqueue_cut; inversion Hqueue_cut; auto.
simpl "+".
rewrite eval_precond_loop.
rewrite slc_ep_transfer_loop_body_correct. simpl.
destruct x. simpl. assert (Cond : (d <=? now env)%Z = true).
destruct (queue_cut_before_false_tl _ _ _ _ Hqueue_cut) as [A B]. auto.
rewrite Z.leb_compare. rewrite Z.leb_compare in Cond.
case_eq ( (d ?= now env)%Z); intros K ; rewrite K in Cond; inversion Cond ; try rewrite K.
+ (* K : (d ?= now env)%Z = Eq *)
split.
*
intros [sum [Heq1 Prd]].
destruct (IHq1 fuel (tz,ts) q2 Hfuel Hqueue_ind sum st) as [Ia Ib].
destruct (Ia Prd) as [am [Alw Prd']].
exists am. split;intuition. exists sum. split. auto.
simpl in Alw. auto.
*
intros [am [A Prd]]. destruct A as [sum [ Heq Alw]].
exists sum. split. auto.
destruct (IHq1 fuel (tz,ts) q2 Hfuel Hqueue_ind sum st) as [Ia Ib].
apply Ib. exists am. split; auto.
+ (*K : (d ?= now env)%Z = Lt*)
split.
*
intros [sum [Heq1 Prd]].
destruct (IHq1 fuel (tz,ts) q2 Hfuel Hqueue_ind sum st) as [Ia Ib].
destruct (Ia Prd) as [am [Alw Prd']].
exists am. split;intuition. exists sum. split. auto.
simpl in Alw. auto.
*
intros [am [A Prd]]. destruct A as [sum [ Heq Alw]].
exists sum. split. auto.
destruct (IHq1 fuel (tz,ts) q2 Hfuel Hqueue_ind sum st) as [Ia Ib].
apply Ib. exists am. split; auto.
+
(* fuel bound *)
omega.
Qed.
(*****************************************************************)
Definition list_empty_b (q2 : data (list (pair timestamp mutez))) : Datatypes.bool :=
match q2 with nil => true | _ => false end.
(* probably not used *)
Lemma slc_ep_transfer_loop_correct3'
env
fuel
psi
(q2 : data (list (pair timestamp mutez)))
(amount : data mutez)
(st : stack (storage_auth_ty ::: nil)) :
6 + Datatypes.length q2 <= fuel ->
(eval_precond (S fuel) env slc_ep_transfer_loop psi (true, ([], (q2, (amount, st)))) <->
eval_precond fuel env slc_ep_transfer_loop psi (negb (list_empty_b q2), (List.rev q2, ([], (amount, st))))).
Proof.
intros Hfuel.
simpl.
rewrite slc_ep_transfer_loop_body_correct by my_simple_fuel.
simpl slc_ep_transfer_loop_body_spec.
unfold slc_ep_transfer_loop.
destruct q2; reflexivity.
Qed.
Lemma slc_ep_transfer_loop_correct3
env
fuel
psi
(x : data (pair timestamp mutez))
(q2 : data (list (pair timestamp mutez)))
(amount : data mutez)
(st : stack (storage_auth_ty ::: nil)) :
7 + Datatypes.length q2 <= fuel ->
(eval_precond (S fuel) env slc_ep_transfer_loop psi (true, ([], (x :: q2, (amount, st)))) <->
eval_precond fuel env slc_ep_transfer_loop psi (true, (List.rev (x :: q2), ([], (amount, st))))).
Proof.
intros Hfuel. simpl.
rewrite slc_ep_transfer_loop_body_correct by my_simple_fuel.
reflexivity.
Qed.
Lemma slc_ep_transfer_loop_correct4
env
fuel
psi
(amount : data mutez)
(st : stack (storage_auth_ty ::: nil)) :
1 <= fuel ->
(eval_precond fuel env slc_ep_transfer_loop psi (false, ([], ([], (amount, st)))) <->
psi (([], ([], (amount, st))))).
Proof.
intros Hfuel. do 1 more_fuel. simpl. intuition.
Qed.
Lemma slc_ep_transfer_loop_correct2
env
fuel
psi
(q1 q2 : data (list (pair timestamp mutez)))
(amount : data mutez)
(st : stack (storage_auth_ty ::: nil)) :
6 + Datatypes.length q1 + Datatypes.length q2 <= fuel ->
queue_cut_before_now q1 (now env) = false ->
eval_precond (Datatypes.length q1 + 1 + fuel) env slc_ep_transfer_loop
psi (true, (q1, (q2, (amount, st)))) <->
exists amount',
allowed_amount q1 amount (now env) amount' /\
eval_precond fuel env slc_ep_transfer_loop
psi (negb (list_empty_b q2), (List.rev q2,([], (amount',st)))).
Proof.
intros Hfuel Hqueue_cut.
destruct q1.
- simpl (Datatypes.length _ + 1 + fuel).
rewrite slc_ep_transfer_loop_correct3'.
+ simpl.
rewrite ex_eq_simpl.
destruct q2; more_fuel; reflexivity.
+ assumption.
- simpl Datatypes.length.
rewrite <- Nat.add_assoc.
replace (2 + fuel) with (S (S fuel)) by omega.
rewrite slc_ep_transfer_loop_correct2'; try my_simple_fuel; try assumption.
apply forall_ex. intro amount'.
now rewrite slc_ep_transfer_loop_correct3' by my_simple_fuel.
Qed.
Lemma queue_cut_before_now_allowed_amount :
forall (q1 q2 : (data (list (pair timestamp mutez)))) ts tresh1 tresh,
queue_cut_before_now q1 ts = true ->
allowed_amount (q1 ++ q2) tresh ts tresh1 <-> allowed_amount q1 tresh ts tresh1.
Proof.
intros q1 q2 ts tresh1.
induction q1.
+ now simpl.
+ destruct a as (ts0, tz).
rewrite <- List.app_comm_cons.
simpl.
intros tresh H.
repeat rewrite Z_match_compare_gt in *.
destruct ((ts <? ts0)%Z).
* intuition.
* apply forall_ex. intro new_rem. now rewrite IHq1.
Qed.
Lemma remove_past_no_now :
forall q n,
queue_cut_before_now q n = false ->
remove_past q n = [].
Proof. simpl. intros.
induction q. now simpl.
destruct a.
simpl in *.
destruct (d ?= n)%Z; auto.
inversion H.
Qed.
Lemma update_queue_not_in :
forall q1 q2 ts,
queue_cut_before_now q1 ts = false ->
update_queue q1 q2 ts =
update_queue (List.rev q2) [] ts.
Proof.
simpl. intros q1 q2 ts H. unfold update_queue.
rewrite H.
destruct (queue_cut_before_now (List.rev q2) ts) eqn:HqueueCut.
- simpl comparable_data in HqueueCut.
rewrite HqueueCut.
reflexivity.
- simpl comparable_data in HqueueCut.
rewrite HqueueCut.
now rewrite remove_past_no_now.
Qed.
Lemma Hupdate_queue_no_now self_ty (env : @proto_env self_ty):
forall (q1 q2 : data (list (pair timestamp mutez))),
queue_cut_before_now q1 (now env) = false ->
queue_cut_before_now (List.rev q2) (now env) = false ->
([], []) = update_queue q1 q2 (now env).
Proof.
intros q1 q2 Hq1 Hq2.
unfold update_queue. rewrite Hq1.
now rewrite remove_past_no_now.
Qed.
Lemma allowed_amount_app1 :
forall q1 ts0 a a1 a2 q2,
queue_cut_before_now q1 ts0 = false ->
allowed_amount q1 a ts0 a1 ->
allowed_amount q2 a1 ts0 a2 ->
allowed_amount (q1 ++ q2) a ts0 a2.
Proof.
simpl. intros q1.
induction q1 as [|(ts,mt)]; intros ts0 a a1 a2 q2.
- simpl. intuition congruence.
- rewrite <- List.app_comm_cons.
simpl.
repeat rewrite Z_match_compare_gt.
destruct ((ts0 <? ts)%Z) eqn:Heq; [intro H; inversion H|].
intros Hsort [a1' [Hadd1 Hq1']] Hq2.
exists a1'. intuition.
apply IHq1 with a1; assumption.
Qed.
Lemma allowed_amount_app2 :
forall q1 ts0 a a2 q2,
queue_cut_before_now q1 ts0 = false ->
allowed_amount (q1 ++ q2) a ts0 a2 ->
exists a1,
allowed_amount q1 a ts0 a1 /\
allowed_amount q2 a1 ts0 a2.
Proof.
simpl. intros q1.
induction q1 as [|(ts,mt)].
- simpl. eauto.
- simpl.
intros ts0 a a2 q2.
repeat rewrite Z_match_compare_gt.
destruct (ts0 <? ts)%Z eqn:Hts0LtTs; [intro H; inversion H|].
intros Hqueue_cut [a' [Ha' Hallowed']].
specialize (IHq1 _ _ _ _ Hqueue_cut Hallowed') as [a1 [Hq1 Hq2]].
exists a1.
rewrite Z_match_compare_gt. rewrite Hts0LtTs. eauto.
Qed.
Lemma allowed_amount_app :
forall q1 ts0 a a2 q2,
queue_cut_before_now q1 ts0 = false ->
allowed_amount (q1 ++ q2) a ts0 a2 <->
exists a1,
allowed_amount q1 a ts0 a1 /\
allowed_amount q2 a1 ts0 a2.
Proof.
simpl. split.
now apply allowed_amount_app2.
intros [a1 [Ha1 Ha1']].
now apply allowed_amount_app1 with a1.
Qed.
Lemma slc_ep_transfer_loop_correct
env
fuel
psi
(q1 q2 : data (list (pair timestamp mutez)))
(amount : data mutez)
(st : stack (storage_auth_ty ::: nil)) :
2 * Datatypes.length q1 + 2 * Datatypes.length q2 + 8 <= fuel ->
eval_precond fuel env slc_ep_transfer_loop psi (true, (q1, (q2, (amount, st)))) <->
(exists (amount' : data mutez),
let (q1', q2') := update_queue q1 q2 (now env) in
allowed_amount (q1 ++ List.rev q2) amount (now env) amount' /\
psi (q1', (q2', (amount', st)))).
Proof.
intros Hfuel.
destruct (queue_cut_before_now q1 (now env)) eqn:Hqueue.
(* If now is in q1 *)
- rewrite slc_ep_transfer_loop_correct1; [| simple_fuel | assumption].
unfold update_queue. rewrite Hqueue.
ex_under (rewrite queue_cut_before_now_allowed_amount at 1 by assumption).
reflexivity.
(* If now is not in q1 *)
- (* extract fuel *)
fuel_replace
((Datatypes.length q1 + 1) +
(Datatypes.length q1 + 2 * Datatypes.length q2 + 7)).
more_fuel_add.
rewrite slc_ep_transfer_loop_correct2; [| simple_fuel | assumption].
destruct q2.
(* if q2 is empty *)
+ rewrite update_queue_not_in by apply Hqueue. simpl.
rewrite List.app_nil_r.
apply forall_ex. intro amount'.
apply and_both.
apply slc_ep_transfer_loop_correct4; try simple_fuel.
(* if q2 is non-empty *)
+ destruct (update_queue q1 (d :: q2) (now env)) eqn:Heq. simpl in d0.
ex_under (rewrite allowed_amount_app at 1 by assumption).
rewrite ex_2. rewrite ex_order. apply forall_ex; intro amount1.
rewrite underex_and_assoc. rewrite <- ex_and_comm. apply and_both.
unfold update_queue in Heq. rewrite Hqueue in Heq.
apply and_pair_eq in Heq as [Heq1 Heq2].
subst l. subst d0.
destruct (queue_cut_before_now (List.rev (d :: q2)) (now env)) eqn:HnowInQ2; try assumption.
(* If now is in (List.rev (d :: q2)) *)
* rewrite slc_ep_transfer_loop_correct1; [reflexivity| | assumption].
(* fuel arithmetic *)
simpl (Datatypes.length []). rewrite List.rev_length. simple_fuel.
(* If now is not in (List.rev (d :: q2)) *)
* (* extract fuel *)
fuel_replace
((Datatypes.length (List.rev (d :: q2)) + 1) +
(Datatypes.length q1 + Datatypes.length (d :: q2) + 6));
[|rewrite List.rev_length; simpl; omega].
more_fuel_add.
rewrite slc_ep_transfer_loop_correct2; [| |assumption].
-- apply forall_ex; intro amount'.
rewrite slc_ep_transfer_loop_correct4; [|my_simple_fuel].
rewrite remove_past_no_now by assumption.
reflexivity.
-- rewrite List.rev_length.
simpl Datatypes.length in *.
omega.
Qed.
Definition sig_header_ty :=
(pair
(contract parameter_ty)
chain_id).
Definition slc_ep_transfer1_check_signature_spec
(env : @proto_env (Some (parameter_ty, None)))
(transactions : data (list (pair mutez (contract unit))))
(slave_key : data key)
(slave_signature : data signature)
(slave_key_hash new_slave_key_hash : data key_hash)
(time_limit : data int)
(queue_left queue_right : data (list (pair timestamp mutez)))
(threshold : data mutez)
(master_salt slave_salt : data nat)
(master_key_hash : data key_hash)
(psi : stack (list (pair mutez (contract unit)) :::
list operation ::: mutez ::: key_hash ::: int ::: mutez ::: list (pair timestamp mutez) :::
list (pair timestamp mutez) ::: storage_auth_ty ::: nil) -> Prop)
:=
hash_key env slave_key = slave_key_hash /\
check_signature env slave_key slave_signature
(pack env (pair (list (pair mutez (contract unit))) key_hash) I (transactions, new_slave_key_hash)
++ pack env sig_header_ty I (self env None I, (chain_id_ env))
++ pack env nat I slave_salt)%string = true /\
(exists (threshold_gc : data mutez) ,
let (q1', q2') := update_queue queue_left queue_right (now env) in
allowed_amount (queue_left ++ List.rev queue_right) threshold (now env) threshold_gc /\
psi
(transactions,
([],
(0 ~Mutez,
(new_slave_key_hash,
(time_limit,
(threshold_gc,
(q1',
(q2', ((master_key_hash, (master_salt, (slave_salt + 2)%N)), tt)))))))))).
Transparent add.
Opaque N.add.
Opaque tez.of_Z.
Lemma slc_ep_transfer1_check_signature_correct
(env : @proto_env (Some (parameter_ty, None)))
(transactions : data (list (pair mutez (contract unit))))
(slave_key : data key)
(slave_signature : data signature)
(slave_key_hash new_slave_key_hash : data key_hash)
(time_limit : data int)
(queue_left queue_right : data (list (pair timestamp mutez)))
(threshold : data mutez)
(master_salt slave_salt : data nat)
(master_key_hash : data key_hash)
(psi : stack (list (pair mutez (contract unit)) :::
list operation ::: mutez ::: key_hash ::: int ::: mutez ::: list (pair timestamp mutez) :::
list (pair timestamp mutez) ::: storage_auth_ty ::: nil) ->
Prop) :
forall fuel,
11 + Datatypes.length queue_left * 2 + Datatypes.length queue_right * 2 <= fuel ->
let params_transfer : data parameter_transfer_ty := ((transactions, new_slave_key_hash), (slave_key, slave_signature)) in
let storage_context : data storage_context_ty := (slave_key_hash, ((threshold, time_limit), (queue_left, queue_right))) in
let storage_auth : data storage_auth_ty := (master_key_hash, (master_salt, slave_salt)) in
eval_seq_precond fuel env (slc_ep_transfer1_check_signature)
psi (params_transfer, (storage_context, (storage_auth, tt)))
<->
slc_ep_transfer1_check_signature_spec
env transactions slave_key slave_signature slave_key_hash new_slave_key_hash
time_limit queue_left queue_right
threshold master_salt slave_salt master_key_hash
psi.
Proof.
intros fuel Hfuel params_transfer storage_context storage_auth.
unfold slc_ep_transfer1_check_signature.
remember slc_ep_transfer_loop as transfer_loop.
change 11 with (6 + 5) in Hfuel.
repeat rewrite <- plus_assoc in Hfuel.
more_fuel_add.
unfold eval_seq_precond.
cbn.
repeat rewrite fold_eval_precond.
unfold slc_ep_transfer1_check_signature_spec.
rewrite (eqb_eq key_hash).
apply and_both_0.
apply eq_sym_iff.
apply and_both.
rewrite Heqtransfer_loop.
(* todo: rewrite precond (eval ) in lemmes to eval_precond *)
rewrite slc_ep_transfer_loop_correct.
- apply forall_ex.
intro amount'.
destruct (update_queue queue_left queue_right (now env)) eqn:Hq1q2.
apply and_both.
rewrite N.add_comm.
reflexivity.
(* fuel stuff *)
- my_simple_fuel.
Qed.
Lemma slc_ep_transfer3_register_correct
env
fuel
psi
(transaction_oplist : data (list operation))
(transaction_list_total : data mutez)
(new_slave_key_hash : data key_hash)
(time_limit : data int)
(threshold : data mutez)
(q1 q2 : data (list (pair timestamp mutez)))
(st : data storage_auth_ty) :
7 <= fuel ->
eval_precond fuel env slc_ep_transfer3_register
psi
(transaction_oplist,
(transaction_list_total,
(new_slave_key_hash,
(time_limit,
(threshold,
(q1, (q2, (st, tt)))))))) <->
exists new_threshold,
let new_storage_context : data storage_context_ty :=
(new_slave_key_hash,
((new_threshold, time_limit),
(q1, ((now env + time_limit)%Z, transaction_list_total) :: q2))) in
sub _ _ _ Sub_variant_tez_tez threshold transaction_list_total = Return new_threshold /\
psi (transaction_oplist, (new_storage_context, (st, tt))).
Proof.
intros Hfuel.
simpl.
do 7 (more_fuel; simpl eval_precond).
rewrite precond_exists.
reflexivity.
Qed.
Open Scope string_scope.
Definition slc_spec (env : @proto_env (Some (parameter_ty, None))) (fuel : Datatypes.nat) (input : data (pair parameter_ty storage_ty)) (output : data (pair (list operation) (storage_ty))) :=
let '(param, storage) := input in
let '(storage_context, (master_key_hash, (master_salt, slave_salt))) := storage in
let '(slave_key_hash, ((threshold, time_limit), (queue_left, queue_right))) := storage_context in
match param with
(* Entrypoint: Receive *)
| inl _ =>
output = (nil, storage)
(* Entrypoint: Master *)
| (inr (inl ((master_key, master_signature), payload))) =>
hash_key env master_key = master_key_hash /\
(* Pre-condition 2: the signature is valid *)
check_signature env master_key master_signature
(pack env payload_ty I payload
++ pack env sig_header_ty I (self env None I, (chain_id_ env))
++ pack env nat I master_salt) = true /\
match payload with
| inl (new_storage_context, new_master_key_hash) =>
(* Install new storage *)
output = (nil, (new_storage_context, (new_master_key_hash, (master_salt + 2, slave_salt)%N)))
| inr (build_lam _ _ _ lam, new_master_key_hash) =>
(* Run lambda *)
precond (eval_seq (no_self env) lam fuel (tt, tt))
(fun '(operations, tt) =>
output = (operations, (storage_context, (new_master_key_hash, (master_salt + 2, slave_salt)%N))))
end
(* Entry point: Transfer *)
| inr (inr ((transactions, new_slave_key_hash), (slave_key, slave_signature))) =>
hash_key env slave_key = slave_key_hash /\
check_signature env slave_key slave_signature
(pack env (pair (list (pair mutez (contract unit))) key_hash) I (transactions, new_slave_key_hash)
++ pack env sig_header_ty I (self env None I, (chain_id_ env))
++ pack env nat I slave_salt) = true /\
(exists spent_amount threshold_gc new_threshold,
(* sum(transactions) = amount *)
total_amount_transaction_list transactions spent_amount /\
(* allowed(queue, threshold, now) = threshold_gc *)
allowed_amount (List.app queue_left (List.rev queue_right)) threshold (now env) threshold_gc /\
(* new_threshold = threshold_gc - spent_amount *)
sub _ _ _ Sub_variant_tez_tez threshold_gc spent_amount = Return new_threshold /\
let new_operations : data (list operation) :=
List.rev (List.map
(fun '(transfered_amount, destination) => transfer_tokens unit I tt transfered_amount destination)
transactions) in
let new_queue :=
(let (l1', l2') := update_queue queue_left queue_right (now env) in
(l1', cons ((now env + time_limit)%Z, spent_amount) l2')) in
let new_context : data storage_context_ty :=
(new_slave_key_hash, ((new_threshold, time_limit), new_queue)) in
output = (new_operations, (new_context, (master_key_hash, (master_salt, (slave_salt + 2)%N)))))
end.
Definition slc_fuel_bound (input : data (pair parameter_ty storage_ty)) :=
let '(param, storage) := input in
let '(storage_context, (master_key_hash, master_salt)) := storage in
let '(slave_key_hash, ((threshold, time_limit), (queue_left, queue_right))) := storage_context in
match param with
(* entry point: receive *)
| inl _ => 0
(* entry point: master *)
| inr (inl _) => 5
(* entry point: transfer *)
| inr (inr ((transactions, _), _)) => Datatypes.length transactions + Datatypes.length queue_left * 2 + Datatypes.length queue_right * 2 + 11
end.
Lemma slc_ep_transfer_correct :
forall env (p3 : data parameter_transfer_ty) (slave_key_hash : data key_hash) (threshold : data mutez) (time_limit : data int)
(queue_left queue_right : data (list (pair timestamp mutez))) (master_key_hash : data key_hash) (master_salt slave_salt : data nat)
(output : data (pair (list operation) storage_ty)) (fuel : Datatypes.nat),
(let (d, _) := p3 in
let (transactions, _) := d in Datatypes.length transactions + Datatypes.length queue_left * 2 + Datatypes.length queue_right * 2 + 12) <=
fuel ->
eval_seq_precond (4 + fuel) env dsl (fun x : stack [pair (list operation) storage_ty] => x = (output, tt))
(inr (inr p3), (slave_key_hash, (threshold, time_limit, (queue_left, queue_right)), (master_key_hash, (master_salt, slave_salt))), tt) <->
slc_spec env (S fuel)
(inr (inr p3), (slave_key_hash, (threshold, time_limit, (queue_left, queue_right)), (master_key_hash, (master_salt, slave_salt))))
output.
Proof.
intros env p3 slave_key_hash threshold time_limit queue_left queue_right master_key_hash master_salt slave_salt output fuel Hfuel.
unfold dsl.
remember slc_ep_transfer as prog.
destruct p3 as ((transactions, new_slave_key_hash), (slave_key, slave_signature)).
unfold eval_seq_precond.
rewrite Nat.add_comm in Hfuel.
simpl eval_seq_precond_body.
do 1 more_fuel'; simpl.
rewrite Nat.add_comm in Hfuel.
repeat rewrite fold_eval_precond.
rewrite Heqprog. unfold slc_ep_transfer.
rewrite Nat.add_comm in Hfuel.
rewrite fold_eval_seq_precond_aux.
rewrite eval_seq_assoc.
rewrite slc_ep_transfer1_check_signature_correct; [| my_simple_fuel].
unfold slc_ep_transfer1_check_signature_spec.
unfold slc_spec.
apply and_both.
apply and_both.
destruct (update_queue queue_left queue_right _) eqn:Hupdate_queue. simpl in d.
rewrite ex_order.
apply forall_ex; intro threshold_gc.
unfold seq_aux.
change (eval_seq_precond ?fuel env (SEQ ?i1 ?i2) ?psi ?st) with (eval_precond fuel env i1 (eval_seq_precond fuel env i2 psi) st).
unfold slc_ep_transfer2_transaction_iter.
rewrite (slc_ep_transfer2_transaction_iter_correct env _ _ _ _ (S (S fuel))); [| my_simple_fuel].
unfold total_amount_transaction_list.
rewrite iff_comm.
rewrite <- underex2_and_comm at 1.
rewrite underex2_and_assoc.
rewrite <- ex2_and_comm.
apply and_both.
apply forall_ex; intro spent_sum.
rewrite underex_and_comm.
rewrite <- ex_and_comm at 1.
apply and_both.
change (eval_seq_precond ?fuel env (SEQ ?i1 ?i2) ?psi ?st) with (eval_precond fuel env i1 (eval_seq_precond fuel env i2 psi) st).
rewrite slc_ep_transfer3_register_correct; [| my_simple_fuel].
apply forall_ex; intro final_threshold.
apply and_both.
unfold eval_seq_precond; simpl.
rewrite and_pair_eq.
rewrite List.app_nil_r.
intuition.
Qed.
Lemma slc_ep_master_correct:
forall env (p2 : data parameter_master_ty) (slave_key_hash : data key_hash) (threshold : data mutez) (time_limit : data int)
(queue_left queue_right : data (list (pair timestamp mutez))) (master_key_hash : data key_hash) (master_salt slave_salt : data nat)
(output : data (pair (list operation) storage_ty)) (fuel : Datatypes.nat),
5 <= fuel ->
eval_seq_precond (5 + fuel) env dsl (fun x : stack [pair (list operation) storage_ty] => x = (output, tt))
(inr (inl p2), (slave_key_hash, (threshold, time_limit, (queue_left, queue_right)), (master_key_hash, (master_salt, slave_salt))), tt) <->
slc_spec env fuel
(inr (inl p2), (slave_key_hash, (threshold, time_limit, (queue_left, queue_right)), (master_key_hash, (master_salt, slave_salt))))
output.
Proof.
intros env p2 slave_key_hash threshold time_limit queue_left queue_right master_key_hash master_salt slave_salt output fuel Hfuel.
destruct p2 as ((key, signature), payload).
unfold eval_seq_precond.
do 5 (more_fuel; simpl).
repeat rewrite fold_eval_precond.
rewrite (eqb_eq key_hash).
unfold slc_spec. rewrite N.add_comm.
destruct payload as [ (foo, slave_key_hash') | ((lam_ff, lam), slave_key_hash') ].
+ unfold data. simpl. intuition; congruence.
+ unfold slc_spec.
apply and_both_0. apply eq_sym_iff.
apply and_both.
change (@eval_seq_precond_body (@eval_precond ?fuel)) with (@eval_seq_precond fuel).
rewrite <- (eval_seq_precond_correct lam).
apply precond_eqv.
intros [st1 []].
intuition.
inversion H. f_equal.
rewrite H. reflexivity.
Qed.
Theorem slc_correct env input output :
forall fuel,
fuel >= slc_fuel_bound input ->
eval_seq env dsl (5 + fuel) (input, tt) = Return (output, tt) <->
slc_spec env fuel input output.
Proof.
unfold ">=".
intros fuel Hfuel.
rewrite return_precond.
rewrite eval_seq_precond_correct.
destruct input as (param,
((slave_key_hash, ((threshold, time_limit), (queue_left, queue_right))),
(master_key_hash, (master_salt, slave_salt)))).
destruct param as [ [] | [ p2 | p3 ] ]; simpl in Hfuel.
(* Entry point: receive *)
- unfold eval_seq_precond, data. simpl.
rewrite pair_equal_spec.
intuition.
(* Entry point : master call *)
- apply slc_ep_master_correct. apply Hfuel.
(* Entry point : transfer *)
- apply slc_ep_transfer_correct. destruct p3 as ((transactions, _), _).
apply le_n_S in Hfuel.
rewrite <- Nat.add_succ_r in Hfuel.
assumption.
Qed.
End Spending_limit_contract_verification.