Upgradeable Smart Contracts

Explainer: Everything You Wanted to Know About Upgradeable Smart Contracts

Smart contracts are designed to be immutable. On the other hand, software quality is strongly reliant on the capacity to upgrade and fix source code to make iterative releases. Even though blockchain-based software benefits greatly from the technology’s immutability, some mutability is required for bug fixes and prospective product upgrades. For those planning smart contract development, this is a must-read.

Why is it required to upgrade a Smart Contract sometimes?

By default, Ethereum smart contracts are immutable. Once they are created, there is no way to change them, effectively operating as an unbreakable contract between participants.

However, there are certain circumstances in which we wish the contracts could be upgraded. There are numerous incidents of millions of dollars’ worth of Ether being stolen or hacked, which might be avoided if smart contracts could be updated.

How do improvements operate behind the scenes?

We can upgrade our contracts in a variety of ways.

The most obvious method from a layman’s point of view is given below :

● Create and deploy a new contract version.

● Transfer all states from the old contract to the new contract manually.

This appears to function; however, it has several flaws. It can be costly to migrate the contract state.

The contract address will change as we build and deploy new contracts. As a result, all contracts that interacted with the old contract would need to be updated to utilize the new version’s address.

You’d also have to contact all of your users and persuade them to start utilizing the new contract and manage both contracts simultaneously, as users are slow to move.

A better solution is to employ a proxy contract with an interface in which each method is delegated to the implementation contract.

A delegate call is similar to a conventional call, except that all code is run in the context of the caller (proxy), not the callee (implementation). As a result, all reads or writes to the contract storage will read or write from the proxy’s storage, and any transfers in the implementation contract’s code will transfer the proxy’s balance.

This method is preferable since users only interact with the proxy contract, and we can update the implementation contract while keeping the proxy contract the same.

This appears to be a better strategy than the prior one; however, any changes to the implementation contract methods would necessitate updating the proxy contract’s methods (as the proxy contract has interface methods). Users will need to adjust their proxy address as a result.

We can utilize a fallback function in the proxy contract to overcome this problem. Any request will trigger the fallback function, redirecting the request to the implementation and returning the result (using opcodes). This is similar to the previous technique, except that the proxy contract has no interface methods and only a fallback function, so there’s no need to update the proxy address if contract methods face any changes.

The Proxy Scheme

Although it is impossible to upgrade the code of a smart contract that has already been deployed, you can set up a proxy contract architecture that allows you to use freshly deployed contracts as if your main logic has been upgraded.

Let’s take a look at the most well-received upgradeable contract offers.

Ethereum Improvement Proposals:

EIP 1538

It comprises a contract version management system that uses standard events to publicly log changes to a contract for traceability. It also has a limitless storage capacity, numerous atomic upgrades, and a standard interface for external calls.

It’s a compromise between an immutable, trustless, transparent contract and upgradeable, obfuscated delegate contracts.

Terminology:

A transparent contract has a fallback that uses the delegatecall opcode to redirect function calls to a delegated contract. If necessary, it also allows for the inclusion of other immutable functions.

Delegate contracts can be updated by calling the updateContract function with the new address. Users can only interact with a fixed address (the address of the transparency).

To safeguard delegate updates from within the transparent contract, the standard should include an authentication method.

To document changes, each function update produces CommitMessage and FunctionUpdate events.

A list of function signatures can be used to make many modifications at once.

When the updateContract function is removed from the newly deployed delegate, mutability can be turned off at any moment.

EIP 2535

It is the successor to EIP-1538, also known as the Diamond standard, and it features a modular design enabling iterative smart contract development, partial and full upgrades, and various aspects.

A diamond is a contract that ties facets to external functions. External functions are exposed by facets, which are freestanding contracts.

Terminology:

When an external function is called, the diamond executes its fallback. It then makes the delegatecall after retrieving the corresponding facet address from the selectorToFacet mapping.

Facet upgrades are performed using the DiamondCut function, which is called with a facet address, selectors, and an action (add, replace, or remove) to avoid function collisions. A new event is also emitted to document the modifications.

State variables for facets should be kept in a static storage slot (Diamond storage pattern).

Diamond storage is a technique that uses a reference to a set of predefined memory slots to control the scope of state variables across many facets.

When facets inherit from the same contract or library, they can share internal functionalities.

Facets can be reused and shared by several diamonds. The Loupe facet of each diamond has four basic external functions for displaying all other facets and their purposes.

EIP 1822

This is a straightforward standard. It uses a proxiable contract for upgrades and compatibility checks and a proxy contract with a unique storage slot that references the new logic contract address. It also allows the bytecode of the proxy contract to be verified.

Internal functionalities can be shared when facets inherit from the same contract or library.

Facets are re-usable and can be shared by multiple diamonds. Each diamond’s Loupe facet has four main exterior roles for displaying all of the other facets and their functions.

Terminology:

Within the proxy contract, the logic contract address is stored in a fixed memory slot: keccak256(“PROXIABLE”) slot address. The standard proxiable contract should be the basis for all subsequent logic contracts.

The updateCodeAddress function in the proxiable contract is used to execute upgrades (update keccak256(“PROXIABLE”) slot address value).

Before the upgrade, a compatibility check is done to confirm that the logic contract complies with the Universal Upgradeable Proxy Standard.

To avoid overwriting existing values within the proxy, the sequence in which variables are instantiated in subsequent logic contracts is critical.

For this purpose, it is advised that you utilize a base contract that all new contracts inherit from. Owner and LibraryLock contracts are typically used in conjunction with the logic contract to regulate the access and restrain potentially harmful functions.

Because it accepts any arbitrary data in its own function Object() { [native code] }, the proxy contract permits numerous constructors provided in the logic contract.

1155 EIP

The standard is flexible to enable new types at any moment and is a facade for numerous token types, including any combination of fungible, non-fungible, and semi-fungible tokens. It also allows for atomic operations across a variety of types.

A unique ID identifies each token. For the preceding check of the destination address, the contract should use the ERC-165 supportsInterface. Each function in the contract takes an ID parameter and sends the request to the token linked. Allows numerous operations to be processed at the same time.

Upgrade Smart Contracts with Antier

Even though these patterns tackle the immutability problem, they all use an opcode-based approach, reducing code readability and making the developer experience uncomfortable.

Some high-stakes instances, such as the DAO attack, might necessitate a low-level revision of the spec itself (a hard fork), compromising the trustworthiness of the protocol.

Other patterns, such as the eternal storage pattern, emphasize the upgradability of data structures rather than the logic and storage separation approach.