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

Deterministic Addresses

On EVM, every entity in the project gets a predictable CREATE2 address derived from its identity arguments. Cross-entity sends, from checks, and deploy all share that same prediction — no hand-rolled factory.

name: det-messaging
target: evm
output_dir: build/
sources:
  - det_guardian.cam
  - det_locker.cam

CREATE2 and CambrianFactory

The EVM backend emits a project-local CambrianFactory. Addresses are derived from:

  • the entity’s deploy bytecode (constructor args = identity fields),
  • a per-deployer factory salt of 0,
  • the fixed factory address known at generation time.

Because the factory address itself is deterministic, every entity can compute every other entity’s address from identity fields alone.

Authors never write a custom factory.

Entity.address(args) and addressOf

Prefer the dot form. An addressOf(…) spelling exists as well; both lower to the same CREATE2 expression:

acceptPing() ~> Locker.address(locker_id)

Arity equals the identity-member count (Identity Members). Singletons use Entity.address() with no arguments.

Use Entity.address(…) anywhere you need a predicted address: send destinations, member initializers, from comparisons.

from Entity(args)

On EVM, sender authentication compares msg.sender to the same CREATE2 expression:

acceptPing()
    from Guardian(m_id)
    => []

Here Locker only accepts pings from the Guardian instance whose identity matches this locker’s m_id. from Entity(args) arity must match the target entity’s identity-member count (V33).

deploy Entity(…)

deploy goes through CambrianFactory:

  1. CREATE2-deploy with identity args as constructor arguments (address matches Entity.address(…)), forwarding any attached value.
  2. Call a generated, factory-guarded initialize(…) for non-identity constructor parameters.
spawnLocker(locker_id: u64) => [
    deploy Locker(locker_id)
]

createPair(tokenA: address, tokenB: address) => [
    deploy UniswapV2Pair(min_addr(tokenA, tokenB),
                          max_addr(tokenA, tokenB))
]

Only the factory owner (bootstrap / tests) or addresses already deployed by this factory may call deployX. EOAs cannot occupy identity CREATE2 slots directly; child deploys go through entity routes the factory already knows. initialize() requires msg.sender == _factory and rejects re-initialization.

Summary

ExpressionRole
Entity.address(args)Predict CREATE2 address from identity
addressOf(…)Equivalent spelling; same CREATE2
from Entity(args)msg.sender == that CREATE2 address
deploy Entity(args)Factory CREATE2 + guarded initialize

Keep identity, addressing, authentication, and deployment aligned — one argument list, one address, everywhere.