Skip to content
LogoLogo

Why not Solidity?

Solidity is the most successful smart-contract language, with a decade of production use, excellent tooling, and a huge ecosystem. Lithe compiles to the same EVM, speaks the same ABI, and lays out storage the same way, so none of that ecosystem is lost.

Lithe exists because of Solidity, not in spite of it. We have deep respect for the Solidity team and for everyone who has built on it: the compiler engineers, the library authors at OpenZeppelin and Solady, the auditors, the tool builders behind Foundry, Hardhat, and Slither, and the thousands of developers whose contracts secure real value every day. The ABI, custom errors, storage layout, ERC-7201 namespaces, and the patterns this page lists all came out of that work, and the hard lessons behind them were paid for by that community. Lithe stands on what they built.

This page explains where Lithe makes different choices and why. It is not an argument that Solidity is wrong. Solidity serves a far wider range of programs than Lithe tries to, and its trade-offs are deliberate ones.

The difference is philosophy. Solidity exposes nearly the full power of the EVM and relies on developers to use it safely. Lithe removes the dangerous parts from ordinary code and makes the compiler enforce what Solidity leaves to convention.

Security by convention

Writing secure Solidity means remembering, in every function:

  • checks-effects-interactions
  • a reentrancy guard where calls could re-enter
  • access modifiers on every privileged function
  • initializer protection on every upgradeable contract
  • storage gaps or namespaces so upgrades don't corrupt state
  • the return value of every low-level call

Each is a known pattern with a known fix, and each is still a common source of losses. Patterns depend on someone remembering them: the author, the reviewer, the auditor, and now the model generating the code.

Solidity conventionLithe rule
Checks-effects-interactionsCalls are deferred until the transition commits
nonReentrant modifierexternal nonreentrant function, plus compiler warnings on every re-entry path
onlyOwner on each functionShared dispatch guards, and nothing exposed without an explicit declaration
initializer / reinitializerMigrations that run only inside an upgrade
Storage gaps, ERC-7201 annotationsNamed storage as a language feature, with lithe upgrade-check
Check (bool ok, ) = x.call(...)Failures revert unless handled with Ok / Error
revert("message"), errors discoverable by reading codeTyped errors, with every external function's raises checked
unchecked { } for gasAlways checked. Where wrapping is the intent, it is named: Uint.wrappingAdd

Why not Solidity plus linters?

Static analyzers such as Slither and Aderyn are valuable, and they catch real bugs. But they analyze a language that still allows the dangerous behavior, so they warn after the fact, with false positives that train people to ignore warnings and false negatives that stay silent.

Analysis warns about dangerous programs. Language rules make classes of them unwritable.

A linter can tell you an external call comes before a state update. In Lithe that order doesn't exist. A linter can guess which functions are privileged. Lithe's compiler knows, because guards are structural, and it prints them.

Why not a safer subset of Solidity?

A restricted Solidity profile, such as no low-level calls, mandatory guards, or linted patterns, gets part of the way. But Solidity's syntax, type system, and standard patterns were designed around the unrestricted model. Rules added from the outside tend to become a list of things not to do, rather than a coherent model.

Lithe designs its effect vocabulary (query, emit, defer), its errors (raises), its storage (named domains), its composition (modules instead of inheritance), and its dispatch (variant groups) around the restrictions from the start. The safe path is the only path, and it's short to write.

What you keep

  • the EVM, and every chain that runs it
  • standard selectors, custom errors, and events, so wallets, explorers, indexers, and SDKs just work
  • slot-for-slot Solidity storage layout, so storage tooling and cross-language upgrades work
  • ERC-1967/UUPS proxies and ERC-7201 namespaces
  • calls to and from Solidity contracts, through interfaces
  • Foundry-style artifacts, so deployment and testing tools can load Lithe output
  • about 30% smaller bytecode than optimized solc

What you give up

Lithe is new, and being honest about the trade matters more than winning the argument.

  • Maturity. Solidity has a decade of production use and many audits. Lithe does not, yet.
  • Ecosystem. There's no Lithe equivalent of OpenZeppelin yet. You'll write or port more yourself. The by-example programs are a start.
  • Unbounded loops. A for loop's iteration count is fixed when it starts, there is no while and no recursion, and a loop over storage is warned about unless it carries a bound. Some algorithms must be restructured. See Loops and Recursion.
  • Low-level power. There's no inline assembly. Raw calls, delegatecall, receive, and fallback exist, but each is spelled out and deferred or queried like any other call. If your design depends on hand-written EVM code, Lithe doesn't fit today.
  • Synchronous call results. A state-changing call's result arrives in a continuation, not inline. This is a different style to learn.

If you are building a protocol where a missed check costs millions, and more and more of the code is written or reviewed by AI, these are good trades. Read Why now for the numbers.