Syntax of the Act language
A specification of a contract written in act
consists of a
constructor
and a set of behaviours
.
The constructor
specification defines the structure of the contract
state, the initial value for the state, and a list of invariants that
the contract should satisfy.
Each behaviour
specification determines how a contract method updates
the state, and its return value, and the conditions that must be
satisfied in order for it to be applied.
Alternatively, they can be thought of an initial state and a set of state transitions, determining an inductively defined state transition system.
The specifications can be exported to different backends in order to prove that the claims that are being made hold with respect to an implementation.
This document is a high level description of the syntax of act specifications. For a more formal treatment of the syntax, study the definitions of RefinedAst.hs.
Types
The types of Act consist of three basic primitives:
Integers, Booleans and ByteStrings. Integers are unbounded,
with true integer operations. However, as our integer expressions
will often represent words in the EVM, we allow ourselves a slight
abuse of notation and denote by uintN/intN
integers together with the
constraint that the value fits into a uintN/intN
.
Using conventional ABI types for typing also allows us to specify function signatures in a concise way.
As an example consider the specification of overflow safe addition:
behaviour add of SafeMath
interface add(uint x, uint y)
iff in range uint
x + y
returns x + y
In more verbose terms, this specification would read:
Given any pair of integers x
and y
, s.t. 0 <= x < 2^256
and
0 <= y < 2^256
, an ABI encoded call to the contract SafeMath
with the signature add(uint256,uint256)
, and x
and y
, will:
- return
x + y
if0 <= x + y < 2^256
- revert otherwise
Contructor
A constructor
specification is indicated by the
constructor of <contractName>
header, as in:
constructor of A
A constructor section consists of the following fields:
interface
Specifying the arguments the constructor takes. Example:
interface constructor(address _owner)
iff (optional)
The conditions that must be satisfied in order for the contract to be created. These must be necessary and sufficient conditions, in the sense that if any condition isn't met, the contract cannot be created.
Example:
iff
CALLER == _owner
creates
Defines the storage layout of the contract.
Example:
creates
uint256 totalSupply := _totalSupply
mapping(address => uint) balanceOf := [CALLER := _totalSupply]
External storage changes (optional)
If the contract creation updates the state of other contracts, they should be specified as:
contract B
x => x + 1
contract C
y => y - 1
Ensures (optional)
A list of predicates that should hold over the poststate. All references to storage
variables in ensures
sections need to specify whether they talk about the variable's
value in the pre- or the poststate, by using pre(x)
or post(x)
.
Example:
ensures
(post(x) == _x) or (pre(x) == _x)
Invariants (optional)
A list of predicates over the state of the contract that should hold before and after every contract invocation:
invariants
totalSupply = initialSupply
_k = x * y
Behaviour
A behaviour
specification is indicated by the
behaviour of <contractName>
header, as in:
behaviour of A
and consists of the following fields:
interface
Specifying which method the behaviour refers to. Example:
interface transfer(address to, uint value)
iff (optional)
The conditions that must be satisfied in order for the transition to apply. These must be necessary and sufficient conditions, in the sense that if any condition isn't met, a call to the given method must revert.
Example:
iff
balanceOf[CALLER] >= value
CALLVALUE == 0
cases and state updates
Each behaviour must specify the state changes that happens a result of
a valid call to the method, and its return argument, if any.
All references to storage variables in returns
arguments need to
specify whether they talk about the variable's
value in the pre- or the poststate, by using pre(x)
or post(x)
.
This can be specified in two ways. Either directly, as in:
storage
balanceOf[CALLER] => balanceOf[CALLER] - value
balanceOf[to] => balanceOf[to] + value
returns post(balanceOf[CALLER])
or split between a number of cases, as in:
case to == CALLER:
returns -1
case to =/= CALLER:
storage
balanceOf[CALLER] => balanceOf[CALLER] - value
balanceOf[to] => balanceOf[to] + value
returns post(balanceOf[CALLER])
Note that currently, either a storage
or returns
section, or both is required in every spec.
External storage changes (optional)
If the contract method updates the state of other contracts, they should be specified as:
contract B
x => x + 1
contract C
y => y - 1
Ensures (optional)
A list of predicates that should hold over the poststate. Example:
ensures
x == _x
Referencing Storage Variables
Storage locations that are read and used in other expressions must be declared in a storage block.
Some examples:
storage
x => y
y
storage
x
y
returns x + y
``