user ⇒ proxy contract(holds data) ⇒ delegate call⇒logic contract
The diamond storage must implement the diamondCut
function. With this function, one can (un-)register functions for a specific logic contract. Once a function is registered, the diamond storage can recognize the function selector of any call within its fallback function, retrieve the correct facet address and then run a delegate call for it.
interface IDiamondCut {
enum FacetCutAction {Add, Replace, Remove}
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
}
contract FacetA {
struct FacetData {
address owner;
bytes32 dataA;
}
function facetData()
internal
pure
returns(FacetData storage facetData) {
bytes32 storagePosition = keccak256("diamond.storage.FacetA");
assembly {facetData.slot := storagePosition}
}
function setDataA(bytes32 _dataA) external {
FacetData storage facetData = facetData();
require(facetData.owner == msg.sender, "Must be owner.");
facetData.dataA = _dataA;
}
function getDataA() external view returns (bytes32) {
return facetData().dataA;
}
}
To avoid any storage slot collisions, we can use a simple trick for where to store the data. While usually, Solidity will store data in subsequent slots as defined by the state variables, we can also explicitly set the storage slot with assembly.
When doing so we only need to ensure the storage slot is in a unique position that won't create conflicts with other facets. One can easily achieve this by hashing the facet name.
Now whenever we want to read or write FacetData
, we don't have to worry about overwriting other facets of data, because our storage slot position is unique. And you can of course access this data from any other facet as well, you just need to inherit from FacetA
and use the facetData
function. That's why it's particularly helpful for creating reusable facets.
However an alternative design would be the AppStorage pattern which we won't go into detail about here, but it sacrifices reusability for slightly improved code readability. AppStorage is useful when sharing state variables between facets that are specific to a project and won't be reused in diamonds for other projects or protocols.
interface IDiamondLoupe {
struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}
function facets() external view returns (Facet[] memory facets_);
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);
function facetAddresses() external view returns (address[] memory facetAddresses_);
function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}
Adding/Removing/Replacing functions
function addFunctions(
address _facetAddr,
bytes4[] memory _functionSelectors
) internal {
DiamondStorage storage ds = diamondStorage();
uint16 selectorCount = uint16(ds.selectors.length);
for (uint256 idx; idx < _functionSelectors.length; idx++) {
bytes4 selector = _functionSelectors[idx];
ds.facetAddressAndSelectorPosition[selector] =
FacetAddressAndSelectorPosition(_facetAddr, selectorCount);
ds.selectors.push(selector);
selectorCount++;
}
}
function removeFunctions(
address _facetAddr,
bytes4[] memory _functionSelectors
) internal {
DiamondStorage storage ds = diamondStorage();
uint256 selectorCount = ds.selectors.length;
for (uint256 idx; idx < _functionSelectors.length; idx++) {
bytes4 selector = _functionSelectors[idx];
FacetAddressAndSelectorPosition memory old =
ds.facetAddressAndSelectorPosition[selector];
selectorCount--;
if (old.selectorPosition != selectorCount) {
// replace selector with last selector
bytes4 lastSelector = ds.selectors[selectorCount];
ds.selectors[old.selectorPosition] = lastSelector;
ds.facetAddressAndSelectorPosition[lastSelector]
.selectorPosition = old.selectorPosition;
}
// delete last selector
ds.selectors.pop();
delete ds.facetAddressAndSelectorPosition[selector];
}
}
IERC65
stay away from inheritance as much as possible
Solid State: Diamond with Upgradeable-first Solidity smart contract development library 💠
function ownerOf(uint256 tokenId) public view virtual override returns(address) {
require(_exists(tokenId), "ERC721A: owner query for nonexistent token");
uint256 lowestTokenToCheck;
if(tokenId >= maxBatchSize) {
lowestTokenCheck = tokenId - maxBatchSize + 1;
}
for(uint256 curr = tokenId; curr >= lowesrTokenToCheck; curr--) {
address owner = _owners[curr];
if(owner != address(0)) {
return owner;
}
}
revert("ERC721A: unable to determine the owner of the token");
}
write your own implementation as much as possible
diamond standard faucet images for better understanding: the good thing about faucets you can identify the different functions/contracts
if a parking lot is dull there are people who will help you identify your car so they’re like proxy contracts(holds data) and your car is like a logic contract
DIAMOND-3-HARDHAT(SIMPLE LOUPE FUNCTION) in Typescript
smart contracts are not built to be upgradable but immutable
wallets need better UX (blockchain UX sucks)
Libraries hold constant values: if the same values will be used in several diamond standard faucets use libraries
upgrade initializers
diamond.sol
upgrade diamond contracts: function identifiers in a diamond contract. Every function has a selector in Diamond Standard Contract
faucet address change from time to time: creates new faucets and new address
when dealing with diamond standard contracts be careful with your code
erc721 marketplace has an issue
contract interface is like an aesthetic of contract
VM code: assembly language each machine needs instructions. Whatever you write in solidity is an instruction. To implement instruction your machine block:: extract byte32 (user signature): private key
wasn’t possible in solidity but possible in assembly. people use assembly for a lot of things.
darkweb: can’t access via browser
Namespec: details about contract
app storage/DaimondStorage: where particular thing is stored (like a street where your house is located)
Diamond_Storage_Location(random string can be anything)
when you engage in bounties you don’t expect to expand your knowledge but when you get started with getting a job they’ll need full skills(when did you get started learning solidity do you know when I learned an array of structs? Only time I get to learn something and understand on my own is great. We’re exposing you to complex stuff)
Understand this
Diamond Storage takes advantage
interacting with diamond address and function is not on diamond address so you get a fallback. Its a calldata....safe transfer from ERC21
Arbiteary contract (pass into a diamond while calling it)
know if function selector exists: if it doesn’t exists it retrieves
must pass through a fallback
implementation of libDiamond on github
msg.sig
// extract function selector using msg.sig
blockhash //(uint blockNumber) returns (bytes32): hash of the given block when blocknumber is one of the 256 most recent blocks; otherwise returns zero
block.basefee //(uint): current block’s base fee (EIP-3198 and EIP-1559)
block.chainid //(uint): current chain id
block.coinbase //(address payable): current block miner’s address
block.difficulty //(uint): current block difficulty
block.gaslimit //(uint): current block gaslimit
block.number //(uint): current block number
block.timestamp //(uint): current block timestamp as seconds since unix epoch
gasleft() //returns //(uint256): remaining gas
msg.data //(bytes calldata): complete calldata
msg.sender //(address): sender of the message (current call)
msg.sig //(bytes4): first four bytes of the calldata (i.e. function identifier)
msg.value //(uint): number of wei sent with the message
tx.gasprice //(uint): gas price of the transaction
tx.origin //(address): sender of the transaction (full call chain)
how solidity important for mapping
people sending tokens to contracts since they don’t know.
connection of faucet to storage
function diamondStorage() internal pure returns (DiamondStorage storage ds) {
bytes32 position = DIAMOND_STORAGE_POSITION;
assembly {
ds.slot := position
}
}
deal with strict that have mappings in libraries