Skip to content
Advanced10 min readUpdated September 2026

Upgradeability Is A Trust Decision

Every upgrade mechanism is a trust assumption imposed on users. Proxy patterns and their trade-offs, timelocks and signer topology, emergency pause, and how to document honestly what you are and are not guaranteeing.

The case for upgradeability looks unanswerable from inside an engineering team. You cannot patch a deployed contract. Defects are inevitable. So you need a mechanism to change the code, or a single mistake becomes permanent. Nobody argues, a proxy goes in, and the decision is recorded as an implementation detail.

It is not an implementation detail. An upgradeable contract is one whose behaviour can be replaced by whoever holds the upgrade authority, so every user is trusting not the code they can read but the people who can change it. You have not removed the risk of a defect; you have added a second risk — that the authority is captured, compromised or used badly — and transferred part of the first onto your users without necessarily telling them.

This is a legitimate trade, and serious systems make it deliberately. What is not legitimate is making it accidentally, calling the result trustless, and leaving users to discover the actual guarantee by reading your deployment scripts. The engineering question is which mechanism to use; the honest question is what you are asking users to trust, and whether you have said so plainly.

The two risks you are trading between

Immutable code carries defect risk. An error is permanent. Your only remedies are social — warn users, deploy a replacement, help people migrate — and your ability to limit damage depends entirely on what was designed in before deployment.

Upgradeable code carries authority risk. Defect risk falls, because you can respond. But the behaviour is now only as trustworthy as the upgrade authority, and the failure modes differ in kind: a compromised key, a coerced signer, governance captured by whoever accumulates enough voting power, an insider, or an upgrade pushed under pressure without the verification the original received.

Authority risk is not a tail case. It is a live exposure every day the system runs, whereas defect risk is largely fixed at deployment and decays as the code survives scrutiny. Over a long enough horizon, the upgrade key is the likelier thing to fail. The two also land differently: defect risk is borne by everyone including you; authority risk is borne by users and controlled by you. When a design moves risk to a party that cannot influence it, you owe that party a clear statement of what you have done.

Proxy patterns and their trade-offs

The mechanics are well trodden, and each pattern moves the risk around rather than removing it.

Transparent proxy. Storage lives in the proxy; logic is delegated to an implementation an admin can replace. Mature and heavily reviewed.

UUPS. Upgrade logic lives in the implementation, making calls cheaper. The sharp edge is unforgiving: deploy an implementation omitting the upgrade function and the contract is frozen at it permanently. A mechanical pipeline check for that is not optional.

Beacon proxy. Many proxies point at one beacon, so one upgrade changes all of them — excellent for fleets of identical instances, and one very valuable target.

Diamond and facet patterns. Function-level routing across multiple implementations. More complex, harder to review and harder for a user to reason about — complexity buying flexibility most systems do not need.

Immutable with migration. No upgrade mechanism: to change behaviour you deploy a new system and users move voluntarily. Strongest guarantee, slowest response.

Parameter-only mutability. Logic fixed; a narrow set of parameters adjustable within hard-coded limits. Frequently the best trade in the list, because it gives most of the flexibility teams want while leaving the behaviour a user reads in the code intact.

PatternResponse to a defectOngoing authority riskComplexitySuits
Transparent proxyFast, fullHigh — logic fully replaceableLowSystems expecting substantial change
UUPSFast, fullHigh, plus lock-out failure modeLow to moderateGas-sensitive, disciplined teams
BeaconFast, fleet-wideVery high — one target, many instancesModerateMany identical instances
Parameter-onlyPartial, boundedBounded by hard-coded limitsLowMost systems, more often than chosen
ImmutableNone on-chainNoneLowestSmall, critical, stable components

The pattern deserving more consideration than it gets is the second from the bottom. Much of what teams want upgradeability for is operational — adjusting a fee within a range, changing a rate limit, pointing at a new price source from a pre-approved set. All of that is achievable with bounded parameters and no ability to replace logic, and the user can still read the code and know what it does. If the honest answer to what you expect to change is parameters and rare emergencies, a full logic-replacement proxy is more authority than the requirement justifies.

One structural note applies whichever pattern you choose: upgrades are where storage-layout errors live. Appending is safe; reordering, removing or retyping an existing variable corrupts state irreversibly. Automated layout comparison between current and proposed implementations belongs in the pipeline as a hard gate.

Timelocks, signer topology and role design

Given an upgrade mechanism, the design work is constraining who can use it, how fast, and with how much warning.

Timelocks convert a silent capability into a visible one. A queued upgrade that cannot execute for a defined delay gives users time to observe it and exit. It is the most valuable constraint available and depends on trusting nobody: a timelock holds even if the authority is fully compromised, because the attacker must wait in public too.

The delay length is a real trade: too short gives no practical opportunity to react, too long makes legitimate operational changes impractical. The common resolution is tiered — longer for logic replacement, shorter for bounded parameters, and a separate fast path for pausing, which can only ever restrict behaviour.

Multisig topology is a threat model, not a number. An n-of-m arrangement is usually chosen by picking numbers that feel prudent. The questions that matter are different. Are the signers genuinely independent, or reachable by the same pressure? Are their keys on distinct hardware? Do they share an operational process a single compromise would defeat? Would enough be reachable in a real emergency, across time zones? Is there a rehearsed procedure for rotating a signer who loses a key or leaves?

Keys in hardware wallets or HSMs, geographically and organisationally separated, with documented rotation, matter more than the threshold. A five-of-nine where seven signers sit in one office is weaker than a truly independent two-of-three.

Roles should be separated by capability, not bundled into an owner. A single admin address that can do everything is an unnecessary single point of catastrophic failure. Separate the authority to replace logic, change parameters, pause, unpause and change the roles themselves, giving each the weakest holder and strongest constraint consistent with its job. Pausing can be fast and broadly held because it only restricts; logic replacement should be the slowest and most tightly held thing in the system.

Governance is an attack surface with its own mechanics. Where upgrade authority sits with token holders, the risks are ownership concentration, borrowed voting power, low participation making a small stake decisive, and proposals whose described effect differs from their encoded effect. Governance does not remove the trust assumption; it redistributes it to whoever can assemble a majority, which may be cheaper than compromising a well-run multisig. Proposal delays, execution timelocks, quorum requirements and independent verification of payloads deserve the same seriousness as the contracts.

Emergency pause and its honest cost

A pause function looks like a pure safety feature. It is not: it is another authority, and needs the same scrutiny. The case for it is strong. An exploit in progress can sometimes be stopped, and the gap between noticing and acting is the difference between a contained incident and a complete loss. If you have a pause it must be genuinely fast — an emergency capability behind a multi-day timelock is decoration.

The costs should be stated too. Whoever can pause can freeze user funds, a serious power with implications beyond engineering; a compromised authority can trigger it as a denial of service; and its existence gives an attacker reason to move faster.

The design that generally holds up:

Make pausing fast and unpausing slow. Restricting is low-risk and time-critical; restoring is high-risk and should be deliberate.

Give the pause key to a group assemblable at three in the morning. A capability that depends on unreachable people is not a capability.

Scope the pause precisely. Halting deposits while leaving withdrawals open is often better than a blanket freeze, and must be decided in advance because you will not decide it well under fire.

Rehearse it. Run the pause on a fork, on testnet, and ideally once in a controlled window on mainnet. An untested emergency procedure is an assumption, and assumptions fail under the conditions you built it for.

Documenting the guarantee you are actually offering

Here is the part most often skipped, and it matters most. Write down plainly what users can rely on and what they cannot, in language they can follow rather than buried in technical documentation.

What can change. Logic, parameters within stated bounds, or nothing.

Who can change it, and how much warning there is. Named roles, the topology behind each, how independent the signers are, and the timelock for each class of change.

What can be done immediately, without notice. Pausing, usually — say so explicitly, because this is the capability users most often do not realise exists.

Whether user funds can be moved by anyone other than the user. The most important question in the document. Answer it directly.

What would remove these powers, and when. If you intend to relinquish upgrade authority once the system has proven itself, say what the condition is. If you have no such plan, say that instead.

The temptation is to present all this in the most favourable light — to describe a system as decentralised when an upgrade key exists, or non-custodial while retaining the ability to replace the logic governing custody. Resist it. Users who find the gap later will reasonably conclude the framing was deliberate, and that judgement attaches to everything else you have said. Stating plainly that the team retains upgrade authority under a timelock, with named constraints and a path to reducing it, is a credible position many serious systems hold. Claiming a guarantee you do not provide is not. Internally, treat upgrade authority as a control with documented objectives, evidence and periodic review, exactly as agile in regulated environments describes.

Upgrades are deployments, and deployments are irreversible

One failure pattern deserves naming, because it undoes everything above. Teams apply enormous rigour to the initial deployment — threat modelling, invariants, fuzzing, external review, a staged rollout — then treat upgrades as routine, and the second implementation goes out with a fraction of the scrutiny the first received.

An upgrade is a deployment to a contract that currently holds value. If anything it is higher risk than the original, because there is real money in it and it meets live state a fresh deployment does not. The full verification loop applies, including external review scoped to the change and its blast radius, with the lead time that implies — see audit scheduling and the code freeze. And delivering software you cannot patch applies without discount: the upgrade transaction is as final as the deployment was.

An upgrade mechanism used casually is worse than none: it carries the authority risk while abandoning the discipline that justified taking it.

What to do on Monday

List every privileged capability in your deployed or planned system — everything any address can do that an ordinary user cannot. Read the code, not the documentation. Most teams find at least one capability nobody remembered granting, usually an owner role on a component never meant to be mutable.

For each, write the holder, the topology behind it, the delay before it takes effect, and whether it can move or freeze user funds. Then show that table to someone outside the team and ask what they would need to trust in order to use the system. Their answer is your actual trust model, and if it surprises you, your documentation is wrong.

Then publish it, in plain language, where users will find it. And pick the one capability broader than it needs to be — there is almost always one — and narrow it: bound it with limits, put it behind a timelock, or split it into separate roles. That is a real reduction in the risk you are asking other people to carry, and it can generally be done in a week.