访问控制是智能合约安全的基础,用于限制谁可以执行特定的操作。不当的访问控制可能导致合约被恶意利用,造成严重的安全问题。
本章你将学到:
最简单的访问控制是 Owner(所有者)模式,只有合约所有者可以执行特定操作:
pragma solidity ^0.8.0;
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Invalid address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(owner, address(0));
owner = address(0);
}
}
contract MyContract is Ownable {
uint256 public value;
// 只有 owner 可以调用
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
// 只有 owner 可以提取资金
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
}
使用映射实现白名单或黑名单:
pragma solidity ^0.8.0;
contract WhitelistBlacklist is Ownable {
mapping(address => bool) public whitelist;
mapping(address => bool) public blacklist;
modifier onlyWhitelisted() {
require(whitelist[msg.sender], "Not whitelisted");
_;
}
modifier notBlacklisted() {
require(!blacklist[msg.sender], "Blacklisted");
_;
}
function addToWhitelist(address account) public onlyOwner {
whitelist[account] = true;
}
function removeFromWhitelist(address account) public onlyOwner {
whitelist[account] = false;
}
function addToBlacklist(address account) public onlyOwner {
blacklist[account] = true;
}
function removeFromBlacklist(address account) public onlyOwner {
blacklist[account] = false;
}
}
添加时间延迟增强安全性:
pragma solidity ^0.8.0;
contract TimelockController is Ownable {
uint256 public constant DELAY = 2 days;
struct Transaction {
address target;
uint256 value;
bytes data;
uint256 executeTime;
bool executed;
}
mapping(bytes32 => Transaction) public transactions;
event TransactionQueued(bytes32 indexed txId, address target, uint256 executeTime);
event TransactionExecuted(bytes32 indexed txId);
function queueTransaction(
address target,
uint256 value,
bytes memory data
) public onlyOwner returns (bytes32) {
bytes32 txId = keccak256(abi.encode(target, value, data, block.timestamp));
transactions[txId] = Transaction({
target: target,
value: value,
data: data,
executeTime: block.timestamp + DELAY,
executed: false
});
emit TransactionQueued(txId, target, block.timestamp + DELAY);
return txId;
}
function executeTransaction(bytes32 txId) public onlyOwner {
Transaction storage txn = transactions[txId];
require(!txn.executed, "Already executed");
require(block.timestamp >= txn.executeTime, "Too early");
txn.executed = true;
(bool success, ) = txn.target.call{value: txn.value}(txn.data);
require(success, "Execution failed");
emit TransactionExecuted(txId);
}
}
❌ 错误示例:
contract Vulnerable {
uint256 public value;
// 任何人都可以修改!
function setValue(uint256 _value) public {
value = _value;
}
}
✅ 正确示例:
contract Secure is Ownable {
uint256 public value;
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
}
❌ 错误示例:
contract Vulnerable {
address public owner;
// 不要使用 tx.origin!
modifier onlyOwner() {
require(tx.origin == owner, "Not owner");
_;
}
}
✅ 正确示例:
contract Secure {
address public owner;
// 使用 msg.sender
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
}
contract Secure {
address public owner;
function transferOwnership(address newOwner) public {
require(msg.sender == owner, "Not owner");
require(newOwner != address(0), "Invalid address");
require(newOwner != owner, "Already owner");
owner = newOwner;
}
}
OpenZeppelin 提供了成熟的访问控制实现:
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyContract is Ownable, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function mint(address to) public onlyRole(MINTER_ROLE) {
// mint logic
}
}
访问控制是智能合约安全的重要的防线,必须谨慎设计和实现。