Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/facets/strategies/AaveStrategyFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ contract AaveStrategyFacet {

event AaveConfigSet(IAavePool indexed pool, IERC20 indexed aToken);

/// @dev erc7201:vaultrouter.strategy.aave
/// @dev Precomputed erc7201("vaultrouter.strategy.aave"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant AAVE_STORAGE_SLOT = 0x340080245a7d3e67835fb5055646777827d09fc7212fda4d8d724367e1215700;

/// @custom:storage-location erc7201:vaultrouter.strategy.aave
struct AaveStorage {
IAavePool pool;
IERC20 aToken;
Expand Down
5 changes: 3 additions & 2 deletions src/facets/strategies/MorphoStrategyFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ contract MorphoStrategyFacet {
/// @param vault The Metamorpho ERC4626 vault now active for this strategy.
event MorphoVaultSet(IMorpho indexed vault);

/// @notice EIP-7201 namespaced storage slot for the Morpho strategy state.
/// @dev erc7201:vaultrouter.strategy.morpho
/// @dev Precomputed erc7201("vaultrouter.strategy.morpho"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant MORPHO_STORAGE_SLOT = 0xf4b3fd2d8603f5a74e31f8c3250c4c70408eaa33a47a7d4535036bfa6799e900;

/// @notice Storage layout for the Morpho strategy facet.
/// @dev `vault` is the configured Metamorpho ERC4626 vault.
/// @custom:storage-location erc7201:vaultrouter.strategy.morpho
struct MorphoStorage {
IMorpho vault;
}
Expand Down
4 changes: 3 additions & 1 deletion src/facets/strategies/PendlePtStrategyFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ contract PendlePtStrategyFacet {
// Storage
// -----------------------------------------------------------------------

/// @dev erc7201:vaultrouter.strategy.pendle
/// @dev Precomputed erc7201("vaultrouter.strategy.pendle"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant PENDLE_STORAGE_SLOT = 0xb0e016db49ce2cfbe35770c2200cbf5f1a9b502bca57dbaaddf328cb9e0cef00;

/// @dev Basis-points denominator.
Expand All @@ -97,6 +98,7 @@ contract PendlePtStrategyFacet {
/// @dev Slippage tolerance (bps) used when none is explicitly configured: 1%.
uint16 internal constant DEFAULT_MAX_SLIPPAGE_BPS = 100;

/// @custom:storage-location erc7201:vaultrouter.strategy.pendle
struct PendleStorage {
/// @notice PendleRouterV4 — handles all swap and redemption paths.
IPendleRouter router;
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/LibAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pragma solidity ^0.8.24;
/// LibDiamond's selector table.
/// @dev keccak256(abi.encode(uint256(keccak256("vaultrouter.storage.allocator")) - 1)) & ~bytes32(uint256(0xff))
library LibAllocator {
/// @dev Precomputed erc7201("vaultrouter.storage.allocator"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant ALLOCATOR_STORAGE_SLOT =
0x2f4e489fd9fdb4c68f60ae0ec4a19ea4d9796e41932a74c08f691957213bd500;

Expand Down
4 changes: 3 additions & 1 deletion src/libraries/LibFees.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ library LibFees {
uint16 internal constant MAX_MANAGEMENT_FEE_BPS = 1000; // 10% / year sanity ceiling
uint256 internal constant SECONDS_PER_YEAR = 365 days;

/// @dev erc7201:vaultrouter.storage.fees
/// @dev Precomputed erc7201("vaultrouter.storage.fees"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant FEE_STORAGE_SLOT = 0xd8263cd2923de1a73423e53eeb7d7ffc12f7b4ef6a8eadaee1bbca5e38dbe600;

/// @custom:storage-location erc7201:vaultrouter.storage.fees
struct FeeStorage {
address feeRecipient;
uint16 performanceFeeBps;
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/LibGuard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ library LibGuard {
/// share-price math below never drift apart.
uint8 internal constant DECIMALS_OFFSET = 6;

/// @dev Precomputed erc7201("vaultrouter.storage.guard"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant GUARD_STORAGE_SLOT = 0x2e670cc2b429ff4c75b2b5ce7b57521bb8c3d00aaafa77116d454e88d382a900;

/// @notice Reverted on any deposit/withdraw while the breaker is latched.
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/LibLock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ library LibLock {
/// @notice Reverted when shares are moved before their lock window elapses.
error SharesLocked(address account, uint256 lockedUntil);

/// @dev erc7201:vaultrouter.storage.lock
/// @dev Precomputed erc7201("vaultrouter.storage.lock"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant LOCK_STORAGE_SLOT = 0x98796fb009fa2d66e5ccc76be36c65ba72c5853e7fe1b4f23987457f018b5800;

/// @custom:storage-location erc7201:vaultrouter.storage.lock
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/LibRoles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { LibDiamond } from "./LibDiamond.sol";
/// table, or any other facet's namespace.
/// keccak256(abi.encode(uint256(keccak256("vaultrouter.storage.roles")) - 1)) & ~bytes32(uint256(0xff))
library LibRoles {
/// @dev Precomputed erc7201("vaultrouter.storage.roles"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant ROLES_STORAGE_SLOT = 0x72812988d549c1f62ecdf8218c688f5047bef5695066f17d8d1060ecc0962300;

error NotCurator(address caller);
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/LibWithdrawQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ pragma solidity ^0.8.24;
/// Storage location:
/// keccak256(abi.encode(uint256(keccak256("vaultrouter.storage.withdrawqueue")) - 1)) & ~bytes32(uint256(0xff))
library LibWithdrawQueue {
/// @dev erc7201:vaultrouter.storage.withdrawqueue
/// @dev Precomputed erc7201("vaultrouter.storage.withdrawqueue"):
/// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant WITHDRAW_QUEUE_STORAGE_SLOT =
0x3d5a7857d3d4e9dbe18f39f41bd8fd54a510e284a7d9a4464e9cc2159e9f9100;

Expand Down
62 changes: 62 additions & 0 deletions test/unit/StorageNamespaces.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { Test } from "forge-std/Test.sol";

import { LibAllocator } from "../../src/libraries/LibAllocator.sol";
import { LibFees } from "../../src/libraries/LibFees.sol";
import { LibGuard } from "../../src/libraries/LibGuard.sol";
import { LibLock } from "../../src/libraries/LibLock.sol";
import { LibRoles } from "../../src/libraries/LibRoles.sol";
import { LibWithdrawQueue } from "../../src/libraries/LibWithdrawQueue.sol";
import { AaveStrategyFacet } from "../../src/facets/strategies/AaveStrategyFacet.sol";
import { MorphoStrategyFacet } from "../../src/facets/strategies/MorphoStrategyFacet.sol";
import { PendlePtStrategyFacet } from "../../src/facets/strategies/PendlePtStrategyFacet.sol";

// The strategy facets keep their slot constant `internal` at contract scope, which is not
// reachable via qualified access, so a thin heir exposes it for the invariant check.
contract AaveExposer is AaveStrategyFacet {
function exposedSlot() external pure returns (bytes32) {
return AAVE_STORAGE_SLOT;
}
}

contract MorphoExposer is MorphoStrategyFacet {
function exposedSlot() external pure returns (bytes32) {
return MORPHO_STORAGE_SLOT;
}
}

contract PendleExposer is PendlePtStrategyFacet {
function exposedSlot() external pure returns (bytes32) {
return PENDLE_STORAGE_SLOT;
}
}

/// @title Storage namespace invariant
/// @notice Every precomputed storage-slot literal in the protocol must equal the ERC-7201
/// hash of its declared namespace string. This pins each named literal to its
/// namespace so the two can never silently drift: change a namespace string
/// without recomputing the slot, or paste a wrong literal, and this test fails.
/// @dev The detector proves slots do not collide with each other; this proves each slot
/// is the one its namespace actually resolves to.
contract StorageNamespacesTest is Test {
/// @dev erc7201(id) = keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
function _erc7201(string memory id) internal pure returns (bytes32) {
return keccak256(abi.encode(uint256(keccak256(bytes(id))) - 1)) & ~bytes32(uint256(0xff));
}

function test_storageSlotsMatchTheirNamespaces() public {
assertEq(LibAllocator.ALLOCATOR_STORAGE_SLOT, _erc7201("vaultrouter.storage.allocator"), "allocator");
assertEq(LibFees.FEE_STORAGE_SLOT, _erc7201("vaultrouter.storage.fees"), "fees");
assertEq(LibGuard.GUARD_STORAGE_SLOT, _erc7201("vaultrouter.storage.guard"), "guard");
assertEq(LibLock.LOCK_STORAGE_SLOT, _erc7201("vaultrouter.storage.lock"), "lock");
assertEq(LibRoles.ROLES_STORAGE_SLOT, _erc7201("vaultrouter.storage.roles"), "roles");
assertEq(
LibWithdrawQueue.WITHDRAW_QUEUE_STORAGE_SLOT, _erc7201("vaultrouter.storage.withdrawqueue"), "withdrawqueue"
);
assertEq(new AaveExposer().exposedSlot(), _erc7201("vaultrouter.strategy.aave"), "aave");
assertEq(new MorphoExposer().exposedSlot(), _erc7201("vaultrouter.strategy.morpho"), "morpho");
assertEq(new PendleExposer().exposedSlot(), _erc7201("vaultrouter.strategy.pendle"), "pendle");
}
}