Solady Assembly Libraries
Smart contract security audit by TierZero · 2024-03-15
Independent research. This is an unsolicited security analysis published by TierZero — not a commissioned audit, and no certificate is issued. It reflects our own review of public code and on-chain events.
Findings summary
What it is
Solady (maintained by Vectorized, formerly transmissions11's Solmate lineage) is a Solidity library built almost entirely in inline assembly. It covers the primitives every EVM protocol eventually needs: ERC20/ERC721/ERC1155 implementations, SafeTransferLib, ECDSA and EIP712 signature verification, LibClone for proxy deployment, FixedPointMathLib for full-precision arithmetic, MerkleProofLib, LibSort, LibBitmap, ReentrancyGuard, and Ownable/UUPSUpgradeable. It has become a default dependency across a large share of new Ethereum deployments precisely because it strips out Solidity's compiler-inserted safety padding — bounds checks, memory zeroing, ABI-decoding overhead — and replaces it with hand-written Yul that does the equivalent work in fewer opcodes.
This is not a certification of Solady. It is a review of the design patterns that let assembly-heavy code stay safe, and a catalogue of the mistakes we see when teams vendor or imitate this style without preserving its invariants. Firms doing this kind of comparative pattern analysis on client code is exactly what a smart contract audit should surface before deployment, not after.
Threat model
The relevant attacker capabilities against a library like this are narrower than against an application, but the failure modes are more severe because a single bug is inherited by every consumer:
- Malicious or non-standard token contracts calling back into
SafeTransferLibconsumers, or returning malformed/absent return data. - Crafted calldata designed to exploit missing bounds checks in hand-rolled
calldataload/calldatacopysequences. - Signature malleability and edge-case inputs (zero address, s-value in the upper half of the curve order) fed to
ECDSA.recover. - Memory corruption via layout assumptions — assembly that writes past the free memory pointer, or that assumes call arguments are cleaned to their declared type width when they are not.
- CREATE2/CREATE3 address prediction and salt front-running in
LibClone. - Optimizer interaction bugs where assembly blocks lacking correct
memory-safeannotations cause the Solidity optimizer to make invalid assumptions when compiled alongside high-level code.
Why the design holds
Solady's assembly is disciplined in ways that are easy to miss on a skim:
Scratch space and free-memory-pointer discipline. Every temporary write goes through the reserved scratch region (0x00–0x40) or respects the free memory pointer at 0x40, then restores or advances it correctly. This is what lets Solady's assembly compose safely with surrounding Solidity code and with the optimizer's memory-safe mode.
Manual bounds checks are actually present, just inlined. Where a naive read would calldataload an attacker-controlled offset, Solady checks calldatasize() first. The saving comes from when the check happens (once, batched) and how it's expressed (branchless where possible), not from omitting it.
Token transfer defensiveness. SafeTransferLib treats the three real-world ERC20 behaviors as first-class cases: revert-on-failure, false-on-failure, and no-return-value-at-all (USDT-style). Pseudocode of the invariant it enforces:
success := call(gas(), token, 0, ptr, len, 0, 32)
ok := and(success, or(iszero(returndatasize()), and(gt(returndatasize(), 31), eq(mload(0), 1))))
require(ok and extcodesize(token) > 0)
The extcodesize check matters as much as the return-data check — without it, a call to an EOA or an unreachable address silently "succeeds."
Signature hygiene. ECDSA rejects s values above secp256k1n/2 (malleability) and treats a recovered zero address as a failure rather than a valid signer, which closes the classic "unset signer passes an all-zero signature" trap.
Cheap, structured reverts. Custom error selectors are written to memory and reverted with a fixed-size return rather than using require(string), which is a gas optimization but also removes a class of string-concatenation bugs.
Differential and fuzz testing against reference implementations. The functional correctness argument for Solady rests less on inspection and more on extensive Foundry fuzz and invariant suites checked against OpenZeppelin and Solmate behavior — the same kind of verification discipline covered in our piece on certifying trading program logic with formal methods, applied here through property-based testing rather than a prover.
Where implementers get it wrong
The library itself has a mature audit history and a narrow, well-tested surface. The bugs we actually see are in how teams use or imitate it:
- Copy-pasting assembly out of context. Lifting a Yul snippet from Solady into a different function without carrying over the surrounding scratch-space and free-memory-pointer bookkeeping. The snippet was memory-safe in place; it isn't automatically memory-safe elsewhere.
- Dropping the
memory-safeannotation, or adding one that isn't true, when adapting assembly for a custom use case — this can silently corrupt optimizer output in ways that only show up under specific compiler versions. - Assuming dirty-bit cleanliness. Solady's internal assembly routines frequently rely on the Solidity ABI-decoding step having already zeroed upper bits of a
uint160/bool/etc. Calling these routines directly from another assembly block, bypassing normal Solidity calldecoding, can pass dirty high bits through and produce wrong comparisons or address truncation — the same class of confusion we describe in type confusion from unchecked account layouts, just triggered by raw word reuse instead of a missing discriminator check. - Treating
SafeTransferLibsuccess as ERC20 compliance. It guarantees the call pattern succeeded, not that the token isn't fee-on-transfer, rebasing, or pausable — accounting logic still needs to measure balance deltas where that matters. - CREATE2/CREATE3 salt handling.
LibClone's deterministic addresses are only unpredictable if the salt incorporates something the deployer controls exclusively; naive salts (sequential nonces, public data) enable address squatting or front-running of the deployment.
Takeaways
Solady holds up because its assembly encodes the same checks the compiler would generate, just placed deliberately and stripped of redundancy — it is disciplined, not reckless. The risk isn't in the library; it's in extraction. Any team vendoring Solady patterns into custom contracts should treat each assembly block as carrying implicit preconditions (memory layout, calldata bounds, bit cleanliness) that must be re-verified at the new call site, which is the kind of line-by-line reasoning a code review and audit engagement is built to catch before it reaches mainnet.
If your contracts lean on hand-written assembly, gas-golfed libraries, or clone factories and you want a second set of eyes on the invariants, talk to us about an audit.
Need your contracts audited?
Manual review + tooling across TON, Solana and EVM — certificate and full report included.
Get an audit