Upgradeable contracts using solidstate solidity diamond pattern

Date: November 10, 2024

Victor Yeo
2 min readNov 9, 2024

When i was working on ERC2535 (https://eips.ethereum.org/EIPS/eip-2535) diamond pattern using solidstate solidity library, i discovered some missing pieces of information that is not covered in the solidstate documentation. This article will explain and highlight the details of using solidstate solidity library with foundry tool.

To create a diamond using solidstate library, we will create a diamond contract, as well as facet contract, internal contract, and storage contract.

The diamond contract simply inherits from SolidStateDiamond.

The facet contract contains the public/external functions that constitute the logic of the diamond.

The internal contract contains the internal functions.

The storage contract contains the unique storage slot of the diamond, identified by keccak256 of a unique string. The storage contract also contains the state variables.

By default, when we instantiate a diamond contract, it will be built with a default facet that contains 12 selectors. (See https://github.com/solidstate-network/solidstate-solidity/blob/master/contracts/proxy/diamond/SolidStateDiamond.sol#L31)

To have a diamond that contains application logic, we need to upgrade the diamond with our own facet contract. To do so in solidity:

MyDiamond mydiamond = new MyDiamond();
bytes4[] memory selectors = new bytes4[](1);
uint256 selectorIndex;
selectors[selectorIndex++] = MyFacet.helloWorld.selector;
facetInstance = new MyFacet(“name”);
facetCuts[0] = IERC2535DiamondCutInternal.FacetCut({
target: address(facetInstance),
action: IERC2535DiamondCutInternal.FacetCutAction.ADD,
selectors: selectors
});
mydiamond.diamondCut(facetCuts, address(0), "");

The diamond in the above code snippet is the instance of the diamond contract. After the upgrade is performed, we can call the diamond facet from solidity contract.

To call the facet function, we typecast the diamond address to the diamond facet ABI, and call the facet function.

Facet(address(diamond)).function(params);

So the above code will become:

MyFacet(address(diamond)).helloWorld("message");

There is another thing on facet initializer. When the facet is added to the diamond, the facet’s constructor is not called. We need to add a initialize function to do the initialization.

// encode function call to send to the contract at _init
bytes memory thecalldata = abi.encodeWithSelector(MyFacet.initialize.selector, params);
counter.diamondCut(facetCuts, address(MyFacet), thecalldata);

The above simply says that the call data contains the function to be called by the contract address of MyFacet.

I have created a simple diamond pattern at https://github.com/victoryeo/solidstate_diamond

The code in the git repo above illustrates the simple diamond pattern.

Hope it helps others on using solidstate diamond pattern.

--

--

No responses yet