Testing Overview
Cambrian embeds a test language in .cam sources. The same declarations lower to Foundry tests or Lean theorems depending on the target — you do not maintain a separate Solidity or Lean test suite by hand.
Write tests next to the entity they exercise (same file or a sibling *.test.cam merged via project.yaml). Validation rules T* / I* catch arity, context, and forall mistakes before codegen.
The triad
| Construct | Role | Typical lowering |
|---|---|---|
test | Concrete, deterministic scenario | Foundry test_*, Lean theorem with fixed values |
property | Parameterised statement + nested test / fuzz instances | Lean: one ∀ theorem. EVM: concrete tests and fuzz cases |
invariant | Stateful random call sequences + checks | Foundry invariant handler, Lean runTrace |
entity Counter {
routes {
increment(amount: u64) => []
reset() => []
getCount() -> u64 => [ return(m_count) ]
}
m_count: u64 {
in increment(amount) => m_count + amount
in reset() => 0
}
}
test "starts at zero" for Counter with { m_count: 0 } {
call getCount()
expect return 0
}
property "reset zeroes" for Counter with { m_count: * } {
call reset()
expect state { m_count: 0 }
fuzz { }
}
invariant "count stays non-negative" for Counter {
init { m_count: 0 }
action increment(amount: u64) { bound amount in 0..1000 }
action reset() { }
check m_count >= 0
}
How they relate
- A
testanswers “does this exact scenario pass?” - A
propertyanswers “does this hold for a family of inputs?” — Lean keeps the family; fuzz backends sample it. - An
invariantanswers “does this hold after arbitrary sequences of allowed actions?”
Prefer properties for single-step algebraic facts; reach for invariants when order and interleaving matter.
Backend map (public targets)
| Backend | How you run it | Driven by |
|---|---|---|
| Foundry | forge test after --target evm | test / property instances / invariant |
| Lean Spec | lake build / --check-lean | Raw property + test + invariant theorems |
See Backend matrix for the feature grid (multi-entity invariants, trace::*, fuzz types, …).
Design rules worth remembering
- Entity state vs context: pins and forall markers for members live in
with/init;msg::/sys::live inctx { … }(T22). assumevsbound: logical preconditions vs sampling ranges — onlyassumereaches Lean.- Desugar:
--target evmlowerspropertybefore codegen; Lean keeps properties intact for clean theorems. - Expects stack: after each
call, you may assertstate,throw,return, and (where supported) effects. Lean conjoins everyexpecton that call. - Shared fixtures: Domain EVM cores (
evm,lean) should reuse the same.cammatrices where the surface is accepted.
Project-level knobs
fuzz:
runs: 256
seed: 0
invariant:
runs: 256
depth: 50
fail_on_revert: false
These feed Foundry profiles. Per-decl attributes (#[runs], #[depth], #[fail_on_revert]) override globals where the backend honours them.
Chapter map
| Chapter | Contents |
|---|---|
| Unit tests | test syntax, lenses, multi-step, skip from, with/ctx |
| Properties | Nested instances, assume/bound, forall * |
| Invariants | Actions/checks, track/derived, trace::*, multi-entity, attributes |
| Backends | Foundry and Lean — support matrix |