The init Block
The init block is Fe’s constructor. It runs once when the contract is deployed and sets up the initial state.
Basic Syntax
Section titled “Basic Syntax”Declare an init block inside a contract:
contract Token { mut store: TokenStorage,
init() { // Initialization logic }}The init block runs automatically during deployment.
Init Parameters
Section titled “Init Parameters”Init blocks can accept parameters passed during deployment:
contract Token { mut store: TokenStorage,
init(initial_supply: u256, owner: Address) uses (mut store) { store.total_supply = initial_supply store.balances.set(key: owner, value: initial_supply) }}These parameters are ABI-encoded and appended to the creation bytecode in the deployment transaction.
Initializing Storage
Section titled “Initializing Storage”The primary purpose of init is setting up storage:
pub struct TokenStorage { pub balances: StorageMap<Address, u256>, pub total_supply: u256, pub owner: Address,}
contract Token uses (ctx: Ctx) { mut store: TokenStorage,
init(initial_supply: u256) uses (ctx, mut store) { // Set primitive fields directly store.total_supply = initial_supply store.owner = ctx.caller()
// Set map entries store.balances.set(key: ctx.caller(), value: initial_supply) }}Direct Field Access in Init
Section titled “Direct Field Access in Init”The init block accesses storage fields through the uses clause:
init(supply: u256) uses (mut store) { store.total_supply = supply // Direct access via uses clause }This is because init runs in a special context during deployment, before normal message handling begins.
Init Without Parameters
Section titled “Init Without Parameters”Contracts can have parameter-less init blocks:
contract Counter { mut store: CounterStorage,
init() uses (mut store) { store.count = 0 }}Optional Init
Section titled “Optional Init”The init block is optional for contracts whose fields are all mut. That state starts at zero:
contract Simple { mut store: SimpleStorage, // No init block - storage starts at default values
recv SimpleMsg { GetData -> u256 { 0 } }}Immutable (non-mut) contract fields are embedded in the deployed code, so a contract that declares one needs an init block that assigns it on every successful constructor exit. Otherwise compilation fails with error[8-0086]: immutable contract field is not initialized:
contract Owned { owner: Address,
init(initial_owner: Address) uses (mut owner) { owner = initial_owner }
recv OwnedMsg { Owner -> Address uses owner { owner } }}Deployment is non-payable by default. Deploying with a non-zero ETH value reverts unless the init block is marked #[payable], and this also applies to contracts that have no init block at all:
contract Vault { mut store: VaultStorage,
// Accept ETH sent with the deployment transaction #[payable] init() {}}Complex Initialization
Section titled “Complex Initialization”Init can perform multiple setup operations:
pub struct TokenStorage { pub balances: StorageMap<Address, u256>, pub total_supply: u256, pub owner: Address, pub paused: bool,}
contract Token uses (ctx: Ctx) { mut store: TokenStorage,
init(name_hash: u256, initial_supply: u256) uses (ctx, mut store) { let deployer = ctx.caller()
// Set owner store.owner = deployer
// Initialize supply store.total_supply = initial_supply store.balances.set(key: deployer, value: initial_supply)
// Contract starts unpaused store.paused = false }}What Init Cannot Do
Section titled “What Init Cannot Do”The init block has some restrictions:
- Declared effects only: Like handlers, init can only use the effects it names in
uses. Calling other contracts works, but requires the call effect: declareuses (call: mut Call)on the contract anduses (mut call)on init - No message receiving: Init handles deployment, not incoming messages
- Single execution: Init runs exactly once per contract deployment
Init and Effects
Section titled “Init and Effects”While init can access storage directly, it doesn’t use the effect system in the same way as handlers:
contract Token { mut store: TokenStorage,
// In init: access via uses clause init(supply: u256) uses (mut store) { store.total_supply = supply }
// In handlers: effect-based access recv TokenMsg { TotalSupply -> u256 uses (store) { store.total_supply } }}This difference exists because init is a special deployment-time operation, while handlers are for runtime message processing.
Deployment Flow
Section titled “Deployment Flow”When a Fe contract is deployed:
- Contract bytecode is sent to the network
- Init parameters are decoded from calldata
- The init block executes
- Storage is initialized
- The contract is ready to receive messages
Deploy Transaction │ ▼┌──────────────┐│ Decode init ││ parameters │└──────────────┘ │ ▼┌──────────────┐│ Execute init ││ block │└──────────────┘ │ ▼┌──────────────┐│ Contract ││ ready │└──────────────┘Summary
Section titled “Summary”| Concept | Description |
|---|---|
init() { } | Constructor block |
init(params) { } | Constructor with parameters |
| Direct access | Init can access store.field directly |
| Optional | Contracts with only mut fields work without init (default values) |
#[payable] init | Required to accept ETH at deployment |
| Single run | Executes once at deployment |