Solidity合约的权限管理!在区块链世界里,智能合约就是你的金库,里面装着资金、数据,啥啥都值钱!要是权限没管好,等于把金库大门敞开,喊着“来抢吧”!权限管理就是给合约装上金刚锁,决定谁能开锁、谁能动钱、谁能改规则。权限管理的硬核逻辑先把基本功打扎实,权限管理是啥?简单说,就是控制谁能调用合
Solidity合约的权限管理!在区块链世界里,智能合约就是你的金库,里面装着资金、数据,啥啥都值钱!要是权限没管好,等于把金库大门敞开,喊着“来抢吧”!权限管理就是给合约装上金刚锁,决定谁能开锁、谁能动钱、谁能改规则。
先把基本功打扎实,权限管理是啥?简单说,就是控制谁能调用合约里的关键函数。比如,谁能铸新代币?谁能暂停交易?谁能升级合约逻辑?在Solidity里,权限管理得靠代码实现,EVM可不会帮你管门。咱们得搞清楚几个重点:
Ownable的owner),也可以是多角色(如AccessControl的角色),还能是多签(多个人同意才行)。Ownable、AccessControl、ReentrancyGuard库。onlyOwner)或条件检查实现。先把工具准备好,用Hardhat建个开发环境,方便写代码和测试。
跑这些命令,初始化项目:
mkdir access-control-pro
cd access-control-pro
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts @openzeppelin/contracts-upgradeable
npm install ethers
初始化Hardhat,选TypeScript模板:
npx hardhat init
装点依赖,跑测试用:
npm install --save-dev ts-node typescript @types/node @types/mocha
项目目录长这样:
access-control-pro/
├── contracts/
│ ├── SimpleAccess.sol
│ ├── RoleAccess.sol
│ ├── MultiSigAccess.sol
│ ├── NestedRoleAccess.sol
│ ├── UpgradableAccess.sol
├── scripts/
│ ├── deploy.ts
├── test/
│ ├── AccessPro.test.ts
├── hardhat.config.ts
├── tsconfig.json
├── package.json
tsconfig.json配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./"
},
"include": ["hardhat.config.ts", "scripts", "test"]
}
hardhat.config.ts配置:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
hardhat: {
chainId: 1337,
},
},
};
export default config;
启动本地节点:
npx hardhat node
环境OK,接下来直接上代码!
先从最简单的开始,用OpenZeppelin的Ownable搞个单一管理员权限。
contracts/SimpleAccess.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SimpleAccessToken is ERC20, Ownable {
constructor() ERC20("SimpleAccessToken", "SAT") Ownable() {
_mint(msg.sender, 1000000 * 10**decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function pauseTransfers() public onlyOwner {
_pause();
}
function _pause() internal {
// Simulate pause logic (e.g., set a flag)
}
}
ERC20:搞个标准代币,能转账、查余额。Ownable:只有owner能调用关键函数。mint:owner可以给任意地址铸新代币。pauseTransfers:模拟暂停转账,onlyOwner限制。owner,初始铸100万代币。onlyOwner修饰符确保只有owner能操作。Ownable自带transferOwnership和renounceOwnership,方便权限移交。owner私钥丢了或被黑,合约就危险。mint:~30k Gas。pauseTransfers:~5k Gas(简单逻辑)。test/AccessPro.test.ts:
import { ethers } from "hardhat";
import { expect } from "chai";
import { SimpleAccessToken } from "../typechain-types";
describe("SimpleAccess", function () {
let token: SimpleAccessToken;
let owner: any, user1: any;
beforeEach(async function () {
[owner, user1] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("SimpleAccessToken");
token = await TokenFactory.deploy();
await token.deployed();
});
it("lets owner mint tokens", async function () {
await token.mint(user1.address, ethers.utils.parseEther("500"));
expect(await token.balanceOf(user1.address)).to.equal(ethers.utils.parseEther("500"));
});
it("blocks non-owner from minting", async function () {
await expect(token.connect(user1).mint(user1.address, ethers.utils.parseEther("500")))
.to.be.revertedWith("Ownable: caller is not the owner");
});
it("lets owner pause transfers", async function () {
await token.pauseTransfers(); // No revert, owner can call
});
it("blocks non-owner from pausing", async function () {
await expect(token.connect(user1).pauseTransfers())
.to.be.revertedWith("Ownable: caller is not the owner");
});
});
跑测试:
npx hardhat test
owner能铸币,余额更新正确。owner调用mint或pauseTransfers直接报错。onlyOwner的权限控制。单一owner不保险,咱们用AccessControl搞多角色管理,分权给不同用户。
contracts/RoleAccess.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract RoleAccessToken is ERC20, AccessControl, ReentrancyGuard {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bool public paused;
event Minted(address indexed to, uint256 amount);
event Paused(address indexed account);
event Unpaused(address indexed account);
constructor() ERC20("RoleAccessToken", "RAT") {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(MINTER_ROLE, msg.sender);
_setupRole(PAUSER_ROLE, msg.sender);
_mint(msg.sender, 1000000 * 10**decimals());
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) nonReentrant {
_mint(to, amount);
emit Minted(to, amount);
}
function transfer(address to, uint256 amount) public override returns (bool) {
require(!paused, "Transfers paused");
return super.transfer(to, amount);
}
function pause() public onlyRole(PAUSER_ROLE) {
paused = true;
emit Paused(msg.sender);
}
function unpause() public onlyRole(PAUSER_ROLE) {
paused = false;
emit Unpaused(msg.sender);
}
}
ERC20、AccessControl、ReentrancyGuard。MINTER_ROLE和PAUSER_ROLE。mint:只有MINTER_ROLE能铸币,加nonReentrant防重入。transfer:覆盖,检查暂停状态。pause/unpause:PAUSER_ROLE控制,触发事件。msg.sender拿管理员和两个角色。nonReentrant防止外部调用漏洞。mint:~35k Gas(含事件)。pause/~5k Gas。test/AccessPro.test.ts(add):
import { RoleAccessToken } from "../typechain-types";
describe("RoleAccess", function () {
let token: RoleAccessToken;
let owner: any, minter: any, pauser: any, user1: any;
beforeEach(async function () {
[owner, minter, pauser, user1] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("RoleAccessToken");
token = await TokenFactory.deploy();
await token.deployed();
await token.grantRole(await token.MINTER_ROLE(), minter.address);
await token.grantRole(await token.PAUSER_ROLE(), pauser.address);
await token.transfer(user1.address, ethers.utils.parseEther("1000"));
});
it("lets minter mint tokens", async function () {
await expect(token.connect(minter).mint(user1.address, ethers.utils.parseEther("500")))
.to.emit(token, "Minted")
.withArgs(user1.address, ethers.utils.parseEther("500"));
expect(await token.balanceOf(user1.address)).to.equal(ethers.utils.parseEther("1500"));
});
it("lets pauser pause transfers", async function () {
await expect(token.connect(pauser).pause())
.to.emit(token, "Paused")
.withArgs(pauser.address);
await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
.to.be.revertedWith("Transfers paused");
});
it("lets pauser unpause transfers", async function () {
await token.connect(pauser).pause();
await expect(token.connect(pauser).unpause())
.to.emit(token, "Unpaused")
.withArgs(pauser.address);
await token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"));
expect(await token.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("999100"));
});
it("blocks non-role users", async function () {
await expect(token.connect(user1).mint(user1.address, ethers.utils.parseEther("500")))
.to.be.revertedWith("AccessControl: account is missing role");
await expect(token.connect(user1).pause())
.to.be.revertedWith("AccessControl: account is missing role");
});
});
MINTER_ROLE铸币成功,事件触发。PAUSER_ROLE能暂停和恢复转账。单人或单角色还是有风险,搞个多签机制,关键操作得多人同意。
contracts/MultiSigAccess.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MultiSigAccessToken is ERC20, Ownable, ReentrancyGuard {
address[] public signers;
uint256 public required;
uint256 public transactionCount;
mapping(uint256 => Transaction) public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
struct Transaction {
address to;
uint256 amount;
bool executed;
uint256 confirmationCount;
}
event SubmitMint(uint256 indexed txId, address indexed to, uint256 amount);
event ConfirmMint(uint256 indexed txId, address indexed signer);
event ExecuteMint(uint256 indexed txId, address indexed to, uint256 amount);
event RevokeConfirmation(uint256 indexed txId, address indexed signer);
modifier onlySigner() {
bool isSigner = false;
for (uint256 i = 0; i < signers.length; i++) {
if (signers[i] == msg.sender) {
isSigner = true;
break;
}
}
require(isSigner, "Not signer");
_;
}
constructor(address[] memory _signers, uint256 _required) ERC20("MultiSigAccessToken", "MSAT") Ownable() {
require(_signers.length > 0, "Signers required");
require(_required > 0 && _required <= _signers.length, "Invalid required");
signers = _signers;
required = _required;
_mint(msg.sender, 1000000 * 10**decimals());
}
function submitMint(address to, uint256 amount) public onlySigner {
uint256 txId = transactionCount++;
transactions[txId] = Transaction({
to: to,
amount: amount,
executed: false,
confirmationCount: 0
});
emit SubmitMint(txId, to, amount);
}
function confirmMint(uint256 txId) public onlySigner nonReentrant {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction executed");
require(!confirmations[txId][msg.sender], "Already confirmed");
confirmations[txId][msg.sender] = true;
transaction.confirmationCount++;
emit ConfirmMint(txId, msg.sender);
if (transaction.confirmationCount >= required) {
executeMint(txId);
}
}
function executeMint(uint256 txId) internal {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction executed");
require(transaction.confirmationCount >= required, "Not enough confirmations");
transaction.executed = true;
_mint(transaction.to, transaction.amount);
emit ExecuteMint(txId, transaction.to, transaction.amount);
}
function revokeConfirmation(uint256 txId) public onlySigner {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction executed");
require(confirmations[txId][msg.sender], "Not confirmed");
confirmations[txId][msg.sender] = false;
transaction.confirmationCount--;
emit RevokeConfirmation(txId, msg.sender);
}
}
signers和required定义多签成员和最低确认数。submitMint:signer提交铸币提案。confirmMint:signer确认,够票后自动执行。executeMint:铸币,触发事件。revokeConfirmation:撤销确认。nonReentrant保护确认逻辑。test/AccessPro.test.ts(add):
import { MultiSigAccessToken } from "../typechain-types";
describe("MultiSigAccess", function () {
let token: MultiSigAccessToken;
let owner: any, signer1: any, signer2: any, signer3: any, user1: any;
beforeEach(async function () {
[owner, signer1, signer2, signer3, user1] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("MultiSigAccessToken");
token = await TokenFactory.deploy([signer1.address, signer2.address, signer3.address], 2);
await token.deployed();
});
it("executes mint with enough signatures", async function () {
await token.connect(signer1).submitMint(user1.address, ethers.utils.parseEther("1000"));
await token.connect(signer2).confirmMint(0);
await expect(token.connect(signer3).confirmMint(0))
.to.emit(token, "ExecuteMint")
.withArgs(0, user1.address, ethers.utils.parseEther("1000"));
expect(await token.balanceOf(user1.address)).to.equal(ethers.utils.parseEther("1000"));
});
it("blocks mint without enough signatures", async function () {
await token.connect(signer1).submitMint(user1.address, ethers.utils.parseEther("1000"));
await token.connect(signer2).confirmMint(0);
expect(await token.balanceOf(user1.address)).to.equal(0);
});
it("allows revoking confirmation", async function () {
await token.connect(signer1).submitMint(user1.address, ethers.utils.parseEther("1000"));
await token.connect(signer2).confirmMint(0);
await token.connect(signer2).revokeConfirmation(0);
await token.connect(signer3).confirmMint(0);
expect(await token.balanceOf(user1.address)).to.equal(0);
});
});
多角色还不够,搞个嵌套权限,管理员能管角色分配。
contracts/NestedRoleAccess.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract NestedRoleAccessToken is ERC20, AccessControl {
bytes32 public constant SUPER_ADMIN_ROLE = keccak256("SUPER_ADMIN_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bool public paused;
event RoleGranted(address indexed account, bytes32 indexed role, address indexed granter);
event RoleRevoked(address indexed account, bytes32 indexed role, address indexed revoker);
event Minted(address indexed to, uint256 amount);
event Paused(address indexed account);
event Unpaused(address indexed account);
constructor() ERC20("NestedRoleAccessToken", "NRAT") {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(SUPER_ADMIN_ROLE, msg.sender);
_setRoleAdmin(MINTER_ROLE, SUPER_ADMIN_ROLE);
_setRoleAdmin(PAUSER_ROLE, SUPER_ADMIN_ROLE);
_mint(msg.sender, 1000000 * 10**decimals());
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
emit Minted(to, amount);
}
function transfer(address to, uint256 amount) public override returns (bool) {
require(!paused, "Transfers paused");
return super.transfer(to, amount);
}
function pause() public onlyRole(PAUSER_ROLE) {
paused = true;
emit Paused(msg.sender);
}
function unpause() public onlyRole(PAUSER_ROLE) {
paused = false;
emit Unpaused(msg.sender);
}
function grantRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
emit RoleGranted(account, role, msg.sender);
}
function revokeRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
emit RoleRevoked(account, role, msg.sender);
}
}
SUPER_ADMIN_ROLE、MINTER_ROLE、PAUSER_ROLE。SUPER_ADMIN_ROLE可分配/撤销MINTER_ROLE和PAUSER_ROLE。mint/pause/unpause:角色控制,触发事件。grantRole/revokeRole:覆盖,添加事件。SUPER_ADMIN_ROLE控制角色分配。SUPER_ADMIN_ROLE得可信,权限集中风险仍存。mint:~35k Gas。test/AccessPro.test.ts(add):
import { NestedRoleAccessToken } from "../typechain-types";
describe("NestedRoleAccess", function () {
let token: NestedRoleAccessToken;
let owner: any, superAdmin: any, minter: any, pauser: any;
beforeEach(async function () {
[owner, superAdmin, minter, pauser] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("NestedRoleAccessToken");
token = await TokenFactory.deploy();
await token.deployed();
await token.grantRole(await token.SUPER_ADMIN_ROLE(), superAdmin.address);
});
it("lets super admin grant roles", async function () {
await expect(token.connect(superAdmin).grantRole(await token.MINTER_ROLE(), minter.address))
.to.emit(token, "RoleGranted")
.withArgs(minter.address, await token.MINTER_ROLE(), superAdmin.address);
await token.connect(minter).mint(minter.address, ethers.utils.parseEther("500"));
expect(await token.balanceOf(minter.address)).to.equal(ethers.utils.parseEther("500"));
});
it("lets super admin revoke roles", async function () {
await token.connect(superAdmin).grantRole(await token.PAUSER_ROLE(), pauser.address);
await token.connect(superAdmin).revokeRole(await token.PAUSER_ROLE(), pauser.address);
await expect(token.connect(pauser).pause())
.to.be.revertedWith("AccessControl: account is missing role");
});
it("blocks non-super admin from granting roles", async function () {
await expect(token.connect(minter).grantRole(await token.MINTER_ROLE(), minter.address))
.to.be.revertedWith("AccessControl: account is missing role");
});
});
SUPER_ADMIN_ROLE能分配/撤销角色。MINTER_ROLE铸币成功。权限固定死了不灵活,搞个可升级合约,权限也能升级。
contracts/UpgradableAccess.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
contract UpgradableAccessToken is ERC20Upgradeable, AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bool public paused;
event Minted(address indexed to, uint256 amount);
event Paused(address indexed account);
event Unpaused(address indexed account);
function initialize() public initializer {
__ERC20_init("UpgradableAccessToken", "UAT");
__AccessControl_init();
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(MINTER_ROLE, msg.sender);
_setupRole(PAUSER_ROLE, msg.sender);
_mint(msg.sender, 1000000 * 10**decimals());
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) nonReentrant {
_mint(to, amount);
emit Minted(to, amount);
}
function transfer(address to, uint256 amount) public override returns (bool) {
require(!paused, "Transfers paused");
return super.transfer(to, amount);
}
function pause() public onlyRole(PAUSER_ROLE) {
paused = true;
emit Paused(msg.sender);
}
function unpause() public onlyRole(PAUSER_ROLE) {
paused = false;
emit Unpaused(msg.sender);
}
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
}
contracts/UpgradableAccessV2.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
contract UpgradableAccessTokenV2 is ERC20Upgradeable, AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bool public paused;
uint256 public mintFee = 1 * 10**16; // 1% fee
event Minted(address indexed to, uint256 amount);
event Paused(address indexed account);
event Unpaused(address indexed account);
function initialize() public initializer {
__ERC20_init("UpgradableAccessTokenV2", "UATV2");
__AccessControl_init();
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(MINTER_ROLE, msg.sender);
_setupRole(PAUSER_ROLE, msg.sender);
_mint(msg.sender, 1000000 * 10**decimals());
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) nonReentrant {
uint256 fee = (amount * mintFee) / 1e18;
_mint(to, amount - fee);
_mint(msg.sender, fee);
emit Minted(to, amount - fee);
}
function transfer(address to, uint256 amount) public override returns (bool) {
require(!paused, "Transfers paused");
return super.transfer(to, amount);
}
function pause() public onlyRole(PAUSER_ROLE) {
paused = true;
emit Paused(msg.sender);
}
function unpause() public onlyRole(PAUSER_ROLE) {
paused = false;
emit Unpaused(msg.sender);
}
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
}
contracts/UUPSProxy.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract UUPSProxy is ERC1967Proxy {
constructor(address logic, bytes memory data) ERC1967Proxy(logic, data) {}
}
UpgradableAccessToken:继承升级版库,支持代币、角色、UUPS代理。initialize:初始化代币、角色、代理。mint/transfer/pause:角色控制,防重入。_authorizeUpgrade:DEFAULT_ADMIN_ROLE控制升级。UpgradableAccessTokenV2:加1%铸币费,保持存储布局。mint:~40k Gas(含费)。test/AccessPro.test.ts(add):
import { UUPSProxy, UpgradableAccessToken, UpgradableAccessTokenV2 } from "../typechain-types";
describe("UpgradableAccess", function () {
let proxy: UUPSProxy;
let token: UpgradableAccessToken;
let tokenV2: UpgradableAccessTokenV2;
let owner: any, minter: any;
beforeEach(async function () {
[owner, minter] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("UpgradableAccessToken");
token = await TokenFactory.deploy();
await token.deployed();
const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
const initData = TokenFactory.interface.encodeFunctionData("initialize");
proxy = await ProxyFactory.deploy(token.address, initData);
await proxy.deployed();
const TokenV2Factory = await ethers.getContractFactory("UpgradableAccessTokenV2");
tokenV2 = await TokenV2Factory.deploy();
await tokenV2.deployed();
const proxyAsToken = await ethers.getContractFactory("UpgradableAccessToken").then(f => f.attach(proxy.address));
await proxyAsToken.grantRole(await proxyAsToken.MINTER_ROLE(), minter.address);
});
it("lets minter mint tokens", async function () {
const proxyAsToken = await ethers.getContractFactory("UpgradableAccessToken").then(f => f.attach(proxy.address));
await expect(proxyAsToken.connect(minter).mint(minter.address, ethers.utils.parseEther("1000")))
.to.emit(proxyAsToken, "Minted")
.withArgs(minter.address, ethers.utils.parseEther("1000"));
expect(await proxyAsToken.balanceOf(minter.address)).to.equal(ethers.utils.parseEther("1000"));
});
it("upgrades and applies mint fee", async function () {
const proxyAsToken = await ethers.getContractFactory("UpgradableAccessToken").then(f => f.attach(proxy.address));
await proxyAsToken.upgradeTo(tokenV2.address);
const proxyAsTokenV2 = await ethers.getContractFactory("UpgradableAccessTokenV2").then(f => f.attach(proxy.address));
await proxyAsTokenV2.connect(minter).mint(minter.address, ethers.utils.parseEther("1000"));
expect(await proxyAsTokenV2.balanceOf(minter.address)).to.equal(ethers.utils.parseEther("990"));
expect(await proxyAsTokenV2.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("1000010"));
});
});
MINTER_ROLE铸币成功。scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const [owner, signer1, signer2, signer3] = await ethers.getSigners();
const SimpleAccessFactory = await ethers.getContractFactory("SimpleAccessToken");
const simpleAccess = await SimpleAccessFactory.deploy();
await simpleAccess.deployed();
console.log(`SimpleAccessToken deployed to: ${simpleAccess.address}`);
const RoleAccessFactory = await ethers.getContractFactory("RoleAccessToken");
const roleAccess = await RoleAccessFactory.deploy();
await roleAccess.deployed();
console.log(`RoleAccessToken deployed to: ${roleAccess.address}`);
const MultiSigAccessFactory = await ethers.getContractFactory("MultiSigAccessToken");
const multiSigAccess = await MultiSigAccessFactory.deploy([signer1.address, signer2.address, signer3.address], 2);
await multiSigAccess.deployed();
console.log(`MultiSigAccessToken deployed to: ${multiSigAccess.address}`);
const NestedRoleAccessFactory = await ethers.getContractFactory("NestedRoleAccessToken");
const nestedRoleAccess = await NestedRoleAccessFactory.deploy();
await nestedRoleAccess.deployed();
console.log(`NestedRoleAccessToken deployed to: ${nestedRoleAccess.address}`);
const UpgradableAccessFactory = await ethers.getContractFactory("UpgradableAccessToken");
const upgradableAccess = await UpgradableAccessFactory.deploy();
await upgradableAccess.deployed();
const initData = UpgradableAccessFactory.interface.encodeFunctionData("initialize");
const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
const proxy = await ProxyFactory.deploy(upgradableAccess.address, initData);
await proxy.deployed();
console.log(`UpgradableAccessToken deployed to: ${upgradableAccess.address}, Proxy: ${proxy.address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
跑部署:
npx hardhat run scripts/deploy.ts --network hardhat 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!