
Advanced Solidity: Implementing Upgradable Smart Contracts with Proxy Patterns
Upgradable smart contracts typically utilize a proxy contract that delegates calls to a logic contract. This separation allows developers to upgrade the logic contract while maintaining the same address for users interacting with the proxy. In this article, we will explore the two most common proxy patterns: the Transparent Proxy and the Universal Upgradeable Proxy Standard (UUPS).
Proxy Patterns Overview
| Proxy Pattern | Description |
|---|---|
| Transparent Proxy | A proxy that forwards calls to a single implementation contract, allowing for upgradeability while maintaining user interactions. This pattern includes an admin role to manage upgrades. |
| UUPS | A more gas-efficient proxy pattern that allows the implementation contract itself to manage upgrades. This reduces the complexity of the proxy contract by embedding upgrade logic within the implementation. |
Transparent Proxy Pattern
The Transparent Proxy pattern involves creating two contracts: a proxy contract and a logic contract. The proxy forwards calls to the logic contract, which contains the business logic.
Step 1: Create the Logic Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Logic {
uint256 public value;
function setValue(uint256 _value) external {
value = _value;
}
}Step 2: Create the Proxy Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Proxy {
address public implementation;
address public admin;
constructor(address _implementation) {
implementation = _implementation;
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
function upgrade(address _newImplementation) external onlyAdmin {
implementation = _newImplementation;
}
fallback() external {
(bool success, ) = implementation.delegatecall(msg.data);
require(success, "Delegatecall failed");
}
}Step 3: Deploying and Interacting with the Contracts
- Deploy the
Logiccontract. - Deploy the
Proxycontract, passing the address of theLogiccontract. - Interact with the
Proxycontract to set the value.
const proxy = await Proxy.deployed();
await proxy.setValue(42); // Calls Logic.setValue(42) through delegatecallUpgrading the Logic Contract
To upgrade the logic, deploy a new version of the Logic contract and call the upgrade function from the Proxy.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract NewLogic {
uint256 public value;
function setValue(uint256 _value) external {
value = _value;
}
function incrementValue() external {
value += 1;
}
}After deploying NewLogic, upgrade the proxy:
await proxy.upgrade(newLogicAddress);
await proxy.incrementValue(); // Calls NewLogic.incrementValue() through delegatecallUniversal Upgradeable Proxy Standard (UUPS)
The UUPS pattern simplifies the upgrade process by allowing the implementation contract to manage its own upgrades. This pattern reduces gas costs and enhances security.
Step 1: Create the UUPS Logic Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
contract UUPSLogic is UUPSUpgradeable {
uint256 public value;
function initialize() external initializer {
value = 0;
}
function setValue(uint256 _value) external {
value = _value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}Step 2: Deploying and Upgrading the UUPS Contract
- Deploy the
UUPSLogiccontract. - Call the
initializefunction to set the initial state. - To upgrade, deploy a new version of the
UUPSLogiccontract and call theupgradeTofunction.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract NewUUPSLogic is UUPSUpgradeable {
uint256 public value;
function initialize() external initializer {
value = 0;
}
function setValue(uint256 _value) external {
value = _value;
}
function incrementValue() external {
value += 1;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}Conclusion
Implementing upgradable smart contracts using proxy patterns is essential for maintaining the longevity and adaptability of decentralized applications. The Transparent Proxy and UUPS patterns each offer unique benefits, allowing developers to choose the best approach based on their project requirements. Understanding these patterns not only enhances your Solidity skills but also positions you to build more robust and future-proof smart contracts.
