Diamonds - Introduction
The parts of a diamond
Diamond
Main contract, no need to create a new address when upgrading!
Facets
A deployed contract containing functions that can be attached to a diamond
functionSelectors
A mappping of the function cutted into the diamonds and their Facet
getSubscriptionId() => 0x3F382DbD960E3a9bbCeaE22651Ef8158d2791550
AirdropRegisters() => 0x3F382DbD960E3a9bbCeaE22651Ef8158d2791550
AdjustAirdropPeriod() => 0x3F382DbD960E3a9bbCeaE22651Ef8158d2791550
DistributeCommunityShare(uint256,uint256) => 0x0910FcCD3E3a349944415BCEf75c5b48DAeCd617
We are not using the string but the function signature
// javascript
web3.eth.abi.encodeFunctionSignature('DistributeCommunityShare(uint256,uint256)');
//solidity
msg.sig
So it becomes
0xde3d9fb7 => 0x3F382DbD960E3a9bbCeaE22651Ef8158d2791550
0xee944ba7 => 0x3F382DbD960E3a9bbCeaE22651Ef8158d2791550
0x3e52cb99 => 0x3F382DbD960E3a9bbCeaE22651Ef8158d2791550
0xd44e9f70 => 0x0910FcCD3E3a349944415BCEf75c5b48DAeCd617
Fallback fn
Lets play in Remix!
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Fallback {
event FallbackCalled(address,bytes,uint);
fallback() external payable {
emit FallbackCalled(msg.sender, msg.data, msg.value);
}
}
Storage: AppStorage vs Diamond Storage
AppStorage: the Diamond storage is a struct that begins at the position 0 in the contract storage.
All the variables must be in the same struct
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestFacet {
struct AppStorage {
uint8 firstVar;
uint8 secondVar;
uint8 thirdVar;
uint8 fourthVar;
string fifthVar;
}
AppStorage internal s;
function myFacetFunction(uint8 n) external {
s.firstVar = n;
}
}
DiamondStorage: instead of having all the variables in a single struct we can have multiple structs. In AppStorage the struct must start from 0, in DiamondStorage each "struct storage" has to define where it starts.
library LibA {
struct DiamondStorage {
address owner;
bytes32 dataA;
}
function diamondStorage() internal pure returns(DiamondStorage storage ds) {
bytes32 storagePosition = keccak256("diamond.storage.LibA");
// or bytes32 storagePosition = keccak256(abi.encodePacked(ERC1155.interfaceId, ERC1155.name, address(this)));
assembly {ds.slot := storagePosition}
}
}
// Our facet uses the Diamond Storage defined above.
contract FacetA {
function setDataA(bytes32 _dataA) external {
LibA.DiamondStorage storage ds = LibA.diamondStorage();
require(ds.owner == msg.sender, "Must be owner.");
ds.dataA = _dataA;
}
function getDataA() external view returns (bytes32) {
return LibA.diamondStorage().dataA;
}
}
https://eips.ethereum.org/EIPS/eip-2535#facets-state-variables-and-diamond-storage
Basic Diamonds functions
cut()
The cut functions is used to connect functions to facets.
loupe()
The function used to get all the functionSelectors
fallback()
When calling the Diamond, instead read from the functionSelectors where is the called function (which facet) and do a delegate call to the facet
Play with Nick's code!
Clone the repo
git clone git@github.com:mudgen/diamond-2-hardhat.git
Install the project
cd diamond-2-hardhat
npm install
A framework for Diamonds: gemcutter
Install
Clone the repo
git clone git@github.com:marcocastignoli/habitat_poc.git
Install (it takes a while because of Sourcify)
cd habitat_poc/
yarn
While it's installing read this
- developed for 0xhabitat
- the framework simplifies working with diamonds both locally and remotly
- CLI tool to easily cut facets and deploy/upgrade
- Check the docs (they don't represent the actual implementation https://0xhabitat.org/docs/Developers/Gemcutter)
Run the development environment
yarn dev:start
Configure
yarn diamond:init:test
Run the tests
yarn test