Posted By : Sarthak
Once deployed, smart contracts cannot be changed or tampered with since they are immutable. However, a contemporary method of smart contract development that can be upgraded is the Ethereum blockchain's Universal Upgradeable Proxy Standard (UUPS). By making the upgrading process easier and improving gas efficiency, it overcomes some drawbacks of earlier proxy patterns, most notably the Transparent Proxy Pattern.
UUPS consists of two main components: the proxy and implementation contracts.
`When deploying a UUPS setup, it's essential to initialize the implementation through the proxy to ensure that state variables are stored correctly in the proxy's storage rather than in the implementation's storage, which is essential for maintaining the integrity and upgradeability of the contract.
All the versions of the implementation contract share the same storage space so that`s why sequencing matters while initializing variables.
In the UUPS pattern, constructors are generally not used due to the proxy design.
The implementation contract does not directly manage state variables; instead, these variables are stored in the proxy's storage. Since constructors are executed during contract deployment and would initialize state variables in the implementation contract, using them would lead to incorrect storage allocation and could result in state variables being stored in the implementation rather than the proxy.
Also, Check | How to Create a Simple Supply Chain Smart Contract
These contracts utilize an initializer function that is called after deployment. This function is designed to set up state variables and can include security mechanisms to ensure it is only called once, preventing re-initialization attacks.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
contract Version1 is
Initializable,
ERC20Upgradeable,
UUPSUpgradeable,
OwnableUpgradeable
{
uint256 public value;
// Initializer function to replace constructor
function initialize() public initializer {
__ERC20_init('Mars', 'MARS');
__Ownable_init(_msgSender()); // Pass the owner address here
value = 5;
__UUPSUpgradeable_init();
_mint(msg.sender, 10000000 * 10 ** decimals());
}
// Upgradeable authorization for upgrades (only owner can upgrade)
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
function getValue() public view returns (uint256) {
return value;
}
}
contract Version2 is Version1 {
function version() public pure returns (string memory) {
return 'V2';
}
}
contract Version3 is Version1 {
function version() public pure returns (string memory) {
return 'V3';
}
}
For the above contract we are upgrading the contract versions from 'V`1' to 'V3', below are the test cases for the proxy contract.
Also, Explore | How to Write and Deploy Modular Smart Contracts
const {
loadFixture,
} = require('@nomicfoundation/hardhat-toolbox/network-helpers');
const hardhat = require('hardhat');
const assert = require('assert');
describe('Proxy', function () {
describe('Deployment', async function () {
async function deployOneYearLockFixture() {
const contract = await hardhat.ethers.getContractFactory('Version1');
const contractVersion2 = await hardhat.ethers.getContractFactory('Version2');
const contractVersion3 = await hardhat.ethers.getContractFactory('Version3');
const proxyContract = await hardhat.upgrades.deployProxy(contract, { kind: 'uups' })
return { proxyContract, contractVersion3, contractVersion2 }
}
describe('Versions', function () {
it('Should set the right output', async function () {
const { contractVersion3, proxyContract, contractVersion2 } = await loadFixture(deployOneYearLockFixture);
assert(await proxyContract.name() == 'Mars')
assert(await proxyContract.getValue() == 5n)
const contractV2 = await hardhat.upgrades.upgradeProxy(proxyContract, contractVersion2)
assert(await contractV2.getValue() == 5n)
assert(await contractV2.version() == 'V2')
const contractV3 = await hardhat.upgrades.upgradeProxy(proxyContract, contractVersion3)
assert(await contractV3.getValue() == 5n)
assert(await contractV3.version() == 'V3')
});
});
})
})
Use the following command to run and verify the test cases for the proxy contract
- npx hardhat test
Also, Read | How to Create Play-to-Earn Gaming Smart Contracts
In conclusion, the Universal Upgradeable Proxy Standard (UUPS) provides a robust framework for developing upgradeable smart contracts on Ethereum. By leveraging a proxy architecture that separates logic from state, it allows for efficient upgrades while maintaining critical aspects of security and decentralization inherent to blockchain technology. As smart contract developers continue to navigate the complexities of smart contract deployment and management, UUPS stands out as a preferred method for ensuring that decentralized applications can evolve over time without compromising their foundational integrity.
December 2, 2024 at 06:38 pm
Your comment is awaiting moderation.