Solidity-EVM
--target evm lowers a Cambrian program to flat Solidity under src/, with an optional Foundry test harness. The pragma is ^0.8.24. Semantics follow the EVM domain: synchronous calls, ETH balances, and (when enabled) CREATE2 addresses.
Output layout
Single file:
out/
src/Counter.sol # entity contract
test/… # Foundry tests (if .cam has test/property/invariant)
foundry.toml
setup.sh
Multi-file project (project.yaml):
out/
src/_<project>_project.sol # combined program
src/<Entity>.sol # thin import stubs per entity
test/…
foundry.toml
cambrian-transpiler project.yaml -o /tmp/out-evm
cd /tmp/out-evm && forge test
Never invoke solc directly for Cambrian projects — use forge build / forge test.
Foundry configuration
The emitter writes foundry.toml with [profile.default] plus tiered profiles:
| Profile | Role |
|---|---|
default | Full local runs (fuzz / invariant knobs from project.yaml) |
cambrian | Cheap CI (fuzz.runs ≈ 100, invariant.runs ≈ 50) |
cambrian_night | Overnight stress (thousands of runs / deep traces) |
Override via foundry: and top-level fuzz: / invariant: blocks in project.yaml. Run with FOUNDRY_PROFILE=cambrian forge test.
Deterministic addresses and CambrianFactory
With deterministic_addresses: true in project.yaml:
- The backend emits a
CambrianFactory(andICambrianFactory) that deploys via CREATE2. - Entity constructors split into a deploy-time stub plus a factory-guarded
initialize(). Entity.address(args)/addressOf<Entity>(...)lower to the same CREATE2 prediction used in sends andfromclauses.
Singletons (no identity members) get a fixed address; Entity.address() takes no arguments.
Without deterministic mode, from Entity(addr) expects a single address argument (rule V33).
Payable auto-detection
There is no Cambrian payable keyword. Solidity payable is inferred:
- Any route whose body (or reachable AST) reads
msg::valuebecomespayable. receiveis alwayspayable;fallbackispayableonly when it readsmsg::value.- Constructors are emitted
payablesodeploy … with { value: … }works.
Events and errors (ABI)
Cambrian event / error declarations become Solidity ABI items:
event Transfer(from: address indexed, to: address indexed, amount: U256)
error InsufficientBalance(have: U256, need: U256)
routes {
withdraw(amount: U256)
where (m_balance >= amount) : throw InsufficientBalance(m_balance, amount)
=> [
emit Transfer(msg::sender, msg::sender, amount)
]
}
emit Name(args)→emit Name(args);throw Name(args)→revert Name(args);- At most three
indexedevent parameters (V36). - Unphased routes keep SSTORE-before-CALL ordering (checks-effects-interactions).
Project knobs (EVM)
name: my-dapp
target: evm
deterministic_addresses: true
foundry:
solc_version: "0.8.24"
# remappings, fuzz_runs, profiles, …
fuzz:
runs: 256
invariant:
runs: 256
depth: 50
See project.yaml and Testing backends.