ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.88k stars 5.27k forks source link

EIP-2535: Diamonds #2535

Closed mudgen closed 2 years ago

mudgen commented 4 years ago

EIP-2535 Diamonds exists here: https://eips.ethereum.org/EIPS/eip-2535

Below is a feedback and discussion of the standard.

nataouze commented 2 years ago

@mudgen In this case, there would be several initializations called within a single diamondCut. But the DiamondCut event is designed for a single initialization. So there could be two possible scenarios:

1/ Emit a DiamondCut for each initialization. The first event will have the _diamondCut field as provided in the function argument, while _init and _calldata correspond to the first initialization call. The subsequent events will use an empty array for the _diamondCut field.

2/ Emit a single DiamondCut event, effectively dropping the _init and _calldata fields.

I am inclined to go for 2/ as I am actually not really clear of what use the _init and _calldata fields can be for an event listener: they do not represent a state change and do not allow to make a conclusion as to what consequence the initialization call can have.

mudgen commented 2 years ago

The _init and _calldata parameters in the DiamondCut contain important information in an upgrade. They tell someone what function call was made with what arguments on what contract during an upgrade.

A person who wants to know all changes made in an upgrade would look at the DiamondCut event to see what functions were added/replaced/removed with what facets. And the person would look at the _init and _calldata arguments of DiamondCut event to see what function call was made with what arguments on what contract. Then the person could look at the verified source code of the contract the function was called on to see what the function did.

Only emitting one DiamondCut event is needed to get full information about an upgrade. This assumes that full verified source code is available for all changes made.

The DiamondCut event enables anyone to see all changes made in an upgrade. This is done by looking at the following things for any upgrade:

  1. Look at the verified source code for the function that emitted the DiamondCut event.
  2. Look at the functions added/replaced/removed in the upgrade. This information is contained in the DiamondCut event, which includes the function selectors and the contract addresses of the functions added/replaced/removed. A person can look at the verified source code for the functions added, replaced and removed.
  3. The DiamondCut event contains function call information in the _init and _calldata parameters. Those parameters can be used to find the verified source code of a function and contract that were called during an upgrade, if any.

Any number of initiation function calls can be done in single upgrade by the function call specified in the _init and _calldata arguments making other function calls. Information about these calls can be found by looking at the verified source code of the function making these function calls.

mudgen commented 2 years ago

@mudgen Thank you for your comment.

One point about the DiamondCut event is that it would not be possible to emit all the initializations data in a single event. Emitting multiple events does not seem like an elegant solution, so I would rather use by default _init=address(0) and _calldata="". From what I can see, this is still compatible with the standard, but that would be good to get your insight on this.

I am thinking to create an extension ERC for this additional cut function, would this make sense?

I am sorry, I don't really understand what is the problem you are trying to solve or how you are trying to solve it. What is initializations data to you and why can't DiamondCut provide it or link to the function call that has it?

nataouze commented 2 years ago

I am sorry, I don't really understand what is the problem you are trying to solve or how you are trying to solve it. What is initializations data to you and why can't DiamondCut provide it or link to the function call that has it?

struct Initialization {
    address initContract;
    bytes initData;
}

function diamondCut(
    FacetCut[] calldata _diamondCut,
    Initialization[] calldata _initializations
) external;

This is about doing multiple initialization calls using a single function call for an upgrade.

nataouze commented 2 years ago

The _init and _calldata parameters in the DiamondCut contain important information in an upgrade. They tell someone what function call was made with what arguments on what contract during an upgrade.

A person who wants to know all changes made in an upgrade would look at the DiamondCut event to see what functions were added/replaced/removed with what facets. And the person would look at the _init and _calldata arguments of DiamondCut event to see what function call was made with what arguments on what contract. Then the person could look at the verified source code of the contract the function was called on to see what the function did.

Only emitting one DiamondCut event is needed to get full information about an upgrade. This assumes that full verified source code is available for all changes made.

The DiamondCut event enables anyone to see all changes made in an upgrade. This is done by looking at the following things for any upgrade:

  1. Look at the verified source code for the function that emitted the DiamondCut event.
  2. Look at the functions added/replaced/removed in the upgrade. This information is contained in the DiamondCut event, which includes the function selectors and the contract addresses of the functions added/replaced/removed. A person can look at the verified source code for the functions added, replaced and removed.
  3. The DiamondCut event contains function call information in the _init and _calldata parameters. Those parameters can be used to find the verified source code of a function and contract that were called during an upgrade, if any.

Any number of initiation function calls can be done in single upgrade by the function call specified in the _init and _calldata arguments making other function calls. Information about these calls can be found by looking at the verified source code of the function making these function calls.

I would argue that the information provided by the call arguments is not definitive:

1/ The source code may not be verified 2/ The call could be made on a code that has since changed when the event is processed (for example if the target is a proxy) 3/ The logic of the initialization function may depend on a current state which could be hard to track down

Concretely, if some information is absolutely relevant during an initialization call, it would seem more legitimate that the _init contract would be responsible for emitting a tailored event about it.

mudgen commented 2 years ago

would argue that the information provided by the call arguments is not definitive:

1/ The source code may not be verified 2/ The call could be made on a code that has since changed when the event is processed (for example if the target is a proxy) 3/ The logic of the initialization function may depend on a current state which could be hard to track down

Concretely, if some information is absolutely relevant during an initialization call, it would seem more legitimate that the _init contract would be responsible for emitting a tailored event about it.

@nataouze Excellent points. I agree with you.

nataouze commented 2 years ago

Maybe a snippet will help to put things in perspective. This is how I would implement the function mentioned above:

function diamondCut(FacetCut[] memory diamondCut_, Initialization[] memory initializations_) external {
    cutFacets(diamondCut_); 
    emit DiamondCut(diamondCut_, address(0), "");
    for (uint256 i = 0; i < initializations_.length; i++) {
        initializationCall(initializations_[i].initContract, initializations_[i].initData);
    }
}

When I read the standard, I can see that the constraints on emitting a DiamondCut event are related to the add/replace/remove actions. Except for this sentence:

The _diamondCut, _init, and _calldata arguments are passed directly to the DiamondCut event.

But cutting a facet could be operated outside of a call to diamondCut, so this sentence would not be relevant in every case.

mudgen commented 2 years ago

But cutting a facet could be operated outside of a call to diamondCut, so this sentence would not be relevant in every case.

Yes, you are correct.

It makes sense to me how you implemented the function you showed, thanks for showing that.

nataouze commented 2 years ago

It makes sense to me how you implemented the function you showed, thanks for showing that.

Thanks for bearing with me @mudgen :blush: Glad to hear from you that managing the event in this way is acceptable.

mudgen commented 2 years ago

Yes, thanks for bearing with me too. Yes, it is acceptable. I am thinking it is probably a good idea to change the diamondCut parameters in the standard to what you are using, but I'm not sure how much disruption that might cause amongst projects and tools that have already adopted the current version of diamondCut. But anyway, it is perfectly alright for people to make their own versions of diamondCut or other upgrade functions, as long as they emit the DiamondCut event to show functions added/replaced/removed.

mudgen commented 2 years ago

@dotc-dev Hi there. I suggest verifying each facet separately. And I suggest verifying the diamond proxy contract separate from its facets. Verifying the diamond proxy contract or facets is done the same way any other contract is verified.

I did recently talk to someone who had trouble verifying his contracts for his diamond implementation. He was able to fix it. This is what he said:

As far as I can tell, something was wrong with my hardhat repo. After trying various methods I tried to verify an empty contract and it also failed. I created a clean repo and it worked immediately. So I'm not sure what the issue was exactly. Some thing I tried before was linking the libraries which wasn't necessary, I also cleaned artifacts and cache, and tried some code changes to get it to work. It may have been a bug with hardhat.

Once you do get everything verified check that it shows up in louper.dev, which is like etherscan for diamonds.

rhlsthrm commented 2 years ago

Once you do get everything verified check that it shows up in louper.dev, which is like etherscan for diamonds.

This is a great resource thank you. However is there any chance of getting Etherscan support for diamonds?

mudgen commented 2 years ago

Once you do get everything verified check that it shows up in louper.dev, which is like etherscan for diamonds.

This is a great resource thank you. However is there any chance of getting Etherscan support for diamonds?

Yes, if someone will contact the Etherscan team and ask or convince them to add support for diamonds, or if enough people ask them to do this.

rhlsthrm commented 2 years ago

Once you do get everything verified check that it shows up in louper.dev, which is like etherscan for diamonds.

This is a great resource thank you. However is there any chance of getting Etherscan support for diamonds?

Yes, if someone will contact the Etherscan team and ask or convince them to add support for diamonds, or if enough people ask them to do this.

I just contacted asking: https://etherscan.io/contactus

rhlsthrm commented 2 years ago

I also reached out to Tenderly. Their response:

Unfortunately, we are not supporting Diamond contracts. We have this in plan but this is the lowest priority and there are chances we won't implement this in foreseeable future. Sorry for bringing you the bad news 😕

Would love to get this support so everyone who wants this should also reach out to Tenderly!

github-actions[bot] commented 2 years ago

There has been no activity on this issue for six months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

anders-torbjornsen commented 2 years ago

Just adding my agreement that diamondCut should have multiple init addresses and data. I know I can create my own function to do this, but one of the things I work on is a generic evm deployment system, and in that case I have to stick rigidly to the spec.

mudgen commented 2 years ago

@anders-torbjornsen Stick rigidly to what spec?

anders-torbjornsen commented 2 years ago

@anders-torbjornsen Stick rigidly to what spec?

As in my deployment tool can only call the functions defined in this spec, I have to assume that there isn't a version of diamondCut which accepts multiple init addresses and data

mudgen commented 2 years ago

Okay, understood. Out of curiosity is your deployment tool hardhat-deploy? Or what is it?

anders-torbjornsen commented 2 years ago

It's Zem, it came out of my first few NFT projects before I knew hardhat-deploy was a thing, and so here we are hehe. Still, competition is probably not a bad thing :)

mudgen commented 2 years ago

I am glad to see Zem.

I'd argue that Zem and all deployment tools should add the standard diamondCut function to diamonds so that those diamonds can interoperate with other tools.

However Zem and any other deployment tool can also (in addition) add and use their own upgrade function in the diamonds they deploy that better suits the deployment tool. The trick will be how to still emit a standard DiamondCut event with a custom upgrade function. Emitting a standard DiamondCut event is important for tools that show the history of diamond upgrades.

I do acknowledge that a diamondCut that accepts multiple init addresses and data makes deployment/upgrades easier in some cases, especially more automated cases.

Unfortunately the technical aspects of this standard are finalized and won't change.

However it is possible and welcomed to propose a new standard about a new diamondCut upgrade function for diamonds that is better suited for automation.

mudgen commented 2 years ago

@anders-torbjornsen I think that you can still achieve what you want to achieve with the current diamondCut.

Here is how:

Make a single initializer function in its own contract. Make that initializer function take as arguments an array of addresses of the facets or contracts with the init functions you want to call. The second argument is an array of function calldata. So then your initializer function loops through the addresses and calldata and makes a delegatecall with each one. This achieves the same thing as having a diamondCut that takes multiple addresses and calldata.

An example of a diamond int function that calls multiple init functions is here: https://github.com/mudgen/diamond-1-hardhat/blob/main/contracts/upgradeInitializers/DiamondMultiInit.sol

anders-torbjornsen commented 2 years ago

Yes that's true that'd work, and I'd only need to deploy that contract once and then could use for all future deployments

mudgen commented 2 years ago

Yes that's true that'd work, and I'd only need to deploy that contract once and then could use for all future deployments

Yes

Pandapip1 commented 2 years ago

This appears to be superseded by https://ethereum-magicians.org/t/discussion-for-eip2535-diamonds/10459

mudgen commented 2 years ago

Further EIP2535 diamonds discussion has moved here: https://ethereum-magicians.org/t/discussion-for-eip2535-diamonds/10459

mudgen commented 2 years ago

This is great.

Would be curious to see a uniswap build using the Diamond pattern.

Recently a DEX using diamonds was launched. See here: https://www.bsc.news/post/croswap-dex-launches-with-strong-volume-in-first-days