Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

ProfileRole
defaultFull local runs (fuzz / invariant knobs from project.yaml)
cambrianCheap CI (fuzz.runs ≈ 100, invariant.runs ≈ 50)
cambrian_nightOvernight 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 (and ICambrianFactory) 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 and from clauses.

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::value becomes payable.
  • receive is always payable; fallback is payable only when it reads msg::value.
  • Constructors are emitted payable so deploy … 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 indexed event 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.