概述:
该合约名为
"Ownable",它提供了一种基本的访问控制机制,有一个账户(所有者)可以被赋予对特定功能的独家访问权。这个机制是通过继承来使用的,它会使得修饰符
onlyOwner
可用,该修饰符可以应用到你的函数上以限制他们的使用只能是所有者。
初始的所有者设置为部署者提供的地址,后期可以通过
transferOwnership
方法更改。
具体来看,该合约主要包括以下几个功能:
constructor
:构造函数,在合约初始化时设置初始的所有者。onlyOwner
:只有所有者可以执行的修饰符。owner
:获取当前的所有者。_checkOwner
:检查发送者是否为所有者。renounceOwnership
:放弃所有权,只有当前所有者才能调用。放弃所有权后,合约将没有所有者,只有所有者才能使用的功能将被禁用。transferOwnership
:转让所有权,只有当前所有者才能调用。新所有者不能为0地址。_transferOwnership
:内部函数,用于将合约的所有权转移给新账户。
此外,还包括两个错误处理机制 OwnableUnauthorizedAccount
和
OwnableInvalidOwner
,用于处理未授权账户和无效所有者的问题。并且有一个事件
OwnershipTransferred
,在所有权转移时触发。
完整代码:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.19;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
代码分析:
import "../utils/Context.sol";
这段代码是一个 Solidity 的导入语句,它的作用是导入了位于
../utils/
路径下的 Context.sol
文件。在
Solidity
中,导入语句允许你在一个文件中使用另一个文件定义的合约、库、接口等。
Context.sol
通常是一个在 OpenZeppelin
合约库中定义的工具合约,它为合约提供了 _msgSender()
和
_msgData()
方法,这两个方法返回交易的发送者(msg.sender
)和数据载荷(msg.data
),并且在某些上下文(例如当合约作为
delegatecall
的目标)中,这些方法比直接使用
msg.sender
和 msg.data
更加安全。
在Ownable
合约中通过导入和继承 Context
合约,获得了使用 _msgSender()
方法的能力,这在
_checkOwner()
函数中使用到,用于判断当前交易的发送者是否为合约的所有者。
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
这段代码定义了一个名为 Ownable
的抽象合约,该合约继承了
Context
合约。抽象合约是一种包含了至少一个未实现(只有声明没有定义)函数的合约,它不能被直接部署,只能被继承。
在 Ownable
合约中,声明了一个私有的地址类型变量
_owner
,用来存储该合约的所有者地址。
Ownable
合约也定义了一个名为
OwnableUnauthorizedAccount
的自定义错误。在 Solidity 0.8.4
及以后的版本中,允许定义自定义错误,以便在合约代码中通过
revert
语句抛出。这个
OwnableUnauthorizedAccount
错误在合约中的任何地方都可以使用
revert OwnableUnauthorizedAccount(account);
来抛出,其中
account
参数会被传递给错误。在代码中,当一个未授权的账户尝试执行某些只能由所有者执行的操作时,可以抛出这个错误。
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
这段代码继续定义了 Ownable
合约的两个要素:一个自定义错误 OwnableInvalidOwner
和一个事件 OwnershipTransferred
。
OwnableInvalidOwner
是一个自定义错误,用于处理所有者账户无效的情况,例如所有者账户地址为address(0)
。在 Solidity 中,address(0)
通常被视为无效地址,因为它是地址空间的默认值。这个错误在合约的transferOwnership
函数中会被用到,如果尝试将所有权转移给address(0)
,则会抛出这个错误。OwnershipTransferred
是一个事件,它在所有权从一个账户转移给另一个账户时被触发。在 Solidity 中,事件主要用于在区块链上记录特定的状态改变,这些记录在以太坊区块链上是永久存储的。OwnershipTransferred
事件接受两个参数:previousOwner
和newOwner
,表示所有权的转移关系。这两个参数都使用了indexed
关键字,这意味着这些参数在记录事件时被索引,以便在搜索和过滤事件时使用。
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
_transferOwnership(initialOwner);
}
这段代码定义了 Ownable
合约的构造函数。在 Solidity
中,构造函数是一个特殊的函数,它在合约被部署(创建)时被执行,且只执行一次。
constructor
函数接收一个 address
类型的参数
initialOwner
,该参数预期是合约的初始所有者的地址。函数体中调用了
_transferOwnership
函数,将合约的所有权转移给
initialOwner
。
这意味着,当 Ownable
合约被部署时,部署者需要提供一个地址作为初始所有者。这个地址就会成为合约的所有者,能够调用合约中被
onlyOwner
修饰符修饰的函数。在此后,所有权可以通过
transferOwnership
函数进行更改。
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
这段代码定义了一个名为 onlyOwner
的修饰符。在 Solidity
中,修饰符是一种特殊的函数,它可以被用于修改其他函数的行为。当一个函数被一个修饰符修饰时,修饰符的代码将在函数的代码之前(有时也可能是之后)执行。
在这个 onlyOwner
修饰符中,首先调用了
_checkOwner
函数。_checkOwner
函数的预期行为应该是检查当前的交易发送者是否为合约的所有者,如果不是,则抛出错误。这样,当一个函数被
onlyOwner
修饰符修饰时,如果这个函数被非所有者调用,则会因为
_checkOwner
函数的错误抛出而终止执行。
符号 _
在修饰符中代表被修饰的函数的代码。在执行到
_
时,控制权会转到被修饰的函数的代码,然后在被修饰的函数代码执行完毕后返回修饰符。在这个
onlyOwner
修饰符中,_
后面没有其他代码,因此被修饰的函数执行完毕后,onlyOwner
修饰符也就执行完毕。
这个 onlyOwner
修饰符可以被用于限制只有合约的所有者才能调用的函数。例如,transferOwnership
和 renounceOwnership
函数可能会被 onlyOwner
修饰,以保证只有当前所有者才能转移或放弃所有权。
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
这段代码定义了一个名为 owner
的公开函数,它返回当前合约的所有者地址。
在 Solidity 中,函数的可见性可以是
public
、private
、internal
或
external
。public
表示该函数可以在合约内部或外部被调用。
view
是一个关键字,表示该函数不会修改合约的状态,只会读取状态。这意味着,调用这个
owner
函数不会消耗任何
Gas,因为它不会改变区块链上的任何数据。
virtual
关键字表明该函数可以在派生合约中被重写。也就是说,任何继承了
Ownable
合约的合约都可以提供自己的 owner
函数实现。
该函数返回的是
_owner
,这是一个在合约中定义的私有变量,存储的是当前合约的所有者地址。当你调用这个
owner
函数时,就可以获取到当前合约的所有者是谁。
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
这段代码定义了一个名为 _checkOwner
的内部函数,它的作用是检查当前交易的发送者是否为合约的所有者。
该函数首先调用 owner
函数获取当前的所有者,然后调用
_msgSender
函数获取当前交易的发送者,然后比较这两个地址是否相等。如果不相等,表示当前的交易发送者不是所有者,然后函数通过
revert
语句抛出一个 OwnableUnauthorizedAccount
错误,并将当前交易的发送者地址传递给错误。
通常来说,这个函数以 _
开头,按照 Solidity
的命名习惯,这通常表示这个函数是一个内部函数,不应该在合约外部被直接调用。
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
这段代码定义了一个名为 renounceOwnership
的公开函数,这个函数允许当前的合约所有者放弃所有权。
函数被 onlyOwner
修饰符修饰,表示只有合约的所有者才能调用这个函数。如果非所有者尝试调用这个函数,onlyOwner
修饰符将抛出一个错误。
函数体中调用了 _transferOwnership
函数,并向其传递了
address(0)
作为参数。_transferOwnership
函数预期会实现所有权的实际转移,并触发一个
OwnershipTransferred
事件。通过将所有权转移给
address(0)
,实际上就是使合约无主,也就是放弃所有权。
需要注意的是,一旦所有权被放弃,就无法再调用被 onlyOwner
修饰符修饰的函数,因为没有账户会满足这个修饰符的所有者条件。因此,使用这个
renounceOwnership
函数需要谨慎,因为它可能会禁用合约的部分功能。
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
这段代码定义了一个名为 transferOwnership
的公开函数,这个函数用于将合约的所有权转移给新的所有者。
transferOwnership
函数接收一个 address
类型的参数 newOwner
,表示新的所有者的地址。
这个函数被 onlyOwner
修饰符修饰,表示只有合约的当前所有者才能调用这个函数。
在函数体中,首先检查 newOwner
是否为
address(0)
,如果是,则通过 revert
语句抛出一个
OwnableInvalidOwner
错误,并传递 address(0)
给错误。这是因为在 Solidity 中,address(0)
通常被视为无效地址,因为它是地址空间的默认值。
然后,函数调用 _transferOwnership
函数,将所有权转移给
newOwner
。_transferOwnership
函数预期会实现所有权的实际转移,并触发一个
OwnershipTransferred
事件。
这个 transferOwnership
函数可以用于在必要时更改合约的所有者。例如,如果原所有者失去了私钥,或者由于某种原因需要将所有权交给其他人,就可以通过这个函数完成。
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
这段代码定义了一个名为 _transferOwnership
的内部函数,这个函数用于将合约的所有权转移给新的所有者。
_transferOwnership
函数接收一个 address
类型的参数 newOwner
,表示新的所有者的地址。
在函数体中,首先将 _owner
(当前的所有者地址)保存到
oldOwner
中,然后将 _owner
设置为
newOwner
,从而完成所有权的实际转移。
然后,函数触发一个 OwnershipTransferred
事件,并传递
oldOwner
和 newOwner
给事件。这个事件可以被外部监听器用于监视所有权的变更。
这个函数是一个内部函数,按照 Solidity 的命名习惯,它以 _
开头,表示这个函数不应该在合约外部被直接调用。而是应该通过其他函数(例如
transferOwnership
和
renounceOwnership
)间接调用。