Address type¶
address is a 20-byte identifier referring to an account on chain. At runtime it lives in a 256-bit word, right-aligned (the address bytes occupy the low 160 bits, the top 96 bits are zero).
Width¶
| Layer | Width |
|---|---|
| Source semantics | 20 bytes (ADDRESS_BYTES = 20) |
| Runtime stack value | 256-bit uint |
| Storage / memory | 256-bit uint |
| Network address representation | 40-char hex string (no 0x prefix) |
The VM vm/state.py enforces 20-byte width at the WorldState API level; values exceeding that are truncated to the low 20 bytes when passed through BALANCE and CALL.
Compile-time enforcement¶
The codegen tracks a "best-effort" type for each local. When a binary op has address on one side and uint / bool on the other, it rejects arithmetic and ordering ops:
let a: address = caller();
let n: uint = 1;
let bad: uint = a + n; // compile error
let bad2: bool = a < n; // compile error
Allowed (equality / inequality always works):
The compile-time check is limited to typed locals. Storage reads return _ (unknown), so the following passes the static check silently:
let counter: uint = some_storage_uint + 1; // ok if some_storage_uint is uint
let bad: uint = owner_storage + 1; // not caught — runtime treats as uint add
Source: _emit_expr for BinOp in fourier/codegen.py.
Zero address¶
There is no special address(0) constructor. Compare against the literal 0:
Writing 0 to a storage owner: address slot deletes the entry from storage (the VM treats writing zero as a delete).
vm/state.py has no concept of a "burn" or "null" address — 0x00 * 20 is an ordinary account that receives any funds sent to it. The chain-level convention (per the WaveLedger transaction docs) treats "0" * 32 as a burn convention; this is a docs convention, not a VM rule.
Address-producing builtins¶
| Builtin | Returns | VM op |
|---|---|---|
caller() | Immediate caller (msg.sender) | CALLER |
origin() | Tx originator (tx.origin) | ORIGIN |
V1 exposes no address(this) builtin. The VM defines ADDRESS (0x70), but it is not surfaced to Fourier.
Future extensions¶
Surfacing the VM's ADDRESS opcode as a Fourier builtin is a planned addition.
Comparing addresses¶
== and != compile to EQ / EQ + ISZERO. Because both operands become full 256-bit words, no padding concerns apply — addresses are identical iff their underlying ints are identical.
Address as map key¶
The address (as a 256-bit word, right-padded with zero-prefix bytes) is hashed with the slot to derive the storage key:
Because two addresses differ in their low 160 bits but share all-zero top 96 bits, the hash domain is effectively unique per address — no collision risk.
Address from a uint¶
Converting a runtime uint to an address (for example, a calldata-supplied recipient) requires no cast: address and uint share the same runtime representation. Bind to an address-typed local:
The codegen treats both as 256-bit words; the address-vs-uint type check is purely static.
tx.origin caveat¶
origin() returns the EOA that signed the tx — the first caller in the chain. Use with care: any called contract observes the origin, and code that uses origin() as an authorization check is vulnerable to phishing-style attacks (a malicious intermediate contract can act on behalf of the origin because the origin matches). Prefer caller() for authorization; use origin() only when recovering the EOA at the bottom of the call stack is required.