1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/*!
Ravencheck is a verification framework for your Rust code.
With it, you can add quantified verification goals to your code
using the `#[verify]` attribute,
just like how you add tests to your code using `#[test]`.
# Getting Started
You use Ravencheck by adding macro attributes to you code.
This example introduces the most important attributes.
First, to use Ravencheck within a module,
you add `#[ravencheck::module]`
to the top.
Note that the module must be entirely within a single file,
as in the example below.
```
#[ravencheck::module]
// Make the `u32` type visible to Ravencheck
// as an uninterpreted sort.
#[declare_types(u32)]
pub mod rvn_u32 {
// Make a constant `ZERO` visible to Ravencheck.
// Ravencheck doesn't see the right-hand side
// of the `=`.
#[declare]
pub const ZERO: u32 = 0;
// Ravencheck only sees the signature of this function,
// so the body can contain arbitrary code.
#[declare]
pub fn less_or_eq(a: u32, b: u32) -> bool {
a <= b
}
// Because we use #[define] here,
// Ravencheck sees both the signature and the body.
// This means that the body cannot include mutable assignments,
// method calls, or any functions/constants/types
// that have not already been made visible to Ravencheck.
// Operators are restricted to `!`, `==`, `!=`, `&&`, and `||`.
#[define]
pub fn less_than(a: u32, b: u32) -> bool {
less_or_eq(a,b) && a != b
}
// Assume the anti-symmetric property for `less_or_eq`.
// This definition only exists at verification time,
// so we can use Ravencheck-only operators like `forall`.
//
// Because we used #[define] to introduce `less_or_eq`,
// Ravencheck knows nothing about its behavior
// except what we tell it with #[assume].
#[assume]
fn le_anti_symmetric() -> bool {
forall(|x: u32, y: u32| {
implies(
less_or_eq(x,y) && less_or_eq(y,x),
x == y
)
})
}
// Assume that `ZERO` is less than or equal to every `u32`.
#[assume]
fn zero_is_least() -> bool {
forall(|x: u32| less_or_eq(ZERO, x))
}
// Check that no `u32` is less than `ZERO`.
// The `x` argument to the function is universally quantified.
// We could also explicitly quantify it using `forall`,
// like in the #[assume] conditions.
#[verify]
fn zero_is_smallest(x: u32) -> bool {
!(less_than(x, ZERO))
}
// Check that it *cannot* be proven
// that all `u32`s are equal to `ZERO`.
// This can be useful as a sanity-check
// for your trusted axioms.
#[falsify]
fn zero_is_only(x: u32) -> bool {
x == ZERO
}
}
```
To perform verification,
you will need the [CVC5 SMT solver](https://cvc5.github.io/) installed,
and in your PATH.
Then, just run `cargo test`.
# Importing from Other Modules
To use Ravencheck declarations, definitions, and assumptions
from one module to verify goals in another module,
use the `#[import]` attribute.
```ignore
#[ravencheck::module]
pub mod import_example {
// Here, `#[import]` makes all the #[declare], #[define] and
// #[assume] items from the `rvn_u32` module visible to Ravencheck
// in this module as well.
#[import]
use crate::doc_examples::rvn_u32::*;
/// `in_range(a,b,c)` is true when `c` is greater than or equal to
/// `a` and less than `b`.
#[define]
pub fn in_range(lower: u32, upper: u32, x: u32) -> bool {
less_or_eq(lower, x)
&& less_than(x, upper)
}
// Check that if the lower bound is smaller than the upper bound,
// then at least one `u32` is in the range.
#[verify]
pub fn range_not_empty(lower: u32, upper: u32) -> bool {
implies(
less_than(lower, upper),
exists(|x: u32| in_range(lower, upper, x))
)
}
}
```
# Sort Cycles
Ravencheck provides *decidable* verification.
This means that every SMT query can be verified or falsified
in a finite amount of time.
That said, as your verification goals grow in size,
you should expect solving time to also grow.
Ravencheck creates a directed graph of the sorts (types)
in your `#[assume]` and `#[verify]` properties.
If this graph contains a cycle,
then the condition is undecidable
and Ravencheck cannot verify or falsify it
(and will give you an error).
Edges between sorts in the graph
are created by two things in `#[verify]` conditions:
1. `exists(|x: A| ... forall(|y: B| ...))` creates an `A -> B` edge.
2. `exists(|x: A| ... foo(...) ...)` creates an `A -> T` edge if `foo`'s return type contains `T`.
In contrast, an `#[assume]` condition only creates an `A -> B` edge
if it contains `forall(|x: A| ... exists(|y: B| ...))`.
Finally, the `#[total]` attribute on a declared function
creates edges from all input types to all output types.
# Fixing Incompleteness with Instantiations
To maintain decidability,
Ravencheck assumes that all declared, non-`#[total]`,
non-bool-output functions are partial,
and it only applies an `#[assume]` rule
when the rule does not contain any function applications inside
that could be undefined.
For example, the following `add_right` condition
on inductive natural number type `Nat`
is falsifiable, even though it seems deducible
from the two `#[assume]` conditions.
```ignore
#[assume]
fn add_left(a: Nat, b: Nat) -> bool {
less_or_eq(a, add(a,b))
}
#[assume]
fn add_commute(a: Nat, b: Nat) -> bool {
add(a,b) == add(b,a)
}
#[falsify]
fn add_right(x: Nat, y: Nat) -> bool {
less_or_eq(y, add(x,y))
}
```
Because `add(y,x)` is not called in the `add_right`,
Ravencheck cannot assume it is defined,
and cannot apply the `add_commute` rule for `x` and `y`,
which would contain the possibly undefined `add(y,x)`.
We can fix this by adding an *instantiation* of `add(y,x)`
to `add_right`.
```ignore
#[verify]
fn add_right(x: Nat, y: Nat) -> bool {
let _ = add(y,x);
less_or_eq(y, add(x,y))
}
```
You don't need to provide instantiations for a declared function
if you give it the `#[total]` attribute.
We can't give `add` the `#[total]` attribute
because that would create a `Nat -> Nat` edge in the sort graph,
which immediately creates a cycle (a self-loop on `Nat`).
# Viewing Solver Input/Output
You can view the complete input/output trace with the solver
using the [`env_logger`](https://crates.io/crates/env_logger) system.
First, add `env_logger`
to your `Cargo.toml`.
```toml
[dependencies]
env_logger = "0.11.5"
```
Next, add the `#[log_solver]` attribute
to a Ravencheck module.
```ignore
#[ravencheck::check_module]
#[log_solver]
...
mod rvn {
...
}
```
Finally, set the `RUST_LOG` variable
to `easy_smt=trace` in your environment
when you `cargo test`.
You can do this on the command line as follows:
```ignore
$ RUST_LOG="easy_smt=trace" cargo test
````
*/
pub use ;
pub use CheckedSig;
pub use Rcc;