Skip to content
LogoLogo

Program structure

A file is a contract

The simplest Lithe program is a file of top-level declarations. The file is the contract:

import "std/evm";
 
storage is record {
  owner : address;
}
 
external function claim() is {
  storage.owner := Evm.sender();
}

To name the contract, or to put several contracts in one file, wrap each in contract Name is { ... }:

contract Registry is {
  storage is record {
    count : uint;
  }
 
  external function register() is {
    storage.count := storage.count + 1;
  }
}

A file uses either top-level declarations or explicit contract blocks, never both. lithe compile file.lithe needs a file with exactly one contract. lithe compile directory builds every contract in every file and writes one artifact per contract.

Declarations

A contract is made of these declarations, in any order. Names are resolved across the whole file, so a function can use a type declared below it.

DeclarationExampleSee
Storagestorage is record { ... }Storage
Named storagestorage config at "app.config" is record { ... }Storage
Transient storagetransient storage is record { ... }Storage
Typetype OrderId is bytes32;Types
Errorerror NotOwner;Errors
Error seterrors admin_errors is | NotOwner | PausedErrors
Eventevent Paid of record { ... }Events
Interfaceinterface Token is { ... }Queries and deferred calls
Functionexternal function f() is { ... }Functions
Modulemodule Ledger is { ... }Modules
Useuse Ledger;Modules
Guardguard M.editor(const id : uint) is { ... }, for a module's guard editor(const id : uint);Guards
Importimport "./lib";Imports
Upgradesupgradeable, upgrade guard is { ... }, migrate f() is { ... }Upgrades
Receive and fallbackpayable receive is { ... }, fallback is { ... }, fallback delegates to target;Functions

Every contract has at least one external function. A contract with no state declares no storage:

external pure function version() : uint is
  1

Comments

Comments are written (* like this *). They can span lines, and they nest, so you can comment out code that already contains comments.

(* A comment.
   (* A nested comment. *)
   Still inside the outer comment. *)

There are no line comments.

Punctuation

Each bracket has one job:

BracketUsed for
( )grouping, calls, parameters, tuples
[ ]list literals, indexing, map lookup
{ }records, blocks, case arms, interfaces, modules, contracts

Statements end with ;. Fields in a record type or record literal also end with ;. Parameters are separated by ;, and arguments by ,:

pure function scale(const amount : uint; const bps : uint) : uint is
  amount * bps / 10_000
 
const fee : uint := scale(1_000, 25);

Naming

Lithe does not enforce a naming style, but its examples and standard modules follow the EVM's, so the names a contract declares are the names its ABI shows:

KindConventionExample
Functions, fields, locals, parameters, constantscamelCasetransferFrom, basisPoints
Types, errors, events, variant casesPascalCaseBasisPoints, NotOwner, Transfer
Modules, interfaces, contractsPascalCaseLedger, IERC20

An acronym keeps its capitals, as in OpenZeppelin's names: tokenURI, ERC20, EIP712Domain. The built-in types are lowercase: uint, address, bytes32, map, list.

Every name appears in the ABI exactly as written, so a name a standard fixes, such as DOMAIN_SEPARATOR, is written as the standard spells it. A variant case a contract dispatches on names its entrypoint with external: | SetFee external setFee of uint.

Keywords

These words are reserved:

and as assert at bound call case const contract create defer do else emit end error
errors event external fail failwith false for function if import in indexed interface
is list migrate module nil nonreentrant not of or payable private pure query raises
record requires skip then true type Unit until upgrade upgradeable use var view with

storage, transient, guard, receive, and fallback are keywords only where a declaration expects them, guarded only after an external's header, delegates, forwards, and to only after fallback, and forward only before a name at the start of a statement. Elsewhere they are names like any other. create also names a function, or a member after ., so a contract can expose, or call, a factory's create(...).