Types and Variables
Every value in Ora has a type, and every type must be explicitly annotated. Ora does not infer types for local variables.
Primitive types
All type examples in this section are inside a function unless noted otherwise.
Unsigned integers
let a: u8 = 255;
let b: u16 = 65535;
let c: u32 = 4_294_967_295;
let d: u64 = 100;
let e: u128 = 1;
let f: u256 = 0;
u256 is the native EVM word size and the most common type in smart contracts.
Signed integers
let x: i8 = -128;
let y: i16 = 32767;
let z: i256 = -1;
Signed integers use two's complement representation, matching the EVM's SDIV, SMOD, and SLT opcodes.
Boolean
let active: bool = true;
let paused: bool = false;
Address
let owner: address = 0x742d35Cc6634C0532925a3b8D0C5e0E0f8d7D2aB;
address is a 160-bit type representing an Ethereum address.
String and bytes
let name: string = "Ora Token";
let data: bytes = hex"DEADBEEF";
string holds UTF-8 text. bytes holds arbitrary byte sequences. Both are dynamic-length types backed by storage encoding.
Void
pub fn doSomething() {
// No return value — implicit void return type
}
Functions that return nothing have an implicit void return type.
Numeric literals
Ora supports several literal formats:
let decimal: u256 = 1_000_000; // underscores for readability
let hex: u256 = 0xFF; // hexadecimal
let binary: u256 = 0b1010; // binary
let big: u256 = 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_913_129_639_935;
Underscores in numeric literals are ignored by the compiler — use them freely for readability.
Variable declarations
Ora has four declaration keywords (let/var inside functions, const/immutable at contract level):
let x: u256 = 10; // immutable binding — cannot be reassigned
var y: u256 = 20; // mutable binding — can be reassigned
const Z: u256 = 30; // compile-time constant
immutable OWNER: address = 0x742d35Cc6634C0532925a3b8D0C5e0E0f8d7D2aB; // set once at deploy
letcreates an immutable binding. Once assigned, the value cannot change.varcreates a mutable binding. The value can be reassigned.constcreates a compile-time constant. The value must be known at compilation.immutablecreates a deploy-time constant. Set during construction, read-only after.
Storage variables
Contract state lives in storage. Storage variables are declared with the storage keyword:
contract Vault {
storage var totalDeposits: u256 = 0;
storage var balances: map<address, u256>;
storage var owner: address;
}
storage var— persistent, mutable state. Each read costs ~2,100 gas (SLOAD), each write costs ~5,000–20,000 gas (SSTORE).storage letorstorage const— persistent, immutable after initialization.
The storage keyword is not optional. Ora never hides where data lives — this is a core design principle.
The vault begins
Here is the start of our running example — a basic vault contract:
comptime const std = @import("std");
contract Vault {
storage var totalDeposits: u256 = 0;
storage var balances: map<address, u256>;
pub fn deposit(amount: u256) {
let sender: address = std.msg.sender();
balances[sender] += amount;
totalDeposits += amount;
}
pub fn withdraw(amount: u256) {
let sender: address = std.msg.sender();
balances[sender] -= amount;
totalDeposits -= amount;
}
pub fn balanceOf(account: address) -> u256 {
return balances[account];
}
pub fn getTotalDeposits() -> u256 {
return totalDeposits;
}
}
This is the simplest useful vault. It has:
- Two storage variables:
totalDepositsandbalances - Four public functions for deposit, withdraw, and queries
std.msg.sender()to get the caller's address (from the standard library — covered in Chapter 13)
The vault has bugs: nothing prevents withdrawing more than your balance, and there's no protection against zero-amount deposits. We'll fix these in later chapters.
Further reading
- Language Basics — quick syntax reference
- Signed Integers — signed integer details