-
Notifications
You must be signed in to change notification settings - Fork 5
Trouble with mutation #545
Description
Consider this program:
module update::update;
use sui::object_table::ObjectTable;
use prover::prover::{asserts, ensures, forall, requires};
public struct S has store, key {id: UID, x: u8, y: u8}
fun update_part(s: &mut S) { s.y = 0 }
#[spec(prove)]
fun update_part_spec(s: &mut S) {
update_part(s);
}
#[spec(prove, focus)]
fun framing(i: ID, j: ID, t: &mut ObjectTable<ID, S>) {
requires(t.contains(i));
requires(t.contains(j));
requires(i != j);
let x0 = t[j].x;
update_part(&mut t[i]);
ensures(t[j].x == x0);
}
The sui-prover does not prove this:
error: prover::ensures does not hold
┌─ ./sources/update.move:22:5
│
22 │ ensures(t[j].x == x0);
│ ^^^^^^^^^^^^^^^^^^^^^
│
= at ./sources/update.move:16: framing
= i = object.ID{bytes = 0x14}
= j = object.ID{bytes = 0x3e}
= t =
= object_table.ObjectTable{
= id = object.UID{id = <? Literal("33")>},
= size =
= <? List([Literal("Table_23490_49924"), List([Literal("_"), List([Literal("as-array")]), List([Literal("k!19")])]), List([Literal("_"), List([Literal("as-array")]), List([Literal("k!14")])]), Literal("1")])>}
= at ./sources/update.move:17: framing
= x0#1#0 = 0u8
= at ./sources/update.move:21: framing
= s =
= update.S{id = object.UID{id = <? Literal("24")>}, x = 0u8, y = 0u8}
= at ./sources/update.move:12: update_part_spec
= s =
= update.S{id = object.UID{id = <? Literal("23")>}, x = 1u8, y = 0u8}
= at ./sources/update.move:21: framing
= t =
= object_table.ObjectTable{
= id = object.UID{id = <? Literal("33")>},
= size =
= <? List([Literal("Table_23490_49924"), List([Literal("_"), List([Literal("as-array")]), List([Literal("k!15")])]), List([Literal("_"), List([Literal("as-array")]), List([Literal("k!14")])]), Literal("1")])>}
= at ./sources/update.move:22: framing
although it does prove it if the specification of update_part is removed. Comparing the BPL in those two cases, the only significant difference is in
procedure {:inline 1} $0_update_update_part(_$t0: $Mutation ($0_update_S)) returns ($ret0: $Mutation ($0_update_S))
which has the same signature in both cases.
With no spec:
$t2 := $ChildMutation($t0, 2, $Dereference($t0)->$y);
$t2 := $UpdateMutation($t2, 0);
$ret0 := $UpdateMutation($t0, $Update'$0_update_S'_y($Dereference($t0), $Dereference($t2)));
with specs:
$ret0 := $0_update_update_part$opaque($t0);
which returns an arbitrary $Mutation ($0_update_S)).
The caller uses that result as follows:
call $t12 := $0_update_update_part($t12);
// write_back[Reference($t2).size (u64)/[]]($t12) at ./sources/update.move:21:5+22
$t2 := $UpdateMutation($t2, (var $$sel0 := $Dereference($t2)->$contents; $Update'$2_object_table_ObjectTable'$2_object_ID_$0_update_S''_contents($Dereference($t2), UpdateTable($$sel0, ReadVec($t12->p, LenVec($t2->p) + 1), $Dereference($t12)))));
So I believe there is a problem here: $t12->p is used to update the table, but in the result of the call with a specification, that is completely undetermined (while with no specification, the path is retained from the input parameter). Shouldn't the opaque result be constrained to have the same path as the input?
Here's a simpler example (now that I think I understand the issue):
fun set(x: &mut u8) { *x = 0 }
#[spec(prove)]
fun set_spec(x: &mut u8) {
set(x)
}
#[spec(prove, focus)]
fun frame(v: &mut vector<u8>, i: u64, j: u64) {
requires(j < v.length());
requires(i < j);
let vj = v[j];
set(&mut v[i]);
ensures(v[j] == vj);
}
Again, this can be proved when the spec of set is removed, but the proof fails with a counterexample when the spec is in place.