要梳理 OpenZeppelin 的知识体系,树状图的结构可以从核心功能模块入手,逐步细分到具体的合约类型、工具和用途。以下是 OpenZeppelin 的核心模块及其分支的树状结构图的文本描述:
能够减轻你智能合约开发工作量的一个工具集。
如果使用的是hardhat,truffle 框架 :
npm install @openzeppelin/contracts
如果是使用的是foundry
forge install OpenZeppelin/openzeppelin-contracts
安装完成之后,可以通过导入库来使用它们
// contracts/MyNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
constructor() ERC721("MyNFT", "MNFT") {
}
}
这里有个问题: 可以导入哪些工具?我们在下文进行回答。
关键字 is , 合约继承是通过 is 关键字来进行继承。
// contracts/MyNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
constructor() ERC721("MyNFT", "MNFT") {
}
}
继承之后, 就可以使用父合约的函数和属性。
关键字 overridde
如果需要修改父合约的函数,那么就需要进行重写。重写是针对函数而言的
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 父合约
contract Parent {
// 虚函数,允许在子合约中重写
function greet() public virtual returns (string memory) {
return "Hello from Parent!";
}
}
// 子合约
contract Child is Parent {
// 重写父合约中的 greet 函数
function greet() public override returns (string memory) {
return "Hello from Child!";
}
}
super
super 关键字用于在继承的子合约中调用父合约的函数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Parent {
function foo() public virtual returns (string memory) {
return "Parent foo";
}
}
contract Child is Parent {
function foo() public virtual override returns (string memory) {
// 使用 super 调用父合约的 foo 函数
return string(abi.encodePacked("Child and ", super.foo()));
}
}
在 OpenZeppelin 中,Upgrades 是指智能合约的可升级性解决方案。由于智能合约一旦部署到区块链上就变得不可更改,任何代码逻辑上的错误或需要的改进都可能导致项目无法正常运作。OpenZeppelin Upgrades 提供了一种机制,使智能合约能够在不改变其合约地址的前提下进行升级,确保合约的状态和数据在升级过程中保留不变。
npm install @openzeppelin/contracts-upgradeable @openzeppelin/contracts
-import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
-contract MyCollectible is ERC721 {
+contract MyCollectible is ERC721Upgradeable {
在可升级合约中,构造函数(constructor)被替换为初始化函数(initializer),因为在使用代理模式进行合约升级时,代理合约不会执行逻辑合约中的构造函数。因此,合约需要通过初始化函数来完成状态的初始化,而不是像普通合约那样使用构造函数。
- constructor() ERC721("MyCollectible", "MCO") public {
+ function initialize() initializer public {
+ __ERC721_init("MyCollectible", "MCO");
}
// scripts/deploy-my-collectible.js
const { ethers, upgrades } = require("hardhat");
async function main() {
const MyCollectible = await ethers.getContractFactory("MyCollectible");
const mc = await upgrades.deployProxy(MyCollectible);
await mc.waitForDeployment();
console.log("MyCollectible deployed to:", await mc.getAddress());
}
main();
OpenZeppelin 合约使用 语义化版本控制(semantic versioning 来传达其API和存储布局的向后兼容性。一般来说,补丁(patch)和次要(minor)更新将保持向后兼容,除了一些罕见的例外情况(如下所述)。主要(major)更新则通常与先前版本不兼容。本页面提供了这些兼容性保证的详细信息。
这部分内容简单来说就是: 谁能做什么事情。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is Ownable {
constructor(address initialOwner) Ownable(initialOwner) {}
function normalThing() public {
// anyone can call this normalThing()
}
function specialThing() public onlyOwner {
// only the owner can call specialThing()!
}
}
步骤:
基于所有权的访问控制(如 Ownable)适用于简单的系统,但许多情况下,需要更复杂的授权级别。 基于角色的访问控制(RBAC)允许你为不同角色分配不同的权限,例如“管理员”、“铸币者”、“审查员”等,而不是仅仅依赖于 onlyOwner。 每个角色可以通过 onlyRole 修饰符进行权限检查,确保只有特定角色的账户可以执行相应操作。
假设你正在创建一个ERC20代币合约,并希望不同的角色具有不同的权限:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract RoleBasedERC20 is ERC20, AccessControl {
// 定义两个角色:铸币者和销毁者
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
// 部署合约时,msg.sender 被授予管理员角色
constructor() ERC20("MyToken", "MTK") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// 铸币功能,只有被授予 MINTER_ROLE 角色的账户才能调用
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
// 销毁功能,只有被授予 BURNER_ROLE 角色的账户才能调用
function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) {
_burn(from, amount);
}
// 为新账户授予铸币者角色,只有管理员可以调用
function grantMinterRole(address account) public {
grantRole(MINTER_ROLE, account); // 管理员授予铸币者角色
}
// 为新账户授予销毁者角色,只有管理员可以调用
function grantBurnerRole(address account) public {
grantRole(BURNER_ROLE, account); // 管理员授予销毁者角色
}
// 撤销账户的铸币者角色,只有管理员可以调用
function revokeMinterRole(address account) public {
revokeRole(MINTER_ROLE, account); // 管理员撤销铸币者角色
}
// 撤销账户的销毁者角色,只有管理员可以调用
function revokeBurnerRole(address account) public {
revokeRole(BURNER_ROLE, account); // 管理员撤销销毁者角色
}
}
访问控制对于防止未经授权的访问至关重要,但管理员本身可能会对系统进行恶意操作,导致用户受损。TimelockController 是专门为解决这个问题而设计的。TimelockController 的作用:当 TimelockController 成为某个智能合约的管理员时,它确保任何重要的操作(如铸造代币、冻结转账或升级合约)都需要经过一个延迟。这个延迟为用户提供了时间去审查即将执行的操作,并决定是否要退出系统。
我们来创建一个带有 TimelockController
的智能合约,展示如何使用它来确保关键操作需要经过延迟执行。我们会结合
ERC20
代币合约,并通过 TimelockController
来控制谁有权提议和执行代币的铸造和销毁操作。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/governance/TimelockController.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract TimelockControlledToken is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
// 使用 TimelockController 进行控制
TimelockController public timelock;
constructor(
address timelockAddress,
address initialMinter,
address initialBurner
) ERC20("MyToken", "MTK") {
// 设置 TimelockController 地址
timelock = TimelockController(timelockAddress);
// 将合约的 MINTER_ROLE 和 BURNER_ROLE 分配给 TimelockController 进行管理
_grantRole(MINTER_ROLE, initialMinter);
_grantRole(BURNER_ROLE, initialBurner);
}
// 只有具有 MINTER_ROLE 的账户才能铸造代币
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
// 只有具有 BURNER_ROLE 的账户才能销毁代币
function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) {
_burn(from, amount);
}
}
contract MyTimelockController is TimelockController {
constructor(
uint256 minDelay, // 最小延迟时间(秒)
address[] memory proposers, // 提议者账户
address[] memory executors // 执行者账户
) TimelockController(minDelay, proposers, executors) {}
}
TimelockController
允许对关键操作(如代币铸造、合约升级等)设置一个延迟时间,在此期间用户可以审查操作内容。如果用户认为即将执行的操作不利于他们,他们可以选择退出系统。mint
)和销毁(burn
)操作设置了权限,并通过
TimelockController
来确保这些操作需要经过延迟执行。ERC20
代币合约,通过
AccessControl
来管理权限。
MINTER_ROLE
(铸币者)和
BURNER_ROLE
(销毁者),分别控制代币的铸造和销毁操作。TimelockController
进行管理,确保任何代币铸造或销毁操作都需要遵守延迟规则。TimelockController
的具体实现,用来设定延迟时间以及哪些账户可以提出提议或执行操作。TimelockControlledToken
的工作流程:timelockAddress
,这是 TimelockController
的地址,用于管理合约的关键操作。这个 TimelockController
拥有 MINTER_ROLE
和 BURNER_ROLE
的权限。_grantRole
函数,将铸币者和销毁者的角色分配给传入的初始地址。TimelockController
来提议和执行操作。TimelockController
的工作流程:TimelockController
被配置为管理者时,任何需要执行的操作都会经过延迟,用户可以在延迟期间审核这些操作。TimelockController
中,有两个重要角色:
MyTimelockController
的构造函数:MyTimelockController
继承自
TimelockController
,它的构造函数接收三个参数:
minDelay
:定义延迟的最小时间(以秒为单位)。proposers
:一组具有提议权限的账户地址。executors
:一组具有执行权限的账户地址。MyTimelockController
:
// 部署 TimelockController,延迟时间为7天
address[] memory proposers = [address(0x123)];
address[] memory executors = [address(0x456)];
MyTimelockController timelock = new MyTimelockController(7 days, proposers, executors);
TimelockControlledToken
:
timelock
作为代币的管理者,同时指定初始的铸币者和销毁者。TimelockControlledToken token = new TimelockControlledToken(address(timelock), minterAddress, burnerAddress);
TimelockController
提出铸造或销毁代币的操作。TimelockController
的作用:它引入了一种延迟机制,确保系统的关键操作不会立即执行,给用户足够的时间去审查和退出系统,防止管理员恶意操作。TimelockController
通过提议者和执行者的角色分离来管理操作的执行(通过写合约来实现),并允许用户对操作进行提前审查。AccessManager 是一个集中管理合约权限的工具,设计用来简化多个合约的权限管理。通常,在每个合约中单独使用 AccessControl 来管理权限会增加系统的复杂性。而 AccessManager 允许你将所有权限集中在一个合约中进行管理,从而减少管理难度,并提高审计和维护的效率。
角色:多个账户可以拥有相同的角色,一个账户也可以拥有多个角色。每个角色通过数值(uint64)来标识,而不是像 AccessControl 那样使用 bytes32 类型。 目标函数:目标函数是合约中受限制的函数,只有具有特定角色的账户才能调用。 在 AccessManager 中,只有被授予角色的账户才能调用与角色相关的目标函数。调用者必须具备与目标函数相匹配的角色才能被授权执行该操作。
await manager.setTargetFunctionRole(
tokenContractAddress, // 目标合约地址(如 AccessManagedERC20Mint 的合约地址)
['0x40c10f19'], // 函数选择器:mint(address,uint256)
MINTER // 角色:MINTER_ROLE
);
函数选择器这个概念需要另起说明。
Token 是区块链中的一个表示,可以代表各种东西,如金钱、时间、服务、公司股份、虚拟宠物等。通过将这些事物表示为token,智能合约能够与它们交互、交换、创建或销毁。
由于以太坊上的一切都是智能合约,智能合约没有强制规则,社区为token开发了一些标准,以便不同的智能合约之间能够互操作。 常见的token标准包括:
ERC 代表 Ethereum Request for Comment,即以太坊请求意见书。
这些数字本身没有特别的技术含义,只是方便我们识别每个标准的编号。
ERC20代币合约主要用于记录和管理可替代的代币,这意味着每个代币彼此完全相同,没有特殊的权利或行为。这种特性使ERC20代币非常适合用于交换货币、投票权、质押等场景。OpenZeppelin 提供了许多与ERC20相关的合约,这些合约使我们可以轻松地创建和管理自己的ERC20代币合约。
通过使用OpenZeppelin的合约,我们可以很容易地创建自己的ERC20代币合约。下方展示了一个GLD(Gold)的代币合约代码示例,该代币将被用作假想游戏中的内部货币。
// contracts/GLDToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract GLDToken is ERC20 {
constructor(uint256 initialSupply) ERC20("Gold", "GLD") {
_mint(msg.sender, initialSupply);
}
}
这段代码中,通过构造函数constructor设置代币的名称为“Gold”(黄金),符号为“GLD”,并指定初始供应量initialSupply。_mint函数用于铸造初始供应量的代币,并将它们分配给部署合约的地址。
一旦部署合约后,你可以使用以下命令查询代币的余额和转移代币:
ERC20 是一种广泛使用的以太坊代币标准,OpenZeppelin 提供了其实现。标准的ERC20合约本身并未定义如何生成供应量,因此在部署默认ERC20合约时,代币是没有供应的。每个代币可以定义自己的供应机制,从最中心化到最去中心化,具体实现方式非常灵活。
假设我们想要创建一个具有固定供应量的代币,比如 1000 个代币,初始供应量分配给部署合约的账户。
早期的合约(如Contracts v1)可能会直接修改 totalSupply 和 balances 变量来创建固定供应量:
contract ERC20FixedSupply is ERC20 {
constructor() {
totalSupply += 1000;
balances[msg.sender] += 1000;
}
}
但从OpenZeppelin Contracts v2开始,totalSupply 和 balances 被设为私有,不能直接修改。代替的方法是使用 _mint 函数,它可以安全地创建供应并同步更新。
contract ERC20FixedSupply is ERC20 {
constructor() ERC20("Fixed", "FIX") {
_mint(msg.sender, 1000);
}
}
这种方式更安全,因为 _mint 会自动处理 totalSupply 和 balances 的同步更新,并触发标准要求的 Transfer 事件,防止忘记这些操作。
我们可以扩展 ERC20 的供应机制,为生产区块的矿工提供代币奖励。通过 Solidity 的 block.coinbase 变量,可以访问当前区块的矿工地址。每次调用 mintMinerReward() 函数时,会铸造1000个代币作为奖励发送给矿工。
contract ERC20WithMinerReward is ERC20 {
constructor() ERC20("Reward", "RWD") {}
function mintMinerReward() public {
_mint(block.coinbase, 1000);
}
}
我们可以在每次代币转账时自动触发矿工奖励。通过扩展 _update 函数,可以在每个代币转账操作时自动为矿工铸造奖励代币。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC20WithAutoMinerReward is ERC20 {
constructor() ERC20("Reward", "RWD") {
_mintMinerReward();
}
function _mintMinerReward() internal {
_mint(block.coinbase, 1000);
}
function _update(address from, address to, uint256 value) internal virtual override {
if (!(from == address(0) && to == block.coinbase)) {
_mintMinerReward();
}
super._update(from, to, value);
}
}
ERC721 是用于创建非同质化代币(NFT)的标准,每个代币都是唯一的,不能互换,适用于具有独特属性的资产,如游戏物品、收藏品等。下方代码展示了如何使用ERC721标准创建一个游戏物品(GameItem)合约,每个物品都有自己的独特属性和元数据。
// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract GameItem is ERC721URIStorage {
uint256 private _nextTokenId;
constructor() ERC721("GameItem", "ITM") {}
function awardItem(address player, string memory tokenURI)
public
returns (uint256)
{
uint256 tokenId = _nextTokenId++;
_mint(player, tokenId);
_setTokenURI(tokenId, tokenURI);
return tokenId;
}
}
tokenURI 指向一个 JSON 文件,描述了每个代币的属性。这个 URI 通常是一个存储在外部服务器上的链接,指向代币的详细信息,例如图像、描述和自定义属性。
{
"name": "Thor's hammer",
"description": "Mjölnir, the legendary hammer of the Norse god of thunder.",
"image": "https://game.example/item-id-8u5h2m.png",
"strength": 20
}
ERC1155 是一种新型的多代币标准,它结合了 ERC20、ERC721 和 ERC777 的优点,旨在创建一种对代币可替代性不敏感并且Gas 效率高的合约。该标准允许在一个智能合约中表示多种代币,既可以支持可替代代币(如游戏内的货币),也可以支持非同质化代币(如独特的物品或收藏品)。
// contracts/GameItems.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
contract GameItems is ERC1155 {
uint256 public constant GOLD = 0;
uint256 public constant SILVER = 1;
uint256 public constant THORS_HAMMER = 2;
uint256 public constant SWORD = 3;
uint256 public constant SHIELD = 4;
constructor() ERC1155("https://game.example/api/item/{id}.json") {
_mint(msg.sender, GOLD, 1018, "");
_mint(msg.sender, SILVER, 1027, "");
_mint(msg.sender, THORS_HAMMER, 1, "");
_mint(msg.sender, SWORD, 109, "");
_mint(msg.sender, SHIELD, 109, "");
}
}
> gameItems.balanceOf(deployerAddress,3) // 查询持有 SWORD 代币的数量
1000000000
> gameItems.safeTransferFrom(deployerAddress, playerAddress, 2, 1, "0x0") // 转移 1 个 THORS_HAMMER
> gameItems.safeBatchTransferFrom(deployerAddress, playerAddress, [0,1,3,4], [50,100,1,1], "0x0") // 批量转移多个代币
> gameItems.balanceOfBatch([playerAddress,playerAddress,playerAddress,playerAddress,playerAddress], [0,1,2,3,4])
[50,100,1,1,1]
> gameItems.uri(2) // 获取 Thor's Hammer 的元数据 URI
"https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json"
由于 ERC1155 允许通过一个合约管理多个代币类型,并且提供了批量操作功能,因此在处理多个代币时,ERC1155 可以极大地减少 Gas 消耗。例如,批量转移多个代币只需发起一笔交易,相比于单独调用每个代币的转移操作,大幅降低了交易成本。
代币的可替代性由其供应量和用途决定,供应量为1且独特的代币是 NFT。供应量大于1且每个代币相同的则是 虚拟货币。
ERC4626 是 ERC20 标准的扩展,专门用于代币化金库(tokenized vaults)。在 ERC4626 中,用户可以存入基础资产(如 ETH、USDC),并收到对应的“shares”(股份)。这些股份代表了用户在金库中持有的份额,用户可以通过销毁这些股份来赎回他们的资产。ERC4626 的标准接口为各种不同的合约(如借贷市场、聚合器、和自带利息的代币)提供了统一的交互方式。
通胀攻击(Inflation Attack)是 ERC4626 金库面临的潜在安全问题之一。它利用了舍入误差和金库中股份与资产的兑换率变化,攻击者可以通过操纵资产与股份的比率,使其他用户的存款无法得到应有的股份,从而导致用户损失资产。
为防止通胀攻击,ERC4626 可以使用虚拟偏移(Virtual Offset)的策略。这种方法通过在计算股份与资产比率时引入虚拟的资产和股份来增加安全性。
防御机制的两部分: - 精度偏移:在表示股份和资产时,使用更高的精度来减少计算时的舍入误差。 - 虚拟资产和股份:在金库刚开始为空时,使用虚拟的资产和股份来维护转换率,避免因为攻击者操控比率造成的影响。 通过这种方式,攻击者即使进行了大量捐赠,仍然无法通过操纵比率来导致用户存款的损失。
这四种标准(ERC20、ERC721、ERC1155 和 ERC4626)各自适用于不同的应用场景,具体如下:
应用场景: - 虚拟货币:ERC20 是目前最常用的可替代代币标准,适用于表示像 ETH、USDT、DAI 这样的虚拟货币。这些代币可以互相替代,且每个单位都具有相同的价值。 - 去中心化金融(DeFi):ERC20 代币被广泛用于 DeFi 应用中,包括借贷平台、流动性挖矿、收益聚合器等。用户可以用 ERC20 代币进行交易、抵押、借贷和流动性提供。 - 支付系统:ERC20 代币可以作为支付手段,用于商品或服务的购买。这些代币是可替代的,因此非常适合支付场景。
典型应用: - 虚拟货币:USDT、DAI、UNI、LINK 等。 - DeFi 平台:Aave、Compound、Uniswap 等平台都使用 ERC20 代币进行流动性提供和交易。
应用场景: - 数字收藏品和艺术品:ERC721 标准用于表示每个代币都是独一无二的,如 CryptoKitties、数字艺术品(如 Beeple 的作品)等。每个代币都有其独特的属性和价值。 - 虚拟地产:在 Decentraland、The Sandbox 等元宇宙平台中,虚拟土地使用 ERC721 标准,因为每块土地都是独特的、不可替代的。 - 游戏道具:在区块链游戏中,ERC721 代币可以表示独特的游戏道具,如角色、装备等,这些物品不可互换。
典型应用: - NFT 项目:CryptoPunks、Bored Ape Yacht Club、Art Blocks 等。 - 元宇宙项目:Decentraland、The Sandbox 等。
应用场景: - 游戏道具和资产:ERC1155 允许同时管理可替代和不可替代的代币,非常适合区块链游戏。在游戏中,像金币(可替代的)和独特武器(不可替代的)可以通过一个合约来管理,极大节省了 Gas 费用。 - 批量交易和管理:ERC1155 支持批量转移多个代币,因此特别适合那些需要一次性处理多个代币交易的场景,如游戏内奖励发放或批量交易收藏品。 - 数字收藏品:ERC1155 可以用于管理同一系列中的多个收藏品,每个代币可以是独一无二的(NFT),也可以是相同的(如游戏代币)。
典型应用: - 区块链游戏:Gods Unchained、Enjin、Axie Infinity 等。 - 批量交易收藏品:支持批量发行和交易的数字收藏品平台。
应用场景: - 收益聚合器和金库:ERC4626 是专门为代币化金库设计的标准,它允许用户将资产存入金库并获得相应的股份,股份代表了金库中的资产份额。它适用于收益聚合器(如 Yearn Finance)等场景,用户可以存入资产赚取利息或其他收益。 - 借贷平台:ERC4626 可以用于借贷市场,允许用户存入抵押资产并获得股份,股份的价值会随金库中资产的变化而变化。 - 自动化收益策略:用户可以通过存入资产参与 DeFi 策略,金库会自动执行收益最大化的策略,并按比例分发给所有股份持有人。
典型应用: - Yearn Finance:用户存入 ERC20 代币,金库会自动将资金分配给各种收益策略并返还股份。 - 借贷市场:Compound、Aave 等平台可以基于 ERC4626 来管理用户的存款和利息分配。
标准 | 描述 | 应用场景 | 典型应用 |
---|---|---|---|
ERC20 | 可替代代币标准 | 虚拟货币、支付系统、DeFi 应用、流动性提供 | USDT、DAI、Uniswap、Aave |
ERC721 | 非同质化代币(NFT)标准 | 数字艺术品、收藏品、游戏道具、虚拟地产 | CryptoKitties、Decentraland |
ERC1155 | 多代币标准,可同时支持可替代和不可替代代币 | 游戏资产管理、批量转移、数字收藏品 | Gods Unchained、Enjin |
ERC4626 | 金库代币标准 | 收益聚合器、借贷市场、自动化收益策略 | Yearn Finance、借贷平台 |
通过这些标准,你可以根据不同的应用场景选择合适的代币标准进行开发。
在 OpenZeppelin 中,“governance”(治理)是指通过智能合约实现去中心化组织或项目的决策机制和管理流程。治理模型允许持有代币的用户(通常是项目的利益相关者)参与项目的管理和决策,比如升级合约、修改参数、分配资金等。OpenZeppelin 提供的治理工具是为了帮助开发者方便地实施去中心化治理机制,使项目能够在无需依赖单一中心化实体的情况下进行管理和演化。
在 OpenZeppelin 中,“governance”(治理)是指通过智能合约实现去中心化组织或项目的决策机制和管理流程。治理模型允许持有代币的用户(通常是项目的利益相关者)参与项目的管理和决策,比如升级合约、修改参数、分配资金等。OpenZeppelin 提供的治理工具是为了帮助开发者方便地实施去中心化治理机制,使项目能够在无需依赖单一中心化实体的情况下进行管理和演化。
OpenZeppelin 的治理模块通常用于去中心化自治组织(DAO),DeFi 项目,和其他依赖社区决策的区块链项目。以下是一些典型的使用场景: - 去中心化金融(DeFi)项目:比如在一个DeFi协议中,治理代币持有者可以通过提案决定协议的参数修改,流动性奖励分配,或者资金池的管理。 - 协议升级:当区块链协议需要升级时,治理模块允许社区投票决定是否接受新的合约部署。 - 资金管理:DAO项目中,社区成员可以通过提案和投票决定如何使用项目的资金,或者如何激励开发者。
OpenZeppelin 的 Governance 模块提供了一种可靠和灵活的机制,使项目能够通过去中心化的方式管理自身。它通过提案、投票、时间锁等方式,确保了治理过程的透明性和社区参与度。治理代币持有者可以对项目的未来进行表决,并通过智能合约直接执行这些决策,使治理过程公开、公平且高效。
这个治理模块非常适合任何希望实施去中心化管理的项目,尤其是DAO或去中心化金融(DeFi)协议。
这段代码主要介绍了 OpenZeppelin Contracts 提供的实用工具,这些工具可以帮助你在项目中简化区块链开发过程。以下是梳理后的中文解释:
1. 签名验证(Checking Signatures On-Chain): -
ECDSA 用于恢复和管理以太坊账户的ECDSA签名。签名通常通过
web3.eth.sign
生成,是一个包含65字节数组的签名,结构为
[v, r, s]
。 - 通过 ECDSA.recover
可以恢复签名者的地址并与目标地址进行对比,验证签名的合法性。签名验证时,需要使用
toEthSignedMessageHash
方法处理带有以太坊前缀的签名数据。
using ECDSA for bytes32;
using MessageHashUtils for bytes32;
function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
return data
.toEthSignedMessageHash()
.recover(signature) == account;
}
verify
:证明某个值属于Merkle树的一部分。multiProofVerify
:证明多个值属于Merkle树的一部分。supportsInterface
方法。using ERC165Checker for address;
myAddress._supportsInterface(bytes4)
myAddress._supportsAllInterfaces(bytes4[])
contract MyContract {
using ERC165Checker for address;
bytes4 private InterfaceId_ERC721 = 0x80ac58cd;
function transferERC721(
address token,
address to,
uint256 tokenId
) public {
require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN");
IERC721(token).transferFrom(address(this), to, tokenId);
}
}
Math
和 SignedMath
库,用于处理额外的数学运算,如求平均值。它还支持有符号整数的运算。contract MyContract {
using Math for uint256;
using SignedMath for int256;
function tryOperations(uint256 a, uint256 b) internal pure {
(bool overflowsAdd, uint256 resultAdd) = x.tryAdd(y);
(bool overflowsSub, uint256 resultSub) = x.trySub(y);
(bool overflowsMul, uint256 resultMul) = x.tryMul(y);
(bool overflowsDiv, uint256 resultDiv) = x.tryDiv(y);
}
function unsignedAverage(int256 a, int256 b) {
int256 avg = a.average(b);
}
}
OpenZeppelin 提供增强的数据结构: - BitMaps:用于存储布尔值。 - Checkpoints:记录并查找检查点的值。 - DoubleEndedQueue:支持队列操作的双端队列。 - EnumerableSet/EnumerableMap:支持枚举操作的集合和映射,可以轻松查询链上和链下的存储内容。
Base64: - 将 bytes32
数据转为 Base64
字符串,适用于ERC721或ERC1155中生成URL安全的 tokenURI
。
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
contract My721Token is ERC721 {
using Strings for uint256;
constructor() ERC721("My721Token", "MTK") {}
function tokenURI(uint256 tokenId) public pure override returns (string memory) {
bytes memory dataURI = abi.encodePacked(
'{',
'"name": "My721Token #', tokenId.toString(), '"',
'}'
);
return string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(dataURI)
)
);
}
}
Multicall
合约允许将多个调用打包成一个外部调用,方便外部账户在一次交易中进行多个操作。import "@openzeppelin/contracts/utils/Multicall.sol";
contract Box is Multicall {
function foo() public {}
function bar() public {}
}
使用 multicall
函数进行批量调用的示例:
OpenZeppelin 提供了大量的实用工具和库,包括加密、数学操作、接口检测、数据结构以及多重调用等。这些工具帮助开发者快速、安全地开发区块链应用,减少代码冗余并提升效率。
OpenZeppelin Subgraphs 是一种帮助你索引和查询区块链数据的工具,主要用于自动记录和分析区块链上的合约活动。它的作用是在区块链上发生的事件(如交易、智能合约调用等)被触发时,通过子图(subgraph)记录这些活动,并将数据存储到数据库中,以便后续可以通过 GraphQL 查询这些数据。
简化理解一下: 1. 区块链事件监听:当区块链上某个合约(比如 ERC20 代币合约)发生了事件,比如代币转移或合约调用,Subgraph 会监听到这些事件。
数据存储和查询:Subgraph 把这些事件的数据索引到数据库中,并且你可以用类似于查询数据库的方式(使用 GraphQL)来获取这些数据。
自动化索引:OpenZeppelin Subgraphs 提供现成的模板和工具,简化了你去编写和手动处理这些索引逻辑的工作。你只需要配置好合约地址、事件等,子图会自动帮你处理数据的采集和存储。
区块链项目监控:如果你开发了一个基于区块链的项目(比如发行代币、部署智能合约等),你可能想实时了解项目上的合约运行情况,比如查询所有的代币转移记录、智能合约执行情况等,Subgraph 可以自动帮你把这些活动整理成数据,并提供接口让你轻松查询。
去中心化应用(DApp)数据查询:你可以使用 Subgraph 记录和查询你 DApp 的用户行为、代币余额变化等操作,方便在前端展示信息。
简而言之,Subgraph 就是一个自动化工具,帮助你记录和查询区块链合约活动的数据,让你能够轻松访问和分析这些数据,而不必自己编写复杂的监听和处理代码。