Replaced by EIP-2535 Diamond Standard.
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.
A transparent contract is a proxy contract design pattern that provides the following:
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 and security vulnerabilities are unwittingly written into immutable contracts that ruin them.
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.
Why have an upgradeable contract instead of a centralized, private, mutable database? Here are some reasons:
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.
Transparent contracts provide a middle ground between immutable trustless contracts that can't be improved and upgradeable contracts that can't be trusted.
This standard is for use cases that benefit from the following:
This standard enables a form of contract version control software to be written.
Software and user interfaces can be written to filter the FunctionUpdate and CommitMessage 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.
Note: The solidity
delegatecallopcode enables a contract to execute a function from another contract, but it is executed as if the function was from the calling contract. Essentiallydelegatecallenables a contract to "borrow" another contract's function. Functions executed withdelegatecallaffect the storage variables of the calling contract, not the contract where the functions are defined.
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.
delegatecall.A contract is a transparent contract if it implements the following design points:
updateContract function with a contract that implements the ERC1538 interface. The updateContract 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 the CommitMessage and FunctionUpdate events that document function changes.FunctionUpdate event is emitted for each function that is added, replaced or removed. The CommitMessage event is emitted one time for each time the updateContract function is called and is emitted after any FunctionUpdate 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.
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 and FunctionUpdate 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:
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:
Functions are removed by passing address(0) as the first argument to the updateContract function. The list of functions that are passed in are removed.
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.
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.
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 and functionSignatures 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)"
updateContract function.See the reference implementation for examples of these 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.
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. The CommitMessage and FunctionUpdate events should only be emitted when changes go into effect.
This standard refers to owner(s) as one or more individuals that have the power to add/replace/remove functions of an upgradeable contract.
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.
Note: The design and implementation of contract ownership is not part of this standard. The examples given in this standard and in the reference implementation are just examples of how it could be done.
"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.
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.
The updateContract function takes a string list of functions signatures as an argument instead of a bytes4[] array of function selectors for three reasons:
updateContract to prevent selector clashes.Delegating function calls does have some gas overhead. This is mitigated in two ways:
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.
It is possible to make a transparent contract become immutable. This is done by calling the updateContract function to remove the updateContract function. With this gone it is no longer possible to add, replace and remove 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.
More information, tools, tutorials and best practices concerning transparent contracts need to be developed and published.
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
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.
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.
A reference implementation of this standard is given in the transparent-contracts-erc1538 repository.
Copyright and related rights waived via CC0.