Skip to content
LogoLogo

Rust library

The compiler is a Rust crate, lithe, with no dependencies beyond its lexer. Everything the lithe command does, a Rust program can do too, and it gets typed values back instead of text.

[dependencies]
lithe = { path = "../lithe" }

Run cargo doc --open in the compiler's checkout for the full API.

Compiling

lithe::compile builds every contract an entry file defines:

use std::path::Path;
use lithe::{Options, Sources};
 
let output = lithe::compile(&Sources::disk(), Path::new("token.lithe"), &Options::default())?;
let token = output.single()?;

output.contracts has each contract the file defines, in order. single() is for a file that must define exactly one, as lithe compile file.lithe requires.

A Contract has:

Field
nameThe contract's name.
bytecodeThe creation code. For a contract with facets, the router's.
deployed_bytecodeThe runtime code.
abiThe typed ABI.
method_identifiersEach external's signature and selector.
planWhat to deploy first, when the contract has facets or creates one that has: each facet's address, code, and ABI, and the CREATE2 factory.
warningsThe warnings lithe compile prints, a contract or facet over the size limit included.

contract.plan_json() renders the plan as lithe compile prints it.

Sources in memory

Sources::disk() reads files and resolves imports as the lithe command does. Sources::memory takes the files by path, so a program with imports compiles without touching the file system, in a browser, a test, or a build tool:

let sources = Sources::memory([
    ("lib/math.lithe", "pure function twice(const n : uint) : uint is n + n\n"),
    ("counter.lithe", COUNTER_SOURCE),
]);
let counter = lithe::compile(&sources, Path::new("counter.lithe"), &Options::default())?.single()?;

An import resolves relative to the importing file's path, . and .. included: import "./lib/math"; in counter.lithe names lib/math.lithe. The standard library, import "std/...", is built into the compiler and works with either.

The typed ABI

contract.abi is an Abi: its items, in the order the JSON ABI lists them. The types follow alloy-json-abi's names and fields.

use lithe::{AbiItem, StateMutability};
 
for item in &counter.abi.items {
    if let AbiItem::Function(f) = item {
        let payable = f.state_mutability == StateMutability::Payable;
        println!("{}({} inputs) payable: {payable}", f.name, f.inputs.len());
    }
}
Type
AbiItemConstructor, Function, Event, Error, Fallback, or Receive.
Functionname, inputs, outputs, and state_mutability.
Eventname, inputs (each an EventParam, with indexed), and anonymous.
AbiErrorname and inputs.
Paramty (uint256, tuple[], ...), name, components for a tuple, and internal_type.
StateMutabilityPure, View, NonPayable, or Payable.

abi.to_json() prints the Solidity JSON ABI, byte for byte what lithe abi prints. lithe::abi(&sources, entry) returns the ABI alone. It needs only the checked program, so a contract too large to build still has one.

Errors

A failed compile returns an Error for each contract that fails, so a file with ten contracts and three broken ones reports all three. Each Error has the file it is in, a 1-based line and column, and a message. file is the path the compiler read, canonical on disk and as given for Sources::memory, and None for an error about no one file. Its Display is path:line:column: message, with the path relative to the working directory, as the command prints it.

Everything else

The other functions take the same sources and entry path, and return what the matching command prints:

FunctionCommand
abi_treelithe abi-tree
footprintlithe footprint
analysislithe analysis
errorslithe errors
specslithe specs
provelithe prove, with ProveOptions, whose source proves the source alone; its Proof counts what was proved, failed, and unknown
facetslithe facets
stubslithe stubs
storage_layout, storage_difflithe storage
upgrade_checklithe upgrade-check
upgrade_planlithe upgrade
compile_dir, abi_dirlithe compile directory, lithe abi directory

Options holds the build flags: size_limit, deployer, and evm, an EvmVersion, and check_specs. The library doesn't read lithe.toml on its own: lithe::build_options(path) answers the Options the [build] table nearest path sets, each unset key at its default, and lithe::set_build_option(&mut opts, key, value) applies one flag as the command line spells it.