---
tags: Solidity 開發班
---
# UUPS Proxies: A Tutorial
In this tutorial we will deploy an upgradeable contract using the UUPS proxy pattern.
[TOC]
## Introduction to UUPS
The original proxies included in OpenZeppelin followed the [Transparent Proxy Pattern](https://blog.openzeppelin.com/the-transparent-proxy-pattern/). While this pattern is still provided, our recommendation is now shifting towards UUPS proxies, which are both lightweight and versatile. The name UUPS comes from [EIP1822](https://eips.ethereum.org/EIPS/eip-1822), which first documented the pattern.
## OpenZeppelin Tooling
We will use OpenZeppelin tooling to easily and securely deploy an upgradeable contract using the UUPS proxy pattern.
1. We will write our contract source code using [OpenZeppelin Upgradeable Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable).
2. We will deploy this contract behind a proxy using [OpenZeppelin Upgrades Plugins](https://github.com/OpenZeppelin/openzeppelin-upgrades), which additionally provides security checks to make sure we respect some [basic rules of upgradeability](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable).
## Project Setup
In a new directory, start an npm project and install Hardhat along with the OpenZeppelin tools we mentioned in the previous section:
```
npm init -y
npm install hardhat @nomiclabs/hardhat-ethers ethers
npm install @openzeppelin/contracts-upgradeable @openzeppelin/hardhat-upgrades
```
Create the Hardhat config set to use Solidity 0.8.2 and the plugins we've installed:
```
// hardhat.config.js
require('@nomiclabs/hardhat-ethers');
require('@openzeppelin/hardhat-upgrades');
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.3",
};
```
> Solidity 0.8.2 is technically supported, but the Windows build of the compiler currently has an unresolved issue, so 0.8.3 is recommended.
## Writing the Contract
We will start with a basic ERC20 token with some initial supply minted for the deployer. We will see below that this contract is not yet ready for UUPS deployment.
```
// contracts/MyTokenV1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
contract MyTokenV1 is Initializable, ERC20Upgradeable {
function initialize() initializer public {
__ERC20_init("MyToken", "MTK");
_mint(msg.sender, 1000 * 10 ** decimals());
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
}
```
## Upgradeable Deployment
We can now write a test and start by deploying this contract normally:
```
// test/MyToken.test.js
const { ethers, upgrades } = require('hardhat');
describe('MyToken', function () {
it('deploys', async function () {
const MyTokenV1 = await ethers.getContractFactory('MyTokenV1');
await MyTokenV1.deploy();
});
});
```
Run the test using npx hardhat test, and you should see it run successfully.
```
MyToken
✓ deploys (923ms)
```
In order to make this an upgradeable deployment, we need to use the function deployProxy from the Upgrades plugin.
```
await upgrades.deployProxy(MyTokenV1); // instead of MyTokenV1.deploy()
```
This function will be doing several things behind the scenes. The contract will be checked for [unsafe patterns](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable) that shouldn't be used in an upgradeable deployment, such as using the `selfdestruct` operation. If these checks pass, it will then deploy the implementation contract, and then deploy a proxy connected to that implementation.
The proxy we just deployed was not a UUPS proxy, however, as the current default is still Transparent Proxies. In order to use UUPS we have to manually specify so with the option `kind: 'uups'`.
```
await upgrades.deployProxy(MyTokenV1, { kind: 'uups' });
```
If we try to run this test now, we will actually see it fail with this error.
```
contracts/MyTokenV1.sol:7: Implementation is missing a public `upgradeTo(address)` function
Inherit UUPSUpgradeable to include this function in your contract
@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol
https://zpl.in/upgrades/error-008
```
This is because, as we mentioned at the beginning of the tutorial, UUPS proxies do not contain an upgrade mechanism on the proxy, and it has to be included in the implementation. We can add this mechanism by inheriting `UUPSUpgradeable`, and this will also require that we implement an authorization function to define who should be allowed to upgrade the contract. For the example we will use `Ownable`.
To authorize the owner to upgrade the contract we implement _authorizeUpgrade with the onlyOwner modifier.
```
function _authorizeUpgrade(address) internal override onlyOwner {}
```
Full Solidity code:
```
// contract/MyTokenV1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract MyTokenV1 is Initializable, ERC20Upgradeable, UUPSUpgradeable, OwnableUpgradeable {
function initialize() initializer public {
__ERC20_init("MyToken", "MTK");
__Ownable_init();
__UUPSUpgradeable_init();
_mint(msg.sender, 1000 * 10 ** decimals());
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
function _authorizeUpgrade(address) internal override onlyOwner {}
}
```
Full test code:
```
// test/MyToken.test.js
const { ethers, upgrades } = require('hardhat');
describe('MyToken', function () {
it('deploys', async function () {
const MyTokenV1 = await ethers.getContractFactory('MyTokenV1');
await upgrades.deployProxy(MyTokenV1, { kind: 'uups' });
});
});
```
Once we have a new version of the contract code and we want to upgrade a proxy, we can use `upgrades.upgradeProxy`. It's no longer necessary to specify kind: 'uups' since it is now inferred from the proxy address.
```
await upgrades.upgradeProxy(proxyAddress, MyTokenV2);
```
## More Resources
You can find an updated list of resources related to upgradeability in the [Upgrades](https://docs.openzeppelin.com/openzeppelin/upgrades) page of the documentation.
## Reference
https://forum.openzeppelin.com/t/uups-proxies-tutorial-solidity-javascript/7786