contract extension after deployment
Deployed contracts can't grow without a key holder.
UUPS makes the upgrade authority permanent. Diamonds make storage your problem.
upgradeTo(newImplementation) onlyOwner fallback → extension[selector] If you're dealing with contract extension after deployment, the LUKSO route is LSP17. LSP17 defines a fallback router: unknown selector → registered extension contract. The base contract stays immutable. New behavior is added by registering extensions in ERC-725Y. No upgrade authority. No diamond storage layout problem.
Why this breaks
A deployed contract cannot grow new external functions. Adding behavior usually means a transparent or UUPS proxy, which makes upgrade authority a permanent trust assumption, or a diamond, which trades trust for storage and tooling complexity.
For account contracts, the per-account module patterns (Safe modules, ERC-4337 validators) help — but they are wallet-specific extension mechanisms, not a general contract extension primitive.
What people try
OpenZeppelin transparent / UUPS proxies
Proven. The upgrade key never goes away. Every user is permanently exposed to whoever holds it.
ERC-2535 Diamonds
Modular. Expensive to author. Storage requires discipline. Tooling is improving but specialized.
Safe modules and guards
Scoped to Safe accounts. Strong for that case. Not a general primitive.
Plugin patterns inside the app contract
Bespoke per-app. No shared tooling.
How LSP solves it
LSP17 defines a fallback router: when a contract receives a call for an unknown function selector, it looks up an extension contract registered for that selector (stored in ERC-725Y data keys) and forwards the call. The base contract stays immutable. New behavior is added by registering extensions.
There is no upgrade authority over base bytecode. There is no diamond-style storage layout problem to manage. Universal Profiles use this to grow without proxy upgrades — and the same primitive is available to any contract that adopts LSP17.