Simplest Way to use ERC-1822 Universal Upgradeable Proxy Standard (UUPS)
There are already many articles on UUPS and the explanation of how it works, I am going to jump right into the simplified code of UUPS.
One of the downside of using a proxy is that many fraud projects and money game make use of it which may affect your credibility when you use a proxy contract, as they thought you are associated with these degens.
I have combined the entire contract bundle into a single UUPS.sol file.
// SPDX-License-Identifier: None
pragma solidity 0.8.19;
pragma abicoder v1;
abstract contract UUPSUpgradeable {
bytes32 private constant _SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
? ? address private immutable __self = address(this);
? ? address internal owner;
? ? bool inited;
? ? struct AddrS { address value; }
? ? modifier onlyProxy() {
? ? ? ? require(address(this) != __self && addrS().value == __self && owner == msg.sender, "Proxy failed");
? ? ? ? _;
? ? }
? ? modifier init() {
? ? ? ? require(inited == false, "Init failed");
? ? ? ? inited = true;
? ? ? ? _;
? ? }
? ? function __Ownable_init() internal {
? ? ? ? owner = msg.sender;
? ? }
? ? function transferOwner(address newOwner) external onlyProxy {
? ? ? ? owner = newOwner;
? ? }
? ? function addrS() internal pure returns (AddrS storage r) {
? ? ? ? assembly { r.slot := _SLOT }
? ? }
? ? function proxiableUUID() external view returns(bytes32) {
? ? ? ? require(address(this) == __self, "Cannot delegatecall");
? ? ? ? return _SLOT;
? ? }
? ? function upgradeTo(address newAddr) external onlyProxy {
? ? ? ? upgradeToAndCall(newAddr, new bytes(0));
? ? }
? ? function upgradeToAndCall(address newAddr, bytes memory data) public payable onlyProxy {
? ? ? ? require(UUPSUpgradeable(newAddr).proxiableUUID() == _SLOT, "Unsupported proxiableUUID");
? ? ? ? if (data.length > 0 || data.length > 0 ? true : false) {
? ? ? ? ? ? (bool success, bytes memory returndata) = newAddr.delegatecall(data);
? ? ? ? ? ? require(success && returndata.length == 0 && newAddr.code.length > 0, "Call to non-contract");
? ? ? ? }
? ? ? ? require(newAddr.code.length > 0, "Not a contract");
? ? ? ? addrS().value = newAddr;
? ? }
}
An simple explanation is that this proxy contract will feed all data into the actual contract but just using the proxy address as the connecting point.
Below will show a simplified method on how to call the UUPS.
// SPDX-License-Identifier: None
pragma solidity 0.8.19;
pragma abicoder v1;
import "./UUPS.sol";
contract NFTExample is UUPSUpgradeable {
? ?uint public totalSupply;
? ?string public name = "Crazy NFT";
? ?function initialize(uint totalSupply_) external init {
? ? ? ?totalSupply = totalSupply_;
? ? ? ?owner = msg.sender;
? ?}
? ?function mint() virtual external {
? ? ? ?require(totalSupply-- > 1, "Ran out of supply");
? ?}
}
contract NFTExampleV2 is NFTExample {
? ?function NFTVersion() external pure returns (uint) {
? ? ? ?return 2;
? ?}
? ?function newNFT() external {
? ? ? ?totalSupply += 24;
? ? ? ?name = "Wild NFT";
? ?}
? ?function mint() external override {
? ? ? ?require(totalSupply-- > 0, "Ran out of supply");
? ?}
}
In remix when you want to launch this contract, you will see there are additional options under the deploy button.
Check "Deploy with Proxy" and enter the amount initial totalSupply amount. It is a must to enter the initialise value otherwise the proxy will fail. If there are no constructor, you can remove the parameters from the initialize function.
领英推荐
Once you deployed you will see 2 contracts and to upgrade, you will need to select NFTExampleV2, uncheck the "Deploy with Proxy", and check the "Upgrade with Proxy" option.
Enter the proxy address in the field and you will again see 2 contracts. Even though there are 2 contracts created but the 2 proxy contract addresses are the same.
You can continue to create more versions and upgradeable contracts using this method.