Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upDiamond Standard #2535
Diamond Standard #2535
Comments
|
This is great. Would be curious to see a uniswap build using the Diamond pattern. |
|
@androolloyd yes, me too! |
|
I think you need to add the proper table to the top of this file. Wait, this is an issue not a PR. I'm confused are you trying to make a new EIP? (Putting discussion here since I don't see an external link) This looks interesting, albeit very ahead of it's time. You've clearly put a lot of thought into it, and it is very well written. I'm curious to see how this intersects with governance. My jerk reaction is that this is only useful in a specific application that includes permission-based "cut" control. But even that would introduce lots of systemic risk. For now I think we are all stuck with upgrades being controlled by a single source of control, like Maker and rDAI, which publish documentation to record the history of changes. I look forward to the day where this fine-grained control is reality, and small changes can be made on-the-fly without introducing additional risk! Am I missing another use-case for this? Edit: (follow-up) how can we isolate storage on the same permissioned system that we use to allow cuts? |
|
@pi0neerpat thanks for your kind comments! The discussion is here. An official EIP for this standard is coming soon. It will be EIP 2535. Companies are already using this kind of architecture (ERC1538) for their contracts, such as Caesar's Triumph, Enjin and others. It is real and in use today. Enjin's NFT standard EIP 1155 recommends using an architecture like ERC1538 for upgrades. I don't know why you think a diamond can't be controlled by a single source. The reference implementation of the standard is implemented that way -- only the single owner of the diamond can make changes. Maybe I am understanding you wrong? Authentication is not part of the standard, it can be fine grained or not. A big use case is contracts with designs that exceed the max size of contracts, since diamonds don't have a max size. It is also very nice that while diamonds can be large their functionality can still be compartmented by facets. Particularly NFT contracts tend to exceed the max size limit, such as implementations of ERC721 and ERC998 and others that implement an NFT but also need to implement custom functionality for the application. Permissions and authentication for cuts and access to storage variables can be handled in the same way as any other contract. Yes, if you wanted to, you could add various different permissions/authentications for different changes and handling different storage variables. Or you could keep it simple and not do that. |
Well, exactly. How is this standard any different from the centralized owned upgradeable smart contracts out there? Why not a standard that abstracts upgrades being opt-in only by default? |
To me Uniswap is great exactly because it's not upgradeable/centralized. |
|
@leonardoalt It is different in a lot of ways, but it is not different in regards to ownership/authentication. Ownership/authentication is not part of this standard. The ownership/authentication can be implemented in a diamond any way anybody wants. I realize now that the way the diamond standard is currently written it misleads people into thinking that the diamond standard specifies how authentication/ownership is implemented or should work. It doesn't. So I think I will change the text you quoted because it is misleading. It certainly does not have to be that way, it just could be that way, depending on how ownership/authentication is implemented. But note that the standard does suggest a different way to do ownership or authentication. See the "Decentralized Authority" section. Also, the upgrade functionality can be removed in a diamond making a diamond immutable. This could be done by removing the
Standards or tutorials or implementations that build on top of the diamond standard to provide different ownership/authentication/upgrade schemes are very much wanted! The diamond standard provides basic architecture and structure and points of interoperability with user interfaces and software, and it leaves up to the implementer what the diamond does and how it works. |
The only reason I say this is because the single source would just publish documentation whenever they made a change. There's no real reason to emitt changes as events or capture this on-chain. Maker and other protocols already have governance processes to document this already.
That's a very good feature!
I'll have to look more closely at these. This could be really useful for @austintgriffith DAOG game where the rules of the game are updated on-the-fly. Right now the rules are limited in scope, but this could allow more open-ended rules to be added or removed.
I'm glad you think this is possible because I really think this is the best potential use-case for this system. For instance, in the DAOG game, you could allow players to add/remove rules, without letting them interfere with the core game logic (i.e. the logic and storage deciding who wins and can withdraw the prize pot). Or with a Moloch, you could set certain thresholds for high/low impact changes to the dao contract itself. Dao members could pass smaller rules more easily, to run segregated and quick experiments, without risking the dao's funds. |
The upgrade functionality can also be removed by the contract not being upgradeable in the first place.
Exactly my point. It is basically a really complicated way to make contracts as mutable as simply delegating everything to a dynamic address, which is actually a lot more transparent and simpler to read and check what it's doing. Complicated upgrade standards enable backdoors by obfuscation, especially when you can literally change everything. |
|
@leonardoalt Its is really not very complicated. It is new and the standard does provide a lot of information to help use and implement the standard. The diamond standard is this
A big part of the standard is transparency (logging changes), which removes obfuscation. I plan to make a user interface that shows and visualizes all changes to a diamond. |
|
Some good discussion of the diamond standard here: https://ethereum-magicians.org/t/diamond-contract-standard/4038 |
|
Awesome work @mudgen!! I like this new version of the EIP, and have a few comments:
|
|
@spalladino Thank you very much. This is great feedback! I appreciate that you take the time to look at this and write this feedback.
I like the idea of simplifying the argument to
The argument to the
Can you explain this more? I don't understand the scenario you are describing here.
Wow, that is an interesting idea. I see what you are saying, the loupe isn't needed if software can simply look at the events to determine which functions exist. I'm not sure why @spalladino What do you think now? |
|
The commit message is definitely interesting, but shouldn't it be handled off chain? Perhaps the contract should just have a small identifier that points to an actual commit or release off-chain, for traceability from the source to the deployment? Still, one option (following the
Sorry, forget about this one. I mixed up event topics and function selectors. The first topic for an event, which is derived from a hash of its name and args and identifies the type of event, is 32 bytes long -not 4 bytes like a function selector. So clashes between event names are not possible.
Maybe there is an opportunity to have this standard be automatically ERC165 compliant...? Haven't looked at it in-depth. |
Have the identifier where? The commit messages are not stored in the contracts, they are just emitted with the event that shows the changes. I'd rather just emit the commit message in the event than emit a hash of a commit stored somewhere because I don't think this would be useful for user interfaces that show people all the changes to a diamond, but the commit messages describing the changes could be useful. The idea of the user interface is that it would pull all the verified source code from somewhere like etherscan so people could easily see all the source code of all the facets used by a diamond, and in addition people could see the verified source code of how a diamond was in the past if it was cut. And people would see the commit messages describing the upgrades, why they were done etc.
I don't see how it could be automatically compliant. I do like ERC165 and I think it is good for people to use it. |
|
I recommend that this standard use ERC-165, just for the Because this contract is VERY general in purpose, the The event has arrays in it, so this limits the ability to search logs. The API is overly complex:
It can be: struct DiamondBatchCuts {
bytes4[] functionSelectors;
address[] implementations;
}
interface Diamond {
function diamondCut(DiamondBatchCuts calldata diamondCuts) external;
event DiamondCut(bytes4 functionSelector, address oldImplementation, address newImplementation);
}It is unnecessary to categorize adding, changing and removing. Simply, a zero address corresponds to no implementation and a non-zero address is an implementation. Use extra bytes. Nobody asked for this feature but I'll suggest it any way. I assume the standard contract is implemented like: contract DiamondImplementation {
mapping (bytes4 => address) implementations;
function diamondCut(DiamondBatchCuts calldata diamondCuts) external {
for (uint i = 1; i < diamondCuts.functionSelectors.length, I++) {
address old = diamondCuts.functionSelectors[I];
implementations[diamondCuts.functionSelectors[i]] = diamondCuts.implementations[i];
emit DiamondCut(diamondCuts.functionSelectors[I], old, diamondCuts.implementations[I]);
}
}
}So this means the storage is You can store more... but what? The selector in the target contract! So you can have a function selector OR you can ignore this suggestion entirely if all the implementation contracts are implemented using the fallback function, which is better. E.g. specify that all implementations are: interface DiamondFunctionImplementation {
fallback () external {
// code goes here
}
}^^ this will be more efficient. I didn't actually read the EIP some maybe you already specified this. Documentation on storage mutability is insufficient. This is a major design consideration. And as somebody that audits contracts I'll hate auditing this kind of contract :-~~~ Mutability is bad. As stated before in my prior related review. I'm still not a fan of using this EIP or Zeppelin OS for upgradeable contracts. If you want to upgrade your contract then best practice is to deploy a new contract and spend your marketing budget to inform everybody of the new version. This is what I did, multiple times, when working on ERC-721. Since I published the first "ERC-721 compliant contract" (it's Su Squares, check it out) that means I needed to redeploy it every time there was a new ERC-721 draft. That's okay, and all of the wallet providers know me because I kept having to bother them to update the contract address in MetaMask, MyEtherWallet, etc. And that's a good thing. An exception is zero-knowledge proof contracts. These require an enormous amount of code. And these require a limited caveat to my note above. It might be reasonable for a ZKP contract to be deployed in multiple stages. But the contract should not be open to the public until deployment is completed (dependent contracts are loaded) and no further changes should be possible after deployment. Even still, the functionality of Diamond contracts should not be necessary for this deployment strategy. |
|
@fulldecent I appreciate your feedback on this.
Yes, I'll add this to diamond standard.
I agree that
There is a section about function selector conflicts in the security section.
The argument to An important part of the diamond standard is creating user interfaces that pull all the verified source code that is used and displaying it in such a way that a person can see and understand all the code that is currently used by a diamond and also look at past code that was used before it was cut.
It is necessary to prevent function selector conflicts. That's why it is there. The alternative is to let the user or off-chain software first verify that they aren't making any function selector clashes before calling
I understand your DiamondImplementation and I understand that an address is 160 bits and that additional data can be stored in the 256 slot. But after that I am lost. But I am interested. Can you explain more?
I think I kinda remember your prior review but for some reason I can't find it. Do you happen to know where it is? Because I'd like to review it if we can find it. Nevermind I found it! It was an email to me and I found it. I remember you working and handling Su Squares and I thought you did a good job with it and the way you did things with it was good. |
|
This change is complete: I renamed the |
Okay, here's the truth, I just don't totally trust 100 percent that events will be available all the time, forever, and with good performance all the time, forever. Maybe that is dead wrong and hope it is wrong and I'd love someone to prove it to me that it is wrong so I am totally convinced. I want to be overly safe until then -- after all we are dealing with diamonds!. Having the loupe functions implemented is a very good guarantee that you will be able to inspect your diamonds for facets and functions. If it is implemented right and it doesn't work then that means ethereum contracts don't work anymore and we have bigger problems. So I'm keeping the loupe functions in the standard. Actually I removed two of them: |
|
I am going to support @leonardoalt fully here. Rare are the occasions where upgradeability on-chain cannot be replaced with off-chain mechanisms to achieve the same end result. As an added problem, the more you complicate on-chain upgradeability mechanisms, the more obfuscated and less auditable these become. This means that clients' trust on the system is greatly reduced. If everything is mutable why not just delegate execution? |
I feel like this approach takes the stance that we're never going to improve the way we do things today. The audit log itself ensures that no central party has to prove what the state is, as it's self managed. As a user if you want any real trust with proxies, you want to have your own, anytime an app needs a proxy they deploy one for you, as a user, you could have A proxy that you trust, that you cut with any features that you need, without having to extend that trust to anyone else to verify what the state of your proxy is. Governance maintained Diamonds with the ability to cut in new features seems like a huge boon in terms of how we manage and maintain upgradeability. No question there are new security challenges to deal with, but these types of patterns work well in other application development to date. |
|
As a user you should be asked to opt-in an upgrade, not be forced to trust obfuscated code. |
No disagreements there, which is why its great for user owned contracts. |
|
@leonardoalt @androolloyd @GNSPS One thing to keep in mind, which @fulldecent pointed out, is that this standard is very general, which also means that it is extremely flexible. It is easy to make the error of pegging this standard to a particular use case or to make assumptions about it.
@leonardoalt This standard is probably flexible enough to accommodate that. I'm interested in more details about how that could work. I myself am very guilty of pegging this standard to a particular use case: "upgradeable contracts". This standard is useful for creating very useful immutable contracts that can't be upgraded. How so? Well there might be many ways (being so general and all) but I think of two really good use cases. But before I tell you the use cases let me tell you how to make useful immutable contracts with this standard. The standard has been carefully edited to say that a diamond "uses" the
Obfuscation exists when there are no tools to make something transparent and clear. This standard standardizes diamonds so that tools can be written for them so they are transparent and clear. |
|
@androolloyd I love this use case:
I want everyone to have their own diamond. |
|
Right now when using a diamond from EIP-2535 Diamond Standard, and using the diamondCut function to remove functions from a diamond, should trying to remove a function that already does not exist cause the transaction to revert? Or should it be a noop and the transaction succeeds? When trying to replace a function with the same function using the same facet, should the transaction succeed or should it be a noop and the transaction succeeds? Upgrades should be done in a careful, intentional way, so it makes sense to me to revert in these cases. But if it does not revert then the diamondCut function is a little more flexible. What do you think? Here's how it is currently written in EIP-2535 Diamond Standard:
I am thinking of changing this in the standard so that these cases revert. Please give me your feedback and reasoning. |
Opposed because the suggested change would require an additional |
Gas and idempotence are out of scopeThe intended use case is not specified (missing in Simple Summary, and EIP is woefully failing to meet correct EIP format). But I understand that the use case is a human administrator manually performing diamond upgrades. In this case one-time Idempotence is badSee also the ERC-721 discussion that led to the requirement that all token transfers will also specify the current token owner. Theoretically, this information is redundant (you could query it with |
|
Thanks @wjmelements ! The SLOAD on the prior value is done anyway. You are correct that with this change diamondCut loses its idempotence. |
|
@fulldecent Thank you for your input! Fully noted. Safety is really important. And I'd rather have security auditors pointing out less things. |
|
I'm also considering another change to the standard. Currently the function signature for struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
Facet[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;I am thinking of changing it to this: interface IDiamondCut {
enum FacetCutAction {Add, Replace, Remove}
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
The change is adding I have two reasons for possibly making this change:
The downside to making this change is that it slightly complicates the diamondCut function by adding something to it. I appreciate any feedback on this change, for or against. |
Why? |
|
@wjmelements The SLOAD on the prior value is done as a safety check. To make sure that a new function being added does not exist already. |
|
if a diamondCut tries to remove a function that does not exist
That sounds like an error, although benign, to me.
if a diamondCut tries to tries to upgrade a function with a facet it is already using
That sounds benign, to me.
Jules
… On 23 Sep 2020, at 23:17, Nick Mudge ***@***.***> wrote:
@everyone <https://github.com/everyone> I have a question: Right now if a diamondCut tries to remove a function that already does not exist or tries to upgrade a function with a facet it is already using it is noop and nothing happens and the transaction succeeds.
Should it continue to work like this or revert?
Upgrades should be done in a careful, intentional way, so it makes sense to me to revert in this case. But if it does not revert then the diamondCut function is a little more flexible. What do you think?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#2535 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AOPZGXTKW46XRGFVYVRFLM3SHJXX3ANCNFSM4K3SHYQQ>.
|
|
Would it make more sense to use different functions for the different operations, add, reface, remove?
Jules
… On 24 Sep 2020, at 01:17, Nick Mudge ***@***.***> wrote:
I'm also considering another change to the standard.
Currently the function signature for diamondCut is this:
struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
Facet[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
I am thinking of changing it to this:
struct FacetCut {
address facetAddress;
bool replace; // If true then replace selectors, otherwise add selectors
bytes4[] functionSelectors;
}
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
The change is adding replace to the struct.
I have two reasons for possibly making this change:
It makes it more explicit what you are doing. I think that contract upgrades should be explicit and intentional. Saying exactly what you are replacing and what you are adding makes it more explicit and intentional.
This prevents selector clash. Without this there is a very small possibility of trying to add a function whose signature hashes to the same 4 bytes as an existing function in a diamond. With this change, this selector clash is not possible.
The downside to making this change is that it slightly complicates the diamondCut function by adding something to it.
I appreciate any feedback on this change, for or against.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#2535 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AOPZGXVQ5TMJZDGGOYBPOBDSHKFYDANCNFSM4K3SHYQQ>.
|
@JulesGoddard You are correct, they are benign. |
No. Upgrades need to be atomic or else storage data corruption is possible. For example if new functionality depended on adding 1 function and replacing 1 function then those two changes need to occur in the same transaction. Otherwise it is possible that a transaction is sent inbetween one transaction to add and one transaction to replace. |
|
That is a good point.
Would the replace flag you propose apply to all the selectors being replaced?
Is that the same as having a separate function to replace selectors?
Jules
… On 29 Sep 2020, at 15:35, Nick Mudge ***@***.***> wrote:
Would it make more sense to use different functions for the different operations, add, reface, remove? Jules
No. Upgrades need to be atomic or else storage data corruption is possible. For example if new functionality depended on adding 1 function and replacing 1 function then those two changes need to occur in the same transaction. Otherwise it is possible that a transaction is sent inbetween one transaction to add and one transaction to replace.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#2535 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AOPZGXWKB4YHV6MR3T75TMTSIHWEBANCNFSM4K3SHYQQ>.
|
|
Embrace zero. No need to make things unnecessarily complicated. interface IDiamondCut {
struct FacetCut {
bytes4 functionSelector;
address oldProxyTarget; // can be zero for "no proxy"
address newProxyTarget; // can be zero for "no proxy"
}
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _facetCuts Details every change being made
/// @param _initializationContract The address of the contract or facet to execute _initializationCalldata
/// @param _initializationCalldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
/// @apiSpec Function throws if a specified oldProxyTarget does not match the actual current proxy target
function diamondCut(
FacetCut[] calldata _facetCuts,
address _initializationContract,
bytes calldata _initializationCalldata
) external;
event DiamondCut(FacetCut[] _facetCuts, address _initializationContract, bytes _initializationCalldata);
}
|
Yes. The I updated EIP-Diamond Standard with the following:
|
|
That looks great.
… On 29 Sep 2020, at 16:21, Nick Mudge ***@***.***> wrote:
Would the replace flag you propose apply to all the selectors being replaced?
Is that the same as having a separate function to replace selectors?
Yes.
The diamondCut function takes an array of FacetCut structs.
I updated EIP-Diamond Standard with the following:
To add new functions create a FacetCut struct with facetAddress set to the facet that has the new functions and functionSelectors set with the function selectors to add. Set the action enum to Add.
To replace functions create a FacetCut struct with facetAddress set to the facet that has the replacement functions and functionSelectors set with the function selectors to replace. Set the action enum to Replace.
To remove functions create a FacetCut struct with facetAddress set to address(0) and functionSelectors set with the function selectors to remove. Set the action enum to Remove.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#2535 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AOPZGXSUUU5RVLRKJ67HNGTSIH3PJANCNFSM4K3SHYQQ>.
|
Great! @fulldecent Thank you for your proposed design of |
|
Leaving a note on this claiming to solve the issue with maximum contract size. Really any contract that uses an underlying proxy that looks up function signatures for respective implementations is already solving that problem. I spoke about this at the first TruffleCon in 2018 but it is an existing idea used by many and note really owned by anyone in particular. |
|
@elenadimitrova EIP-2535 does not claim to own the idea, it provides a standardized way to use it. I'm sorry if it gives that kind of impression and I am open to changing the text to make it better. It does reference older implementations of the idea in the Inspiration & Development section. Diamond storage is new and the modularity made possible by it is new, since it wasn't possible until 10 March 2020. That's great that you spoke about the technique to look up function signatures for respective implementations in 2018. I would like to share that kind of material if possible. |
|
Should diamonds have loupe functions? I wrote an article about it here: https://dev.to/mudgen/why-loupe-functions-for-diamonds-1kc3 |
@dguido I'm very interested in this review (and I'm sure others are as well), when can we expect to read about this? |
|
Currently, the words "storage" and "data" are used interchangeably across:
For clarity, I request that only one of the terms is used. On a separate note, I request that there be a base contract class for a "Storage" (or "Data") contract. Benefits include:
|
Use clear languageCurrently, every contract every deployed to Ethereum is a valid EIP-2535: Diamond Standard contract. This is because the specification does not actually have any requirements. Please study RFC 2119 and see how this is used in ERC-721 to make a specification that actually specifies things. |
Add motivationPlease see EIP-1. Diamond Standard is complicated. And it is not a valid specification. At the moment this is a fatal flaw. Further, nobody can make sense of it because there is no project scope (i.e. "Motivation"). 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. |
Factor out loupe requirementThe loupe interface may or may not actually be required (Diamond Standard actually does not have any requirements, see above). But all of the reference implementations support loupe. The loupe specification (assuming I understand what you mean, not what is written) requires a whole lot of overhead in all diamond contracts which provide no benefit towards the stated goal... I am GUESSING the goal of Diamond Standard is to make it easy to upgrade contracts. (I could make a better argument here if the goal of the project was stated, see MOTIVATION notes above.) (Weakly) therefore, loupe should be removed or factored out from the main specification because it is complicated and does not support the goal. Also, the reference implementations should include an example ("THE" example) where a loupe and the extra complexity this requires is not in it. |
|
|
@fulldecent Thanks for these points. I agree that EIP-2535 Diamond Standard need some informational editing and specifically the motivation section. The Specification section is meant to be the specification. The Can you be more specific about what would be catastrophic? The loupe functions are required. The new Motivation section provides information about why they exists and their use. Also, there is this article about the loupe functions: https://dev.to/mudgen/why-loupe-functions-for-diamonds-1kc3 |
I added a Motivation section: https://eips.ethereum.org/EIPS/eip-2535#motivation |
|
@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 |
|
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. |
EIP-2535 Diamond Standard exists here: https://eips.ethereum.org/EIPS/eip-2535
Below is a feedback and discussion of the standard.