Skip to main content

Standard Library

Comprehensive reference for Ora's built-in standard library.

Overview

The Ora standard library provides direct access to EVM primitives through a minimal set of built-in functions and constants:

  • Zero-overhead: Compiles directly to EVM opcodes
  • Type-safe: Full compile-time type checking
  • Always available: No imports needed - just use std

Philosophy

Ora's standard library is designed for direct EVM access without abstraction overhead:

  1. Map cleanly to EVM opcodes (one-to-one correspondence)
  2. Prevent common bugs through static type checking
  3. Provide explicit, predictable behavior
  4. Guarantee zero runtime overhead

Available Features

The standard library includes 17 built-in functions and constants:

CategoryFunctions
Block Data5 functions for current block information
Transaction Data2 functions for transaction context
Message Data3 functions for call information
Constants5 pre-defined constant values

Block Data

Access current block information.

std.block.timestamp()

Get the current block timestamp (seconds since Unix epoch).

pub fn getTimestamp() -> u256 {
return std.block.timestamp();
}

Returns: u256 - Current block timestamp
EVM Opcode: TIMESTAMP
Gas Cost: 2 gas (base cost)

Example Use Case:

// Check if deadline has passed
pub fn hasExpired(deadline: u256) -> bool {
return std.block.timestamp() > deadline;
}

std.block.number()

Get the current block number.

pub fn getBlockNumber() -> u256 {
return std.block.number();
}

Returns: u256 - Current block number
EVM Opcode: NUMBER
Gas Cost: 2 gas

Example Use Case:

// Check if enough blocks have passed
pub fn canExecute(lastBlock: u256) -> bool {
return std.block.number() >= lastBlock + 100;
}

std.block.gaslimit()

Get the current block's gas limit.

pub fn getGasLimit() -> u256 {
return std.block.gaslimit();
}

Returns: u256 - Block gas limit
EVM Opcode: GASLIMIT
Gas Cost: 2 gas


std.block.coinbase()

Get the current block's miner address.

pub fn getMiner() -> address {
return std.block.coinbase();
}

Returns: address - Miner's address
EVM Opcode: COINBASE
Gas Cost: 2 gas

Example Use Case:

// Reward the miner
pub fn rewardMiner(amount: u256) {
let miner = std.block.coinbase();
balances[miner] = balances[miner] + amount;
}

std.block.basefee()

Get the current block's base fee (EIP-1559).

pub fn getBaseFee() -> u256 {
return std.block.basefee();
}

Returns: u256 - Base fee in wei
EVM Opcode: BASEFEE
Gas Cost: 2 gas
Availability: Post-London hard fork


Transaction Data

Access transaction-level information.

std.transaction.sender()

Get the original transaction sender (origin).

pub fn getOrigin() -> address {
return std.transaction.sender();
}

Returns: address - Transaction origin
EVM Opcode: ORIGIN
Gas Cost: 2 gas

⚠️ Security Note: Be careful with std.transaction.sender() - it returns the original EOA, not the immediate caller. For access control, usually prefer std.msg.sender().

Example Use Case:

// Track which EOAs have interacted
storage visitedAddresses: map[address, bool];

pub fn recordVisit() {
let origin = std.transaction.sender();
visitedAddresses[origin] = true;
}

std.transaction.gasprice()

Get the gas price for this transaction.

pub fn getGasPrice() -> u256 {
return std.transaction.gasprice();
}

Returns: u256 - Gas price in wei
EVM Opcode: GASPRICE
Gas Cost: 2 gas


Message Data

Access call-level information.

std.msg.sender()

Get the immediate caller's address.

pub fn recordCaller() -> address {
return std.msg.sender();
}

Returns: address - Caller's address
EVM Opcode: CALLER
Gas Cost: 2 gas

Best Practice: Use std.msg.sender() for access control and authentication.

Example Use Case:

storage owner: address;

pub fn onlyOwner() -> bool {
let caller = std.msg.sender();
if (caller != owner) {
return false;
}
return true;
}

std.msg.value()

Get the amount of wei sent with this call.

pub fn getValue() -> u256 {
return std.msg.value();
}

Returns: u256 - Wei sent with call
EVM Opcode: CALLVALUE
Gas Cost: 2 gas

Example Use Case:

pub fn deposit() -> bool {
let caller = std.msg.sender();
let amount = std.msg.value();

balances[caller] = balances[caller] + amount;
return true;
}

std.msg.data.size()

Get the size of calldata in bytes.

pub fn getCalldataSize() -> u256 {
return std.msg.data.size();
}

Returns: u256 - Calldata size
EVM Opcode: CALLDATASIZE
Gas Cost: 2 gas


Constants

Pre-defined constant values.

std.constants.ZERO_ADDRESS

The zero address (0x0000000000000000000000000000000000000000).

pub fn isZeroAddress(addr: address) -> bool {
return addr == std.constants.ZERO_ADDRESS;
}

Type: address
Value: 0x0000000000000000000000000000000000000000
Compilation: Inlined as arith.constant 0 : i160

Example Use Case:

pub fn transfer(to: address, amount: u256) -> bool {
// Reject transfers to zero address
if (to == std.constants.ZERO_ADDRESS) {
return false;
}

// Transfer logic...
return true;
}

std.constants.U256_MAX

Maximum value for a u256.

pub fn getMaxSupply() -> u256 {
return std.constants.U256_MAX;
}

Type: u256
Value: 115792089237316195423570985008687907853269984665640564039457584007913129639935
Compilation: Inlined as arith.constant -1 : i256 (all bits set)


std.constants.U128_MAX

Maximum value for a u128.

pub fn checkOverflow(value: u256) -> bool {
return value > std.constants.U128_MAX;
}

Type: u128
Value: 340282366920938463463374607431768211455


std.constants.U64_MAX

Maximum value for a u64.

pub fn withinU64Range(value: u256) -> bool {
return value <= std.constants.U64_MAX;
}

Type: u64
Value: 18446744073709551615


std.constants.U32_MAX

Maximum value for a u32.

pub fn withinU32Range(value: u256) -> bool {
return value <= std.constants.U32_MAX;
}

Type: u32
Value: 4294967295


Compilation and Performance

Zero-Overhead

Standard library built-ins compile directly to EVM opcodes:

// Ora code
let timestamp = std.block.timestamp();
// Generated Yul
let timestamp := timestamp()

The built-in is replaced with the raw EVM opcode at compile time - no function call overhead.

Type Safety

Built-ins are type-checked at compile time:

// ✅ Correct
let time: u256 = std.block.timestamp();

// ❌ Compile error: type mismatch
let time: address = std.block.timestamp();

Validation

The compiler validates built-in usage:

// ❌ Compile error: unknown built-in
let invalid = std.block.nonexistent();

// ❌ Compile error: missing ()
let sender = std.msg.sender;

Complete Example: ERC20 Token

Here's a token contract using the standard library:

contract SimpleToken {
storage totalSupply: u256;
storage balances: map[address, u256];
storage allowances: doublemap[address, address, u256];

pub fn initialize(initialSupply: u256) -> bool {
let deployer = std.msg.sender();
totalSupply = initialSupply;
balances[deployer] = initialSupply;
return true;
}

pub fn transfer(recipient: address, amount: u256) -> bool {
let sender = std.msg.sender();
let senderBalance = balances[sender];

// Validate recipient
if (recipient == std.constants.ZERO_ADDRESS) {
return false;
}

// Check balance
if (senderBalance < amount) {
return false;
}

// Perform transfer
balances[sender] = senderBalance - amount;
let recipientBalance = balances[recipient];
balances[recipient] = recipientBalance + amount;

return true;
}

pub fn approve(spender: address, amount: u256) -> bool {
let owner = std.msg.sender();

if (spender == std.constants.ZERO_ADDRESS) {
return false;
}

allowances[owner][spender] = amount;
return true;
}
}

Key Usage:

  • std.msg.sender() - Get the caller's address
  • std.constants.ZERO_ADDRESS - Validate addresses

How It Works

The standard library compiles in three stages:

Ora Source          →  MLIR IR          →  Yul           →  EVM Bytecode
std.msg.sender() ora.evm.caller() caller() CALLER (0x33)

Each built-in maps to a single EVM opcode, guaranteeing zero overhead.


Best Practices

✅ DO

  • Use std.msg.sender() for access control
  • Check for std.constants.ZERO_ADDRESS on address inputs
  • Use std.block.timestamp() for time-based logic (with care)

❌ DON'T

  • Don't use std.transaction.sender() for access control (use std.msg.sender())
  • Don't rely on std.block.timestamp() for critical randomness
  • Don't assume std.block.number() increments by exactly 1

Security Considerations

Timestamp Manipulation: Miners can manipulate std.block.timestamp() by ~15 seconds. Don't use it for critical randomness.

Block Number: Can be used for rough time estimates (1 block ≈ 12 seconds), but not precise.

msg.sender vs transaction.sender:

  • std.msg.sender() = immediate caller (could be a contract)
  • std.transaction.sender() = original EOA (can't be a contract)

Use std.msg.sender() for most access control!


FAQ

Q: Do I need to import the standard library?

A: No. The standard library is always available - just use std. prefix.

Q: What's the gas cost?

A: All built-ins compile to single EVM opcodes (2 gas each for most). Zero function call overhead.

Q: Can I redefine std?

A: No. std is a reserved namespace.

Q: Why so minimal?

A: Ora provides direct EVM access without abstraction. Higher-level utilities should be implemented as user libraries, not built-ins.