Posted By : Ashutosh
Smart contracts are immutable by default. Once deployed, their code cannot be changed. While this ensures trustlessness, it becomes a problem when you need to fix bugs, add features, or adapt to new requirements.
Upgradable smart contracts solve this problem by allowing developers to update contract logic without changing the contract's address.
This is where proxies come into play. Proxies act as middlemen, delegating function calls to the latest version of your logic contract. In this guide, we'll explain how proxies work and walk you through building your first upgradable contract. If you are looking to know more about smart contracts, visit our smart contract development services.
For simplicity, we'll focus on the Transparent Proxy pattern in this guide.
Also, Check | Creating Cross-Chain Smart Contracts with Polkadot and Substrate
Install Hardhat and OpenZeppelin (a library for secure smart contracts):
mkdir upgradable-contracts && cd upgradable-contracts
npm init -y
npm install --save-dev hardhat
npx hardhat init # Choose "Create a JavaScript project"
npm install @openzeppelin/contracts @openzeppelin/hardhat-upgrades
Create contracts/StorageV1.sol
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract StorageV1 {
uint256 public value;
// Initialize instead of a constructor
function initialize(uint256 _value) public {
value = _value;
}
function updateValue(uint256 _value) public {
value = _value;
}
}
Create a deployment script (scripts/deploy.js
):
const { ethers, upgrades } = require("hardhat");
async function main() {
// Deploy the implementation (StorageV1) and proxy
const Storage = await ethers.getContractFactory("StorageV1");
const proxy = await upgrades.deployProxy(Storage, [100], {
initializer: "initialize"
});
await proxy.waitForDeployment();
console.log("Proxy deployed to:", await proxy.getAddress());
}
main();
Run the Script:
npx hardhat run scripts/deploy.js --network localhost
Create test/Storage.test.js
:
const { expect } = require("chai");
const { ethers, upgrades } = require("hardhat");
describe("Storage (Proxy)", function () {
it("Should deploy and initialize", async function () {
const Storage = await ethers.getContractFactory("StorageV1");
const proxy = await upgrades.deployProxy(Storage, [100]);
expect(await proxy.value()).to.equal(100);
});
});
Run tests:
npx hardhat test
Create contracts/StorageV2.sol
with a new function:
contract StorageV2 is StorageV1 {
function increment() public {
value += 1;
}
}
Update the proxy to use StorageV2(scripts/upgrade.js
):
async function main() {
const StorageV2 = await ethers.getContractFactory("StorageV2");
const proxy = await upgrades.upgradeProxy("YOUR_PROXY_ADDRESS", StorageV2);
console.log("Proxy upgraded to StorageV2");
}
You may also like | Optimism Platform: Developing and Implementing Layer 2 Smart Contracts
initialize
functions instead.
Upgradable smart contracts are essential for building flexible, future-proof dApps. By using proxy patterns, you can update your contract's logic while preserving its state and address. Start with the Transparent Proxy pattern, follow best practices, and always prioritize security.
May 2, 2025 at 03:40 am
Your comment is awaiting moderation.