From 4678b4dd14cbec4726703c5cc62879aba8b4a63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Tue, 12 Nov 2024 15:15:44 +0100 Subject: [PATCH 1/2] implement diamond storage pattern --- README.md | 6 ++-- src/TransparentVerifiableProxy.sol | 45 ++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2770e25..5618f66 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ A system for deploying and verifying proxy contracts with predictable storage la ### 2. TransparentVerifiableProxy - Transparent proxy pattern with verified storage layout -- Fixed storage slots: - - Slot 0: `salt` (uint256) - - Slot 1: `owner` (address) +- Fixed storage slots via Diamond Storage pattern: + - Slot 'proxy.verifiable.salt': `salt` (uint256) + - Slot 'proxy.verifiable.owner': `owner` (address) - Immutable `creator` field (set in bytecode) - Implements secure upgrade mechanism - Initializable to prevent implementation tampering diff --git a/src/TransparentVerifiableProxy.sol b/src/TransparentVerifiableProxy.sol index 08174af..d19de15 100644 --- a/src/TransparentVerifiableProxy.sol +++ b/src/TransparentVerifiableProxy.sol @@ -9,6 +9,21 @@ import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +// EIP-2535 Diamond Storage pattern +// ref: https://eips.ethereum.org/EIPS/eip-2535#storage +library StorageSlot { + bytes32 constant SLOT_SALT = keccak256("proxy.verifiable.salt"); + bytes32 constant SLOT_OWNER = keccak256("proxy.verifiable.owner"); + + function getSaltSlot() internal pure returns (bytes32) { + return SLOT_SALT; + } + + function getOwnerSlot() internal pure returns (bytes32) { + return SLOT_OWNER; + } +} + interface ITransparentVerifiableProxy { /// @dev See {UUPSUpgradeable-upgradeToAndCall} function upgradeToAndCall(address newImplementation, bytes calldata data) external payable; @@ -18,10 +33,6 @@ contract TransparentVerifiableProxy is Proxy, Initializable { // immutable variable (in bytecode) address public immutable creator; - // storage variables (in storage slots) - uint256 public salt; // Salt, being used creating the proxy (slot 0) - address public owner; // The owner of the proxy contract (slot 1) - // ### EVENTS error ProxyDeniedOwnerAccess(); @@ -52,12 +63,34 @@ contract TransparentVerifiableProxy is Proxy, Initializable { { require(implementation != address(0), "New implementation cannot be the zero address"); - salt = _salt; - owner = _owner; + bytes32 saltSlot = StorageSlot.getSaltSlot(); + bytes32 ownerSlot = StorageSlot.getOwnerSlot(); + assembly { + sstore(saltSlot, _salt) + sstore(ownerSlot, _owner) + } ERC1967Utils.upgradeToAndCall(implementation, data); } + function salt() public view returns (uint256) { + bytes32 slot = StorageSlot.getSaltSlot(); + uint256 value; + assembly { + value := sload(slot) + } + return value; + } + + function owner() public view returns (address) { + bytes32 slot = StorageSlot.getOwnerSlot(); + address value; + assembly { + value := sload(slot) + } + return value; + } + /** * @dev Returns the current implementation address. * From bfcf334bfad011287be15a0b346ae1c60a125e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Thu, 21 Nov 2024 17:06:14 +0100 Subject: [PATCH 2/2] use SlotDerivation for the fixed storage slot --- README.md | 6 +-- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- src/TransparentVerifiableProxy.sol | 64 +++++++++++++++--------------- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 5618f66..382edf4 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ A system for deploying and verifying proxy contracts with predictable storage la ### 2. TransparentVerifiableProxy - Transparent proxy pattern with verified storage layout -- Fixed storage slots via Diamond Storage pattern: - - Slot 'proxy.verifiable.salt': `salt` (uint256) - - Slot 'proxy.verifiable.owner': `owner` (address) +- Fixed storage slots via [SlotDerivation](https://docs.openzeppelin.com/contracts/5.x/api/utils#SlotDerivation) under `proxy.verifiable` namespace + - `salt` (uint256) + - `owner` (address) - Immutable `creator` field (set in bytecode) - Implements secure upgrade mechanism - Initializable to prevent implementation tampering diff --git a/lib/forge-std b/lib/forge-std index 1714bee..2b59872 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1714bee72e286e73f76e320d110e0eaf5c4e649d +Subproject commit 2b59872eee0b8088ddcade39fe8c041e17bb79c0 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index dbb6104..a277d47 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 +Subproject commit a277d472d657dbbcd88a3de5cae0c806130e2df2 diff --git a/src/TransparentVerifiableProxy.sol b/src/TransparentVerifiableProxy.sol index d19de15..787457b 100644 --- a/src/TransparentVerifiableProxy.sol +++ b/src/TransparentVerifiableProxy.sol @@ -8,21 +8,8 @@ pragma solidity ^0.8.20; import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; - -// EIP-2535 Diamond Storage pattern -// ref: https://eips.ethereum.org/EIPS/eip-2535#storage -library StorageSlot { - bytes32 constant SLOT_SALT = keccak256("proxy.verifiable.salt"); - bytes32 constant SLOT_OWNER = keccak256("proxy.verifiable.owner"); - - function getSaltSlot() internal pure returns (bytes32) { - return SLOT_SALT; - } - - function getOwnerSlot() internal pure returns (bytes32) { - return SLOT_OWNER; - } -} +import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol"; +import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; interface ITransparentVerifiableProxy { /// @dev See {UUPSUpgradeable-upgradeToAndCall} @@ -30,6 +17,14 @@ interface ITransparentVerifiableProxy { } contract TransparentVerifiableProxy is Proxy, Initializable { + using StorageSlot for bytes32; + using SlotDerivation for bytes32; + using SlotDerivation for string; + + string internal constant _VERIFICATION_SLOT = "proxy.verifiable"; + string internal constant _SALT = "salt"; + string internal constant _OWNER = "owner"; + // immutable variable (in bytecode) address public immutable creator; @@ -63,32 +58,19 @@ contract TransparentVerifiableProxy is Proxy, Initializable { { require(implementation != address(0), "New implementation cannot be the zero address"); - bytes32 saltSlot = StorageSlot.getSaltSlot(); - bytes32 ownerSlot = StorageSlot.getOwnerSlot(); + bytes32 baseSlot = _VERIFICATION_SLOT.erc7201Slot(); + _setSalt(baseSlot, _salt); + _setOwner(baseSlot, _owner); - assembly { - sstore(saltSlot, _salt) - sstore(ownerSlot, _owner) - } ERC1967Utils.upgradeToAndCall(implementation, data); } function salt() public view returns (uint256) { - bytes32 slot = StorageSlot.getSaltSlot(); - uint256 value; - assembly { - value := sload(slot) - } - return value; + return _getSalt(_VERIFICATION_SLOT.erc7201Slot()); } function owner() public view returns (address) { - bytes32 slot = StorageSlot.getOwnerSlot(); - address value; - assembly { - value := sload(slot) - } - return value; + return _getOwner(_VERIFICATION_SLOT.erc7201Slot()); } /** @@ -129,5 +111,21 @@ contract TransparentVerifiableProxy is Proxy, Initializable { ERC1967Utils.upgradeToAndCall(newImplementation, data); } + function _getSalt(bytes32 baseSlot) internal view returns (uint256) { + return baseSlot.deriveMapping(_SALT).getUint256Slot().value; + } + + function _setSalt(bytes32 baseSlot, uint256 _salt) internal { + baseSlot.deriveMapping(_SALT).getUint256Slot().value = _salt; + } + + function _getOwner(bytes32 baseSlot) internal view returns (address) { + return baseSlot.deriveMapping(_OWNER).getAddressSlot().value; + } + + function _setOwner(bytes32 baseSlot, address _owner) internal { + baseSlot.deriveMapping(_OWNER).getAddressSlot().value = _owner; + } + receive() external payable {} }