Skip to main content

Memory Regions

Ora makes data location explicit. Every variable lives in a named region, and the compiler enforces which transitions between regions are valid. There are no implicit copies — you see every data movement in the source.

Regions

RegionKeywordPersistenceMutabilityEVM Mechanism
Storagestorage varPermanent (across transactions)Read/writeSLOAD / SSTORE
Memorymemory varTransaction onlyRead/writeMemory allocation
Calldatafunction paramsCall onlyRead-onlyCALLDATALOAD
Transienttstore varTransaction only (EIP-1153)Read/writeTLOAD / TSTORE

Storage

Persistent state that survives across transactions:

contract Counter {
storage var count: u256;
storage var owner: address;

pub fn inc() {
count = count + 1;
}
}

Immutables and constants

contract Token {
immutable NAME: string = "Ora";
storage const MAX_SUPPLY: u256 = 1_000_000;
}

Memory

Local variables that exist for the duration of a transaction:

pub fn clamp(amount: u256) -> u256 {
memory var x: u256 = amount;
if (x > 100) return 100;
return x;
}

Variables declared with let or var without a region annotation default to stack/register allocation and can be assigned to any region:

let x: u256 = 42;        // no explicit region — can flow anywhere

Calldata

Function parameters arrive in calldata. Calldata is read-only — attempting to write to a calldata variable is a compile error:

fn bad(x: u256) {
calldata var tmp: u256 = x;
tmp = 1; // Compile error: calldata is immutable
}

Transient storage

Transient storage (EIP-1153) persists within a transaction but is cleared afterward. Useful for re-entrancy locks and cross-call state within a single transaction:

contract Guarded {
storage var counter: u256;
tstore var lock: bool;
tstore var temp_map: map<address, u256>;

pub fn increment() {
lock = true;
counter = counter + 1;
lock = false;
}
}

Region transitions

Cross-region data movement must go through memory. The compiler rejects direct assignments between non-memory regions.

Valid transitions

contract Transitions {
storage var counter: u256;
tstore var temp: u256;

// storage → memory (read out)
fn read_counter() -> u256 {
memory var tmp: u256 = counter;
return tmp;
}

// memory → storage (write back)
fn write_counter(x: u256) {
memory var tmp: u256 = x;
counter = tmp;
}

// tstore → memory (read out)
fn read_temp() -> u256 {
memory var tmp: u256 = temp;
return tmp;
}

// memory → tstore (write back)
fn write_temp(x: u256) {
memory var tmp: u256 = x;
temp = tmp;
}

// calldata → memory (function params to local)
fn copy_param(x: u256) -> u256 {
memory var local: u256 = x;
return local;
}
}

Invalid transitions

Direct assignment between storage and transient storage is a compile error:

contract Invalid {
storage var counter: u256;
tstore var temp: u256;

fn bad_storage_to_tstore() {
temp = counter; // Compile error: cannot assign storage → tstore directly
}

fn bad_tstore_to_storage() {
counter = temp; // Compile error: cannot assign tstore → storage directly
}
}

To move data between storage and transient storage, go through a memory intermediate:

fn ok_storage_to_tstore() {
memory var tmp: u256 = counter; // storage → memory
temp = tmp; // memory → tstore
}

Region transition summary

From \ ToStorageMemoryTransientCalldata
Storage--OKError--
MemoryOK--OK--
TransientErrorOK----
Calldata--OK----

Region checks are part of the type system. Invalid transitions are rejected at compile time.