AU-COBRA/ConCert

Problems reusing proofs proven by contract_induction

Closed this issue · 9 comments

In ConCert the contract_induction lemma lets us prove statements of the form

  forall bstate caddr (trace : ChainTrace empty_state bstate),
    env_contracts bstate caddr = Some (contract : WeakContract) ->
    exists dep cstate inc_calls,
      deployment_info Setup trace caddr = Some dep /\
      contract_state bstate caddr = Some cstate /\
      incoming_calls Msg trace caddr = Some inc_calls /\
      P (chain_height bstate)
        (current_slot bstate)
        (finalized_height bstate)
        caddr
        dep
        cstate
        (env_account_balances bstate caddr)
        (outgoing_acts bstate caddr)
        inc_calls
        (outgoing_txs trace caddr).

However it is not always possible to reuse proofs on this form inside another proof proven by contract_induction. Let's say that I have already proven a lemma for some property Q which was proved using contract_induction and to prove the property P above I need the information in Q, since I have already proven Q I would like to simply apply that lemma in my proof however to apply I will need to know

reachable bstate /\
env_contracts bstate caddr = Some (contract : WeakContract) /\
deployment_info Setup trace caddr = Some dep /\
contract_state bstate caddr = Some cstate /\
incoming_calls Msg trace caddr = Some inc_calls /\

This information is not available in the goals that are produced by contract_induction so you cannot apply the lemma for Q.
As far as I can tell the CallFacts, DeployFacts and AddBlockFacts used in contract_induction were intended for this purpose and it is possible to use e.g. CallFacts if Q only need cstate. However these facts cannot be used in general to introduce any lemma in this format since none of the facts gives you both cstate, dep, inc_calls and bstate at the same time.

So if contract_induction should allow reuse of proofs it either need to change how the facts work or give the needed information in the goals produced.

@jakobbotsch did you ever run into this?

@4ever2 Could you state your suggested form (or forms) for an improved contract induction?
What would the statement look like?

In addition to what Bas said, do you have an example where this ends up being a problem?
It's been a while, so I don't exactly remember how the proof of this principle goes through, and I don't know if we can allow the other goals access to that information.

however to apply I will need to know

env_contracts bstate caddr = Some (contract : WeakContract) /\
deployment_info Setup trace caddr = Some dep /\
contract_state bstate caddr = Some cstate /\
incoming_calls Msg trace caddr = Some inc_calls /\

Will you? The precondition of the statement is weaker than what you put here, it shows existence and that P holds, not that P holds under that precondition. It probably means you will not be able to connect parts of the statement to the goal you are proving, but only the parts that the particular goal is not depending on anyway.
I could be wrong -- an example would be good. I remember having to expand some of the information at one point to be able to apply this lemma below:

Lemma no_self_calls bstate caddr :
reachable bstate ->
env_contracts bstate caddr = Some (Escrow.contract : WeakContract) ->
Forall (fun abody => match abody with
| act_transfer to _ => (to =? caddr)%address = false
| _ => False
end) (outgoing_acts bstate caddr).

@jakobbotsch did you ever run into this?

Only to a certain degree, see the example in the link above.

@jakobbotsch You are right to apply the lemma I only need to know that the ChainState is reachable and there is a contract deployed in it under the address.

I don't have any specific examples, and I am not entirely sure if it is a problem. My main worry was that it was not possible to reuse any proofs about deploy_info or inc_calls because no goals give sufficient information to connect that part of the statements to the goal. However as you pointed out none of the goals really depend on deploy_info or inc_calls so maybe it is not a problem since you would never need to use a lemma stating something about deploy_info or inc_calls.

@spitters @jakobbotsch
I now have a concrete example where a useful lemma cannot be used in contract_induction.

Lemma contract_balance_bound : forall bstate caddr (trace : ChainTrace empty_state bstate),
  let effective_balance := (env_account_balances bstate caddr -
                                      (sumZ (fun act => act_body_amount act) (outgoing_acts bstate caddr)))%Z in
  env_contracts bstate caddr = Some (contract : WeakContract) ->
  exists cstate,
    contract_state bstate caddr = Some cstate
    /\ (P cstate -> effective_balance = 0).

While proving the non-recursive call goal in contract_induction there will be some entrypoint which will produce a transfer action transfering the entire contract balance to another address which means that we have to prove that
(ctx_contract_balance ctx - (ctx_contract_balance ctx + sumZ (fun act : ActionBody => act_body_amount act) prev_out_queue))%Z = 0%Z, i.e. sumZ (fun act : ActionBody => act_body_amount act) prev_out_queue = 0%Z.
I have already proved Q cstate -> outgoing_acts bstate caddr = [] in another lemma, this would be enough to prove above however it is not possible to instantiate CallFacts with this because outgoing acts not is an input to CallFacts, it is also not possible to apply the lemma directly where needed.

I have opened a pull request #111 with my suggested change. The change does not break any proofs or require changing the contract_induction lemma proof and allows proving the above example.

I think it's reasonable to add outgoing_actions to CallFacts.

I have a general comment, however. contract_induction is an induction principle that helps to organise proofs and give some convenience. Of course, it might be not general enough (which is, to some extent is its purpose), and adding missing things to it might be a good idea. We just should be careful that we don't make it too general, defeating the purpose of making a convenient induction principle.

PS: I think the title of the issue is a bit confusing. At first, I thought that lemmas proved by contract_induction cannot be used in some cases, but this depends on how you stated these lemmas. It seems the problem is that contract_induction is not general enough for certain cases and generates goals that does not allow for using other lemmas.

@4ever2 The compositionality question will probably pop up more often, with reasoning about the interacting contracts. So, it's good that you brought it up. Feel free to add different specialised induction principles if needed.

@4ever2 The PR #111 is merged now. If it resolves this issue, feel free to close it.