Here is one way to do it.
The problem is that Dafny does not know anything about the sum
function, so we have to teach it a few facts.
function sum(items: seq<int>): int
decreases |items| {
if items == []
then 0
else items[0] + sum(items[1..])
}
lemma sum_append(xs: seq<int>, ys: seq<int>)
ensures sum(xs + ys) == sum(xs) + sum(ys)
{
if xs == [] {
assert [] + ys == ys;
} else {
assert (xs + ys)[1..] == xs[1..] + ys;
}
}
lemma sum_focus(xs: seq<int>, i: int)
requires 0 <= i < |xs|
ensures sum(xs) == sum(xs[..i]) + xs[i] + sum(xs[i+1..])
{
calc {
sum(xs);
{ assert xs == xs[..i] + [xs[i]] + xs[i+1..]; }
sum(xs[..i] + [xs[i]] + xs[i+1..]);
{ forall xs, ys { sum_append(xs, ys); } }
sum(xs[..i]) + xs[i] + sum(xs[i+1..]);
}
}
method transfer(accounts: array<int>, balance: int, from: int, to: int)
requires from >= 0 && from < accounts.Length;
requires to >= 0 && to < accounts.Length;
requires accounts.Length > 0;
requires sum(accounts[..]) == balance;
ensures sum(accounts[..]) == balance;
modifies accounts;
{
sum_focus(accounts[..], from);
accounts[from] := accounts[from] - 1;
sum_focus(accounts[..], from);
sum_focus(accounts[..], to);
accounts[to] := accounts[to] + 1;
sum_focus(accounts[..], to);
}
The idea is that the sum_focus
lemma allows us to break up the sum of an array into the sum of elements before i
, plus the element at i
, plus the sum of elements after i
. This "focusing" step allows Dafny to reason about the assignment statements that write to the array elements accounts[from]
and accounts[to]
.
Alternative version of sum_focus
:
lemma sum_focus(xs: seq<int>, i: int)
requires 0 <= i < |xs|
ensures sum(xs) == sum(xs[..i]) + xs[i] + sum(xs[i+1..])
{
calc {
sum(xs);
{ assert xs == xs[..i] + [xs[i]] + xs[i+1..]; }
sum(xs[..i] + [xs[i]] + xs[i+1..]);
{
sum_append(xs[..i] + [xs[i]], xs[i+1..]);
sum_append(xs[..i], [xs[i]]);
}
sum(xs[..i]) + xs[i] + sum(xs[i+1..]);
}
}