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)
]
}
| Route | Role |
|---|---|
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 previousm_countplusamount. - On
reset, the new value is0. - 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.