The contract deployed using the OpenZeppelin upgrade plug-in has an upgradeable feature: it can be upgraded to modify its code while retaining its address, status and balance. You can iteratively add new functionality to the project or fix any errors that might be found in the online version.
Configure development environment
Create a new npm project
mkdir mycontract && cd mycontract npm init -y
Install and initialize Truffle
npm i --save-dev truffle npx truffle init
Install Truffle upgrade plug-in
npm i --save-dev @openzeppelin/truffle-upgrades
Create upgradeable contracts
Note that the upgradeable contract uses the initialize function instead of the constructor to initialize the state.
Box.sol
// contracts/Box.sol // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; contract Box { uint256 private value; // Emitted when the stored value changes event ValueChanged(uint256 newValue); // Stores a new value in the contract function store(uint256 newValue) public { value = newValue; emit ValueChanged(newValue); } // Reads the last stored value function retrieve() public view returns (uint256) { return value; } }
Deploy contracts to public networks
We will use Truffle migration to deploy the Box contract. The Truffle upgrade plug-in provides a deployProxy function to deploy scalable contracts. It will deploy the contract we implemented, and ProxyAdmin will act as the project agent and agent administrator, and call the initialization function.
Create the following 2 in the migrations directory_ deploy_ box. JS script.
In this article, we don't have the initialize function yet, so we'll use the store function to initialize the state.
2_deploy_box.js
// migrations/2_deploy_box.js const Box = artifacts.require('Box'); const { deployProxy } = require('@openzeppelin/truffle-upgrades'); module.exports = async function (deployer) { await deployProxy(Box, [42], { deployer, initializer: 'store' }); };
Use Rinkeby network to run truss migration for deployment. We can see three contracts: box Sol, ProxyAdmin and agent contract AdminUpgradeabilityProxy.
truffle migrate --network rinkeby
...
2_deploy_box.js
===============
Deploying 'Box'
---------------
> transaction hash: 0x3263d01ce2e3eb4ba51abf882abbdd9252364b51eb972f82958719d60a8b9ebe
> Blocks: 0 Seconds: 5
> contract address: 0xd568071213Ea31B01AA2247BC9eC7285087cf882
...
Deploying 'ProxyAdmin'
----------------------
> transaction hash: 0xf39e8cb97c332b8bbdf0c66b13f26a9a3dc97b207d2caec73ba6df8d5bb6b211
> Blocks: 1 Seconds: 17
> contract address: 0x2A210B6d5EffC0A3BB47dD3791a4C26B8E31f161
...
Deploying 'AdminUpgradeabilityProxy'
------------------------------------
> transaction hash: 0x439711597b694f03b1065582ab44ac0bea5e22b0c6e3c460ae7b4536f004c355
> Blocks: 1 Seconds: 17
> contract address: 0xF325bB49f91445F97241Ec5C286f90215a7E3BC6
...
Release validation contract
truffle run verify Box --network rinkeby
We can use the truss console to interact with our contracts.
Note: box Deployed () is the address of our agency contract.
truffle console --network rinkeby
truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> box.address
'0xc2ea7DE43F194bB397761a30a05CEDcF28835F24'
truffle(rinkeby)> (await box.retrieve()).toString()
'42'
The administrator of the current agent (who can perform the upgrade) is ProxyAdmin contract. Only the owner of ProxyAdmin can upgrade the agent. Warning: when the ownership of ProxyAdmin is transferred, please make sure to go to the address under our control.
3_transfer_ownership.js
// migrations/3_transfer_ownership.js const { admin } = require('@openzeppelin/truffle-upgrades'); module.exports = async function (deployer, network) { // Use your wallet address const admin = '0x1c14600daeca8852BA559CC8EdB1C383B8825906'; // Don't change ProxyAdmin ownership for our test network if (network !== 'test') { // The owner of the ProxyAdmin can upgrade our contracts await admin.transferProxyAdminOwnership(admin); } };
Run migration on Rinkeby network
truffle migrate --network rinkeby
...
3_transfer_ownership.js
=======================
> Saving migration to chain.
-------------------------------------
...
Implement a new upgraded version
After some time, we decided to add features to the contract. In this article, we will add an increment function.
Note: we cannot change the storage layout implemented by the previous contract. For more details about technical restrictions, see upgrading.
Use the following solid code to create a new implementation boxv2 in your contracts directory sol .
BoxV2.sol
// contracts/BoxV2.sol // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; contract BoxV2 { uint256 private value; // Emitted when the stored value changes event ValueChanged(uint256 newValue); // Stores a new value in the contract function store(uint256 newValue) public { value = newValue; emit ValueChanged(newValue); } // Reads the last stored value function retrieve() public view returns (uint256) { return value; } // Increments the stored value by 1 function increment() public { value = value + 1; emit ValueChanged(value); } }
Deploy a new upgrade
Once you have tested the new implementation, you are ready to upgrade. This will validate and deploy the new contract. Note: we are only preparing to upgrade..
4_prepare_upgrade_boxv2.js
// migrations/4_prepare_upgrade_boxv2.js const Box = artifacts.require('Box'); const BoxV2 = artifacts.require('BoxV2'); const { prepareUpgrade } = require('@openzeppelin/truffle-upgrades'); module.exports = async function (deployer) { const box = await Box.deployed(); await prepareUpgrade(box.address, BoxV2, { deployer }); };
Run the migration on the Rinkeby network to deploy the new contract implementation
truffle migrate --network rinkeby
...
4_prepare_upgrade_boxv2.js
==========================
Deploying 'BoxV2'
-----------------
> transaction hash: 0x078c4c4454bb15e3791bc80396975e6e8fc8efb76c6f54c321cdaa01f5b960a7
> Blocks: 1 Seconds: 17
> contract address: 0xEc784bE1CC7F5deA6976f61f578b328E856FB72c
...
Enter the truss console
truffle console --network rinkeby
truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> boxV2 = await BoxV2.deployed()
truffle(rinkeby)> box.address
'0xF325bB49f91445F97241Ec5C286f90215a7E3BC6'
truffle(rinkeby)> boxV2.address
'0xEc784bE1CC7F5deA6976f61f578b328E856FB72c'
Upgrade contract
upgrade method for executing ProxyAdmin contract
proxy: the address of the TransparentUpgradeableProxy contract
implementation: address of BoxV2 contract
Then you need to sign the transaction in MetaMask (or the wallet you are using).
Now we can interact with the upgraded contract. We need to use the proxy address to interact with BoxV2. Then, we can call the new "increment" function and observe that the state is maintained throughout the upgrade process.
Enter the truss console
truffle console --network rinkeby
truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> boxV2 = await BoxV2.at(box.address)
truffle(rinkeby)> (await boxV2.retrieve()).toString()
'42'
truffle(rinkeby)> await boxV2.increment()
{ tx:
...
truffle(rinkeby)> (await boxV2.retrieve()).toString()
'43'