Temporal Properties
Temporal properties allow users to specify a property over arbitrary amounts of time. They are extremely useful for checking protocol behaviors amid unknown user interaction. These properties are specified by composing one or more [V] Statements using the logical and temporal operators described below.
Temporal Operators
Unlike logical operators, temporal operators specifically specify properties over time. All but the sequence operator are inherited from Linear Temporal Logic (LTL) and have the same semantics. In addition, the sequence operator has the same semantics that it does in Sequential Properties.
Operator | Use | Description |
---|---|---|
Always | [] a |
a holds from now on |
Eventually | <> a |
a either holds now or at some point in the future |
Next | X a |
a holds in the next state |
Until | a U b |
a holds at least until b holds and b eventually holds |
Sequence | a ; b |
a holds followed by b in the appropriate slot of the next transaction |
Logical Operators
In addition to temporal operators, statements can be composed with the following logical operators.
Operator | Use | Description |
---|---|---|
Logical AND | a && b |
Checks if both a and b hold |
Logical OR | a || b |
Checks if either a , b or both hold |
Logical NOT | !a |
Checks if a does not hold |
Implication | a ==> b |
Checks that b holds if a holds |
Common Mistakes
As it can be difficult to reason about the implications of temporal operators at time, here we discuss a few common mistakes we’ve seen.
[V] Statements Refer to Specific Moments in Time
While conventional LTL operates over program states, [V] operates over specific moments in the lifecycle of a contract as described by the execution model. As such, there are certain temporal expressions that are useful in LTL but have trivial counterexamples in [V] since they require a violation of the execution model to hold. Such temporal expressions typically imply that time has either stopped or that it is not linear.
As an example, consider the temporal expression [] action(txn, ...)
. This statement has a trivial counterexample since it is impossible to stay in a state consistent with action
and txn
. More specifically, consider [] started(token.transfer)
. For this to hold, a transfer
transaction must always be starting its execution on token
. Note that this implies that time halts since the transaction never completes.
As another example, consider the temporal expression action1(t1, ...) && action2(t2, ...)
. This statement also has a trivial counterexample since it is impossible to be in a state consistent with action1
and t1
at the same time as action2
and t2
unless action1 = action2
and t1 = t2
. If this were to occur time could not be linear as it would have to be in two states at once. More specifically, consider started(c.foo) && started(c.bar)
. For this to hold two transactions must have been started on contract c
at the same time.
Example
Example 1
Whenever usr
deposits X
funds in the wallet, they can eventually withdraw them.
[](finished(wallet.deposit, sender = usr && value = X) ==>
<>finished(wallet.withdraw(amt), sender = usr && amt = X))
Example 2
usr
cannot withdraw
their funds until close
has been called in a state where failed
holds.