Smart contracts are integral components of blockchain applications, providing the foundational logic for decentralized operations. However, as blockchain technology evolves, the need for smart contract upgradability becomes apparent. This README explores the necessity of smart contract upgrades and various strategies for achieving them.
-
Adding Missing Features: Sometimes, you may need to add new features to your smart contract that were not initially included.
-
Bug Fixes: Smart contracts may contain bugs or vulnerabilities that need to be addressed.
-
Gas Optimization: To optimize gas usage, especially in high-traffic applications.
-
Regulatory Changes: Changes in government regulations might necessitate modifications to your contract.
-
Compatibility: Compatibility issues may arise as libraries are upgraded with ERC standards or security updates.
-
Contract Migration:
- Example: Imagine you have a decentralized exchange (DEX) smart contract. To upgrade it, deploy a new DEX contract and transfer user balances and data from the old contract to the new one.
- Platform: Ethereum and other blockchain platforms have used contract migration for upgrading various smart contracts.
-
Data Separation:
- Example: In a token smart contract, separate the business logic (e.g., token transfers) into one contract and data storage (e.g., user balances) into another contract. The logic contract interacts with the storage contract.
- Platform: This approach is commonly used in Ethereum-based token contracts.
-
Proxy Patterns:
- Example: Create a proxy contract that delegates function calls to a logic contract. Users interact with the proxy, and you can upgrade the logic contract by changing the address in the proxy.
- Platform: The Gnosis Safe Multisig Wallet uses a proxy pattern to facilitate upgradability.
-
Strategy Pattern:
- Example: In a lending protocol, have a main contract that interfaces with various lending strategies. Upgrade the protocol by adding a new lending strategy and configuring the main contract to use it.
- Platform: Platforms like Yearn Finance use the strategy pattern for upgrading DeFi protocols.
-
Diamond Pattern:
- Example: Consider a complex DeFi protocol with many functions. Use a diamond pattern to split functions across multiple logic contracts (facets) and configure a proxy to delegate calls to the appropriate facet.
- Platform: The Aave Protocol has implemented the diamond pattern to handle its large and complex smart contract codebase efficiently.
- An external caller makes a function call to the proxy.
- The proxy delegates the call to the delegate, where the function code is located.
- The result is returned to the proxy, which forwards it to the caller.
The delegatecall
opcode is used to delegate the call, executing the function in the context of the proxy. This means that the proxy's storage is used for function execution.
DelegateCall is a low-level Solidity opcode that allows a contract to execute code from another contract, using the state and storage of the calling contract.
Given the pattern's logic, the proxy is also known as a dispatcher that delegates calls to specific modules, which are called delegates by the proxy contract.
- Storage Collisions: Read more
- Function Selector Clashes: Read more
- Example:
collate_propagate_storage(bytes16)
andburn(uint256)
have the same function selector0x42966c68
.
- Example:
-
Transparent Proxy Pattern:
- Admin (can only call) -> Admin Functions
- Users (can only call) -> Implementation Functions
- This helps avoid function selector clashes.
-
Universal Upgradable Proxies (UUPS):
- Puts all the logic of upgrading in the implementation itself.
- Advantages: Gas efficient (less read), small proxy contract.
- Issue: If you deploy an implementation contract without any upgradable functions, you are stuck.
-
Diamond Pattern:
- Transfer -> Diamond Proxy -> Facet Transfer -> Facet Approve . . -> Implementation
- This pattern is useful for very large contracts, splitting logic into different facets.
- You can't have a constructor but can use an initializer function.
- Deployment example:
const proxy = await upgrades.deployProxy( Contract_name, [Initializing_value], { initializer: "function_name" } );
- Upgrade example:
let box = await upgrades.upgradeProxy( "0xb6a24f3de5ACd15C18Db932C425AcB1D224A8e56", BoxV2 );
- Step-by-step guide by OpenZeppelin: OpenZeppelin Upgrades Tutorial
- OpenZeppelin Upgrades Plugins Documentation: OpenZeppelin Upgrades Plugins
- clone the repo
git clone https://github.com/sarvagnakadiya/upgradable-smart-contract-demo.git
cd upgradable-smart-contract-demo
The library seems conflicting with ethers lib, so have to use legacy peer deps
npm install --legacy-peer-deps
Add this .env file
here's an eg.
PRIVATE_KEY=0xabc
MUMBAI_RPC_URL=https://polygon-mumbai.g.alchemy.com/v2/YOUR_API_KEY
To deploy your V1 contract
npx hardhat run scripts/deploy.js --network mumbai
To upgrade your contract to BoxV2
npx hardhat run scripts/upgrade.js --network mumbai
Now, you can call increment function from v2 and call the retrieve function to check the value is updated or not, as we have used v2. And if it is, CONGRATULATIONS its upgraded.
There's a check.js file to retrieve the value.