Counter¶
Source: fourier/examples/counter.fou.
The minimal Fourier contract: one storage slot, one init, one read, one write.
Full source¶
// Counter — single uint storage, init plus a mutating method.
contract Counter {
storage value: uint @ 0;
fn init() {
value = 42;
}
pub fn get() -> uint {
return value;
}
pub fn inc() {
value = value + 1;
}
}
Annotated source¶
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 is valid. 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 which caller invokes get() first.
init is private (no pub) and is not assigned a selector. The compiler enforces no parameters, no return, and 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 to revert on overflow instead.
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.
Variants¶
- Add
pub fn dec() { value = value - 1; }. Its selector is the thirdpub fn,0x03.dec()fromvalue == 0wraps to2**256 - 1; switch tosafe_subto revert instead. - Add a precondition:
pub fn inc_if_owner() { require(caller() == owner); value = value + 1; }. This requires astorage owner: address @ 1;set in init. - Replace
value + 1withsafe_add(value, 1)to change overflow behavior from wrap to revert.