OpenZeppelin
security
主要是实现了几个security相关的通用的安全设计模式
- Pausable 可以让合约停止服务
- PullPayment 不自动转账而是让用户来自己领取
- ReentrancyGuard 防止回调重入
上面三个合约的代码和功能都很简单,就直接看看代码就行了。
Pausable
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
// 两个时间,暂停和解除暂停的时候抛出
event Paused(address account);
event Unpaused(address account);
// 暂停状态标识符
bool private _paused;
// 初始化为未暂停
constructor() {
_paused = false;
}
// 修饰器,需要处在 未暂停状态 才能进入函数
modifier whenNotPaused() {
_requireNotPaused();
_;
}
// 需要处在 暂停状态 才能进入函数
modifier whenPaused() {
_requirePaused();
_;
}
// 查看暂停状态
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* 以下两个内部函数都是为上面的 modifier 服务的
*/
// 需要处在 未暂停状态,否则抛出异常
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
// 需要处在 暂停状态,否则抛出异常
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* 下面两个函数都是internal的,所以外部无法访问,
* 当我们自己设计合约要依赖这个合约的时候,
* 我们需要自己设计外部函数来调用下面这两个内部函数以控制 _paused 的切换
*/
// 修改状态为 暂停状态,并抛出 Paused事件
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
// 修改状态为 未暂停状态,并抛出 Unpaused事件
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
PullPayment
主要的功能是靠另一个合约 Escrow.sol
去实现的。需要注意的是 Escrow.sol
也继承了 Ownable.sol
,而 Escrow.sol
的owner就是 PullPayment.sol
的地址,因为 Escrow.sol
是在其内部创建的。
PullPayment.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/PullPayment.sol)
pragma solidity ^0.8.0;
import "../utils/escrow/Escrow.sol";
abstract contract PullPayment {
Escrow private immutable _escrow;
constructor() {
_escrow = new Escrow();
}
// 提款
function withdrawPayments(address payable payee) public virtual {
_escrow.withdraw(payee);
}
// 查看 dest地址 里的余额
function payments(address dest) public view returns (uint256) {
return _escrow.depositsOf(dest);
}
// 给deposit里存钱的函数,这是个内部函数,想要用这个,需要在设计自己的合约的时候设计这个函数的使用方式
function _asyncTransfer(address dest, uint256 amount) internal virtual {
_escrow.deposit{value: amount}(dest);
}
}
Escrow.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/escrow/Escrow.sol)
pragma solidity ^0.8.0;
// 做权限控制
import "../../access/Ownable.sol";
import "../Address.sol";
contract Escrow is Ownable {
using Address for address payable;
event Deposited(address indexed payee, uint256 weiAmount);
event Withdrawn(address indexed payee, uint256 weiAmount);
// 托管钱的地方
mapping(address => uint256) private _deposits;
// 查看 _deposits 中的余额
function depositsOf(address payee) public view returns (uint256) {
return _deposits[payee];
}
// 给 _deposits 里存钱,payable可接受转账,只有 owner 可以使用
function deposit(address payee) public payable virtual onlyOwner {
uint256 amount = msg.value; // 接收到金额
_deposits[payee] += amount; // 存进_deposits里对应的账户里
emit Deposited(payee, amount); // 抛出 Deposited事件
}
// 取钱的函数,只有owner可以调用
function withdraw(address payable payee) public virtual onlyOwner {
uint256 payment = _deposits[payee];
_deposits[payee] = 0;
payee.sendValue(payment); // 调用的是Address里的方法,里面包含了转钱的方法
emit Withdrawn(payee, payment); // 抛出 Withdraw事件
}
}
ReentrancyGuard
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
// 就是很常规的用Modifier在里面通过对标志状态的修改和判断来防止重入
abstract contract ReentrancyGuard {
// 两种状态
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
// 当前状态
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // 重入检测
_status = _ENTERED; // 更新状态,防止重入
_; // 执行函数
_status = _NOT_ENTERED; // 回退状态,等待下一次进入
}
}