Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3f36141
Update natspec authors
dadadave80 Oct 20, 2025
31c000f
Simplify imports
dadadave80 Mar 27, 2026
ed83593
Update get selectors natspec comment
dadadave80 Mar 27, 2026
8dfa286
forge fmt LibDiamond
dadadave80 Mar 28, 2026
a0f3c62
Add ci profile
dadadave80 Mar 28, 2026
81e7223
Update config
dadadave80 Mar 28, 2026
17d61e1
Merge pull request #18 from dadadave80/chore/updates-and-fixes
dadadave80 Mar 28, 2026
1f4ae73
Update Library naming and internal function selectors
dadadave80 Mar 28, 2026
c0748b9
Remove unused errors
dadadave80 Mar 28, 2026
de52d9e
Merge pull request #19 from dadadave80/chore/rename-and-refactor-libs
dadadave80 Mar 28, 2026
fd936c3
Refactor OwnableRolesLib: rename functions and simplify assembly
dadadave80 Apr 3, 2026
989f5a5
Add initializable Diamond pattern
dadadave80 Apr 3, 2026
cec45fb
Extract OwnableInit and ERC165Init from DiamondInit
dadadave80 Apr 3, 2026
392fb29
Fix postReinitializer version storage layout
dadadave80 Apr 3, 2026
64ccb7a
Add initializable pattern test coverage
dadadave80 Apr 3, 2026
df59407
Merge pull request #20 from dadadave80/feat/initializable-diamond
dadadave80 Apr 3, 2026
10f0a78
Use forge inspect via FFI for selector extraction
dadadave80 Apr 3, 2026
a6731b2
Add extensive diamond test coverage
dadadave80 Apr 3, 2026
4a64dd8
Remove memory-variant diamond cut functions
dadadave80 Apr 3, 2026
6204025
Merge pull request #21 from dadadave80/chore/updates-and-fixes
dadadave80 Apr 3, 2026
c944a22
Update remappings
dadadave80 Apr 3, 2026
3046014
Update CONTRIBUTING.md
dadadave80 Apr 3, 2026
8ae8feb
Remove standalone Diamond init
dadadave80 Apr 8, 2026
1fea698
Add Ownable lib and facet
dadadave80 Apr 8, 2026
18d138c
Replace OwnableRoles with Ownable lib
dadadave80 Apr 8, 2026
1dbc25a
Delete OwnableRoles facet and lib
dadadave80 Apr 8, 2026
dd922fc
Merge pull request #22 from dadadave80:chore/replace-ownable-roles-wi…
dadadave80 Apr 8, 2026
7fab8df
Remove unused view helpers and tighten internal visibility
dadadave80 Apr 13, 2026
8cc146f
Update ERC-7201 storage namespace to diamond.lib.storage
dadadave80 Apr 13, 2026
b3d2deb
Update authorship attribution
dadadave80 Apr 13, 2026
5465dd7
Add immutable function guard to addFacet
dadadave80 Apr 13, 2026
a9abfe9
Optimize storage access and extract _removeSelectorFromOldFacet
dadadave80 Apr 13, 2026
7ea4417
Hardcode ERC-165 interface support in assembly and remove ERC165Init
dadadave80 Apr 13, 2026
da90f6d
Extract supportsInterface into dedicated ERC165Facet
dadadave80 Apr 13, 2026
212caa6
Merge pull request #23 from dadadave80:feat/erc165-lib
dadadave80 Apr 13, 2026
de4b408
Make Diamond contract non-abstract for direct deployment
dadadave80 Apr 14, 2026
7d7971e
Consolidate duplicate DiamondLib imports in DiamondLoupeFacet
dadadave80 Apr 14, 2026
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
55 changes: 53 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,54 @@
## How to contribute to ERC2535 Diamond Template
# Contributing to Diamond Lib

#### **Make PR 😀**
Thanks for your interest in contributing! This is a reusable Foundry library for the [EIP-2535 Diamond Standard](https://eips.ethereum.org/EIPS/eip-2535), so contributions that improve reliability, gas efficiency, or developer experience are welcome.

## Prerequisites

- [Foundry](https://book.getfoundry.sh/getting-started/installation) installed
- Familiarity with the [EIP-2535 Diamond Standard](https://eips.ethereum.org/EIPS/eip-2535)

## Setup

```sh
git clone https://github.com/dadadave80/diamond-lib.git
cd diamond-lib
forge install
forge build
```

## Running Tests

Tests use FFI for selector extraction, so the `--ffi` flag is required:

```sh
forge test --ffi -vvv
```

## Making Changes

1. Fork the repo and create a branch from `main`.
2. Make your changes in the appropriate directory:
- `src/facets/` — Facet contracts
- `src/libraries/` — Core libraries
- `src/interfaces/` — Interfaces
- `src/initializers/` — Initializer contracts
3. Add or update tests in `test/` to cover your changes.
4. Run `forge fmt` to format your code.
5. Ensure all tests pass with `forge test --ffi`.
6. Open a pull request against `main`.

## Guidelines

- Keep gas efficiency in mind — this library is optimized for minimal overhead.
- Follow existing code style and naming conventions.
- One logical change per PR — avoid bundling unrelated changes.
- Include test cases for both success and failure paths.

## Reporting Issues

If you find a bug, please open an issue with:
- A clear description of the problem
- Steps to reproduce
- Expected vs. actual behavior

For security vulnerabilities, please follow the process in [SECURITY.md](SECURITY.md) instead.
13 changes: 9 additions & 4 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ remappings = [
'@diamond/=src/',
'@diamond-script/=script/',
'@diamond-test/=test/',
'@diamond-storage/=src/libraries/storage/',
'@diamond-logs/=src/libraries/logs/',
'@diamond-errors/=src/libraries/errors/'
]
optimizer_runs = 999_999
optimizer_runs = 1_000_000
via_ir = false
ffi = true

[profile.ci]
optimizer_runs = 1_000_000
via_ir = false

[profile.ci.fuzz]
runs = 1024

[fmt]
sort_imports = true
3 changes: 0 additions & 3 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
@diamond/=src/
@diamond-script/=script/
@diamond-test/=test/
@diamond-storage/=src/libraries/storage/
@diamond-logs/=src/libraries/logs/
@diamond-errors/=src/libraries/errors/
forge-std/=lib/forge-std/src/
48 changes: 34 additions & 14 deletions script/DeployDiamond.s.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {FacetCut, FacetCutAction} from "@diamond-storage/DiamondStorage.sol";
import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol";
import {MockDiamond} from "@diamond-test/mocks/MockDiamond.sol";
import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol";
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
import {OwnableRolesFacet} from "@diamond/facets/OwnableRolesFacet.sol";
import {DiamondInit} from "@diamond/initializers/DiamondInit.sol";
import {LibContext} from "@diamond/libraries/LibContext.sol";
import {ERC165Facet} from "@diamond/facets/ERC165Facet.sol";
import {OwnableFacet} from "@diamond/facets/OwnableFacet.sol";
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
import {OwnableInit} from "@diamond/initializers/OwnableInit.sol";
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
import {FacetCut, FacetCutAction} from "@diamond/libraries/DiamondLib.sol";
import {Script} from "forge-std/Script.sol";

/// @title DeployDiamond
/// @notice Deployment script for an EIP-2535 Diamond proxy contract with core facets and ERC165 initialization
/// @author David Dada
///
/// @dev Uses Foundry's `Script` and a helper contract to deploy and wire up DiamondCutFacet, DiamondLoupeFacet, and OwnableRolesFacet
/// @dev Uses Foundry's `Script` and a helper contract to deploy and wire up DiamondCutFacet, DiamondLoupeFacet, and OwnableFacet
contract DeployDiamond is Script, GetSelectors {
/// @notice Executes the deployment of the Diamond contract with the initial facets and ERC165 interface setup
/// @dev Broadcasts transactions using Foundry's scripting environment (`vm.startBroadcast()` and `vm.stopBroadcast()`).
Expand All @@ -26,13 +28,15 @@ contract DeployDiamond is Script, GetSelectors {
// Deploy core facet contracts
DiamondCutFacet diamondCutFacet = new DiamondCutFacet();
DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet();
OwnableRolesFacet ownableRolesFacet = new OwnableRolesFacet();
ERC165Facet erc165Facet = new ERC165Facet();
OwnableFacet ownableFacet = new OwnableFacet();

// Deploy ERC165 initializer contract
address diamondInit = address(new DiamondInit());
// Deploy initializer contracts
address multiInit = address(new MultiInit());
address ownableInit = address(new OwnableInit());

// Create an array of FacetCut entries for standard facets
FacetCut[] memory cut = new FacetCut[](3);
FacetCut[] memory cut = new FacetCut[](4);

// Add DiamondCutFacet to the cut list
cut[0] = FacetCut({
Expand All @@ -48,16 +52,32 @@ contract DeployDiamond is Script, GetSelectors {
functionSelectors: _getSelectors("DiamondLoupeFacet")
});

// Add OwnableRolesFacet to the cut list
// Add ERC165Facet to the cut list
cut[2] = FacetCut({
facetAddress: address(ownableRolesFacet),
facetAddress: address(erc165Facet),
action: FacetCutAction.Add,
functionSelectors: _getSelectors("OwnableRolesFacet")
functionSelectors: _getSelectors("ERC165Facet")
});

// Add OwnableFacet to the cut list
cut[3] = FacetCut({
facetAddress: address(ownableFacet),
action: FacetCutAction.Add,
functionSelectors: _getSelectors("OwnableFacet")
});

// Build MultiInit arrays for granular initialization
address[] memory initAddresses = new address[](1);
bytes[] memory initData = new bytes[](1);

initAddresses[0] = ownableInit;
initData[0] = abi.encodeWithSignature("initOwner(address)", ContextLib.msgSender());

// Deploy the Diamond contract with the facets and initialization args
MockDiamond diamond =
new MockDiamond(cut, diamondInit, abi.encodeWithSignature("initDiamond(address)", LibContext._msgSender()));
MockDiamond diamond = new MockDiamond();
diamond.initialize(
cut, multiInit, abi.encodeWithSignature("multiInit(address[],bytes[])", initAddresses, initData)
);
diamond_ = address(diamond);
vm.stopBroadcast();
}
Expand Down
56 changes: 25 additions & 31 deletions src/Diamond.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {FacetCut} from "@diamond-storage/DiamondStorage.sol";
import {LibDiamond} from "@diamond/libraries/LibDiamond.sol";
import {DiamondLib, FacetCut} from "@diamond/libraries/DiamondLib.sol";
import {InitializableLib} from "@diamond/libraries/InitializableLib.sol";

/*
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
Expand Down Expand Up @@ -33,55 +33,49 @@ import {LibDiamond} from "@diamond/libraries/LibDiamond.sol";

/// @title Diamond
/// @notice Implements ERC-2535 Diamond proxy pattern, allowing dynamic addition, replacement, and removal of facets
/// @author Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
/// @author Modified by David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
abstract contract Diamond {
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
/// @author Modified from Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
contract Diamond {
/// @notice Initializes the Diamond proxy with the provided facets and initialization parameters
/// @param _init Address of the initialization contract
/// @param _calldata Calldata to be passed to the initialization contract
constructor(FacetCut[] memory _facetCuts, address _init, bytes memory _calldata) payable {
_diamondCut(_facetCuts, _init, _calldata);
function initialize(FacetCut[] calldata _facetCuts, address _init, bytes calldata _calldata)
external
payable
virtual
{
bytes32 s = InitializableLib.initializableSlot();
InitializableLib.preInitializer(s);

DiamondLib.diamondCut(_facetCuts, _init, _calldata);

InitializableLib.postInitializer(s);
}

/// @notice Fallback function that delegates calls to the appropriate facet based on function selector
/// @dev Reads the facet address from diamond storage and performs a delegatecall; reverts if selector is not found
fallback() external payable virtual {
_beforeDelegate();
_delegate(_facet());
}

/// @notice Internal function to perform a delegatecall to an implementation
/// @param _implementation Address of the implementation to delegate to
function _delegate(address _implementation) internal virtual {
address implementation = DiamondLib.selectorToFacet(msg.sig);
assembly {
// Copy calldata to memory
calldatacopy(0, 0, calldatasize())

// Delegate call to implementation
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

// Copy returned data
returndatacopy(0, 0, returndatasize())

// Revert or return based on the result
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}

/// @notice Internal function to perform a diamond cut
function _diamondCut(FacetCut[] memory _facetCuts, address _init, bytes memory _calldata) internal virtual {
LibDiamond._diamondCut(_facetCuts, _init, _calldata);
}

/// @notice Internal hook function to run before a delegatecall to the facet
/// @dev This function can be replaced to perform additional logic before the delegatecall
function _beforeDelegate() internal virtual {}

/// @notice Retrieves the implementation address for the current function call
/// @dev A Facet is one of many implementations in a Diamond Proxy
function _facet() internal view virtual returns (address) {
return LibDiamond._selectorToFacet(msg.sig);
}
receive() external payable virtual {}
}
14 changes: 6 additions & 8 deletions src/facets/DiamondCutFacet.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {FacetCut} from "@diamond-storage/DiamondStorage.sol";
import {IDiamondCut} from "@diamond/interfaces/IDiamondCut.sol";
import {LibDiamond} from "@diamond/libraries/LibDiamond.sol";
import {LibOwnableRoles} from "@diamond/libraries/LibOwnableRoles.sol";
import {DiamondLib, FacetCut} from "@diamond/libraries/DiamondLib.sol";
import {OwnableLib} from "@diamond/libraries/OwnableLib.sol";

/// @title DiamondCutFacet
/// @notice Simple single owner and multiroles authorization mixin.
/// @author David Dada
/// @author Modified from Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/facets/DiamondCutFacet.sol)
/// @author Modified from Timo (https://github.com/FydeTreasury/Diamond-Foundry/blob/main/src/facets/DiamondCutFacet.sol)
/// @author Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
/// @author Modified by David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
///
/// @dev Note:
/// Remember to add the loupe functions from DiamondLoupeFacet to the diamond.
Expand All @@ -24,8 +22,8 @@ contract DiamondCutFacet is IDiamondCut {
/// _calldata is executed with delegatecall on _init
function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external payable {
// Check that the caller is the owner
LibOwnableRoles._checkOwner();
OwnableLib.checkOwner();
// Call the diamond cut function from the library
LibDiamond._diamondCutCalldata(_diamondCut, _init, _calldata);
DiamondLib.diamondCut(_diamondCut, _init, _calldata);
}
}
43 changes: 13 additions & 30 deletions src/facets/DiamondLoupeFacet.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Facet} from "@diamond-storage/DiamondStorage.sol";
import {IDiamondLoupe} from "@diamond/interfaces/IDiamondLoupe.sol";
import {DiamondStorage, LibDiamond} from "@diamond/libraries/LibDiamond.sol";
import {DiamondLib, DiamondStorage, Facet} from "@diamond/libraries/DiamondLib.sol";

/// @title DiamondLoupeFacet
/// @notice Provides read-only functions to inspect the state of a Diamond proxy, including facets, function selectors, and supported interfaces
/// @author David Dada
/// @author Modified from Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/facets/DiamondLoupeFacet.sol)
/// @author Modified from Timo (https://github.com/FydeTreasury/Diamond-Foundry/blob/main/src/facets/DiamondLoupeFacet.sol)
/// @author Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
/// @author Modified by David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
///
/// @dev Implements the IDiamondLoupe interface as defined in EIP-2535
contract DiamondLoupeFacet is IDiamondLoupe {
/// @notice Gets all facet addresses and their function selectors.
/// @return facets_ Facet
function facets() external view returns (Facet[] memory facets_) {
DiamondStorage storage ds = LibDiamond._diamondStorage();
DiamondStorage storage ds = DiamondLib.diamondStorage();
uint256 facetCount = ds.facetAddresses.length;
facets_ = new Facet[](facetCount);
for (uint256 i; i < facetCount; ++i) {
Expand All @@ -28,37 +26,22 @@ contract DiamondLoupeFacet is IDiamondLoupe {

/// @notice Gets all the function selectors provided by a facet.
/// @param _facet The facet address.
/// @return facetFunctionSelectors_
function facetFunctionSelectors(address _facet)
external
view
override
returns (bytes4[] memory facetFunctionSelectors_)
{
facetFunctionSelectors_ = LibDiamond._diamondStorage().facetToSelectorsAndPosition[_facet].functionSelectors;
/// @return The function selectors for the specified facet.
function facetFunctionSelectors(address _facet) external view override returns (bytes4[] memory) {
return DiamondLib.diamondStorage().facetToSelectorsAndPosition[_facet].functionSelectors;
}

/// @notice Get all the facet addresses used by a diamond.
/// @return facetAddresses_
function facetAddresses() external view override returns (address[] memory facetAddresses_) {
facetAddresses_ = LibDiamond._diamondStorage().facetAddresses;
/// @return Facet addresses.
function facetAddresses() external view override returns (address[] memory) {
return DiamondLib.diamondStorage().facetAddresses;
}

/// @notice Gets the facet that supports the given selector.
/// @dev If facet is not found return address(0).
/// @param _functionSelector The function selector.
/// @return facetAddress_ The facet address.
function facetAddress(bytes4 _functionSelector) external view override returns (address facetAddress_) {
facetAddress_ = LibDiamond._diamondStorage().selectorToFacetAndPosition[_functionSelector].facetAddress;
}

/// @notice Query if a contract implements an interface
/// @param _interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 _interfaceId) external view returns (bool) {
return LibDiamond._diamondStorage().supportedInterfaces[_interfaceId];
/// @return The address of the facet that supports the given selector.
function facetAddress(bytes4 _functionSelector) external view override returns (address) {
return DiamondLib.diamondStorage().selectorToFacetAndPosition[_functionSelector].facetAddress;
}
}
20 changes: 20 additions & 0 deletions src/facets/ERC165Facet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC165Lib} from "@diamond/libraries/ERC165Lib.sol";

/// @title ERC165Facet
/// @notice Diamond facet that implements the ERC-165 standard interface detection
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
/// @dev Delegates to ERC165Lib which hardcodes support for ERC-165, ERC-173, IDiamondCut, and IDiamondLoupe
contract ERC165Facet {
/// @notice Query if a contract implements an interface
/// @param _interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `_interfaceId` and
/// `_interfaceId` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {
return ERC165Lib.supportsInterface(_interfaceId);
}
}
Loading
Loading