在Solidity中,Ownable合约是一种设计模式,用于管理合约的所有权。它通常提供了一些基础功能,如只允许合约所有者执行某些操作,转移合约所有权等;这种权限管理合约在以太坊主网或者其他链的主网上经常会看到。
在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。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!