Skip to content

Conversation

@codermaybe
Copy link

Description

Adds an optional storage layout compatibility check mechanism for UUPS upgrades to prevent storage collision vulnerabilities.

Changes

  • Added StorageLayoutMismatch() error to UUPSUpgradeable.sol
  • Added _checkStorageLayout(address newImplementation) virtual hook (empty by default for backwards compatibility)
  • Provided reference implementation in MockUUPSImplementation.sol using version hash comparison
  • Added test cases for both compatible and incompatible layout scenarios

Usage Pattern

Implementations can override _checkStorageLayout to enforce storage compatibility:

bytes32 private constant _STORAGE_LAYOUT_VERSION = keccak256("MyContract.v1");

function STORAGE_LAYOUT_VERSION() external pure returns (bytes32) {
    return _STORAGE_LAYOUT_VERSION;
}

function _checkStorageLayout(address newImpl) internal view override {
    // Check version hash matches - see MockUUPSImplementation.sol for full example
}

Design Decisions

  • Opt-in mechanism: Default empty implementation preserves existing behavior
  • Non-breaking: Zero impact on contracts that don't override the hook
  • Gas-optimized: Uses assembly for efficient staticcall and error handling
  • Flexible: Implementations can choose their own validation strategy

Addresses the storage layout safety concern raised in the original UUPS test demo.

Closes #1489

Checklist

Ensure you completed all of the steps below before submitting your pull request:

  • Ran forge fmt?
  • Ran forge test?

@atarpara
Copy link
Collaborator

atarpara commented Nov 27, 2025

Etherscan doesn’t support automatic verification of different storage slots as defined in EIP-1967. Therefore, adding a _checkStorageLayout() function isn’t very useful for most applications that aim to remain fully transparent.

Also, if a developer wants to include this kind of logic, they can simply add it inside the _authorizeUpgrade(address) function. So adding 2 virtual hooks are unnecessary.

@codermaybe
Copy link
Author


Comment:

Hi @atarpara, thanks for the review. I understand the hesitation to add surface area, but I'd like to make a case for why this specific safeguard belongs in the core library:

  1. Critical Safety Gap: Storage layout collisions are the most dangerous class of bugs in UUPS upgrades. While users can implement checks manually in _authorizeUpgrade, it conflates permission logic (who can upgrade?) with sanity checks (is the upgrade safe?). Separating these concerns prevents fatal mistakes.
  2. Zero Cost Abstraction: Since the hooks are virtual and empty, they compile to zero runtime gas if not used. There is no performance penalty for the default user.
  3. Standardization: Currently, every team using Solady UUPS has to reinvent this wheel. Providing a standard, opt-in hook allows developers to implement layout guards (like hash checks) cleanly without cluttering their auth logic.

The goal isn't to force overhead, but to make the safest path the default path for developers who want it.

If the concern is the number of hooks, would you be open to a single _checkUpgrade hook that runs before the upgrade? This would satisfy the safety requirement while keeping the API minimal.



/// @dev Optional hook to guard against storage layout incompatibility.
/// Override in implementation if desired (e.g. compare a constant layout hash).
function _checkStorageLayout(address newImplementation) internal virtual {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codermaybe The child contract won’t compile if it doesn’t implement _checkStorageLayout, because solady.UUPSUpgradeable is marked as abstract and the function is declared but not implemented. Abstract contracts with unimplemented functions require the child to provide an implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Therefore, the dev is required to implement both functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UUPS upgrade: support checking slot conflicts

2 participants