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

Your First Entity

This chapter walks through the Counter that ships with cambrian-lang at contracts/counter.cam. The source below is the real file, not a simplified stub.

The entity

entity Counter {

    routes {
        increment(amount: u64) => []
        reset() => []
        getCount() -> u64 => [
            return(m_count)
        ]
    }

    m_count: u64 {
        in increment(amount) => m_count + amount
        in reset() => 0
    }
}

Create your own copy if you prefer editing outside the repo:

cp contracts/counter.cam ~/counter.cam

Cambrian sources use the .cam extension.

Entity

An entity is the top-level unit of stateful behaviour: named members plus a public route interface. One .cam file may declare several entities; the CLI processes the first entity in a single-file invocation, while multi-entity programs use --project and a project.yaml.

entity Counter {
    // routes and members...
}

The name Counter becomes the Solidity contract name on --target evm and the generated module name on Lean.

Routes

Routes are the public entry points. On EVM they lower to external / public functions; the source form is always a route with an action list.

routes {
    increment(amount: u64) => []
    reset() => []
    getCount() -> u64 => [
        return(m_count)
    ]
}
RouteRole
increment(amount: u64) => []Takes amount. The body is empty because state changes live in the member transform below.
reset() => []No parameters; clears the count via its transform.
getCount() -> u64 => [return(m_count)]Declares a return type with -> u64 and returns the current member value.

Routes without a -> T return type are state-changing (or effectful) handlers. A route with -> T returns a value; getCount does not list an m_count transform, so the member is left unchanged.

Members and transforms

Members are persistent state. Each member lists how it changes per route, instead of scattering assignments across function bodies.

m_count: u64 {
    in increment(amount) => m_count + amount
    in reset() => 0
}
  • On increment, the new value is the previous m_count plus amount.
  • On reset, the new value is 0.
  • Any route omitted from the transform block leaves the member unchanged.

Bare m_count in a transform expression is the value before that route’s transforms. Inside a transform, ^other is the post-transform value of another member for the same route (the transpiler orders transforms so that reference is well-defined). Counter does not need ^, but it is the rule for multi-member updates later in the language guide.

Compared to Solidity

An imperative Solidity sketch of the same behaviour:

contract Counter {
    uint64 public m_count;

    function increment(uint64 amount) external {
        m_count += amount;
    }

    function reset() external {
        m_count = 0;
    }

    function getCount() external view returns (uint64) {
        return m_count;
    }
}

Both describe the same API. Cambrian gathers each member’s transitions in one block, which keeps “what happens to m_count?” local and is the shape the backends lower for EVM Solidity and Lean specs. Cambrian is not tied to a single chain: the same .cam source targets evm and lean.

Tests in the repo

contracts/counter.test.cam exercises this entity with Cambrian test blocks (call, expect state, expect return). You can merge entity and tests through a small project.yaml when you want Foundry or Lean specs from the same sources—see Compile and Test.

Next steps

Proceed to Compile and Test to transpile Counter to EVM and Lean.