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.

mudgen commented 3 years ago

Please add a motivation section as per EIP-1, so that people can understanding what you are thinking so that contributions can be accepted to fix the things that are broken.

I added a Motivation section: https://eips.ethereum.org/EIPS/eip-2535#motivation

mudgen commented 3 years ago

@adklempner @tjrush Quantstamp just published a smart contract audit of an implementation of EIP-2535 Diamond Standard.

The audit report is here: https://certificate.quantstamp.com/full/aavegotchi-ghst-staking

The repository the audit was on is here: https://github.com/aavegotchi/ghst-staking

mudgen commented 3 years ago

Josselin Feist, from Trail of Bits wrote a blog post about an old, outdated implementation of EIP-2535 Diamond Standard. I wrote a response to his blog post here: https://dev.to/mudgen/addressing-josselin-feist-s-concern-s-of-eip-2535-diamond-standard-me8

Josselin was hired from Trail of Bits to help us find bugs and improve our code. And he did and we fixed and improved things. Then he published an article featuring our old, unfixed code to represent EIP-2535 Diamond Standard. To be accurate he should provide information and code for current implementations.

mudgen commented 3 years ago

Here is a good high-level overview of the Diamond Standard and the diamond pattern: https://docs.aavegotchi.com/overview/diamond-standard

The diamond pattern is a code implementation and organization strategy. The diamond pattern makes it possible to implement a lot of contract functionality that is compartmented into separate areas of functionality, but still using the same Ethereum address. The code is further simplified and saves gas because state variables are shared between facets.

mudgen commented 3 years ago

@MickdeGraaf wrote a twitter thread about PiaDAO's use of EIP-2535 Diamond Standard in their new PieVault diamonds. See here: https://twitter.com/MickdeG010/status/1339990356163739650

PieVaults are build using The Diamond Standard invented by @mudgen.

The Diamond Standard allows us to build contracts with virtually no size limit in a modular way using facets.

PieVaults can be upgraded on the fly with new features without having to redeploy.

The modular nature of PieVaults also allow us to write facets with very little code to include new features rapidly with a shorter development cycle.

I wanna thank @Evert0x for the amazing development work on this project and @MixBytes for the great audit in which no critical vulnerabilities were found.

A week ago @MixBytes released a smart contract security audit on @PieDAO_DeFi's new PieVault diamonds which use EIP-2535 Diamond Standard. https://twitter.com/MickdeG010/status/1340023233177931780

mudgen commented 3 years ago

I have successfully utilized a new pattern in Ethereum diamonds called AppStorage. It is a nicer and more convenient way to access application specific state variables that are shared among facets. See here: https://dev.to/mudgen/appstorage-pattern-for-state-variables-in-solidity-3lki

mudgen commented 3 years ago

BarnBridge has released the source code for its diamond: https://github.com/BarnBridge/BarnBridge-Barn

BarnBridge has had two security audits on its diamond, which are here: https://github.com/BarnBridge/BarnBridge-PM/tree/master/audits

mudgen commented 3 years ago

Because of an intellectual property right conflict the name "Diamond Standard" needs to change. Please see the poll I made here: https://twitter.com/mudgen/status/1369626205608148992 and very happy to receive name suggestions.

Remscar commented 3 years ago

I believe there exists an error in this EIP document on line 153 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2535.md#L153)

In this function:

function getDataA() external view returns (bytes32) {
    return LibDiamond.diamondStorage().dataA
 }

LibDiamond is referenced, however I believe it it supposed to be referencing LibA instead.

I'm new to this standard, so i'm not sure if this is the case.

mudgen commented 3 years ago

I believe there exists an error in this EIP document on line 153 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2535.md#L153)

In this function:

function getDataA() external view returns (bytes32) {
    return LibDiamond.diamondStorage().dataA
 }

LibDiamond is referenced, however I believe it it supposed to be referencing LibA instead.

I'm new to this standard, so i'm not sure if this is the case.

@Remscar hey, thanks for this. Fixing it.

PatrickAlphaC commented 3 years ago

Looking at the terminology, I'm also curious why the name diamond was chosen. It seems like it actually makes it harder to understand rather than easier.

Reading an article @mudgen wrote responding to Trail of Bits, he mentions:

Originally EIP-2535 Diamonds was called the Transparent Contract Standard, but that name clashed with something from OpenZeppelin and they asked me to change the name.

I'm sure that OpenZeppelin was referring to the transparent proxy pattern outlined here.

It seems that the diamond standard is an improvement or evolution on this version of the transparent proxy pattern, so my suggesting is we call this standard something in line with that. Perhaps something like "Multi-Transparent Proxy Standard" or "MTP".

The terminology like diamondCut for example is supposed to add/replace/remove any number of functions from any number of facets in a single transaction. The cut seems counterintuitive for adding functionality to a smart contract.

And it seems the Single Cut Diamond is quite similar to OpenZeppelin's Transparent Proxy Pattern, so it might make sense to have this standard be an extension of that pattern.

Of course, these are all just thoughts as someone who did a lot of digging around on standards and wasn't able to grasp what a diamond was very quickly, but was able to grasp a proxy very quickly.

mudgen commented 3 years ago

@PatrickAlphaC Thanks for your input about this.

The name diamond was chosen for its conceptual framework. When implementing diamonds people typically put related functions together in their own facets, therefore isolating and organizing functionality. Each facet becomes a different "side" or "facet" of the diamond. For example a diamond can implement an ERC721 token as one facet and implement an ERC1155 token as another facet, and other facets for specific functionality implemented by sets of functions.

The analogy isn't perfect, but it provides a number of ways to conceptually think of implementing diamonds. For example the facets of a real diamond all share the same center of the diamond. All the facets of an EIP-2535 diamond share the same proxy contract where all contract storage is stored.

mudgen commented 3 years ago

So it is known, when a physical diamond is cut, it creates a new facet.

There is a new poll for changing the name of the standard from EIP-2535 Diamonds to EIP-2535 Diamonds, Multi-Facet Contract Standard. Please respond with your vote here: https://twitter.com/mudgen/status/1419840710963081222

mudgen commented 3 years ago

A second poll for the name of the standard is here: https://twitter.com/mudgen/status/1419865781169446915

Amxx commented 3 years ago

With this being in last call, I think it's high time I raise a big concern I have.

Some contracts have a fallback function, not just for proxy redirecting, but because it is part of their logic to have such a function. This behavior is, currently, not reproducible by ERC 2535.

Issue

It is not possible to redirect calls to a default module if the msg.sig is not associated with a specific module.

Possible solution

If address facet = selectorTofacet[msg.sig]; is address(0) then try to fetch the "default facet". The default facet could either be a separate storage slot, of a reserved msg.sig (I personally use 0xffffff)

Example of implementation

mudgen commented 3 years ago

Hi @Amxx, thanks for this feedback. You have a very good point.

The standard did provide some flexibility regarding this. In the Implementation Points section it said this:

The fallback function finds the facet associated with the function and executes the function using delegatecall. If there is no facet for the function and no other mechanism to handle it then execution reverts.

However I think the use case of a default function or module is important enough that more information should be provided about it specifically. So I modified the implementation point to say this:

The fallback function finds the facet associated with the function and executes the function using delegatecall. If there is no facet for the function then optionally a default function may be executed. If there is no facet for the function and no default function and no other mechanism to handle it then execution reverts.

In addition I added a new "Default Function" subsection to the "Rational" section.

It says this:

Default Function

Solidity provides the fallback function so that specific functionality can be executed when a function is called on a contract that does not exist in the contract. This same behavior can optionally be implemented in a diamond by implementing and using a default function, which is a function that is executed when a function is called on a diamond that does not exist in the diamond.

A default function can be implemented a number of ways and this standard does not specify how it must be implemented.

SchoofsKelvin commented 3 years ago

I discovered a potential interaction issue which I explained in the discussion of EIP-1967:

Basically, it's not unthinkable (and for certain use cases, even ideal) to have a diamond represent an implementation contract, where you can deploy new proxy contracts pointing at this diamond. A proxy would then delegate call the diamond, which intuitively should make the diamond determine the proper facet for msg.sig and handle it from there.

The issue is that since we're delegate calling the diamond, the diamond's code will operate on the storage of the caller. That means the diamond loses access to all its stored facets/selectors (and DiamondStorage in general) and tries to use the caller's, which is (most likely) incorrect.

There are of course workarounds for this, e.g. the proxy isn't a regular proxy, but using the implementation as a IDiamondLoupe to request the facet from (using STATICCALL), then delegate call this facet. In this case, the proxy basically acts as a diamond, except it uses the implementation diamond's storage to query with facet to use. Still, this requires you actively design your proxy this way.

TL;DR: Delegate-calling a diamond isn't simply "calling the diamond but use the same storage/msg.sender", it's completely different and counter-intuitive.

Magicking commented 3 years ago

I would recommend avoiding the use of diamond name as it refers to the decades-old inheritance computer science problem of The Diamond Problem and may lead to confusion for new-comers.

dagumak commented 3 years ago

I would recommend avoiding the use of diamond name as it refers to the decades-old inheritance computer science problem of The Diamond Problem and may lead to confusion for new-comers.

I would like to add to this sentiment of avoiding the use of the name diamond. Personally, I am not familiar with how diamonds are processed. It just seems like a cognitive hurdle and a distraction to have to understand another domain to think about how this concept works. I don't have a better suggestion at the moment, but that's my initial impression.

Edit:

I don't have a full understanding of the whole EIP yet. However, I do see similarities with the command pattern here.

What you refer to as a diamond would be similar to an invoker class in the command pattern. The invoker class knows what to call/invoke, but the method's functionality and implementation lives outside of the invoker class in a command class. Similarly, a facet is comparable to a command in the command pattern. A command's implementation can be "upgraded" without the invoker class having to know this which is a great separation of concern.

I'm sure there are nuances that I haven't discovered yet, but that's the similarity I am seeing so far. If you think this is an accurate comparison, then perhaps it would be a good idea to find inspiration from this old design pattern.

https://en.wikipedia.org/wiki/Command_pattern

mudgen commented 3 years ago

@Magicking @dagumak Thank you for the feedback concerning the Diamond name. I hear you about it. The name would have been changed if there was enough feedback and consensus earlier on to change it. At this point I don't think the name will be changed because there is too much built using it, too much documentation using it, too much familiarity and name recognition and understanding connected with it. More than 28 projects are using diamonds. I think the name can still be changed if the majority of those projects (the main users of the standard so far) want to change the name.

I understand that the Diamond name could be counterintuitive at first but it is not arbitrary. The name was chosen after experience implementing the pattern in smart contracts using Solidity. The diamond name is a conceptual model. A real physical diamond has different sides, different facets. An Ethereum diamond also has different sides, facets, as sets of related functions. After gaining experience implementing diamonds your mind will divide into different functions sets that are tied to a common core — the Ethereum address and the state of a diamond. Similar in a way to how the facets of a physical diamond are connected to a common center.

@dagumak You are right that there are strong similarities between the diamond pattern and the command pattern. It is a good comparison. The command pattern is more general. The diamond pattern is a more specific pattern that generally implements the command pattern. A major part of the diamond pattern is that state variables are defined in facets (or in Solidity libraries used by facets), but state variable data is stored in the diamond proxy contract (the invoker) -- and not in the facets. And address(this) in any facet refers to the diamond proxy address, not a facet address. And msg.sender and msg.value in facets refer to values from calls on the diamond proxy. These points are enforced by the evm and embraced by the diamond pattern. These points shape the multi-faceted but centrally focused conceptual model of a diamond.

PatrickAlphaC commented 3 years ago

I don't think 28 projects is "too many". I'm not sure if a Twitter vote should be taken as conclusive results.

I agree with @dagumak and @Magicking

SolidTea commented 3 years ago

I think diamonds is first of all a really cool name and it relates a sense of solidity (no pun intended) and quality. I don't know that the name of a standard or any technology for that matter specifically needs to convey exactly how it works. I think it rarely does most times. When you build something that's highly abstract such as this standard I'm not sure you even could come up with a name that conveys what it does at first glance. But in that regard, a diamond is a pretty solid analogy. Maybe something that's more straightforward would be easier to adopt... something like SPP for Sharded Proxy Pattern?

PatrickAlphaC commented 3 years ago

OOOO I LOVE SPP!!! That sounds like an AMAZING name!!

SolidTea commented 3 years ago

I want to make it clear that I have not yet successfully attempted an implentation of the Diamond Standard and thus my suggestion for SPP is born from a rather limited understanding of the inner workings of this Standard. Hence it could very well not accurately reflect the actual functioning of the Diamond Standard. Moreover it could lead to confusion as there already is something called Sharding in the blockchain world. If you enjoy the more straightforward name of it, maybe Multi Faceted Proxy Pattern (as suggested partly by Mudgen) would make more sense. And the diamond analogy would still be relevant. Bonus point: MFPP is a funny acronym.

mudgen commented 3 years ago

@PatrickAlphaC and @SolidTea, thanks for your feedback on the name. I think "Sharded Proxy Pattern" is relevant and I like that name.

Yes, "Multi-Facet Proxy" was recently added to the name of the standard to make it more clear what it is.

fdarian commented 3 years ago

In my opinion, "Diamond" is already great since I could not think of any other analogy to represent this pattern as a whole. The concept of diamond wraps all facets, or modules, into a single thing and has a single contract as the entry point that any account can interact and describe all of the functionalities it has.

I like "Multi-Facet Proxy" and "Sharded Proxy Pattern", but having a one-word name with an analogy is better. The "modularity" and "proxy" terms help describe this pattern further. Thus, it's necessary to have an explanatory title like it currently has, though it still uses jargon "facet". And I also suggest showing this kind of diagram to help introduce the concept of facet at first glance.

Modularity

However, I experienced one thing when I introduced this pattern to my colleagues: they heard "facade" instead of "facet," which is another similar design pattern.

Furthermore, I support your argument that many documentations and projects have used diamond names and analogies. One perfect example is louper.dev, which acts like a real loupe.

Amxx commented 3 years ago

I could not think of any other analogy to represent this pattern as a whole

I'm sorry to bring that again, but VTables?

IMO proxy patterns is more a concern for developers than end-users. In this case, there is already appropriate terminology in the computer science space. Also, in this same space, diamond often refer to a widely understood but unrelated issue. So can we please stop acting like the terminology must be understood by all the users (because it doesn't) at the expense of confusing the devs that are actually going to interact with it?

mudgen commented 3 years ago

I'm sorry to bring that again, but VTables?

VTables is a good comparison, and a diamond is an implementation of VTables, but VTables is a more general pattern. To say a diamond is a VTable is an incomplete analogy of the diamond pattern. VTable is part of the pattern but not the whole pattern.

The VTable pattern says nothing about how the state of an implementation is handled, which is a major part of the diamond pattern. In the diamond pattern the state variables are defined by the facets or Solidity libraries used by the facets but all the data is stored and accessed centrally by the diamond proxy. The decentralized state definition but centralized data store and access is part of the pattern.

To handle a diamond's state management different techniques can be used such as Diamond Storage, App Storage or Inherited Storage. Info about these are in the article at this link in the "Keep Your Data Right" section: https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard

Amxx commented 3 years ago

I'm not completelly confortable with that part.

IMO, ERC should standardize how people interact with a contract, through standard interfaces (ERC20-ERC721) and signature scheme (ERC712). But I don't think the ERC should have anything to say about how its being implemented, and how the contracts should use their layout their storage.

I have implemented ERC1538 by having all my modules/facets inherit the same abstract contract with contained all the storage slots, and which was expended with upgrades. Does that mean its not diamond?

Then I would argue this ERC needs to be slip in 2:

The first one would be the base for dev tooling. Any UI (etherscan?) that follows it would be able to details the current configuration.

The second one would just be recommendation for developers building platforms through such a proxy

mudgen commented 3 years ago

Understood.

The standard does not specify or require how contracts should use their storage layout beyond requiring that delegatecall is used to borrow functions from other contracts. The use of delegatecall is what causes the general way state is handled in diamonds --- variable data is stored and accessed in the diamond proxy contract and not it its facets. The details and techniques beyond that are up to the developer.

The standard does provide suggestions or possible ways to manage state variable layout (Diamond Storage, AppStorage, Inherited Storage) , but those are only suggestions or options and explicitly says that other techniques could be used. They all follow the same state variable pattern that is caused and enforced by the EVM via delegatecall -- that all variable data is stored and accessed in the diamond proxy contract and not its facets.

How you implemented ERC1538 is Inherited Storage and is one of the suggested ways of managing state variables in a diamond.

A diamond only needs to implement the Specification section to be a diamond.

The use of delegatecall to multiple contracts from a fallback function and how that affects writing/reading state variables in general forms the diamond pattern.

0xpApaSmURf commented 3 years ago

I'm not sure if this has been covered. Apologies in advance

Doesn't the suggestions around access patterns run into overflow problems? Each slot points to a 32-byte block in storage, but having a data structure that takes more than 32-bytes could result in some overflow and unintended overwriting. The larger the structure, the higher the likelihood of overflow.

Obviously using a hash (or other randomisation function unique to the structure) randomises it but wouldn't the data size increase the degrees of collisions to a non-negligible extent? You mentioned that the standard doesn't specify how storage layout should be managed; does that mean we have to implement our own storage address access pattern to avoid problems like this? Or do you just assume a happy path of no collisions if using keccak256? If so, there needs to be an analysis done on the increased rate of storage address collisions for varying data structure sizes to be stored.

Have I misunderstood the proposed methodology or is my thinking above correct?

mudgen commented 3 years ago

@0xpApaSmURf

Doesn't the suggestions around access patterns run into overflow problems? Each slot points to a 32-byte block in storage, but having a data structure that takes more than 32-bytes could result in some overflow and unintended overwriting. The larger the structure, the higher the likelihood of overflow.

How does it overflow? Lots of structs take more space than 32 bytes. That's okay. I'm not quite sure what problem you are trying to describe here.

Obviously using a hash (or other randomisation function unique to the structure) randomises it but wouldn't the data size increase the degrees of collisions to a non-negligible extent?

No. Using a random hash to specify the location of a struct or other type is standard practice in Solidity. It's how Solidity mappings and dynamic arrays work. Diamond Storage allows the programmer to do it explicitly while the Solidity compiler does it behind the scenes when mappings and dynamic arrays are used.

The address space of a smart contract is so large that the chance of data from random locations colliding/overlapping is almost 0.

You mentioned that the standard doesn't specify how storage layout should be managed; does that mean we have to implement our own storage address access pattern to avoid problems like this?

No. Use the suggested storage strategies: Inherited Storage, AppStorage, DiamondStorage. Or use a different one if you want to. The standard just wants to be flexible to allow future innovations.

Or do you just assume a happy path of no collisions if using keccak256?

Yep, like mappings and dynamic arrays.

If so, there needs to be an analysis done on the increased rate of storage address collisions for varying data structure sizes to be stored.

I would love to see the results of such an analysis. I wonder if any has been done. It is a good idea.

If still concerned about storage collisions then use AppStorage. It doesn't use a random location in storage. It uses location 0 in contract storage.

SchoofsKelvin commented 3 years ago

If so, there needs to be an analysis done on the increased rate of storage address collisions for varying data structure sizes to be stored.

Assuming that a regular contract (facet) uses at most 2^32 storage slots [1] and Solidity's key calculation produces completely random 256-bit storage slots [2], the chance of a collision within a facet is 1 in 2^224, i.e. 1 in 2.7e67. Very very unlikely to happen. Now assume your contract has (to pick an easy number) 2^10 (1024) facets, the chance is now higher, being "only" 1 in 2^214, i.e. 1 in 2.6e64. Mind that Ethereum with its 20-byte addresses only has 2^160 (1.5e48) addesses.

Basically, as long as the algorithm that calculates the storage slots isn't heavily flawed (and Solidity's [2] is pretty solid, even while applying DiamondStorage), storage slot collisions aren't really an issue.

[1] 2^32 would be 4.3 billion storage slots. Since setting a storage slot from zero to non-zero costs 20k gas, this would cost a total of 85 trillion gas to initialize. According to Etherscan data, that's all the gas ever used on Ethereum mainnet up until a week ago (12th of October 2021, where the cumulative total passed that number, hitting 8.50274e13 gas). In other words, you'll never even get close to 2^32 storage slots. [2] You can read more on that here on how it calculates the storage slots.

mudgen commented 3 years ago

@SchoofsKelvin Wow, some great analysis. Thank you!

Also keep in mind that in many cases different facets of a diamond share some of the same storage slots -- this is how facets share state variables, they can read and write to the same state variables.

0xpApaSmURf commented 3 years ago

@SchoofsKelvin Thanks for the super helpful response. Very interesting and informative with tangible numerics!

Granted the problem does actually exist but in extremely diminishing rates but definitely requires a remotely unbad storage slot calc; which ends up being the premise of my original message --- the standard SHOULD EITHER describe this to make the vulnerability known to implementers that will write their own storage layout algo OR prescribe a storage layout method that must be adhered to if implementing the standard (which could be as simple as "use Solidity's" if its widely agreed to be robust enough). Leaving this up to the implementer whilst also not detailing the potential pitfalls (however small) is incomplete IMO.

mudgen commented 2 years ago

https://louper.dev is a user interface for EIP-2535 Diamonds.

It uses any diamond's louper functions to show what facets and functions a diamond has and links to verified source code. It also enables people to execute functions that exist in diamonds. It also has more functionality like providing diamond ABIs. It is currently under development for further functionality.

It is financially supported through gitcoin grants. Consider contributing to this public goods project: https://gitcoin.co/grants/1988/louper-tool-for-inspecting-diamond-eip-2535-smart

0xpApaSmURf commented 2 years ago

@SchoofsKelvin Just revisiting your analysis here.

Your calculations are definitely correct but I think we have to include a different rate of collisions. Your calculations took an example value of 1024 facets, assuming that each facet would simply ask to be 'assigned' a single slot and thus we arrive at the value that is exact slot address collision.

If we take into account instead, though, that we are actually running into address range collisions and assume that each facet might wish to access storage which lays out state that spans more than 1-byte, then our calculation changes significantly. It isn't outlandish to see that many contracts will store a lot of data I think the strongest argument here though is that if Solidity's algorithm for calculating storage layout is solid enough, and we don't think that ballooning occupied address spaces will tangibly increase these chances of collision, then we're all good.

I just think this deserves mentioning and especially any detailed analysis that provides concrete evidence of security levels MUST exist if implementations are ultimately again the developer's responsibility.

CryptoFist commented 2 years ago

I read it and built it. While building upgradeable smart contract with diamond standard, there is a problem. I can't use onlyOwner modiifer of Ownable.sol. Because after added to diamond, the owner address is set 0x000....

How can I use it? And also all values were set in origin facet contract, meaning is the contract before added to diamond format by 0. How can I solve this problem

mudgen commented 2 years ago

I read it and built it. While building upgradeable smart contract with diamond standard, there is a problem. I can't use onlyOwner modiifer of Ownable.sol. Because after added to diamond, the owner address is set 0x000....

Make a contract that has the onlyOwner modifier and no external functions. Make any facet inherit that contract to use the modifier. Aavegotchi does this: https://github.com/aavegotchi/aavegotchi-contracts/blob/master/contracts/Aavegotchi/libraries/LibAppStorage.sol#L255

And also all values were set in origin facet contract, meaning is the contract before added to diamond format by 0.

Please clarify because I don't understand what you are saying here.

CryptoFist commented 2 years ago

SubFacet contract has function that set dateTime contract address and it is set in constructor. After deploy the SubFacet with dateTime contract address and add it to diamond. After adding, the dateTime contract address is format with 0x000.....

mudgen commented 2 years ago

SubFacet contract has function that set dateTime contract address and it is set in constructor. After deploy the SubFacet with dateTime contract address and add it to diamond. After adding, the dateTime contract address is format with 0x000.....

I get it. Constructor functions in facets don't work. This is why state should be set in the diamond proxy contract constructor function or an initialization functions should be called during upgrades. More information about this is in the article at this URL: https://eip2535diamonds.substack.com/p/constructor-functions-dont-work-in

FantasyGameFoundation commented 2 years ago

SubFacet contract has function that set dateTime contract address and it is set in constructor. After deploy the SubFacet with dateTime contract address and add it to diamond. After adding, the dateTime contract address is format with 0x000.....

I get it. Constructor functions in facets don't work. This is why state should be set in the diamond proxy contract constructor function or an initialization functions should be called during upgrades. More information about this is in the article at this URL: https://eip2535diamonds.substack.com/p/constructor-functions-dont-work-in

Does this mean that I can hardly inherit from any existing standard contract, to implement my own facet

mudgen commented 2 years ago

@FantasyGameFoundation

Does this mean that I can hardly inherit from any existing standard contract, to implement my own facet

No, it means to inherit from standard contracts that support diamond storage like from SolidState-Solidity or your own contracts, or contracts that don't read/write state variables.

fergarrui commented 2 years ago

@FantasyGameFoundation

Does this mean that I can hardly inherit from any existing standard contract, to implement my own facet

No, it means to inherit from standard contracts that support diamond storage like from SolidState-Solidity or your own contracts, or contracts that don't read/write state variables.

Well it is actually a real concern. Only libraries that support diamond can be used (which is not the case of most common libraries like Openzeppelin)

mudgen commented 2 years ago

It is true that it is a problem that OpenZeppelin and some other contract libraries with contracts that are inherited and that read or write state variables don't work with diamonds. This barrier can be overcome by modifying existing contracts to work with diamonds, by writing contracts that work with diamonds and by using or contributing to smart contract libraries like SolidState-Solidity that support diamonds.

Also, keep in mind that all the Solidity libraries like Counters.sol, SafeERC20.sol, Strings.sol and all the rest of them from OpenZeppelin and other places all work with EIP2535 Diamonds. It is only the contracts that are inherited and read or write state variables that don't work in diamonds unless they are modified.

SolidState-Solidity has implementations for common things people need that work with diamonds.

mainjoke commented 2 years ago

Excuse me, how should I use chainlink vrf in facet?

nataouze commented 2 years ago

It is true that it is a problem that OpenZeppelin and some other contract libraries with contracts that are inherited and that read or write state variables don't work with diamonds. This barrier can be overcome by modifying existing contracts to work with diamonds, by writing contracts that work with diamonds and by using or contributing to smart contract libraries like SolidState-Solidity that support diamonds.

Hoping in on this point as I'm working on a base lib with a similar concept: core features managed on top of diamond-like storages, usable by inheritance (for proxy and non-proxy setting), or deployable as facets. Each facet having its own initialization logic, I realized that diamondCut could beneficiate from being able to call several initialization functions.

struct Initialization {
    address initContract;
    bytes initData;
}

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

For example, we would be able to add several facets and call their respective init sequences in one go. Currently, we have 2 options:

mudgen commented 2 years ago

@nataouze Yes, I see what you mean. I see that this function could be better for tooling and automated systems. Thank you for this valuable feedback.

I want to point out that adding and using the version of the diamondCut function you showed now is perfectly valid for diamonds and does not violate the standard.

The reason is because the standard says that people can create their own versions of the diamondCut function that take and use different parameters.

From the standard:

You can implement your own custom functions that add or replace or remove functions. You can also implement your own non-standard versions of diamondCut that have different parameters.

However the standard says that the DiamondCut event must be emitted for all functions added/replaced/removed. The only things that are actually enforced or standardized are the DiamondCut event, the loupe interface, and the Implementation Points. This is to give a lot of flexibility to implementors and creators while standardizing some things for integration and interoperability with tools and other software.

Would or could your new library use diamonds?

nataouze 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?

mudgen commented 2 years ago

@nataouze I appreciate your attention and work on this. Perhaps tell me more so I can understand better.

The address _init, bytes _calldata parameters of the DiamondCut event are to specify what initialization function call, if any, was done in an upgrade. More info about this is in the standard.