openzeppelin学习系列文章
提示:本文是我在学习智能合约开发过程中的一些思考和总结。在这个复杂且不断发展的领域中,可能存在一些疏漏或不准确之处。我非常欢迎和鼓励大家提出意见和建议,让我们可以共同讨论、纠正错误,并提高我们对区块链技术和智能合约的理解与掌握。希望通过这种开放的交流,我们都能在这一领域得到成长和进步。
文章目录
一、简介
ERC20 是一个基于以太坊的智能合约标准库,提供了一种创建新代币的标准方法。ERC20 代币合约可以让任何人在以太坊网络上创建可交易的代币,这些代币可以代表任何东西,如货币、积分等。
二、接口
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
三、其他合约
1. ERC20Burnable
功能:允许用户销毁(burn)他们的代币,从而减少总供应量。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
contract MyToken is ERC20Burnable {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
}
主要方法:
burn(uint256 amount)
: 销毁调用者持有的指定数量的代币。burnFrom(address account, uint256 amount)
: 从指定账户销毁指定数量的代币,前提是调用者拥有足够的授权。
2. ERC20Capped
功能:设定代币的最大供应量上限。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
contract MyToken is ERC20Capped {
constructor(uint256 initialSupply, uint256 cap) ERC20("MyToken", "MTK") ERC20Capped(cap) {
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
注意:_mint
方法需要在合约中定义,以便遵循 ERC20Capped
的上限限制。
3. ERC20Pausable
功能:允许管理员在紧急情况下暂停和恢复代币的所有传输操作。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20Pausable, Ownable {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Pausable) {
super._beforeTokenTransfer(from, to, amount);
}
}
主要方法:
_pause()
: 暂停所有代币转移操作。_unpause()
: 恢复所有代币转移操作。_beforeTokenTransfer
: 在每次代币转移前调用,检查是否已暂停。
4. ERC20Snapshot
功能:允许创建代币持有者的快照,并查询过去的余额。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20Snapshot, Ownable {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
function snapshot() public onlyOwner {
_snapshot();
}
}
主要方法:
_snapshot()
: 创建一个新的快照,并返回快照 ID。balanceOfAt(address account, uint256 snapshotId)
: 返回指定账户在指定快照时的余额。totalSupplyAt(uint256 snapshotId)
: 返回在指定快照时的总供应量。
5. ERC20Permit
功能:允许持有者通过签名离线授权其他账户转移其代币。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
contract MyToken is ERC20Permit {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") ERC20Permit("MyToken") {
_mint(msg.sender, initialSupply);
}
主要方法:
permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
: 根据签名数据批准spender
转移owner
的代币。
6. ERC20Votes
功能:用于治理代币,通过快照记录历史投票权。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract MyToken is ERC20Votes {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") ERC20Permit("MyToken") {
_mint(msg.sender, initialSupply);
}
// 需要覆盖 _afterTokenTransfer, _mint, _burn 来处理投票权更新
function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) {
super._mint(to, amount);
}
function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) {
super._burn(account, amount);
}
}
主要方法:
delegate(address delegatee)
: 委托投票权给delegatee
。getVotes(address account)
: 获取account
当前的投票权。getPastVotes(address account, uint256 blockNumber)
: 获取account
在指定区块号时的投票权。
7.SafeERC20
功能:SafeERC20
是一个库,为 ERC20 代币操作提供了安全的包装器。这些包装器可以确保操作不会失败,并且在某些情况下会处理代币合约未按预期返回值的问题。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MyContract {
using SafeERC20 for IERC20;
IERC20 private _token;
constructor(IERC20 token) {
_token = token;
}
function safeTransfer(address to, uint256 amount) public {
_token.safeTransfer(to, amount);
}
function safeTransferFrom(address from, address to, uint256 amount) public {
_token.safeTransferFrom(from, to, amount);
}
function safeApprove(address spender, uint256 amount) public {
_token.safeApprove(spender, amount);
}
function safeIncreaseAllowance(address spender, uint256 addedValue) public {
_token.safeIncreaseAllowance(spender, addedValue);
}
function safeDecreaseAllowance(address spender, uint256 subtractedValue) public {
_token.safeDecreaseAllowance(spender, subtractedValue);
}
主要方法:
safeTransfer(IERC20 token, address to, uint256 amount)
: 安全转移代币。safeTransferFrom(IERC20 token, address from, address to, uint256 amount)
: 从一个账户安全转移代币到另一个账户。safeApprove(IERC20 token, address spender, uint256 amount)
: 安全批准代币。safeIncreaseAllowance(IERC20 token, address spender, uint256 addedValue)
: 安全增加允许的代币数量。safeDecreaseAllowance(IERC20 token, address spender, uint256 subtractedValue)
: 安全减少允许的代币数量。
8.TokenTimelock
功能:TokenTimelock
是一个智能合约,用于锁定 ERC20 代币,直到指定的释放时间。
使用方法:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/TokenTimelock.sol";
contract MyTokenTimelock {
IERC20 private _token;
TokenTimelock private _timelock;
constructor(IERC20 token, address beneficiary, uint256 releaseTime) {
_token = token;
_timelock = new TokenTimelock(token, beneficiary, releaseTime);
}
function getTimelockAddress() public view returns (address) {
return address(_timelock);
}
}
主要方法:
TokenTimelock(IERC20 token, address beneficiary, uint256 releaseTime)
: 构造函数,用于初始化代币、受益人和释放时间。release()
: 释放锁定的代币,必须在释放时间之后调用。
四、注意事项
- 版本匹配:确保合约版本与 OpenZeppelin 库版本一致,避免编译错误。
- 安全使用:使用
SafeERC20
来确保代币操作的安全性。 - 权限控制:使用
Ownable
和AccessControl
进行权限管理,确保只有授权用户可以执行特定操作。
五、提问:
与 USDT 等 ERC20 代币交互时,为什么建议使用 SafeERC20
?
- 处理返回值不规范的合约:一些 ERC20 代币合约(如 USDT)在执行
transfer
,transferFrom
,approve
等操作时,不会返回布尔值(true
或false
),而是直接返回交易结果。这种行为不符合 ERC20 标准,可能会导致在处理这些交易时出现问题。 - 避免合约调用失败:如果直接调用这些函数,并假设它们会返回布尔值,那么在与不返回布尔值的代币合约交互时,可能会导致合约调用失败或产生意外的错误。
- 增强安全性:使用
SafeERC20
提供的安全包装函数,可以确保在调用 ERC20 代币函数时,正确处理返回值,并在操作失败时抛出错误。这提高了合约的安全性和可靠性。
欢迎访问我的 GitHub 查看更多相关项目,或通过WeChat(ID: lk34041515)与我联系,共同探讨技术问题。
创作权保护
本文由 [Leon-Kay] 学习总结编写,水平有限,内容仅供参考,作为个人记录使用。若有疏漏,请不吝赐教。版权归作者所有,未经授权,禁止转载、摘编或以其他方式使用本文内容。如需合作或转载本文,请联系作者获得授权。