Dr. Christian Reitwiessner
@ethchris github.com/chriseth chris@ethereum.org
IC3-Ethereum Crypto Boot Camp
2016-07-26
Example (shortened):
contract Token {
mapping(address => uint) balances;
function transfer(address from, address to, uint amount) {
if (balances[from] >= amount) {
balances[from] -= amount;
balances[to] += amount
}
}
}
Testing would probably not find an error here (do you?).
Formal verification uses techniques to "test" a program on
all possible inputs and states.
Example: Is the sum of all balances unchanged by transfer()
?
Write program in existing language and annotate with pre- and postconditions.
Program is compiled into language that understands these conditions
(often why3). VC-generator generates verification conditions,
automated prover verifies conditions.
/*@ requires 1 < num_candidates && num_candidates < MAX_CANDIDATES;
assigns \nothing;
ensures \result >= 1 && \result < num_candidates;
ensures \forall integer i;
1 <= i < num_candidates ==> counters[\result] >= counters[i];
*/
int compute_winner(void) {
int i, winner = 1; /* "No vote" is NOT taken into account */
/*@ loop invariant 2 <= i && i < MAX_CANDIDATES;
loop invariant \forall integer j;
1 <= j < i ==> counters[winner] >= counters[j];
loop invariant winner >= 1 && winner < num_candidates;
*/
for (i = 2; i < num_candidates; i++)
if (counters[i] > counters[winner]) winner = i;
return winner;
}
Following example is verified to be safe against re-entrant calls:
/// @why3 ensures {
/// @why3 to_int (old #shares) - to_int (old this.balance)
/// @why3 = to_int #shares - to_int this.balance
/// @why3 }
contract Fund {
uint shares;
function withdraw(uint amount) {
if (amount <= shares) {
shares = shares - amount;
if (!msg.sender.call.value(amount)())
throw;
}
}
}
Fineprint: We still have to reject incoming Ether
In comparison to C, we are lucky: no concurrency, simple VM, simple programs, simple language.