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 CREATE2 addresses (every entity is deployed through the generated CambrianFactory).

Output layout

Single file, entity only:

out/
  .cambrian-manifest.json  # generated files + SHA-256 hashes
  src/Counter.sol          # entity contract + CambrianFactory

Single file with test / property / invariant blocks, or a multi-file project (project.yaml):

out/
  .cambrian-manifest.json
  src/_<project>_project.sol   # combined program (+ CambrianFactory)
  src/<Entity>.sol             # thin import stubs per entity
  test/…                       # Foundry tests
  foundry.toml
  setup.sh                     # installs forge-std (forge install --no-git)
cambrian-transpiler --project project.yaml   # output_dir comes from the YAML
cd <output_dir> && bash setup.sh && 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

On EVM:

  • The backend emits a CambrianFactory (and ICambrianFactory) with one deploy<Entity>(…) / predict<Entity>(…) pair per entity; deploys use CREATE2. Every entity constructor takes the factory address.
  • An init route with parameters splits into a deploy-time constructor stub plus a factory-guarded initialize(…), which deploy<Entity> calls. Mark the init route #[factory_only] and pass recipients (owner, mint beneficiary) as explicit parameters — under the factory, msg::sender in the init route is the factory.
  • Entity.address(args) and addressOf(Entity.state(args)) 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.

from Entity(args) arity must match the target entity’s identity-member count (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 (and the factory’s deploy<Entity>) are payable by default so deploy … with { value: … } works. Set evm.allow_constructor_payable: false in project.yaml to make them non-payable (the factory then deploys with value: 0).

Events and errors (ABI)

Cambrian event / error declarations become Solidity ABI items:

event Transfer(indexed src: address, indexed dst: address, amount: U256);
error InsufficientBalance(have: U256, need: U256);

entity Wallet {
    routes {
        deposit(amount: U256) => []
        withdraw(amount: U256)
            where (m_balance >= amount) : throw InsufficientBalance(m_balance, amount)
        => [
            emit Transfer(msg::sender, msg::sender, amount);
        ]
    }

    m_balance: U256 {
        in deposit(amount) => m_balance + amount
        in withdraw(amount) => m_balance - 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
foundry:
  solc_version: "0.8.24"
  # remappings, fuzz_runs, profiles, …
fuzz:
  runs: 256
invariant:
  runs: 256
  depth: 50

See project.yaml and Testing backends.