Counter¶
Source: fourier/examples/counter.fou.
The minimum interesting Fourier contract — one slot of storage, an init, a read, a write.
Full source¶
// Minimal counter — demonstrates init + a single mutating method.
contract Counter {
storage value: uint @ 0;
fn init() {
value = 42;
}
pub fn get() -> uint {
return value;
}
pub fn inc() {
value = value + 1;
}
}
Walkthrough¶
storage value: uint @ 0;¶
One storage slot, pinned to slot 0. Holds a single 256-bit unsigned integer. After init, this slot reads 42; after each inc() call, the value at slot 0 increases by 1.
The pin to slot 0 is arbitrary — any slot from 0 to 2**256 - 2 would work. Slot 2**256 - 1 is reserved for the init guard.
fn init() { value = 42; }¶
The optional initializer. Runs exactly once, atomically inside the deploy transaction. After this runs, the contract reads value == 42 regardless of who calls get() first.
init is private (no pub). It is not assigned a selector. The compiler enforces no params, no return, no early return. See Contracts / init.
pub fn get() -> uint { return value; }¶
Selector 0x01 (first declared pub fn). Reads slot 0 and returns it.
Bytecode (approximate):
_fn_get:
JUMPDEST
POP ; discard selector
PUSH 0
SLOAD ; read slot 0
PUSH 0x40
MSTORE
PUSH 32
PUSH 0x40
RETURN
pub fn inc() { value = value + 1; }¶
Selector 0x02. Reads slot 0, adds 1, writes back. No return value.
_fn_inc:
JUMPDEST
POP
PUSH 0
SLOAD ; current value
PUSH 1
ADD ; +1
PUSH 0
SSTORE ; write back
STOP ; implicit terminator for void fn
Note: ADD wraps modulo 2**256. Calling inc() after the counter hits 2**256 - 1 rolls over to 0 with no error. Use safe_add if you'd prefer to revert.
Calldata for each selector¶
| Call | Calldata bytes |
|---|---|
init | (never called by users; runs on deploy) |
get() | 0x01 (1 byte) |
inc() | 0x02 (1 byte) |
Deployment + invocation transcript¶
Conceptual flow:
- Compile
counter.fouto bytecode. - Submit a deploy tx with
data.type = "deploy",data.code = <hex>. The deploy address is derived from(deployer, deployer_nonce). - The VM stores the code; immediately invokes the new code with empty calldata; the init prologue checks the flag (unset), sets it, runs
value = 42, falls through to the empty-calldata short-circuit that halts withSTOP. - Submit a
get()call tx:data.calldata = "01". Receiptreturn_data=0000...002a(42 in hex). - Submit
inc()calls. Each increments the slot.
What to try next¶
- Add
pub fn dec() { value = value - 1; }. What selector does it get? (Hint: it's the thirdpub fn.) Doesdec()fromvalue == 0revert or wrap? - Add a precondition:
pub fn inc_if_owner() { require(caller() == owner); value = value + 1; }. You'll need to add astorage owner: address @ 1;and set it in init. - Swap
value + 1forsafe_add(value, 1)and observe overflow behavior change from wrap to revert.