升级合约
import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol';
contract XXX is UUPSUpgradeable, OwnableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
// ======================== State Variables ========================
// --------------- pool
// --------------- user
// ======================== Events ========================
event UnknownError(address caller, bytes data);
// ======================== Constructor ========================
constructor() {
_disableInitializers();
}
function initialize() external initializer {
__Ownable_init();
}
// 设置指定管理员
// function initialize(address owner_) external initializer {
// __Ownable_init(owner_);
// }
function _authorizeUpgrade(address) internal override onlyOwner {}
// ======================== Functions: Limit ========================
// ======================== Functions: Main ========================
// ======================== Functions: View ========================
// ======================== Functions: Private ========================
function _transfer(address _from, address _to, uint256 _amount) private {
IERC20Upgradeable(address(WETH)).safeTransferFrom(_from, address(this), _amount);
WETH.withdraw(_amount);
(bool success, ) = payable(_to).call{ value: _amount }('');
require(success, 'transfer failed');
}
// ======================== Receive ========================
receive() external payable {}
fallback() external {
emit UnknownError(msg.sender, msg.data);
}
}
其中 initialize
和 _authorizeUpgrade
是固定格式。
或者如下写法
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract Xxx is ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
constructor() {
_disableInitializers();
}
function initialize(
address owner_,
string memory name_,
string memory symbol_
) public initializer {
_baseUrl = "https://xxx.com/file/";
__ERC721_init(name_, symbol_);
__Ownable_init(owner_);
}
function _authorizeUpgrade(address) internal override onlyOwner {}
}
合约的部署
const XXXX = await ethers.getContractFactory("XXXX");
const xxxx = await upgrades.deployProxy(XXXX);
await xxxx.waitForDeployment();
hardhat.config.ts 文件 :
import { HardhatUserConfig } from 'hardhat/config'
import '@nomicfoundation/hardhat-toolbox'
require('@openzeppelin/hardhat-upgrades');
require('dotenv').config()
// require("@nomiclabs/hardhat-etherscan");
require("hardhat-deploy");
require("solidity-coverage");
require("hardhat-gas-reporter");
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
// const REPORT_GAS = process.env.REPORT_GAS || false
const BSCTESTNET_RPC_URL = process.env.RPC_URL_BSC_TEST || "https://data"
const BSCSCAN_API_KEY = process.env.BSCSCAN_API_KEY || "Your etherscan API key"
const PRIVATE_KEY = process.env.PK_ACCOUNT_1 || "0x"
const PRIVATE_KEY_PROD = process.env.PK_ACCOUNT_BSC || "0x"
const config: HardhatUserConfig = {
networks: {
hardhat: {
allowUnlimitedContractSize: true,
},
bsc_test: {
url: BSCTESTNET_RPC_URL,
accounts: PRIVATE_KEY !== undefined ? [PRIVATE_KEY] : [],
timeout: 600000,
gasPrice: 1000000000 * 15,
blockGasLimit: 0x1fffffffffffff,
throwOnTransactionFailures: true,
throwOnCallFailures: true,
allowUnlimitedContractSize: true,
},
bsc: {
url: process.env.RPC_URL_BSC,
accounts: PRIVATE_KEY_PROD !== undefined ? [PRIVATE_KEY_PROD] : [],
timeout: 600000,
},
},
etherscan: {
// npx hardhat verify --network <NETWORK> <CONTRACT_ADDRESS> <CONSTRUCTOR_PARAMETERS>
apiKey: {
bsc: BSCSCAN_API_KEY,
bscTestnet: BSCSCAN_API_KEY,
},
},
solidity: {
version: '0.8.23',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
viaIR: true,
},
},
// 单元测试的超时时间
mocha: {
timeout: 100000000
},
}
export default config
一切特殊定制 deploy.ts :
const { ethers, upgrades } = require("hardhat");
import hre from 'hardhat'
async function main() {
const [owner] = await ethers.getSigners()
console.log('============ BASIC INFO ============')
console.log('Current Network :', hre.network.name)
console.log('Network Url :', hre.network.config.url || 'hardhat temp network')
console.log('Depoly Address :', owner.address)
console.log(`\n ____________ DEPLOY XXX ____________`)
const ownerAds = owner.address;
const name = 'Xxx';
const symbol = 'XXX';
const Xxx = await ethers.getContractFactory("Xxx");
const xxx = await upgrades.deployProxy(Xxx,
[ownerAds, name, symbol],
{
initializer: "initialize",
unsafeAllow: ['constructor'],
kind: 'uups'
}
);
await xxx.waitForDeployment()
console.log(`Prox : ${xxx.target}`)
console.log("ImplementationAddress", await upgrades.erc1967.getImplementationAddress(xxx.target)
);
// console.log("AdminAddress", await upgrades.erc1967.getAdminAddress(xxx.target));
console.log('\n============ End ============\n')
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
升级部署
async function main() {
const [owner] = await ethers.getSigners()
console.log('============ UPGRADE INFO ============')
console.log('Current Network :', hre.network.name)
console.log('UUPS Proxy :', proxyAddress)
console.log(`\n ____________ UPGRADE CONTRAT ____________`)
const Contract = await hre.ethers.getContractFactory("Claim");
await upgrades.forceImport(proxyAddress, Contract);
const V2 = await ethers.getContractFactory("ClaimV2")
const proxy = await upgrades.upgradeProxy(proxyAddress, V2,
{
initializer: "initialize",
unsafeAllow: ['constructor'],
kind: 'uups'
})
console.log(`Proxy : ${proxy.target}`, "(For FE)")
const receipt = await proxy.deployTransaction.wait(2);
console.log(`Implementation : ${await upgrades.erc1967.getImplementationAddress(proxy.target)}`);
console.log('\n============ End ============\n')
}
注意:如果不使用 forceImport 可能会报错: To register a previously deployed proxy for upgrading, use the forceImport function.
可以使用 forceImport 倒入一下。
部署时候如果有unsafe操作
比如:
Error: Contract `contracts/Xxxx.sol:Xxxx` is not upgrade safe
contracts/Xxxx.sol:53: Contract `Xxxx` has a constructor
Define an initializer instead
https://zpl.in/upgrades/error-001
具体的允许,可以在 upgrades.deployProxy
的 unsafeAllow 中修改。
https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#common-options
合约开源
npx hardhat verify --network bsc_test 0x9899999
合约测试
const { time, loadFixture } = require('@nomicfoundation/hardhat-toolbox/network-helpers')
const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs')
const { expect } = require('chai')
const { ethers, upgrades } = require("hardhat");
describe('Deposit', function () {
async function deployFixture() {
const [owner, user1, user2, user3, user4, user5, user6, user7, user8, user9, user10, treasury] = await ethers.getSigners()
const Deposit = await ethers.getContractFactory('Deposit')
const deposit = await upgrades.deployProxy(Deposit)
await deposit.waitForDeployment()
// WETH
const WETH = await ethers.getContractFactory('WETH')
const weth = await WETH.deploy()
await weth.waitForDeployment()
// console.log('金库地址', treasury.address)
console.log(`\n ____________ DEPLOY XXX ____________`)
const ownerAddress = owner.address;
const name = 'xxx';
const symbol = 'XXX';
const Xxx = await ethers.getContractFactory("Xxx");
const xxx = await upgrades.deployProxy(Xxx,
[ownerAddress, name, symbol],
{
initializer: "initialize",
unsafeAllow: ['constructor'],
kind: 'uups'
}
);
await xxx.waitForDeployment()
console.log(`Prox : ${xxx.target}`)
console.log("ImplementationAddress", await upgrades.erc1967.getImplementationAddress(xxx.target)
);
// console.log("AdminAddress", await upgrades.erc1967.getAdminAddress(xxx.target));
// // AddressManager
const AddressManager = await ethers.getContractFactory('AddressManager')
const addressManager = await upgrades.deployProxy(AddressManager)
await addressManager.waitForDeployment()
await addressManager.addAddress(treasury.address)
await addressManager.setWETH(weth.target)
// ETH资金:
await user3.sendTransaction({ to: treasury.address, value: ethers.parseEther('99') })
const balance = await ethers.provider.getBalance(treasury.target);
// 代币转移
await weth.connect(treasury).deposit({ value: ethers.parseEther('39996') })
await weth.connect(treasury).approve(deposit.target, ethers.parseEther('1000000'))
expect(await weth.balanceOf(treasury.address)).to.equal(ethers.parseEther('39996'))
// set address
await deposit.setTreasury(treasury.target)
await deposit.setHistoricalOrders(historicalOrders.target)
await deposit.setAddressManager(addressManager.target)
await deposit.setWETH(weth.target)
return { owner, user1, user2, deposit, addressManager, weth }
}
describe('部署检测', function () {
it('基础变量检测', async function () {
const { owner, user1, deposit, addressManager, weth } = await loadFixture(deployFixture)
expect(await deposit.T()).to.equal("T")
})
})
})
参考
- https://docs.openzeppelin.com/upgrades-plugins/1.x/hardhat-upgrades
- https://www.frank.hk/blog/upgradable-smart-contract-uups
- https://mshk.top/2022/06/solidity-hardhot-erc721-erc1822-erc1976-openzeppelin/
- https://blog.csdn.net/Lyon_Nee/article/details/125515837
- https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializers