DEV Community

loading...

How Diamond Upgrades Work

mudgen profile image Nick Mudge ・3 min read

To understand diamond upgrades, let's first understand how function calls work in contracts created by Solidity.

How Function Calls Work

In order for a transaction to execute a function on a smart contract there needs to be a way for a transaction to specify to the contract what function to execute. This is done by a transaction supplying a four-byte value that identifies for a contract what function to execute.

The four-byte value is called a function selector. It consists of the first four bytes of a hash of a string of a function signature.

A function signature is a string that consists of a function name and the types of its arguments. Let's look at an example. Here's a function signature: "transfer(address,uint256)"

Here we get the first four bytes of a hash of that function signature:

bytes functionSelector = bytes4(keccak256("transfer(address,uint256)"));
Enter fullscreen mode Exit fullscreen mode

The function selector is0xa9059cbb. So if the first four bytes of transaction data is that value and the transaction is sent to a contract, then it tells that contract to execute the "transfer(address,uint256)" function.

How Diamond Upgrades Work

Diamonds store function selectors and contract addresses of contracts that have the functions that the function selectors identify. The contracts that diamonds use are called facets.

A diamond has a mapping that maps function selectors to facet addresses.

Adding functions to a diamond means adding function selectors to a diamond and adding the facet addresses of the facets that have the functions that the function selectors identify.

Replacing functions means associating existing function selectors in a diamond with different facet addresses.

Removing functions means removing function selectors and their facet addresses from the diamond.

The diamondCut function is used to upgrade diamonds. It has a _diamondCut argument that consists of an array of function selectors and facet addresses and an action which specifies whether to add, replace or remove them. Here's an example of a value for the _diamondCut argument:

[
  {
    facetAddress: '0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82',
    action: 0,
    functionSelectors: [
      '0x4cd5d1f7',
      '0x24a6665e',
      '0x4113e5ca',
      '0x49aa1f27',
      '0x24d86f00',
      '0x2e0bcb43',
      '0xbec10cde',
      '0xa3ea00f1'
    ]
  },
  {
    facetAddress: '0x549549085493058324890438543949485a499A1b',                  
    action: 1,
    functionSelectors: [
      '0x45943954',
      '0xa4349495',
      '0x9684834e'
    ]
  }  
]
Enter fullscreen mode Exit fullscreen mode

In the example above the action value of 0 means adding new functions. The action value of 1 means replacing the functions by replacing the facet address for them.

In Javascript scripts the FacetCutAction object can used to more explicitly show the action. Here is its definition:

const FacetCutAction = { Add: 0, Replace: 1, Remove: 2 }
Enter fullscreen mode Exit fullscreen mode

Here is an example of a Javascript script that creates the _diamondCut argument:

const FacetCutAction = { Add: 0, Replace: 1, Remove: 2 }

  const newFuncs = [
    getSelector('function setSleeves(tuple(uint256 sleeveId, uint256 wearableId)[] calldata _sleeves) external'),
    getSelector('function updateSvg(string calldata _svg, tuple(bytes32 svgType, uint256[] ids, uint256[] sizes)[] calldata _typesAndIdsAndSizes) external')
  ]
  const existingFuncs = getSelectors(facet).filter(selector => !newFuncs.includes(selector))

  const _diamondCut = [
    {
      facetAddress: facet.address,
      action: FacetCutAction.Add,
      functionSelectors: newFuncs
    },
    {
      facetAddress: facet.address,
      action: FacetCutAction.Replace,
      functionSelectors: existingFuncs
    }
  ]
  console.log(_diamondCut)
Enter fullscreen mode Exit fullscreen mode

The AavegotchiDiamond has many upgrade scripts. Check them out for more code examples.

You may want to initialize state after an upgrade. That is what the second and third arguments of the diamondCut function are for. The _init argument holds the contract address of a function to call to initialize the state of the diamond. The _calldata argument holds a function call to send to the contract at _init. The function call is executed with delegatecall so that the diamond's state is affected and can be initialized.

Executing an initialization function by using the _init and _calldata arguments when making an upgrade means that the upgrade and state initialization are done in the same transaction. This is good because it prevents the diamond from getting into a bad or inconsistent state, which could possibly happen if an upgrade and state initialization are done in separate transactions.

Discussion (0)

Forem Open with the Forem app