Functions¶
fn name(p1: T1, p2: T2) -> Tret { ... } // private
pub fn name(p1: T1, p2: T2) -> Tret { ... } // selector-dispatched
The -> Tret return type is optional; if absent, the function is void and return EXPR; is rejected with "return with value in void function".
fn vs pub fn¶
| Property | fn (private) | pub fn (public) |
|---|---|---|
| Externally callable | No | Yes |
| Internally callable | No (v1 has no internal-call op) | No |
| Selector assigned | No | Yes (0x01+) |
| Bytecode emitted | Only if name is init | Yes |
| Allowed signatures | init(...) { ... } — params allowed, no return | Any |
Private functions other than init compile to nothing in v1 — there's no internal-call mechanism, so they're dead code. The grammar permits them, but the codegen path that walks c.functions only emits bodies for is_pub functions and inline-includes init's body in the prologue.
Selector assignment¶
(_ContractGen.__init__ in fourier/codegen.py)
- Selectors are assigned in declaration order.
- The first
pub fngets0x01. Reordering source = breaking ABI. initis not included; it has no selector.- There is no
0x00selector — that would collide with the empty-calldata deploy short-circuit. - Maximum 255
pub fns per contract (selectors are one byte;0xFFis the last valid).
To see the selector for a function, count its position among pub fn declarations starting at 1.
Parameters¶
Parameters are decoded from calldata at fixed offsets. The layout depends on whether this is a regular pub fn call or an init invocation:
pub fn call:
calldata[ 0 .. 1] = selector
calldata[ 1 .. 33] = param[0] (32-byte word)
calldata[33 .. 65] = param[1]
calldata[65 .. 97] = param[2]
...
init invocation (deploy tx's init_calldata):
calldata[ 0 .. 32] = init_param[0]
calldata[32 .. 64] = init_param[1]
calldata[64 .. 96] = init_param[2]
...
The difference: a pub fn call carries a 1-byte selector; the init payload does not.
Each param is loaded into a local memory slot during the function prologue (see _emit_fn_body):
PUSH 1
CALLDATALOAD
PUSH 0x80 ; first param goes at 0x80
MSTORE
PUSH 33
CALLDATALOAD
PUSH 0xa0 ; second param goes at 0xa0
MSTORE
...
Locals start at memory offset 0x80 (LOCAL_MEM_BASE) and grow upward. Each local — param or let — takes 32 bytes.
Return types¶
| Return type | Bytecode emitted |
|---|---|
| (absent) | STOP at function tail; return; valid, return EXPR; rejected |
uint / address / bool | Compute value, MSTORE 0x40, RETURN 0x40, 32 |
bytes | Compute pointer; emit a length-aware return (length at [ptr], data at [ptr+32]) |
(T1, T2, ...) | Compute each element, store at 0x40, 0x60, 0x80, ...; RETURN 0x40, 32*n |
RETURN_AT = 0x40 (fourier/codegen.py).
Function locals¶
Each let allocates a fresh 32-byte slot in memory starting from the local-memory cursor (initialized at LOCAL_MEM_BASE = 0x80). Locals are referenced by their memory offset, not by a register or stack position.
pub fn example(a: uint) -> uint {
let b: uint = a + 1; // b at offset 0xa0 (after a at 0x80)
let c: uint = b * 2; // c at offset 0xc0
return c;
}
Locals persist for the lifetime of the call frame. There is no scope narrower than the function — a let inside an if body is visible after the if (and conversely, redeclaring let x in the same function body simply allocates a fresh slot; the prior is shadowed in the symbol table but its memory is leaked).
Calling convention summary¶
| Aspect | Behavior |
|---|---|
| Param passing | Calldata, 32 bytes per param. Pub fns start at offset 1; init starts at offset 0 (no selector). |
| Return passing | Memory at RETURN_AT (0x40), RETURN opcode |
| Caller identity | caller() builtin returns immediate caller |
| Origin identity | origin() returns tx originator |
| Sub-call gas forwarding | Capped at 63/64 of remaining (EVM "1/64th rule") — see vm/machine.py |
Examples¶
Read-only:
Mutating with check:
pub fn transfer(to: address, amount: uint) -> bool {
let sender: address = caller();
let bal: uint = balances[sender];
require(bal >= amount);
balances[sender] = bal - amount;
balances[to] = balances[to] + amount;
emit Transfer(sender, to, amount);
return true;
}
Tuple return: