Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diamond Helper Functions #158

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open

Diamond Helper Functions #158

wants to merge 32 commits into from

Conversation

0xCourtney
Copy link
Collaborator

@0xCourtney 0xCourtney commented Oct 30, 2022

Summary

EIP2535 is a standard quickly growing in popularity and adoption. While Diamonds offer a great pathway for scaling contracts there are not many tools for managing deployments or upgrades. This PR introduces utility functions that provide developers with a consistent and reliable way of maintaining their contracts. The key goal here is to offer tools that perform the minimal changes necessary to add, replace, or remove facets from a Diamond.

Usage

General

  • diamondCut - calls Diamond.diamondCut
  • printDiamond - prints a table containing the registered and unregistered selectors

Deployment

  • addUnregisteredSelectors - adds unregistered selectors to the Diamond.

Upgrade

  • replaceRegisteredSelectors - replaces any selector which is registered to a different target address
  • removeRegisteredSelectors - removes the registered selector from the Diamond

Examples

// TestFacets.sol

contract Test1Facet {
    function test1Func1(bool) public returns (bytes32[] memory) {}
    function test1Func2(int128) external view returns (address) {}
    function test1Func3(address, address, address) external {}
}

contract Test1Facet1 {
    function test1Func1(bool) public returns (bytes32[] memory) {}
    function test1Func2() external view returns (address) {}
}

contract Test2Facet {
    function test2Func1() external pure returns (uint8) {}
    function test2Func2(bytes8) external view returns (bool) {}
}
// deploy.ts

// deploy Diamond
// registers selectors from Test1Facet and Test2Facet on Diamond
let facetCut = await addUnregisteredSelectors(Diamond, [Test1Facet, Test2Facet]);
await diamondCut(Diamond, facetCut);

// upgrade Diamond
// adds Test1Facet1.test1Func2, since the signature has changed
facetCut = await addUnregisteredSelectors(Diamond, [Test1Facet1]);
await diamondCut(Diamond, facetCut);

// replace Test1Facet.test1Func1 with Test1Facet1.test1Func1
facetCut = await replaceRegisteredSelectors(Diamond, [Test1Facet1]);
await diamondCut(Diamond, facetCut);

// removes Test1Facet.test1Func2 and Test1Facet.test1Func3
facetCut = await removeRegisteredSelectors(Diamond, [Test2Facet, Test1Facet1]);
await diamondCut(Diamond, facetCut);

Actions

  • Add tests to repo

@0xCourtney 0xCourtney linked an issue Oct 30, 2022 that may be closed by this pull request
Copy link
Collaborator

@NouDaimon NouDaimon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome initiative! Eventually, these utility functions will prove very useful to third parties.

lib/diamonds.ts Outdated Show resolved Hide resolved
lib/diamonds.ts Outdated Show resolved Hide resolved
lib/diamonds.ts Outdated Show resolved Hide resolved
@0xCourtney
Copy link
Collaborator Author

@NouDaimon thanks for the feedback! I have a bunch of changes on my local which I plan on pushing this week. I will update the description with my checklist of things before this is ready to come out of draft.

@0xCourtney
Copy link
Collaborator Author

Any recommendations on where I should add tests and contracts?

cc @ItsNickBarry

Copy link
Member

@ItsNickBarry ItsNickBarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still a bit unsure about how to organize this, but definitely a good start.

@0xCourtney Can you list all the places where you think a developer would use the print function? As mentioned in an inline comment, in my opinion that function would fit best in a CLI package. How to print something is fundamentally more opinionated than SS is trying to be. But you might have some use cases in mind that would justify including it here.

I'll put some more thought in to a possible Hardhat plugin.

lib/diamonds.ts Outdated Show resolved Hide resolved
lib/diamonds.ts Outdated Show resolved Hide resolved
lib/diamonds.ts Outdated Show resolved Hide resolved
lib/diamonds.ts Outdated Show resolved Hide resolved
lib/diamonds.ts Outdated
}

return groupFacetCuts(facetCuts);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The add/replace/remove function names imply that a transaction is sent, but really they just generate the FacetCut formatting. Should change the name to something like generateFacetCutsForAdd. Can you think of something better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are rather specific, so I think they should throw errors if certain passed data can't be added/removed/replaced.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also like a function that takes a list of selectors and targets, and calculates the FacetCut diff needed to migrate an existing diamond to the given schema.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The add/replace/remove function names imply that a transaction is sent, but really they just generate the FacetCut formatting. Should change the name to something like generateFacetCutsForAdd. Can you think of something better?

I asked ChatGPT for help here, how about addUnregisteredSelectorsToFacetCut? Likewise, the other functions could be renamed replaceRegisteredSelectorsInFacetCut and removeRegisteredSelectorsFromFacetCut

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also like a function that takes a list of selectors and targets, and calculates the FacetCut diff needed to migrate an existing diamond to the given schema.

e702c36

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are rather specific, so I think they should throw errors if certain passed data can't be added/removed/replaced.

45b2dc4

@0xCourtney
Copy link
Collaborator Author

In 3d9d456 I introduced the concept of filters. A filter consists of an object containing a contract address and an array of selectors.

interface FacetFilter {
  contract: string;
  selectors: string[];
}

The only filter will add/replace/remove selectors defined in the only filter ignoring all other selectors for that contract. The exclude filter will ignore all selectors defined in the exclude filter for that contract, all other selectors will get added/replaced/removed.

await d.addUnregisteredSelectors(Diamond, [Test1Facet1])

// [{
//   target: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
//   action: 0,
//   selectors: [ '0xc2985578', '0x0716c2ae' ]
// }]

await d.addUnregisteredSelectors(
  Diamond,
  [Test1Facet1],
  [{ contract: Test1Facet1.address, selectors: ['0xc2985578'] }],
  [],
);

// [{
//   target: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
//   action: 0,
//   selectors: [ '0xc2985578' ]
// }]

await d.addUnregisteredSelectors(
  Diamond,
  [Test1Facet1],
  [],
  [{ contract: Test1Facet1.address,selectors: ['0xc2985578'] }],
)

// [{
//   target: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
//   action: 0,
//   selectors: [ '0x0716c2ae' ]
// }]

The only and exclude filters must not overlap, therefore a contract may define selectors in the only or exclude filter, not both.

@0xCourtney 0xCourtney marked this pull request as ready for review December 20, 2022 16:47
lib/diamonds.ts Outdated Show resolved Hide resolved
lib/package.json Outdated Show resolved Hide resolved
lib/diamond/utils.ts Outdated Show resolved Hide resolved
lib/diamond/utils.ts Outdated Show resolved Hide resolved
lib/diamond/utils.ts Outdated Show resolved Hide resolved
Comment on lines 3 to 6
export interface FacetFilter {
contract: string;
selectors: string[];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like these FacetFilter objects are used in sets of 3 arrays, corresponding to the FacetCutAction types. Would it not be better to include the action in the FacetFilter type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can certainly look into this, I was already planning to replace the only and except parameters with filter in the add/replace/remove API, then add a type field to FacetFilter. The new FacetFilter could look something like this:

export interface FacetFilter {
  type: string; // "only" or "except"
  action: FacetCutAction;
  contract: string;
  selectors: string[];
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib/diamond/filters.ts Outdated Show resolved Hide resolved
target: string = AddressZero,
data: string = '0x',
) {
(await diamond.diamondCut(facetCut, target, data)).wait(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly does wait work?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolves to the TransactionReceipt once the transaction has been included in the chain for confirms blocks. If confirms is 0, and the transaction has not been mined, null is returned.

It may be good to return the TransactionReceipt here. Otherwise, a user may not be able to detect errors or access the receipt, if needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return cuts;
}

export function printFacetCuts(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to print anything.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe getFacetCut is more appropriate, the function only structures the FacetCut object.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

if (!selectorsReplaced) {
throw new Error('No selectors were replaced in FacetCut');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The errors are thrown if all changes are skipped, but I think we'll want to throw if any change is skipped. Need to think about this though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be the result of using filters or something else?

0xCourtney and others added 9 commits December 22, 2022 11:07
Co-authored-by: Nick Barry <itsnickbarry@protonmail.ch>
Co-authored-by: Nick Barry <itsnickbarry@protonmail.ch>
Co-authored-by: Nick Barry <itsnickbarry@protonmail.ch>
@jhubbardsf
Copy link
Contributor

jhubbardsf commented Mar 7, 2023

Hey, anything happening with this PR? Does anyone need some help getting it finished? I saw a lot of promise in it.

@ItsNickBarry
Copy link
Member

@jhubbardsf I've shifted focus to a WIP plugin that manages diamond upgrades. It's easier to add the functions I need there first, and then determine how to structure what's been added here. This will be merged eventually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

diamondCut utilities
4 participants