Skip to content

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:

  1. Compile counter.fou to bytecode.
  2. Submit a deploy tx with data.type = "deploy", data.code = <hex>. The deploy address is derived from (deployer, deployer_nonce).
  3. 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 with STOP.
  4. Submit a get() call tx: data.calldata = "01". Receipt return_data = 0000...002a (42 in hex).
  5. Submit inc() calls. Each increments the slot.

Variants

  • Add pub fn dec() { value = value - 1; }. Its selector is the third pub fn, 0x03. dec() from value == 0 wraps to 2**256 - 1; switch to safe_sub to revert instead.
  • Add a precondition: pub fn inc_if_owner() { require(caller() == owner); value = value + 1; }. This requires a storage owner: address @ 1; set in init.
  • Replace value + 1 with safe_add(value, 1) to change overflow behavior from wrap to revert.