Solidity Diamond Pattern
A diamond is one proxy that routes each function call to a different facet contract. A facet is a small logic contract that holds one group of the diamond's functions.
Two selectors route to two different facets
The owner registers the counter and bonus selectors with their facets. Each call lands on its own facet, and the state lands in the diamond.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract CounterFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function setCount(uint256 v) public {
store.count = v;
}
function count() public view returns (uint256) {
return store.count;
}
}
contract BonusFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function setBonus(uint256 v) public {
store.bonus = v;
}
function bonus() public view returns (uint256) {
return store.bonus;
}
}
contract Diamond {
mapping(bytes4 => address) public facets;
bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
constructor() {
bytes32 slot = ADMIN_SLOT;
address deployer = msg.sender;
assembly { sstore(slot, deployer) }
}
function admin() public view returns (address adm) {
bytes32 slot = ADMIN_SLOT;
assembly { adm := sload(slot) }
}
function addFacet(bytes4 selector, address facet) public {
require(msg.sender == admin(), "only owner");
facets[selector] = facet;
}
fallback() external payable {
address facet = facets[msg.sig];
require(facet != address(0), "unknown selector");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
An unregistered selector reverts
No facet is registered for the stranger function. The diamond fallback finds no facet and reverts.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract CounterFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function setCount(uint256 v) public {
store.count = v;
}
function count() public view returns (uint256) {
return store.count;
}
}
contract BonusFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function setBonus(uint256 v) public {
store.bonus = v;
}
function bonus() public view returns (uint256) {
return store.bonus;
}
}
contract Stranger {
function mystery() public pure returns (uint256) {
return 1;
}
}
contract Diamond {
mapping(bytes4 => address) public facets;
bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
constructor() {
bytes32 slot = ADMIN_SLOT;
address deployer = msg.sender;
assembly { sstore(slot, deployer) }
}
function admin() public view returns (address adm) {
bytes32 slot = ADMIN_SLOT;
assembly { adm := sload(slot) }
}
function addFacet(bytes4 selector, address facet) public {
require(msg.sender == admin(), "only owner");
facets[selector] = facet;
}
fallback() external payable {
address facet = facets[msg.sig];
require(facet != address(0), "unknown selector");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
Only the owner can add a facet
Adding a facet changes what the diamond can do. A stranger call reverts, while the owner call registers the facet.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract CounterFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function setCount(uint256 v) public {
store.count = v;
}
function count() public view returns (uint256) {
return store.count;
}
}
contract Diamond {
mapping(bytes4 => address) public facets;
bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
constructor() {
bytes32 slot = ADMIN_SLOT;
address deployer = msg.sender;
assembly { sstore(slot, deployer) }
}
function admin() public view returns (address adm) {
bytes32 slot = ADMIN_SLOT;
assembly { adm := sload(slot) }
}
function addFacet(bytes4 selector, address facet) public {
require(msg.sender == admin(), "only owner");
facets[selector] = facet;
}
fallback() external payable {
address facet = facets[msg.sig];
require(facet != address(0), "unknown selector");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract CounterFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function setCount(uint256 v) public {
store.count = v;
}
function count() public view returns (uint256) {
return store.count;
}
}
contract BonusFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function setBonus(uint256 v) public {
store.bonus = v;
}
function bonus() public view returns (uint256) {
return store.bonus;
}
}
contract TotalFacet {
struct Store {
uint256 count;
uint256 bonus;
}
Store internal store;
function total() public view returns (uint256) {
return store.count + store.bonus;
}
}
contract Diamond {
mapping(bytes4 => address) public facets;
bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
constructor() {
bytes32 slot = ADMIN_SLOT;
address deployer = msg.sender;
assembly { sstore(slot, deployer) }
}
function admin() public view returns (address adm) {
bytes32 slot = ADMIN_SLOT;
assembly { adm := sload(slot) }
}
function addFacet(bytes4 selector, address facet) public {
require(msg.sender == admin(), "only owner");
facets[selector] = facet;
}
fallback() external payable {
address facet = facets[msg.sig];
require(facet != address(0), "unknown selector");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
Key points
- Facet: a facet is a small logic contract that holds one group of functions for the diamond.
- Selector routing: the diamond looks up the selector of the incoming call and delegatecalls the facet registered for it.
- Selector meaning: a selector is the short identifier of a function, taken from its name and argument types.
- Shared storage: facets share the diamond's storage, so a value written by one facet is visible to the others.
- Unknown selector: a call to a selector with no registered facet reverts.
In this example, the diamond registers a counter facet, a bonus facet, and a total facet. The counter facet sets 10 and the bonus facet sets 4, both in the diamond's shared storage, and the total facet reads that shared storage to return 14.
Note
full EIP-2535, with diamondCut and the loupe, is out of scope for this course.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Remix IDE
Exercise
What decides which facet handles a call to a diamond?