This project contains UUPS upgradeable smart contract of ERC20 tokens derived from OpenZeppelin library with best practices. Besides than that, it comes with test and deploy scripts for that contract.
The ERC20 contract consists of the following functions:
- Basic ERC20 token function including minting derived from ERC20Upgradeable contract
- Maximum mint amounts of 10,000 within 3 minutes of block time
- Burnable ERC20 token function such as burn token derived from ERC20BurnableUpgradeable contract
- Pausable/Freezable ERC20 token function such as freeze or unfreeze token derived from ERC20PausableUpgradeable contract
- Upgradable feature that utilize Initializable contract and UUPSUpgradeable contract
- Owned by Gnosis Safe which is multi-signature with more security measures due to the upgradeability of smart contract.
All contracts shown below are deployed at Rinkeby testnet and can be traced in Etherscan as followed:
- UUPS Proxy Contract
- Implementation Contract V3
- Implementation Contract V2
- Implementation Contract V1
- Gnosis Safe Contract
The whole project directory can be break down into few major parts such as:
folder | Description |
---|---|
contracts |
Folder that contains all contracts revision(s) that inherited from OpenZeppelin's templates |
scripts |
Script files written in JavaScript that normally used for deploying contracts to blockchain based on configuration file settings |
test |
Test files written in JavaScript to test contracts locally before deployed to blockchain based on test setup |
- npm
- Hardhat and OpenZeppelin where their dependencies can be installed using
npm install
- Create a Gnosis Safe with 1 or more Externally Owned Account(s)
- Sign up OpenZeppelin Defender and create API token for upgrading UUPS contract after passing ownership to Gnosis Safe with multi-sig
- Sign up Alchemy and create API key for Rinkeby testnet
- Sign up Etherscan and create API key to verify contract after deployed.
- Create at least 1 Metamask wallet and change to Rinkeby testnet
hardhat.config.js is the configuration file used by hardhat
command to execute scripts stored in scripts folder. Notice that dotenv
library is used to store secret keys or API keys in configuration file and the complete sample can be retrieved at .env file section.
After installing all dependencies using npm install
, all tests should be able to execute using npx hardhat test
command that will:
- Compile contracts
- Generate artifacts
- Run all test scripts in test folder.
However, test can be run partially by passing in test script file path as argument to npx hardhat test
in such a manner:
npx hardhat test test/MakeWorldPeace.js
that will only execute tests in MakeWorldPeace.jsnpx hardhat test test/MakeWorldPeaceV2.js
that will only execute tests in MakeWorldPeaceV2.jsnpx hardhat test test/MakeWorldPeaceV3.js
that will only execute tests in MakeWorldPeaceV3.js
Create .env
file in current path and refer to sample template below that should be inserted with corresponding API token and secret keys based on instructions above.
ALCHEMY_API_KEY=INSERT_YOUR_ALCHEMY_API_KEY_HERE
RINKEBY_PRIVATE_KEY="INSERT_YOUR_METAMASK_WALLET_RINKEBY_PRIVATE_KEY_HERE"
DEPLOYED_PROXY_ADDRESS="INSERT_PROXY_ADDRESS_THAT_SHOW_AFTER_FIRST_STEP_OF_DEPLOYMENT"
GNOSIS_SAFE="INSERT_GNOSIS_SAFE_CONTRACT_ADDRESS_HERE"
DEFENDER_TEAM_API_KEY="INSERT_OPEN_ZEPPELIN_DEFENDER_TEAM_API_KEY_HERE"
DEFENDER_TEAM_API_SECRET_KEY="INSERT_OPEN_ZEPPELIN_DEFENDER_TEAM_SECRET_KEY_HERE"
ETHERSCAN_API_KEY="INSERT_ETHERSCAB_API_HERE"
Change
function upgradeToV2() public
tofunction upgradeToV2() public onlyOwner
in MakeWorldPeaceV2.sol
In order to start deploying upgradeable ERC contract, script file is executed ascendingly as shown below. Make sure .env
file is filled in accordingly except DEPLOYED_PROXY_ADDRESS
as we will get the information after our first script.
- Run
npx hardhat run scripts/1_deploy.js --network rinkeby
command in console and copy the deployed proxy address shown in output of the script intoDEPLOYED_PROXY_ADDRESS
. Check and make sure contract is already been created by searching deployed proxy address in Etherscan. - Run
npx hardhat run scripts/2_transfer_ownership.js --network rinkeby
command and ownership of the proxy address will be transferred from metamask wallet to Gnosis safe contract shown in output of the script. - Since we had transferred ownership to Gnosis Safe multi-sig contract, there is no straightforward way to update the contract using hardhat command. we could however generate upgrade proposal that can be further processed in OpenZeppelin Defender.
- Log in into OpenZeppelin Defender and add proxy address contract into it. Fill in name (any name you like), network (Rinkeby), address(
DEPLOYED_PROXY_ADDRESS
). Notice that there is a space of ABI to be filled in. - In order to fill in the ABI accordingly, first the underlying implementation contract of proxy contract have to be verified and ABI is then retrieved from implementation contract in Etherscan. First, go to Etherscan then enter the verified implementation contract address. Next, click on the
Contract
in the tab section heading and scroll down to find theContract ABI
then copy it. - Paste into the ABI field in Step 5 and press
Add
to add proxy contract into OpenZeppelin Defender. - Run
npx hardhat run scripts/3_propose_upgrade.js --network rinkeby
command and click on the link shown in output of the script. - Approve proposal in OpenZeppelin Defender and proxy contract will be updated with new implementation contract. Keep note that each newly generated implementation contract have to be verified in order to obtain ABI and ABI should be updated in OpenZeppelin Defender proxy contract for contract interaction.
- Create proposal to execute
upgradeToV2()
function in OpenZeppelin Defender and approve the proposal as this is the first function that should be executed after successfully upgraded the proxy contract. - Similar steps are taken to update contract to V3 by running
npx hardhat run scripts/4_propose_upgrade_v3.js --network rinkeby
command and click on the link shown in output of the script. Then,repeat step 8.
Go to proxy contract address in Etherscan and verify it as proxy contract. Copy the implementation contract address if it stated that implementation contract address is not verified yet.
Next, run npx hardhat verify <impl_address> --network rinkeby
where <impl_address>
is the implementation address copied above. Click on the link at the output of the command to verify contract is verified in Etherscan.
Fix bug by adding role access modifier (BURNER_ROLE
) to burn
and burnFrom
function of smart contract.
Change from owner access modifier to role-based access modifier. Each of the function have their own role such as:
PAUSER_ROLE
for allpause
andunpause
functionMINTER_ROLE
formint
functionBURNER_ROLE
forburn_supply
function
Each of the role mentioned above can be granted and revoked by owner of address
Assigned role to the address can renounce their own role as well
Create ERC20 upgradeable contract with mint, pause (freeze) and burn function. Owner access modifier for following functions:
pause
andunpause
mint
burn_supply
_authorizeUpgrade
(for upgrading contract)
Maximum mint amounts of 10,000 within 3 minutes of block time in mint
function