Both projects tell you what they are in their first line. OpenZeppelin's README opens with "A library for secure smart contract development." Solady's opens with "Gas optimized Solidity snippets." That is most of the comparison, and it is also why the usual framing — the cheap one against the safe one — is too coarse to decide anything with.
So I measured it. The same ERC-20, ERC-721 and ERC-1155, the same ownership and role checks, the same reentrancy guards, minimal proxies and utility calls, implemented on top of each library, checked for behavioural equivalence where they are equivalent, pinned with executable tests where they are not, and then measured one operation at a time under one EVM.
Solady is cheaper on 34 of the 42 paired measurements. The interesting part is the spread. On an ERC-20 transfer to an account that already holds a balance it saves 456 gas. On deploying that same token it saves 376,065. And on the eight measurements where OpenZeppelin is cheaper, one of them is a 58% difference caused by a default that most people will never read.
What was measured, and how
One project imports both libraries and implements the same contract twice on top of each. Setup:
@openzeppelin/contracts5.6.1, which is whatnpm installgives you, andsoladyatmaincommit2afba69b- solc 0.8.36, optimizer on, 200 runs, EVM target
osaka, no via-IR - Foundry 1.5.1 with
isolate = false, on an Apple M3 Pro with 12 logical CPUs
Foundry made --isolate the default in 1.8.0, and that flag alone moves an in-test gasleft() window by tens of thousands of gas. A gas figure without a Foundry version and an isolation mode attached to it is not a figure. Both are pinned in the repository's foundry.toml.
The discipline is the one I used for the Foundry vs Hardhat benchmark: report the setup, measure what a user actually pays for, and say what the result cannot prove.
Contracts go up in setUp(). Immediately before every measured window the harness calls vm.cool on the callee and then touches its code.length, which pays the EIP-2929 cold-account premium outside the window while leaving every storage slot cold. That matters more than it sounds: without it, a mint performed two lines earlier leaves the balance slot warm and makes the transfer look 10,000 gas cheaper than any real one. Everything read inside a window is a local or a constant, and each side gets its own actors, its own token instances, its own callback receiver and its own proxy implementation.
The figures exclude the 21,000-gas transaction base cost and calldata, which are identical on both sides. Generator, harness, raw JSON and a per-file content hash of every measured library file are in the repository under examples/articles/openzeppelin-vs-solady-which-contract-library-should-i-use.
The per-call difference is small. The deployment difference is not
| ERC-20 operation | OpenZeppelin | Solady | Difference |
|---|---|---|---|
transfer, recipient already holds a balance |
7,743 | 7,287 | −456 (5.9%) |
transfer, recipient starts at zero |
27,643 | 27,188 | −455 (1.6%) |
transferFrom |
10,751 | 9,963 | −788 (7.3%) |
approve, fresh slot |
25,366 | 25,061 | −305 (1.2%) |
permit |
52,191 | 51,329 | −862 (1.7%) |
_mint to a new account |
27,548 | 27,170 | −378 (1.4%) |
balanceOf |
3,073 | 3,082 | +9 |
| Deployment | 840,608 | 464,543 | −376,065 (44.7%) |
| Deployed bytecode, bytes | 3,792 | 2,154 | −1,638 (43.2%) |
The reason the per-call numbers sit so close together is that a token transfer is mostly storage. In the cheapest case two cold SLOADs and two SSTOREs cost 10,000 gas before either library executes an instruction of its own, and the Transfer event costs another 1,756. Assembly can shave the arithmetic and the dispatch around that. It cannot shave the storage.
Deployment is the opposite. Almost nothing about deployment is fixed cost, so a library that emits half as much bytecode deploys for roughly half as much gas.
Part of that gap is not codegen at all. The idiomatic Solady token returns its name and symbol as compile-time constants, so deployment writes no metadata storage, while the idiomatic OpenZeppelin token takes them as constructor arguments. Both libraries mark name() and symbol() virtual, so I built the two missing variants and measured all four:
| ERC-20 variant | Deployment gas | Deployed bytes |
|---|---|---|
| OpenZeppelin, name in storage | 840,608 | 3,792 |
| OpenZeppelin, name as a constant | 778,540 | 3,689 |
| Solady, name in storage | 540,770 | 2,302 |
| Solady, name as a constant | 464,543 | 2,154 |
Holding the metadata idiom constant, the library difference is 40.3% (778,540 against 464,543) or 35.6% (840,608 against 540,770), depending on which idiom you hold it at. The metadata choice is worth real money — 62,068 gas on the OpenZeppelin side, 76,227 on the Solady side — but it is not where the bulk of the difference comes from. The codegen is.
It is a tradeoff in both directions, too: a token whose name is baked into the bytecode cannot be redeployed under a different name, which matters the moment you want one factory to mint many tokens.
Every bar is labelled with its own value; the shading is redundant. Figures are execution gas with cold storage slots, excluding the transaction base cost.
Where the storage layout actually pays
ERC-721 and ERC-1155 are where Solady's per-call advantage stops being a rounding error.
| ERC-721 operation | OpenZeppelin | Solady | Difference |
|---|---|---|---|
transferFrom by an approved spender |
15,909 | 12,563 | −3,346 (21.0%) |
safeTransferFrom into a contract that answers the callback |
37,692 | 36,177 | −1,515 (4.0%) |
_mint, first token to an account |
50,618 | 50,211 | −407 (0.8%) |
_mint, second token to an account |
28,218 | 27,805 | −413 (1.5%) |
approve |
27,977 | 27,609 | −368 (1.3%) |
ownerOf |
3,525 | 3,498 | −27 |
| Deployment | 867,030 | 535,799 | −331,231 (38.2%) |
| Deployed bytecode, bytes | 3,930 | 2,512 | −1,418 (36.1%) |
| ERC-1155 operation | OpenZeppelin | Solady | Difference |
|---|---|---|---|
safeTransferFrom to an account that already holds the id |
10,127 | 8,607 | −1,520 (15.0%) |
_mint into a fresh balance |
31,765 | 30,818 | −947 (3.0%) |
balanceOf |
3,199 | 3,107 | −92 (2.9%) |
| Deployment | 903,328 | 603,112 | −300,216 (33.2%) |
| Deployed bytecode, bytes | 4,327 | 2,848 | −1,479 (34.2%) |
Solady's ERC-721 describes itself as having "storage hitchhiking": owner, balance, approval and auxiliary data are packed so a transfer touches fewer slots. That is a storage win rather than an assembly win, which is exactly why it is four to eight times larger than any of the ERC-20 differences. The same reasoning covers the ERC-1155 transfer.
Notice that the safeTransferFrom advantage is much smaller than the plain one. The receiver callback costs both libraries the same, so it dilutes the difference. If your NFT contract is mostly used through safeTransferFrom from wallets, the 21% headline is not the number you get.
Eight measurements where OpenZeppelin is cheaper
This is the part I did not expect, and it turned out to be the most useful part of the exercise.
The transient reentrancy guard is not transient off mainnet
Both libraries ship a storage-backed ReentrancyGuard and a transient-storage variant. On a fresh call with a cold guard slot:
| Guard | OpenZeppelin | Solady |
|---|---|---|
nonReentrant, storage-backed |
5,220 | 5,052 |
nonReentrant, transient variant, chain id 31337 |
3,316 | 5,243 |
nonReentrant, transient variant, chain id 1 |
3,309 | 3,247 |
Solady's ReentrancyGuardTransient defaults _useTransientReentrancyGuardOnlyOnMainnet() to true, and the modifier then branches on block.chainid == 1. On Ethereum mainnet it uses TSTORE. Everywhere else — every L2, every testnet, every local chain — it falls back to SSTORE, and costs 5,243 gas instead of 3,247:
// node_modules/solady/src/utils/ReentrancyGuardTransient.sol
/// @dev Guards a function from reentrancy.
modifier nonReentrant() virtual {
if (_useTransientReentrancyGuardOnlyOnMainnet()) {
uint256 s = _REENTRANCY_GUARD_SLOT;
if (block.chainid == 1) {
/// @solidity memory-safe-assembly
assembly {
if tload(s) {
mstore(0x00, s) // `Reentrancy()`.
revert(0x1c, 0x04)
}
tstore(s, address())
}
} else {
/// @solidity memory-safe-assembly
assembly {
if eq(sload(s), address()) {
mstore(0x00, s) // `Reentrancy()`.
revert(0x1c, 0x04)
}
sstore(s, address())
}
}
}
It is a defensible default — EIP-1153 support was uneven for a long time, and a guard that silently did nothing would be much worse than one that silently costs more. But it means the gas-optimized library is 58.1% more expensive than the conservative one on the single operation it is named for, on every chain where gas optimization is most likely to be why you reached for it. Override _useTransientReentrancyGuardOnlyOnMainnet() to return false and the number drops to 3,225, just under OpenZeppelin's.
That generalizes. A default that reads as a safety measure on mainnet reads as a 1,927-gas tax on an L2, and nothing in the type system tells you which one you have.
Ownership and roles deploy for more
| Access-control contract | Deployment gas | Deployed bytes |
|---|---|---|
OpenZeppelin Ownable |
147,114 | 451 |
OpenZeppelin Ownable2Step |
191,668 | 663 |
Solady Ownable |
207,557 | 753 |
OpenZeppelin AccessControl |
282,229 | 1,121 |
Solady OwnableRoles |
334,197 | 1,385 |
Comparing Solady's Ownable against OpenZeppelin's is not a fair fight, because Solady folds a two-step ownership handover into the same contract: requestOwnershipHandover, cancelOwnershipHandover and completeOwnershipHandover are all in there whether you use them or not. OpenZeppelin puts that in a separate Ownable2Step. Against that, Solady is 8.3% more expensive to deploy rather than 41.1%.
This is also the one place where the optimizer setting changes the answer. Re-running everything at 1,000,000 optimizer runs instead of 200 flips both role comparisons: Ownable2Step goes to 244,752 against Solady's 234,553, and AccessControl goes to 377,043 against OwnableRoles' 358,117. Solady wins both at the high setting and loses both at the low one. Every other paired measurement keeps its sign.
Per-call, the access-control story is close to a tie. An onlyOwner-guarded call costs 4,989 against 5,041 — OpenZeppelin by 52 gas — because with a cold slot both pay the same SLOAD and SSTORE and there is almost nothing else left to optimize. Granting a role is the exception: 30,023 against 26,854, Solady by 10.6%, because a bitmask role fits in one slot where a bytes32 role mapping does not.
One data structure inverts
EnumerableSet of addresses |
OpenZeppelin | Solady |
|---|---|---|
add, first element |
67,703 | 23,321 |
contains |
3,176 | 5,420 |
remove |
8,203 | 7,661 |
Adding the first address to an empty set costs OpenZeppelin 67,703 gas and Solady 23,321, a 65.6% difference and the largest proportional gap in the whole exercise. OpenZeppelin's set is an array plus a position mapping, so the first insert writes three fresh slots. Solady's stores small sets inline.
But that compact layout costs an extra cold SLOAD on every membership check, so contains runs 70.7% more expensive. Which one is cheaper for you depends on your read-to-write ratio, and that is a per-application answer rather than a library ranking.
Minimal proxies are a tie
Clones.clone and LibClone.clone deploy for 42,094 and 41,832 gas, produce 45 and 44 bytes of runtime code, and forward a call for 3,006 and 3,004 gas against 2,898 for calling the implementation directly. Both are ERC-1167-shaped and there is nothing left to win. If you picked Solady for its proxies, that is 262 gas.
Utilities, where the assembly does show up
| Operation | OpenZeppelin | Solady | Difference |
|---|---|---|---|
| Merkle proof, depth 8 | 6,548 | 5,509 | −1,039 (15.9%) |
ECDSA.recover from a 65-byte signature |
5,327 | 4,708 | −619 (11.6%) |
uint256 to decimal string |
3,086 | 2,647 | −439 (14.2%) |
| Base64-encode 64 bytes | 7,124 | 5,834 | −1,290 (18.1%) |
These are the cleanest wins in the set: pure computation with almost no storage, which is exactly the shape assembly is good at. They are also the smallest absolute numbers. A 15.9% saving on a Merkle proof is 1,039 gas, which matters if you verify one per claim in an airdrop with a hundred thousand claimants and does not matter at all otherwise.
What the harness cannot prove
One workload, one machine, one compiler version, one optimizer setting as the headline and one more as a control. It measures the base implementations and a set of utility calls, not the extensions, hooks and combinations a real contract is made of.
Two controls are worth reporting because both changed something.
Measurement order. The harness measures OpenZeppelin first and Solady second. Whichever side runs first sits at a slightly lower free-memory pointer, so its calldata encoding pays a few gas less of memory expansion — and, far worse, anything the two sides share gets cold-accessed by the first and found warm by the second.
The first version of this harness shared one ERC-721 callback receiver between the two sides. That is now a deliberate control row, measured alongside the per-side one. With a shared receiver, OpenZeppelin runs first and pays 37,699 while Solady pays 33,678, which reads as a 4,021-gas Solady win. Reverse the order and Solady pays 36,149 while OpenZeppelin pays 35,201, which reads as a 948-gas OpenZeppelin win. The contamination is 2,498 gas on one side and 2,471 on the other — the EIP-2929 cold-account premium, almost exactly — and it is large enough to reverse the sign of the result.
With a receiver per side, the same measurement reads 37,692 against 36,177 in one order and 37,701 against 36,149 in the other. Across the whole suite, reversing the order now changes no difference by more than 1,155 gas, on a measurement whose difference is 300,216, and flips no sign except balanceOf, where the two libraries are 9 gas apart and should be called a tie.
That is the general lesson, and it is worth more than any single number here: when a methodological control changes the answer, the control is the finding.
Optimizer runs. At 1,000,000 runs instead of 200, every runtime figure drops slightly, every bytecode size grows, deployment gets more expensive, and the ordering holds everywhere except the two role-contract deployments described above. Raw data for both settings is in the repository.
It also measures cost, not correctness. Both libraries have been audited, both are heavily tested, and finishing a transfer for less gas is not evidence that either one is safer.
The assumptions you inherit
This should decide the question more often than the gas does, and it is not a matter of opinion: Solady documents its deviations in the header of every file it deviates in.
From src/tokens/ERC20.sol:
The ERC20 standard allows minting and transferring to and from the zero address, minting and transferring zero tokens, as well as self-approvals. For performance, this implementation WILL NOT revert for such actions. Please add any checks with overrides if desired.
I turned each of those into an executable test, because a sentence in a header comment is easy to read past and a failing assertion is not. Six of them, all passing, all in test/Divergence.t.sol:
// test/Divergence.t.sol
/// Solady's ERC20 header says so explicitly: "The ERC20 standard allows
/// minting and transferring to and from the zero address ... For
/// performance, this implementation WILL NOT revert for such actions."
function test_DivergeErc20TransferToZeroAddress() public {
vm.prank(ALICE);
vm.expectRevert(); // ERC20InvalidReceiver(address(0))
ozToken.transfer(address(0), 1e18);
vm.prank(ALICE);
assertTrue(sdToken.transfer(address(0), 1e18));
assertEq(sdToken.balanceOf(address(0)), 1e18);
assertEq(sdToken.balanceOf(ALICE), 999e18);
}
The full list:
- ERC-20 transfer to the zero address. OpenZeppelin reverts. Solady credits the balance.
- ERC-20 mint to the zero address. OpenZeppelin reverts. Solady mints, and total supply now includes tokens nobody can move.
- ERC-20 approve with the zero address as spender. OpenZeppelin reverts. Solady writes the allowance.
- ERC-1155
setApprovalForAllwith the zero address as operator. OpenZeppelin reverts withERC1155InvalidOperator. Solady writes the approval. - ERC-721 transfer functions are
payable. Solady makes them payable "where permitted by the ERC721 standard", so a caller who attaches ether to atransferFromgets the transfer and leaves the ether stuck in the token contract. OpenZeppelin's are non-payable, so the same call reverts. Ownablecan be re-initialized. Solady's_initializeOwner"will not check if there is an existing owner" unless you override_guardInitializeOwner()to returntrue, and the default isfalse. In a constructor that is harmless. Behind a proxy, where the initializer is a reachable function, it is the difference between an owned contract and an unowned one.
None of these is a bug. Every one is a deliberate, documented trade, and most are irrelevant for most contracts. But you are choosing to own them, and the list is longer than "it skips the zero-address check."
There is a seventh difference that is not about checks at all. Solady computes its own storage slots rather than letting the compiler assign them — _BALANCE_SLOT_SEED = 0x87a211a2 and so on — which is what makes it usable behind both regular and upgradeable contracts. The consequence is that the two libraries are not layout-compatible with each other:
// test/Divergence.t.sol
function test_DivergeStorageLayout() public {
assertTrue(
vm.load(address(ozToken), keccak256(abi.encode(ALICE, uint256(0)))) != bytes32(0),
"OZ balance lives in a compiler-assigned mapping at slot 0"
);
assertEq(
vm.load(address(sdToken), keccak256(abi.encode(ALICE, uint256(0)))),
bytes32(0),
"Solady does not use slot 0"
);
// Derive Solady's balance slot from the recipe in its own header
// comment (`mstore(0x0c, seed); mstore(0x00, owner); keccak256(0x0c,
// 0x20)`), then prove the derivation two ways: the value is there, and
// the contract really reads that slot.
bytes32 soladySlot =
keccak256(abi.encodePacked(bytes32((uint256(uint160(ALICE)) << 96) | 0x87a211a2)));
assertEq(uint256(vm.load(address(sdToken), soladySlot)), 1_000e18);
vm.record();
sdToken.balanceOf(ALICE);
(bytes32[] memory reads,) = vm.accesses(address(sdToken));
assertEq(reads.length, 1);
assertEq(reads[0], soladySlot);
}
Switching an upgradeable contract from one library to the other is a storage migration, not a dependency bump.
Five more tests in the same file establish that the contracts the gas harness measures really are doing the same job where they claim to: identical balances, allowances and return values on the ERC-20 happy path, identical ownerOf and approval state after an ERC-721 transfer, the same ERC-165 interface ids, and the same EIP-712 domain separator construction, so a permit signing scheme written for one works for the other. A gas difference between two contracts that behave differently is not a measurement.
Readability, measured
"More readable" is usually where a comparison like this stops being useful, because it turns into taste. One part of it is countable: how much of the code is hand-written EVM assembly, since that is what decides whether a reviewer can read the file at all.
I classified every line of both libraries as blank, comment, assembly or Solidity, matching braces to find assembly regions after stripping comments. Across the whole installed package:
| OpenZeppelin 5.6.1 | Solady @ 2afba69b | |
|---|---|---|
.sol files |
226 | 114 |
| Code lines | 15,639 | 34,605 |
| Of which inline assembly | 2,077 (13.3%) | 19,808 (57.2%) |
| Files containing any assembly | 47 (20.8%) | 111 (97.4%) |
And file by file, for the pairs measured above:
| Same job | OpenZeppelin | Solady |
|---|---|---|
| ERC-20 | 0 of 120 lines | 240 of 360 (66.7%) |
| ERC-721 | 0 of 196 | 310 of 459 (67.5%) |
| ERC-1155 | 9 of 204 (4.4%) | 505 of 737 (68.5%) |
Ownable |
0 of 40 | 69 of 133 (51.9%) |
| Merkle proof | 0 of 235 | 125 of 160 (78.1%) |
| ECDSA | 48 of 147 (32.7%) | 235 of 301 (78.1%) |
EnumerableSet |
24 of 336 (7.1%) | 493 of 694 (71.0%) |
| Base64 | 88 of 118 (74.6%) | 77 of 94 (81.9%) |
Two things fall out of that. OpenZeppelin is not assembly-free — its ECDSA and Base64 are mostly assembly, because those are the places where the compiler's output is genuinely bad. And Solady's ERC-20 is 66.7% assembly where OpenZeppelin's is 0%, which is a real difference in what "reading the dependency" means for an auditor, or for whoever is on call at 3am.
OpenZeppelin's contribution guidelines state the priority order directly: "The code should be simple and straightforward, prioritizing readability and understandability… Sometimes these guidelines may be broken if doing so brings significant efficiency gains, but explanatory comments should be added." The table above is what that policy looks like in line counts, and it is also why OpenZeppelin costs more gas. The two facts are the same fact.
Coverage: they are not the same size library
Counting .sol files in the installed packages, by directory:
| OpenZeppelin 5.6.1 | Solady @ 2afba69b | |
|---|---|---|
| Tokens | 48 | 10 |
| Access / auth | 14 | 4 |
| Account abstraction | 7 | 13 |
| Proxy and upgradeability | 11 | inside utils |
| Governance | 22 | 0 |
| Cross-chain | 5 | 0 |
| Metatransactions | 2 | 0 |
| Finance | 2 | 0 |
| Utilities | 71 | 86 |
| Interfaces | 43 | — |
On the ERC standards they are close. Both ship ERC-20, ERC-721, ERC-1155, ERC-4626, ERC-6909 and royalty support. OpenZeppelin's token directory is larger mostly because of extensions — capped supply, pausable, votes, wrappers, permit, flash minting, enumerable and so on — which Solady mostly does not have.
The parts that exist in only one library are where the decision usually gets made.
Only OpenZeppelin. Governor and its extensions, TimelockController, AccessManager, the transparent and beacon proxy patterns with Initializable and UUPSUpgradeable, ERC-2771 metatransactions, cross-chain bridge contracts, VestingWallet, and the entire interfaces tree. If you are building a DAO, a governed protocol, or anything with an upgrade story someone else has to review, this is not a close call.
Only Solady. A red-black tree, a min-heap, a JSON parser, calldata compression, SSTORE2, CREATE3, an ERC1967Factory, Base58, and account-abstraction primitives including ERC-6551 token-bound accounts, EIP-7702 delegation and ERC-7821 batch execution. OpenZeppelin has caught up on some of that ground since 5.4 and not on the rest. If you need one of these, there is no debate either.
There is also a supporting-cast difference that is easy to miss. @openzeppelin/contracts-upgradeable is a maintained storage-safe mirror of the main package, released in lockstep with it, with @openzeppelin/hardhat-upgrades and @openzeppelin/foundry-upgrades doing layout-compatibility checks at deploy time. Solady's answer to upgradeability is its manual storage layout, which is a genuine engineering answer but not a tool that tells you when you have broken something.
Maintenance: what you get when you install each one
Both libraries are MIT — every .sol file in both trees carries the identifier on line 1. There is no licensing decision here, which is a change from the Solmate comparison, where there was.
The release stories are almost opposites.
OpenZeppelin ships a minor every three to four months — 5.5.0 in October 2025, 5.6.0 in February 2026, 5.7.0 in July 2026 — each preceded by release candidates. npm install @openzeppelin/contracts gives you 5.6.1, not 5.7.0, and that is deliberate: the project uses npm dist-tags to separate audited from unaudited code. Its README is explicit that latest means "Stable, audited versions" and dev means "finalized and feature-complete but have not yet been audited… fully tested, can be used in production and is covered by the bug bounty." 5.7.0 is on dev. If you want the ERC-4337 paymaster contracts it added, you are opting out of the audited line knowingly, which is a far better position than not knowing.
It is worth reading the audit table rather than the word "audited," though. Of the twelve reports in audits/, every one since October 2022 lists OpenZeppelin itself as the auditor. The last external reviews were LevelK in October 2018 and New Alchemy in March 2017, and the three Certora formal-verification reports all stop in 2022. That is a first-party security process run continuously by a security company, with an Immunefi bounty attached — more than most libraries have. It is not the same thing as independent review, and the table says so plainly.
Solady has not cut a tag or published to npm since 25 August 2025. main is 36 commits ahead of v0.1.26, eleven of them bug fixes to LibBytes, LibString, Base58, EnumerableSetLib, ERC1967BeaconProxy and others, with the most recent commit on 2 September 2026. So npm install solady gives you a thirteen-month-old release missing every one of those fixes, and forge install vectorized/solady gives you an untagged commit that no release describes. Neither is a version you can name in an audit scope.
Its five audit reports are real, and some are substantial: a 2023 Cantina public-goods review, and a January 2025 Spearbit review commissioned by Coinbase that found 55 issues including two critical and three high, 44 of which were fixed. But the newest of them targets a commit from December 2024, and main has moved a long way since. The README makes no audit claim at all; what it does say is:
This is experimental software and is provided on an "as is" and "as available" basis. … Please always include your own thorough tests when using Solady to make sure it works correctly with your code.
Take that at face value. It is the correct instruction for any assembly-heavy dependency, and it is the same advice I would give about getting a Solidity project from prototype to production: the tests that matter are the ones you write about your own composition, not the ones your dependency shipped.
The maintenance shapes differ too. OpenZeppelin's default branch took 251 commits in the last twelve months and is maintained by a company with a named security team. Solady took 34 in the same period, and Vectorized has authored 1,478 of its commits against 84 for the next most active contributor. Both projects are concentrated. One of them is concentrated inside an organization with a payroll.
Which brings up the newest fact in this comparison, five days old at the time of writing: on 17 September 2026, S&P Global entered an agreement to acquire OpenZeppelin. The announcement commits that the Contracts libraries "remain open source, free, and publicly maintained on GitHub," that "every released version remains open source permanently and cannot be withdrawn by anyone," and that audits and engineering continue with the same team. MIT is irrevocable for code already published, so the first two of those are guaranteed by the licence regardless of who says them. What an acquisition can change is priorities, cadence, and who the library is optimized for. I would not treat it as a reason to switch. I would treat it as a reason to read the release notes for a while.
What I would do
A gas-sensitive contract with a narrow surface — a token, a claim contract, a vault, anything deployed many times or called at high volume. Solady, pinned to a specific commit rather than the stale tag, with the divergence list above turned into your own tests. The deployment saving alone is 300,216 to 376,065 gas per instance, and this is the case where its trades cost you least.
Anything with governance, upgradeability, or an audit in its future. OpenZeppelin. Not because Solady is unsafe, but because the parts you need are there, they are versioned, an auditor has read them before, and the upgrade tooling will tell you when you have broken your storage layout. Choosing it is not a failure of ambition; it is 456 gas on a transfer.
A mostly-OpenZeppelin codebase with two hot functions. Use both. They are both MIT, they compose fine, and the utility libraries are where Solady's advantage is largest and its trades are smallest — a Merkle verification or a Base64 encoder has no zero-address semantics to get wrong. The one thing not to do is import Solady's ReentrancyGuardTransient onto an L2 and assume you got transient storage.
Not actually gas-bound at all. Check before you optimize. On an L2, or for an operation that happens rarely, the difference between these libraries on the calls users make is one to seven percent, and I have written separately about where the cost of running an Ethereum app actually goes. It is usually not here.
The framing I started with survives the measurement, but only as a summary of two design priorities, not as a ranking. OpenZeppelin's guidelines say readability first and efficiency by exception. Solady's README says gas-optimized snippets and experimental software. Both projects are doing exactly what they said they would. The measurement's job is to say what that costs, and the answer is: far less than the reputation on a transfer, far more than the reputation on a deployment, and occasionally the other way round entirely.