{"id":21936086,"url":"https://github.com/learnweb3dao/upgradeable-smart-contracts","last_synced_at":"2025-10-04T20:58:06.092Z","repository":{"id":37833655,"uuid":"482136055","full_name":"LearnWeb3DAO/Upgradeable-Smart-Contracts","owner":"LearnWeb3DAO","description":null,"archived":false,"fork":false,"pushed_at":"2023-01-03T07:48:59.000Z","size":172,"stargazers_count":6,"open_issues_count":4,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T14:51:11.612Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LearnWeb3DAO.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-16T02:33:41.000Z","updated_at":"2024-01-18T04:24:28.000Z","dependencies_parsed_at":"2023-02-01T05:31:05.698Z","dependency_job_id":null,"html_url":"https://github.com/LearnWeb3DAO/Upgradeable-Smart-Contracts","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FUpgradeable-Smart-Contracts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FUpgradeable-Smart-Contracts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FUpgradeable-Smart-Contracts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FUpgradeable-Smart-Contracts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LearnWeb3DAO","download_url":"https://codeload.github.com/LearnWeb3DAO/Upgradeable-Smart-Contracts/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250237798,"owners_count":21397399,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-29T01:13:07.533Z","updated_at":"2025-10-04T20:58:01.028Z","avatar_url":"https://github.com/LearnWeb3DAO.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Upgradeable Contracts and Patterns\n\nWe know that smart contracts on Ethereum are not upgradeable, as the code is immutable and cannot be changed once it is deployed. But writing perfect code the first time around is hard, and as humans we are all prone to making mistakes. Sometimes even contracts which have been audited turn out to have bugs that cost them millions.\n\n\u003cQuiz questionId=\"591dbc1d-11f2-42ab-9f14-bd17174395ca\" /\u003e\n\nIn this level, we will learn about some design patterns that can be used in Solidity to write upgradeable smart contracts.\n\n## How does it work?\n\nTo upgrade our contracts we use something called the `Proxy Pattern`. The word `Proxy` might sound very familiar to you because it is not a web3-native word.\n\n![](https://i.imgur.com/llJGnTF.png)\n\nEssentially how this pattern works is that a contract is split into two contracts - `Proxy Contract` and the `Implementation` contract.\n\nThe `Proxy Contract` is responsible for managing the state of the contract which involves persistent storage whereas `Implementation Contract` is responsible for executing the logic and doesn't store any persistent state. User calls the `Proxy Contract` which further does a `delegatecall` to the `Implementation Contract` so that it can implement the logic. Remember we studied `delegatecall` in one of our previous levels 👀\n\n![](https://i.imgur.com/NpGQqsL.png)\n\n\u003cQuiz questionId=\"52b0dea1-bf3d-4baa-89b5-7464eb62ae9a\" /\u003e\n\u003cQuiz questionId=\"47df78f6-01be-4e17-a7f4-f4ce4e574dfa\" /\u003e\n\nThis pattern becomes interesting when `Implementation Contract` can be replaced which means the logic which is executed can be replaced by another version of the `Implementation Contract` without affecting the state of the contract which is stored in the proxy.\n\n\u003cQuiz questionId=\"46c61058-42b5-479e-af9c-4d2ff49147bd\" /\u003e\n\nThere are mainly three ways in which we can replace/upgrade the `Implementation Contract`:\n\n1. Diamond Implementation\n2. Transparent Implementation\n3. UUPS Implementation\n\nWe will however only focus on Transparent and UUPS because they are the most commonly used ones.\n\nTo upgrade the `Implementation Contract` you will have to use some method like `upgradeTo(address)` which will essentially change the address of the `Implementation Contract` from the old one to the new one.\n\n\u003cQuiz questionId=\"b5a09112-a3ba-40fc-b7d9-88e3a109d181\" /\u003e\n\nBut the important part lies in where should we keep the `upgradeTo(address)` function, we have two choices that are either keep it in the `Proxy Contract` which is essentially how `Transparent Proxy Pattern` works, or keep it in the `Implementation Contract` which is how the UUPS contract works.\n\n![](https://i.imgur.com/KVY1nHq.png)\n\nAnother important thing to note about this `Proxy Pattern` is that the constructor of the `Implementation Contract` is never executed.\n\nWhen deploying a new smart contract, the code inside the constructor is not a part of the contract's runtime bytecode because it is only needed during the deployment phase and runs only once. Now because when `Implementation Contract` was deployed it was initially not connected to the `Proxy Contract` as a reason any state change that would have happened in the constructor is now not there in the `Proxy Contract` which is used to maintain the overall state.\n\nAs a reason `Proxy Contracts` are unaware of the existence of constructors. Therefore, instead of having a constructor, we use something called an `initializer` function which is called by the `Proxy Contract` once the `Implementation Contract` is connected to it. This function does exactly what a constructor is supposed to do but is now included in the runtime bytecode as it behaves like a regular function and is callable by the `Proxy Contract`.\n\n\u003cQuiz questionId=\"6e98af35-e469-4cca-9dab-9fb1e707aa60\" /\u003e\n\nUsing OpenZeppelin contracts, you can use their `Initialize.sol` contract which makes sure that your `initialize` function is executed only once just like a contructor\n\n```solidity\n// contracts/MyContract.sol\n// SPDX-License-Identifier: MIT\npragma solidity ^0.6.0;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\n\ncontract MyContract is Initializable {\n    function initialize(\n        address arg1,\n        uint256 arg2,\n        bytes memory arg3\n    ) public payable initializer {\n        // \"constructor\" code...\n    }\n}\n```\n\nAbove given code is from [Openzeppelin's documentation](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#the-constructor-caveat) and provides an example of how the `initializer` modifier ensures that the `initialize` function can only be called once. This modifier comes from the `Initializable Contract`\n\n\u003cQuiz questionId=\"3d2b995a-bafd-4f5d-9a46-4f4b0c05f46d\" /\u003e\n\nWe will now study Proxy patterns in detail 🚀 👀\n\n## Transparent Proxy Pattern\n\nThe Transparent Proxy Pattern is a simple way to separate responsibilities between `Proxy` and `Implementation` contracts. In this case, the `upgradeTo` function is part of the `Proxy` contract, and the `Implementation` can be upgraded by calling `upgradeTo` on the proxy thereby changing where future function calls are delegated to.\n\nThere are some caveats though. There might be a case where the `Proxy Contract` and `Implementation Contract` have a function with the same name and arguments. Imagine if `Proxy Contract` has a `owner()` function and so does `Implementation Contract`. In Transparent Proxy contracts, this problem is dealt by the `Proxy` contract which decides whether a call from the user will execute within the `Proxy` contract itself or the `Implementation Contract` based on the `msg.sender` global variable\n\nSo if the `msg.sender` is the admin of the proxy then the proxy will not delegate the call and will try to execute the call if it understands it. If it's not the admin address, the proxy will delegate the call to the `Implementation Contract` even if the matches one of the proxy's functions.\n\n## Issues with Transparent Proxy Pattern\n\nAs we know that the address of the `owner` will have to be stored in the storage and using storage is one of the most inefficient and costly steps in interacting with a smart contract every time the user calls the proxy, the proxy checks whether the user is the admin or not which adds unnecessary gas costs to majority of the transactions taking place.\n\n## UUPS Proxy Pattern\n\nThe UUPS Proxy Pattern is another way to separate responsibilities between `Proxy` and `Implementation` contracts. In this case, the `upgradeTo` function is also part of the `Implementation` contract, and is called using a `delegatecall` through the Proxy by the owner.\n\nIn UUPS whether its the admin or the user, all the calls are sent to the `Implementation Contract` The advantage of this is that every time a call is made we will not have to access the storage to check if the user who started the call is an admin or not which improved efficiency and costs. Also because its the `Implementation Contract` you can customize the function according to your need by adding things like `Timelock`, `Access Control` etc with every new `Implementation` that comes up which couldn't have been done in the `Transparent Proxy Pattern`\n\n## Issues with UUPS Proxy Pattern\n\nThe issue with this is now because the `upgradeTo` function exists on the side of the `Implementation contract` developer has to worry about the implementation of this function which may sometimes be complicated and because more code has been added, it increases the possibility of attacks. This function also needs to be in all the versions of `Implementation Contract` which are upgraded which introduces a risk if maybe the developer forgets to add this function and then the contract can no longer be upgraded.\n\n## BUIDL\n\nLets build an example where you can experience how to build an upgradeable contract. We will be using the UUPS upgradeability pattern through this example, though you can build one with the Transparent Proxy Pattern as well!\n\n- To setup a Hardhat project, Open up a terminal and execute these commands\n\n  ```bash\n  npm init --yes\n  npm install --save-dev hardhat\n  ```\n\n- If you are on Windows, please do this extra step and install these libraries as well :)\n\n  ```bash\n  npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers\n  ```\n\n- In the same directory where you installed Hardhat run:\n\n  ```bash\n  npx hardhat\n  ```\n\n  - Select `Create a basic sample project`\n  - Press enter for the already specified `Hardhat Project root`\n  - Press enter for the question on if you want to add a `.gitignore`\n  - Press enter for `Do you want to install this sample project's dependencies with npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)?`\n\nNow you have a hardhat project ready to go!\n\n- We will be using libraries from openzeppelin which support upgradeable contracts. To install those libraries, in the same folder execute the following command:\n\n```bash\nnpm i @openzeppelin/contracts-upgradeable @openzeppelin/hardhat-upgrades @nomiclabs/hardhat-etherscan --save-dev\n```\n\nReplace the code in your `hardhat.config.js` with the following code to be able to use these libraries:\n\n```javascript\nrequire(\"@nomiclabs/hardhat-ethers\");\nrequire(\"@openzeppelin/hardhat-upgrades\");\nrequire(\"@nomiclabs/hardhat-etherscan\");\n\nmodule.exports = {\n  solidity: \"0.8.4\",\n};\n```\n\nStart by creating a new file inside the `contracts` directory called `LW3NFT.sol` and add the following lines of code to it\n\n```solidity\n//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\n\n\ncontract LW3NFT is Initializable, ERC721Upgradeable, UUPSUpgradeable, OwnableUpgradeable   {\n    // Note how we created an initialize function and then added the\n    // initializer modifier which ensure that the\n    // initialize function is only called once\n    function initialize() public initializer  {\n        // Note how instead of using the ERC721() constructor, we have to manually initialize it\n        // Same goes for the Ownable contract where we have to manually initialize it\n        __ERC721_init(\"LW3NFT\", \"LW3NFT\");\n        __Ownable_init();\n        _mint(msg.sender, 1);\n    }\n    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {\n\n    }\n}\n```\n\nLets try to understand what's happening in this contract in a bit more detail\n\nIf you look at all the contracts which `LW3NFT` is importing, you will realize why they are important. First being the `Initializable` contract from Openzeppelin which provides us with the `initializer` modifier which ensures that the `initialize` function is only called once. The `initialize` function is needed because we cant have a contructor in the `Implementation Contract` which in this case is the `LW3NFT` contract\n\nIt imports `ERC721Upgradeable` and `OwnableUpgradeable` because the original `ERC721` and `Ownable` contracts have a constructor which cant be used with proxy contracts.\n\n\u003cQuiz questionId=\"b66fc85b-6fb8-403c-9e99-7ff78d38e6b1\" /\u003e\n\nLastly we have the `UUPSUpgradeable Contract` which provides us with the `upgradeTo(address)` function which has to be put on the `Implementation Contract` in case of a `UUPS` proxy pattern.\n\nAfter the declaration of the contract, we have the `initialize` function with the `initializer` modifier which we get from the `Initializable` contract.\nThe `initializer` modifier ensures the `initialize` function can only be called once. Also note that the new way in which we are initializing `ERC721` and `Ownable` contract. This is the standard way of initializing upgradeable contracts and you can look at the function [here](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC721/ERC721Upgradeable.sol#L45).\nAfter that we just mint using the usual mint function.\n\n```solidity\nfunction initialize() public initializer  {\n    __ERC721_init(\"LW3NFT\", \"LW3NFT\");\n    __Ownable_init();\n    _mint(msg.sender, 1);\n}\n```\n\nAnother interesting function which we dont see in the normal `ERC721` contract is the `_authorizeUpgrade` which is a function which needs to be implemented by the developer when they import the `UUPSUpgradeable Contract` from Openzeppelin, it can be found [here](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol#L100). Now why this function has to be overwritten is intresting because it gives us the ability to add authorization on who can actually upgrade the given contract, it can be changed according to requirements but in our case we just added an `onlyOwner` modifier.\n\n```solidity\n    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {\n\n    }\n```\n\nNow lets create another new file inside the `contracts` directory called `LW3NFT2.sol` which will be the upgraded version of `LW3NFT.sol`\n\n```solidity\n//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./LW3NFT.sol\";\n\ncontract LW3NFT2 is LW3NFT {\n\n    function test() pure public returns(string memory) {\n        return \"upgraded\";\n    }\n}\n```\n\nThis smart contract is much easier because it is just inheriting `LW3NFT` contract and then adding a new function called `test` which just returns a string `upgraded`.\n\nPretty easy right? 🤯\n\nWow 🙌 , okay we are done with writing the `Implementation Contract`, do we now need to write the `Proxy Contract` as well?\n\nGood news is nope, we dont need to write the `Proxy Contract` because `Openzeppelin` deploys and connects a `Proxy Contract` automatically when we use there library to deploy the `Implementation Contract`.\n\nSo lets try to do that, In your `test` directory create a new file named `proxy-test.js` and lets have some fun with code\n\n```solidity\nconst { expect } = require(\"chai\");\nconst { ethers } = require(\"hardhat\");\nconst hre = require(\"hardhat\");\n\ndescribe(\"ERC721 Upgradeable\", function () {\n  it(\"Should deploy an upgradeable ERC721 Contract\", async function () {\n    const LW3NFT = await ethers.getContractFactory(\"LW3NFT\");\n    const LW3NFT2 = await ethers.getContractFactory(\"LW3NFT2\");\n\n    let proxyContract = await hre.upgrades.deployProxy(LW3NFT, {\n      kind: \"uups\",\n    });\n    const [owner] = await ethers.getSigners();\n    const ownerOfToken1 = await proxyContract.ownerOf(1);\n\n    expect(ownerOfToken1).to.equal(owner.address);\n\n    proxyContract = await hre.upgrades.upgradeProxy(proxyContract, LW3NFT2);\n    expect(await proxyContract.test()).to.equal(\"upgraded\");\n  });\n});\n\n```\n\nLets see whats happening here, We first get the `LW3NFT` and `LW3NFT2` instance using the `getContractFactory` function which is common to all the levels we have been teaching till now. After that the most important line comes in which is:\n\n```solidity\nlet proxyContract = await hre.upgrades.deployProxy(LW3NFT, {\n  kind: \"uups\",\n});\n```\n\nThis function comes from the `@openzeppelin/hardhat-upgrades` library that you installed, It essentially uses the upgrades class to call the `deployProxy` function and specifies the kind as `uups`. When the function is called it deploys the `Proxy Contract`, `LW3NFT Contract` and connects them both. More info about this can be found [here](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades).\n\nNote that the `initialize` function can be named anything else, its just that `deployProxy` by default calls the function with name `initialize` for the initializer but you can modify it by changing the defaults 😇\n\nAfter deploying, we test that the contract actually gets deployed by calling the `ownerOf` function for Token ID 1 and checking if the NFT was indeed minted.\n\nNow the next part comes in where we want to deploy `LW3NFT2` which is the upgraded contract for `LW3NFT`.\n\nFor that we execute the `upgradeProxy` method again from the `@openzeppelin/hardhat-upgrades` library which upgrades and replaces `LW3NFT` with `LW3NFT2` without changing the state of the system\n\n```javascript\nproxyContract = await hre.upgrades.upgradeProxy(proxyContract, LW3NFT2);\n```\n\nTo test if it was actually replaced we call the `test()` function, and ensured that it returned `\"upgraded\"` even though that function wasn't present in the original `LW3NFT` contract.\n\nTo run this test, open up your terminal pointing to the root of the directory for this level and execute this command:\n\n```bash\nnpx hardhat test\n```\n\nIf all your tests passed, this means that you have learned how to upgrade a smart contract.\n\nLFG 🚀\n\n## Readings\n\n- `Timelock` was mentioned in the given article, to learn more about it you can read the [following article](https://blog.openzeppelin.com/protect-your-users-with-smart-contract-timelocks/)\n- `Access Control` was also mentioned and you can read about it more [here](https://docs.openzeppelin.com/contracts/3.x/access-control)\n\n## References\n\n- [OpenZeppelin Docs](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies)\n- [OpenZeppelin Youtube](https://www.youtube.com/watch?v=kWUDTZhxKZI)\n\n\u003cQuiz questionId=\"60e8e9c3-6d69-48a2-bb17-51995aec6a43\" /\u003e\n\n\u003cSubmitQuiz /\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearnweb3dao%2Fupgradeable-smart-contracts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flearnweb3dao%2Fupgradeable-smart-contracts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearnweb3dao%2Fupgradeable-smart-contracts/lists"}