Scribble
Search…
Function Annotations
Function specifications are used to specify the behaviour of a single function.

Successful termination

Scribble currently has a single type of function specification, called if_succeeds. Using if_succeeds, annotations you can assert properties that you expect to hold if (and only if) the annotated function succesfully terminates.
These annotations have the following structure & are placed right above a function declaration:
1
if_succeeds {:msg "<Description of your property>"} <boolean scribble expression>;
Copied!
Example 1
The following is a simple example property, where we check that the function always returns 0.
1
contract ExampleContract {
2
/// #if_succeeds {:msg "result is always zero"} result == 0;
3
function example() public returns (uint result) {
4
...
5
}
6
}
Copied!
Note that as of Scribble 0.4.0 you can also specify an if_succeeds annotation at the contract level. See the section below.

Encoding Pre- and Post- Conditions

Pre- and postconditions are frequently used concepts in verification languages.
  1. 1.
    A precondition is what should be true before a function is executed.
  2. 2.
    A postcondition is what should be true after a condition is done executing.
In it's simplest form if_succeedsallows you to write postconditions.
However, if_succeeds is quite versatile and you can use it to check preconditions as well. You'd do so by using the old() function.
Example 1
Example 2
We expect that a selfdestruct function only succeeds if the transaction sender is the owner of the contract.
1
/// #if_succeeds {:msg ""} old(msg.sender == owner);
2
function selfdestruct() public onlyOwner {}
Copied!
We expect that the following function will only succeed if the input variable amount is not zero.
1
/// if_succeeds {:msg "Amount is not zero"} old(amount) == 0;
2
function transfer(uint amount, addres receiver) public { ... }
Copied!
We're using old( ... ) here to make sure that the property is checked with the value of amount at the beginning of the transaction. This is because the value of amount can change during the transaction!

Encoding conditional "behaviors"

Functions can have multiple behaviors depending on the inputs and contract state. Take the following example of a simple calculator function implementing multiplication and addition.
1
contract Calculator {
2
enum Op {
3
PLUS,
4
TIMES
5
}
6
7
function calc(Op operator, uint left, uint right) public returns (uint result) {
8
if (operator == Op.TIMES) {
9
return left * right;
10
}
11
if (operator == Op.PLUS) {
12
return left + right;
13
}
14
assert (false);
15
}
16
}
Copied!
In this case, there are two distinct behaviors: multiplication and addition. Writing two separate postconditions wouldn't work, since scribble checks the conjunction of all function annotations.
1
/// #if_succeeds {:msg "result is product of left and right"} result == left * right;
2
/// #if_succeeds {:msg "result is sum of left and right"} result == left + right;
3
function calc(Op operator, uint left, uint right) public returns (uint result) { ... }
Copied!
Luckily, you can use the implication operator here to describe the conditions under which each behavior holds:
1
/// #if_succeeds {:msg "result of times operator is product of left and right"} operator == Op.TIMES ==> result == left * right;
2
/// #if_succeeds {:msg "result of plus operator is sum of left and right"} operator == Op.PLUS ==> result == left + right;
3
function calc(Op operator, uint left, uint right) public returns (uint result) { ... }
Copied!

Contract-level if_succeeds annotations

As of Scribble 0.4.0 you can also specify an if_succeeds annotation at the contract level. A contract-level if_succeeds is automatically applied to all public and external functions in the annotated contract, and all inheriting contracts. For example, in the below sample the annotation x > 0 will be applied to the functions A.pubFun, A.externFun and B.pubFunB.
1
/// #if_succeeds x > 0;
2
contract A {
3
uint x;
4
function pubFun() public { ... }
5
function externFun() external { ... }
6
function internalFun() internal { ... }
7
}
8
9
contract B is A {
10
function pubFunB() public { ... }
11
function privFunB() private { ... }
12
}
Copied!
Note that since the same expression needs to hold for an unknown number of functions, when writing a contract-level if_succeeds function you can only talk about the contract's state vars, and other global transaction state. You cannot refer to function arguments/returns (even if all functions in the contract have the same arguments or returns).
Last modified 2mo ago