-
Notifications
You must be signed in to change notification settings - Fork 0
Description
基本概念
在Solidity中,Ownable 合约是一种设计模式,用于管理合约的所有权。它通常提供了一些基础功能,如只允许合约所有者执行某些操作,转移合约所有权等;这种权限管理合约在以太坊主网或者其他链的主网上经常会看到。
实现思路、步骤
我们需要声明一个叫做 Ownable 的合约,这个合约中,有一个状态变量,表示的是合约的所有权属于谁。这个状态变量会在合约部署的时候在构造函数中初始化, 我们并不希望外部直接访问这个变量,所以使用 private修饰符。
为了逻辑复用考虑,我们需要封装一个函数,作用是转移相关权限,这个函数在初始化的时候调用,在后续需要更改合约所有权的时候也需要调用。
基于上面的基础描述,我们用代码实现一下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Ownable {
address private _owner;
constructor() {
_transferOwnership(msg.sender);
}
function _transferOwnership(address newOwner) internal {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}从上面的实现可以看到,_transferOwnership 函数被 internal 修饰,意思是不愿意被外部调用。我们需要封装一个外部可访问的函数,来调用这个内部函数, 并且,我们还需要设计一个 modifier 来限制只有合约的权限管理者才能调用这个方法。
基于这个思路,我们补充完善一下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Ownable {
address private _owner;
constructor() {
_transferOwnership(msg.sender);
}
modifier onlyOwner() {
require(owner() == msg.sender, "Ownable: caller is not the owner");
_;
}
function transferOwnership(address newOwner) public onlyOwner {
// 这里需要注意,新的地址不能是0地址,否则权限就会被永久锁死了
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal {
address oldOwner = _owner;
_owner = newOwner;
}
}在实际的使用场景中,会有一个需求,就是合约的所有者主动放弃所有权,如果想要实现这个功能,直接将自己的所有权转让给0地址。基于这个思路,我们可以设计一个 renounceOwnership 方法。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Ownable {
address private _owner;
constructor() {
_transferOwnership(msg.sender);
}
modifier onlyOwner() {
require(owner() == msg.sender, "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public onlyOwner {
// 这里需要注意,新的地址不能是0地址,否则权限就会被永久锁死了
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal {
address oldOwner = _owner;
_owner = newOwner;
}
}可以看到 renounceOwnership 也是只有合约所有者才可以调用,并且直接调用底层的 _transferOwnership 函数,绕过了0地址的检查。
最后,我们再提供一个函数来查询最新的合约所有者的地址, 还需要添加一个事件,当合约所有者权限更改的时候,我们抛出一个事件。这样一个完整的合约就实现了。
完整代码示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title Ownable
* @dev Ownable合约有一个owner地址类型的状态变量,提供了基础的权限管理函数
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev 初始化,将合约的部署者设置为合约的拥有者
*/
constructor() {
_transferOwnership(msg.sender);
}
/**
* @dev 返回当前的合约所有者
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev 不是合约拥有者调用的时候抛出异常
*/
modifier onlyOwner() {
require(owner() == msg.sender, "Ownable: caller is not the owner");
_;
}
/**
* @dev 当前所有者可以调用这个函数放弃所有权,这会将合约的所有者设置为零地址(`address(0)`)
*/
function renounceOwnership() public onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev 转移合约拥有权到一个新的地址,只能被合约拥有者调用
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* 内部函数,没有权限校验
*/
function _transferOwnership(address newOwner) internal {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}代码说明
- 合约初始化:在合约的构造函数中,设置部署合约的地址为合约的初始所有者。
- 所有者地址:使用私有变量
_owner存储当前合约的所有者地址。 - 所有权转移事件:定义了
OwnershipTransferred事件,当所有权转移时,会记录之前的所有者和新的所有者。 - 只允许所有者执行的修饰符:定义了一个
onlyOwner修饰符,用于限制只有当前合约的所有者可以调用某些函数。 - 查看当前所有者:提供了
owner函数,返回当前所有者的地址。 - 放弃所有权:提供了
renounceOwnership函数,当前所有者可以调用这个函数放弃所有权,这会将合约的所有者设置为零地址(address(0))。 - 转移所有权:提供了
transferOwnership函数,当前所有者可以将合约的所有权转移给新的地址。新地址不能是零地址。 - 内部所有权转移函数:使用
_transferOwnership内部函数执行实际的所有权转移操作,并触发所有权转移事件。
总结
我们熟知的OpenZeppelin提供了最广泛使用和高度可信赖的权限管理工具,包括 Ownable 和 AccessControl。可以帮助你轻松实现智能合约中的角色和权限管理。
此外,其他一些库如ConsenSys和DappSys也提供了有用的权限管理解决方案,但它们的使用范围和知名度不如OpenZeppelin。