-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
ERC1538: Transparent Contract Standard #1538
Comments
First Questions:
|
Great questions. I'll answer the last three questions first:
|
There are various ideas about different designs and implementations of upgradeable contracts. These are found on the web. I didn't find any that provide the same list of benefits that this standard provides. The closest I found was zepplin's vtable implementation. ERC1538 was inspired by the vtable implementation, specifically from the idea of managing an upgradeable contract by function, instead of by contract address. |
Looks pretty good on the face of it. I'll have to go deeper and think any caveats when I get a little more time to jump in but yeah, nice format to allow flexibility while making things have some standard visibility on change history/reason. I think there could be some improvements or cuts to ERC1538Query perhaps, quick example being in what format should functionSignatures() return, comma/semi-colon delimited for eg. but that is minor detail. Question on this: "Delegate contracts can be small, reducing gas costs. Because it costs more gas to call a function in a contract with many functions than a contract with few functions." Do you have numbers or have links to numbers? edit: One thing you should perhaps think about adding/talking about is this is not something that should be done by default, and if it is used then people should be ideally putting in capability to pull authority out on being able to change the functions and where they delegate to. |
@AC0DEM0NK3Y thanks for the great feedback! When a function is called on a contract there is a linear search to find the function in the contract. So every function costs a different amount of gas to be called. I did a simple test with a simple function. I made a contract with one function. The gas cost to execute it was 384. I made a second contract with the same exact function 26 times but with slightly different function names. The most expensive function to call was 934. |
@AC0DEM0NK3Y Yes, people can call the |
From solidity documentation:
This is not a security vulnerability in a transparent contract because the code above does check that a contract address exists before making a delegate call. It is possible for a user to submit an invalid address to the |
Why not simply create a generic worklow engine SC that understands BPMN workflow templates that have been transcompiled into a BPMN specific byecode? |
@mwherman2000 Sorry, I am not familiar with how that works so I can't answer that question. |
I would suggest that you get the Aaragon and zepplinOS team involved in this EIP, they are leading upgradeable contract researchers. There are in fact multiple ways to implement upgradeable contracts using delegate call and the would not all be compatible.
I would closely check that this is in line with the zepplinOS unstructured storage pattern, as I think this will be the most widely adopted. |
@mudgen It is a very nice approach to make the size of the contract unlimited. My worry point is how you can make communication between two different implementation contracts. AFAICT It only works when we have independent functions that don't depend on the other functions or internal functions. Ex - some X function is used to change the state that used by other functions and it is implemented in the contract1 and then some Y function is implemented in the contract2 and want to use the X function the how do contract1 and contract2 function will communicate (communication between two implementation contract). |
@mudgen this seems like something quite handy, however it seems that a big bottleneck is the admin key which could be compromised to lead to a dodgy contract or simply lost. Seems you would need a sort of consortium to manage it... |
@mudgen One thing I think is missing is a default |
@jackandtheblockstalk Thanks for your feedback. I will check with the Aaragon and zepplinOS teams. To be clear, multiple functions from the same delegate contract can be added to a transparent contract at the same time with the |
@satyamakgec communication between functions from different contracts is not a problem. If The problem can also be avoided by putting all the related functionality into one contract and then adding all the external/public functions to the transparent contract with one call to |
@James-Sangalli yes, I think it is possible to build various kinds of systems on top of a transparent contract, such as governance, decentralized schemes, authentication schemes etc. I modified the proposal, saying that the scheme for ownership or authentication is not part of the standard. |
@adibas03 I see that a default delegate address could be convenient. Would it be necessary? The initial functions of a transparent contract could be added to it in the transparent contract's constructor. Various kinds of initialization of a transparent contract could be done in the constructor. Here is an example that adds initial functions to a transparent contract in the constructor. constructor(address _erc1538Delegate) public {
contractOwner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
// adding ERC1538 updateContract function
bytes memory signature = "updateContract(address,string,string)";
bytes4 funcId = bytes4(keccak256(signature));
delegates[funcId] = _erc1538Delegate;
emit FunctionUpdate(funcId, address(0), _erc1538Delegate, string(signature));
emit CommitMessage("Added ERC1538 updateContract function at contract creation");
// add initial functions
// uses the updateContract function to add initial functions
bytes memory initialFunctions = "myFirstFunction()mySecondFunction(string)";
address initialDelegate = 0x343434353222222;
bytes memory calldata = abi.encodeWithSelector(signature, initialDelegate, initialFunctions, "Adding initial functions");
bool success;
assembly {
success := delegatecall(gas, _erc1538Delegate, add(calldata, 0x20), mload(calldata), calldata, 0)
}
require(success, "Adding initial functions failed.");
} @adibas03 I agree with you that a consortium need not be part of the standard. |
@mudgen This is great. There is no chance of selector clashing exploit with this implementation. As far as storage goes, the reference implementation is the way to go as best practice (i.e. inherited storage). All delegates will probably also want to inherit from the ERC1538Delegate reference implementation for updateContract(). I'm trying to remember... assume a transparent contract's storage variables are all in the inherited base contract, and delegate contract 1 and delegate contract 2 also inherit these variables from the same base contract. Can delegate contract 1 and 2 then define their own separate storage vars on top of that or do they also need to line up in memory? I was assuming the former, as that is how it is worded in the description (also, it would be a pretty clear, hassle-free implementation at that point). |
@Droopy78 Yes! The lack of functions directly in a transparent contract completely avoids selector clashing and the complexity of dealing with that issue. Yes, the variable storage of contract1 and contract2 need to line up. I found this sort of thing easiest to manage by creating a new storage contract for each new delegate contract that inherits the storage class from the previous delegate contract. This strategy keeps the storage variables lined up without duplicate code and without the chance of creating bugs. Here's some examples of storage scenarios: Let's say that contract1 was created and added to a transparent contract. Now you want to add contract2 to the transparent contract.
I am also interested in unstructured storage. The advantage of this approach is that later contracts can create new storage locations without having to define previous ones that it doesn't use. |
@mudgen You are right, it seems not necessary, but it might help to have it as part of the standard. |
That's a cool pattern. One thing to add would be to add a security mechanism that invalidate transaction if the contract changed while the transaction was in transit, or simply if the user was not yet aware of the changes. Whenever the contract change, a version number is generated and that version is always used as part of the transaction data. Implementation could then check the version number matches the current contract version. If it matches, the transaction is allowed. If not it is rejected since the sender was not aware of the changes at that point. This would prevent frontrunning attack where the owner of the contract could change the contract logic just in time to compromise a wealthy sender. |
@wighawag Yes, you bring up a good point here. Thanks for this insight. It would definitely be good to prevent frontrunning attacks. |
@wighawag One way to handle frontrunning attacks is to verify the expected state after the call to the function you care about in the same transaction. If the state is not as expected then revert. This is how Project Wyvern handles frontrunning. Project Wyvern or its protocol handles the exchange contracts for OpenSea.io and other exchanges. So here is an example of this. Let's say that ContractA is a transparent contract and you want to call function Handling frontrunning like this works. No change to ERC1538 is needed to implement this. |
Another implementation of this is openZeppelin's re-entrancy guard. |
It would really help the standard is people wrote some blog posts or made some videos about it to help educate more more about it, because I think it is a great and very useful standard. |
Hello! Will you seek to publish this as draft document? Right now you have a reference from ERC-1155 which is in Last Call status. If possible I would like that document to link to the published (DRAFT) version of this document rather than this issue. |
@fulldecent Yes, I will create a draft later today. |
Before going into a draft, can I make one last push to suggest a rename? We have been using the "transparent proxy" name for quite some time in ZeppelinOS for something entirely different, and I'd like to avoid clashes. Thanks! |
@spalladino @fulldecent I'll postpone making a draft for a day to give changing the name some thought. Here are some immediate ideas for a different name:
Ideas? Likes? |
Thank you for your email update!
…On Tue, Mar 26, 2019, 2:51 PM Nick Mudge ***@***.***> wrote:
@spalladino <https://github.com/spalladino> @fulldecent
<https://github.com/fulldecent> I'll postpone making a draft for a day to
give changing the name some thought.
Here are some immediate ideas for a different name:
1. Open Contract Standard
2. Open Source Contract Standard
3. Open Proxy Contract Standard
4. Unlimited and Open Proxy Contract Standard
Ideas? Likes?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#1538 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/Ar1NZw53WRZIIkFwGrxEeQlHz5fG__Z7ks5vano0gaJpZM4YCrTF>
.
|
By "Unlimited" do you mean unlimited size? Might be nice to also have such words as "upgradeable functions" since that's at the heart of what you can do with this (besides make unlimited size contracts). Though I know you want to convey that the functions can also be locked down and made immutable again, so it's a flexible standard. What about something like "Flexible & Upgradable Proxy Contract Standard" or "Upgradable Functions & Unlimited Size Contract Standard" |
@Droopy78 yes, I am liking your ideas. |
I'd personally try to keep the name short, rather than having it be fifteen words long and noting every single available feature. 'Upgradeable functions' has a nice ring to it, and is precisely what this does (as opposed to e.g. upgrading the whole contract at once). |
@nventuro yes, good point. |
How about the Proxy Functions Standard ? |
That could also work, but you then need the reader to understand what a proxy is. Everyone gets 'upgradeable'. |
Okay, I didn't find a suitable, agreeable, alternative name so I'm keeping the name as is. Thank you for everyone's input. |
@fulldecent I just made a pull request to add draft EIP 1538. |
Why not "function-upgradeable proxy", a twist on the original suggestion by @nventuro? |
I was just knocking up an example using this standard and some of the ref code and was considering does the "function updateContract" actually need to be part of the standard? It seems like an implementation could update the signatures (and any other data) however they wish, but must obey the requirement of outputting the FunctionUpdate and CommitMessage events as necessary. For my example code for instance, it would be convenient/simpler to update the delegate but also pass in extra info at the same time. |
@AC0DEM0NK3Y You have an excellent point about this. I will think about this some. I am interested in what others think about this point as well. |
@AC0DEM0NK3Y I considered more about the point that you brought up. The That being said, the ERC1538 standard does not say you can't have other functions that also add/remove/replace functions and it does not violate the standard to add those. So in your use case if that will help your contract then do it. |
If you force an API with the updateContract function being standardized, doing impl specific side updates may be infeasible as you can't rely on logic in those side functions anymore always being called, given there will always be the standard path that has to be catered for. So its a trade-off, do you sacrifice flexibility in implementation now in order for the possible future software having a rigid api to call into? edit: Maybe one option would be to add a bytes32 "data" parameter so extra impl specific info can still be passed. |
I don't know if that has been addressed before, but I had to modify my implementation (which was initially based on the example implementation) to support delegation of messages without a signature. This is necessary when the delegate contains a fallback. My implementation is available here More details: |
I am considering making a major breaking change to this standard:
Doing this would simplify the code and make transparent contracts easier to implement. It will also give the ability to use transparent contracts with functions that take structs and string/byte arrays as arguments. In addition I will update the code shown in the standard to use the latest production release of Solidity. Any thoughts, ideas or arguments about this? |
I am considering making another major change to the standard:
Here are the reasons for this:
|
Okay, so there were a number of major changes to make to this standard. Instead of making them I instead created a whole new standard and I just published it here: #2535 One of the major changes was a new name for the standard. The name of the new standard is Diamond Contract Standard. @spalladino I finally found a new name for the Transparent Contract Standard, but since there were a number of major changes I created a new standard that replaces the Transparent Contract Standard. Check out the Diamond Contract Standard: #2535 |
There has been no activity on this issue for two 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. |
This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment. |
eip: 1538
title: Transparent Contract Standard
author: Nick Mudge nick@perfectabstractions.com
status: Draft
type: Standards Track
category: ERC
created: 31 October 2018
None: An EIP has been written for this standard and is here: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1538.md
Simple Summary
This standard provides a contract architecture that makes upgradeable contracts flexible, unlimited in size, and transparent.
A transparent contract publicly documents the full history of all changes made to it.
All changes to a transparent contract are reported in a standard format.
Abstract
A transparent contract is a proxy contract design pattern that provides the following:
Motivation
A fundamental benefit of Ethereum contracts is that their code is immutable, thereby acquiring trust by trustlessness. People do not have to trust others if it is not possible for a contract to be changed.
However, a fundamental problem with trustless contracts that cannot be changed is that they cannot be changed.
Bugs
Bugs and security vulnerabilities are unwittingly written into immutable contracts that ruin them.
Improvements
Immutable, trustless contracts cannot be improved, resulting in increasingly inferior contracts over time.
Contract standards evolve, new ones come out. People, groups and organizations learn over time what people want and what is better and what should be built next. Contracts that cannot be improved not only hold back the authors that create them, but everybody who uses them.
Upgradeable Contracts vs. Centralized Private Database
Why have an upgradeable contract instead of a centralized, private, mutable database?
Here are some reasons:
Immutability
In some cases immutable, trustless contracts are the right fit. This is the case when a contract is only needed for a short time or it is known ahead of time that there will never be any reason to change or improve it.
Middle Ground
Transparent contracts provide a middle ground between immutable trustless contracts that can't be improved and upgradeable contracts that can't be trusted.
Purposes
Benefits & Use Cases
This standard is for use cases that benefit from the following:
New Software Possibilities
This standard enables a form of contract version control software to be written.
Software and user interfaces can be written to filter the
FunctionUpdate
andCommitMessage
events of a contract address. Such software can show the full history of changes of any contract that implements this standard.User interfaces and software can also use this standard to assist or automate changes of contracts.
Specification
General Summary
A transparent contract delegates or forwards function calls to it to other contracts using
delegatecode
.A transparent contract has an
updateContract
function that enables multiple functions to be added, replaced or removed.An event is emitted for every function that is added, replaced or removed so that all changes to a contract can be tracked in a standard way.
A transparent contract is a contract that implements and complies with the design points below.
Terms
delegatecall
.Design Points
A contract is a transparent contract if it implements the following design points:
updateContract
function with a contract that implements the ERC1538 interface. TheupdateContract
function can be an "unchangeable function" that is defined directly in the transparent contract or it can be defined in a delegate contract. Other functions can also be associated with contracts in the constructor.updateContract
function.updateContract
function associates functions with contracts that implement those functions, and emits theCommitMessage
andFunctionUpdate
events that document function changes.FunctionUpdate
event is emitted for each function that is added, replaced or removed. TheCommitMessage
event is emitted one time for each time theupdateContract
function is called and is emitted after anyFunctionUpdate
events are emitted.updateContract
function can take a list of multiple function signatures in its_functionSignatures
parameter and so add/replace/remove multiple functions at the same time.delegatecall
. If there is no delegate contract for the function then execution reverts.The transparent contract address is the address that users interact with. The transparent contract address never changes. Only delegate addresses can change by using the
updateContracts
function.Typically some kind of authentication is needed for adding/replacing/removing functions from a transparent contract, however the scheme for authentication or ownership is not part of this standard.
Example
Here is an example of an implementation of a transparent contract. Please note that the example below is an example only. It is not the standard. A contract is a transparent contract when it implements and complies with the design points listed above.
As can be seen in the above example, every function call is delegated to a delegate contract, unless the function is defined directly in the transparent contract (making it an unchangeable function).
The constructor function adds the
updateContract
function to the transparent contract, which is then used to add other functions to the transparent contract.Each time a function is added to a transparent contract the events
CommitMessage
andFunctionUpdate
are emitted to document exactly what functions where added or replaced and why.The delegate contract that implements the
updateContract
function implements the following interface:ERC1538 Interface
Function Signatures String Format
The text format for the
_functionSignatures
parameter is simply a string of function signatures. For example:"myFirstFunction()mySecondFunction(string)"
This format is easy to parse and is concise.Here is an example of calling the
updateContract
function that adds the ERC721 standard functions to a transparent contract:Removing Functions
Functions are removed by passing
address(0)
as the first argument to theupdateContract
function. The list of functions that are passed in are removed.Source Code Verification
The transparent contract source code and the source code for the delegate contracts should be verified in a provable way by a third party source such as etherscan.io.
Function Selector Clash
A function selector clash occurs when a function is added to a contract that hashes to the same four-byte hash as an existing function. This is unlikely to occur but should be prevented in the implementation of the
updateContract
function. See the reference implementation of ERC1538 to see an example of how function clashes can be prevented.ERC1538Query
Optionally, the function signatures of a transparent contract can be stored in an array in the transparent contract and queried to get what functions the transparent contract supports and what their delegate contract addresses are.
The following is an optional interface for querying function information from a transparent contract:
See the reference implementation of ERC1538 to see how this is implemented.
The text format for the list of function signatures returned from the
delegateFunctionSignatures
andfunctionSignatures
functions is simply a string of function signatures. Here is an example of such a string:"approve(address,uint256)balanceOf(address)getApproved(uint256)isApprovedForAll(address,address)ownerOf(uint256)safeTransferFrom(address,address,uint256)safeTransferFrom(address,address,uint256,bytes)setApprovalForAll(address,bool)transferFrom(address,address,uint256)"
How To Deploy A Transparent Contract
updateContract
function.See the reference implementation for examples of these contracts.
Wrapper Contract for Delegate Contracts that Depend on Other Delegate Contracts
In some cases some delegate contracts may need to call external/public functions that reside in other delegate contracts. A convenient way to solve this problem is to create a contract that contains empty implementations of functions that are needed and import and extend this contract in delegate contracts that call functions from other delegate contracts. This enables delegate contracts to compile without having to provide implementations of the functions that are already given in other delegate contracts. This is a way to save gas, prevent reaching the max contract size limit, and prevent duplication of code. This strategy was given by @amiromayer. See his comment for more information. Another way to solve this problem is to use assembly to call functions provided by other delegate contracts.
Decentralized Authority
It is possible to extend this standard to add consensus functionality such as an approval function that multiple different people call to approve changes before they are submitted with the
updateContract
function. Changes only go into effect when the changes are fully approved. TheCommitMessage
andFunctionUpdate
events should only be emitted when changes go into effect.Security
General
The owners(s) of an upgradeable contract have the ability to alter, add or remove data from the contract's data storage. Owner(s) of a contract can also execute any arbitrary code in the contract on behalf of any address. Owners(s) can do these things by adding a function to the contract that they call to execute arbitrary code. This is an issue for upgradeable contracts in general and is not specific to transparent contracts.
Unchangeable Functions
"Unchangeable functions" are functions defined in a transparent contract itself and not in a delegate contract. The owner(s) of a transparent contract are not able to replace these functions. The use of unchangeable functions is limited because in some cases they can still be manipulated if they read or write data to the storage of the transparent contract. Data read from the transparent contract's storage could have been altered by the owner(s) of the contract. Data written to the transparent contract's storage can be undone or altered by the owner(s) of the contract.
In some cases unchangeble functions add trustless guarantees to a transparent contract.
Transparency
Contracts that implement this standard emit an event every time a function is added, replaced or removed. This enables people and software to monitor the changes to a contract. If any bad acting function is added to a contract then it can be seen. To comply with this standard all source code of a transparent contract and delegate contracts must be publicly available and verified.
Security and domain experts can review the history of change of any transparent contract to detect any history of foul play.
Rationale
String of Function Signatures Instead of bytes4[] Array of Function Selectors
The
updateContract
function takes astring
list of functions signatures as an argument instead of abytes4[]
array of function selectors for three reasons:updateContract
to prevent selector clashes.Gas Considerations
Delegating function calls does have some gas overhead. This is mitigated in two ways:
Storage
The standard does not specify how data is stored or organized by a transparent contract. But here are some suggestions:
Inherited Storage
The storage variables of a transparent contract consist of the storage variables defined in the transparent contract source code and the source code of delegate contracts that have been added.
A delegate contract can use any storage variable that exists in a transparent contract as long as it defines within it all the storage variables that exist, in the order that they exist, up to and including the ones being used.
A delegate contract can create new storage variables as long as it has defined, in the same order, all storage variables that exist in the transparent contract.
Here is a simple way inherited storage could be implemented:
Unstructured Storage
Assembly is used to store and read data at specific storage locations. An advantage to this approach is that previously used storage locations don't have to be defined or mentioned in a delegate contract if they aren't used by it.
Eternal Storage
Data can be stored using a generic API based on the type of data. See ERC930 for more information.
Becoming Immutable
It is possible to make a transparent contract become immutable. This is done by calling the
updateContract
function to remove theupdateContract
function. With this gone it is no longer possible to add, replace and remove functions.Versions of Functions
Software or a user can verify what version of a function is called by getting the delegate contract address of the function. This can be done by calling the
delegateAddress
function from the ERC1538Query interface if it is implemented. This function takes a function signature as an argument and returns the delegate contract address where it is implemented.Best Practices, Tools and More Information
Below is a growing list of articles concerning transparent contracts and their use. If you have an article about transparent contracts you would like to share then please submit a comment to this issue about it to get it added.
ERC1538: Future Proofing Smart Contracts and Tokens
The ERC1538 improving towards the “transparent contract” standard
Inspiration
This standard was inspired by ZeppelinOS's implementation of Upgradeability with vtables.
This standard was also inspired by the design and implementation of the Mokens contract from the Mokens project. The Mokens contract has been upgraded to implement this standard.
Backwards Compatibility
This standard makes a contract compatible with future standards and functionality because new functions can be added and existing functions can be replaced or removed.
This standard future proofs a contract.
Implementation
A reference implementation of this standard is given in the transparent-contracts-erc1538 repository.
Copyright
Copyright and related rights waived via CC0.
The text was updated successfully, but these errors were encountered: