In this blog post, we will delve into an important, yet often overlooked feature in Ethereum smart contracts – upgradability. The Ethereum blockchain as a decentralized platform for applications provides powerful capabilities, but it introduces unique challenges. One such challenge is that once a smart contract is deployed to the blockchain, its code cannot be modified. Hence, a probable solution is making smart contracts upgradeable.
A contract is termed upgradeable if its functionality can be changed without changing the contract’s address. This implies that any data stored in the contract will not be lost, a critical requirement for any dApp operating in production.
The most common method to make a contract upgradeable is by implementing a proxy contract system. The method separates the storage and logic into two separate contracts – a Proxy contract (stores data) and an Implementation contract (stores logic). All external function calls are routed through the Proxy contract, which then delegates the execution to the Implementation contract.
Here's a simple Proxy contract written in Solidity, the commonly used language for Ethereum smart contracts:
pragma solidity ^0.6.0; contract Proxy { address public implementation; constructor(address _implementation) public { implementation = _implementation; } fallback() external payable { address _impl = implementation; require(_impl != address(0)); assembly { let ptr := mload(0x40) calldatacopy(ptr, 0, calldatasize()) let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0) let size := returndatasize() returndatacopy(ptr, 0, size) switch result case 0 { revert(ptr, size) } default { return(ptr, size) } } } }
In the above code snippet, an external function call to the Proxy is redirected to the Implementation contract using low-level delegatecall
.
In a Proxy contract system, upgrading the smart contract involves deploying a new Implementation contract and updating the Implementation address in the Proxy contract. Take note that the data in the Proxy contract remains intact.
function upgrade(address newImplementation) external { require(msg.sender == owner, "Only owner can upgrade"); implementation = newImplementation; }
In this function, the owner of the contract can change the implementation
address to a new version.
The Proxy contract pattern allows for more flexibility in the blockchain environment, and it's a practical solution to one of Ethereum’s hard limitations. Developers need to understand its benefits and potential pitfalls and ensure they balance performance, complexity, and gas costs. Always plan for upgradability.