Web3小白必知:ERC20、721、1155与OpenZeppelin深度揭秘_erc721 vs 1155
一、同质化代币之王:ERC20
(一)ERC20 是什么
ERC20,全称为 Ethereum Request for Comments 20,是以太坊上的一种代币标准,于 2015 年由以太坊开发者提出并制定 。它就像是一个通用的 “代币模板”,定义了一系列规则和接口,使得开发者可以基于以太坊区块链创建同质化代币。所谓同质化代币,就如同现实中的货币,每一个代币之间没有任何区别,它们具有相同的价值和属性,可进行互换。例如,你钱包里的 1 个 ETH 和其他人钱包里的 1 个 ETH,在价值和功能上完全一样 。
ERC20 标准的诞生,为以太坊生态系统带来了极大的便利性和互操作性。在 ERC20 之前,每个项目创建的代币都有自己独特的规则和接口,这使得不同代币之间的交互变得异常复杂。而 ERC20 的出现,统一了代币的标准,使得任何遵循 ERC20 标准的代币都能在以太坊的生态系统中被广泛支持和应用。无论是钱包、交易所还是其他去中心化应用(DApps),只要支持 ERC20 标准,就能轻松处理各种 ERC20 代币。这极大地促进了以太坊上的代币发行、交易和应用开发,为整个区块链行业的发展奠定了坚实的基础。如今,在以太坊上发行的绝大多数代币都遵循 ERC20 标准,像我们熟知的 USDT(泰达币)、USDC(美元币)等稳定币,以及许多项目的原生代币,如 UNI(Uniswap 的治理代币)、AAVE(Aave 借贷协议的代币)等 ,都是 ERC20 代币的典型代表。它们在以太坊生态中广泛流通,支撑着各种金融和非金融应用场景,成为了 Web3 经济体系中不可或缺的一部分。
(二)ERC20 如何使用
1. 创建 ERC20 代币
在以太坊上创建 ERC20 代币,通常需要编写智能合约。智能合约是一段自动执行的代码,部署在以太坊区块链上,用于定义代币的各种行为和规则。以 Solidity 语言为例,一个基本的 ERC20 代币智能合约包含以下关键部分:
pragma solidity ^0.8.0;contract MyToken { // 代币名称 string public name = \"My Token\"; // 代币符号 string public symbol = \"MTK\"; // 小数位数 uint8 public decimals = 18; // 总供应量 uint256 public totalSupply; // 余额映射,记录每个地址的代币余额 mapping(address => uint256) public balanceOf; // 授权映射,记录每个地址对其他地址的授权额度 mapping(address => mapping(address => uint256)) public allowance; // 构造函数,在合约部署时执行 constructor(uint256 initialSupply) { totalSupply = initialSupply * 10 ** uint256(decimals); balanceOf[msg.sender] = totalSupply; } // 转账函数 function transfer(address to, uint256 value) public returns (bool success) { require(balanceOf[msg.sender] >= value, \"Insufficient balance\"); balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); return true; } // 授权函数 function approve(address spender, uint256 value) public returns (bool success) { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } // 授权转账函数 function transferFrom(address from, address to, uint256 value) public returns (bool success) { require(balanceOf[from] >= value, \"Insufficient balance\"); require(allowance[from][msg.sender] >= value, \"Insufficient allowance\"); balanceOf[from] -= value; balanceOf[to] += value; allowance[from][msg.sender] -= value; emit Transfer(from, to, value); return true; } // 转账事件 event Transfer(address indexed from, address indexed to, uint256 value); // 授权事件 event Approval(address indexed owner, address indexed spender, uint256 value);}
在这个合约中,name
、symbol
、decimals
和totalSupply
分别定义了代币的名称、符号、小数位数和总供应量。balanceOf
和allowance
是两个映射,分别用于记录每个地址的代币余额和授权额度。constructor
构造函数在合约部署时被调用,用于初始化总供应量并将所有代币分配给合约部署者。transfer
函数用于将代币从一个地址转移到另一个地址,approve
函数用于授权其他地址使用自己的代币,transferFrom
函数则用于在授权的情况下,从指定地址转移代币到另一个地址。Transfer
和Approval
事件用于记录转账和授权操作,以便外部应用程序可以监听这些事件。
2. 转账操作
在以太坊钱包(如 imToken)中进行 ERC20 代币转账非常简单。首先,打开 imToken 钱包,确保已经添加了要转账的 ERC20 代币。如果没有添加,可以在钱包中点击 “添加资产”,然后在搜索框中输入代币的合约地址,即可添加该代币。添加成功后,在钱包主界面中找到要转账的代币,点击进入代币详情页面,然后点击 “转账” 按钮。在弹出的转账页面中,填写接收方的以太坊地址、转账数量和矿工费(Gas Fee)等信息。矿工费是支付给以太坊网络矿工的费用,用于激励矿工打包交易。设置好这些信息后,点击 “确认转账” 按钮,输入钱包密码,即可完成转账操作。转账完成后,可以在钱包的交易记录中查看转账状态和交易详情。如果转账成功,接收方的钱包中将会收到相应数量的代币;如果转账失败,钱包会提示失败原因,用户可以根据提示进行相应的处理 。
在智能合约中进行 ERC20 代币转账,通常需要调用transfer
或transferFrom
函数。例如,假设我们有一个智能合约MyContract
,要从该合约的地址向另一个地址recipient
转账 100 个代币(假设代币合约地址为tokenAddress
),代码如下:
pragma solidity ^0.8.0;interface IERC20 { function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool);}contract MyContract { function transferTokens(address tokenAddress, address recipient, uint256 amount) public { IERC20 token = IERC20(tokenAddress); require(token.transfer(recipient, amount), \"Transfer failed\"); }}
在这段代码中,首先定义了一个IERC20
接口,用于与 ERC20 代币合约进行交互。然后在MyContract
合约中,定义了一个transferTokens
函数,该函数接收代币合约地址、接收方地址和转账数量作为参数。在函数内部,通过IERC20
接口调用transfer
函数,实现代币转账操作。如果转账成功,transfer
函数会返回true
;如果转账失败,会返回false
,并通过require
语句抛出错误。
3. 余额查询
在 imToken 钱包中查询 ERC20 代币余额,只需打开钱包,在主界面中找到要查询的代币,即可看到该代币的余额。钱包会实时同步区块链上的最新数据,确保余额信息的准确性。
在智能合约中查询 ERC20 代币余额,同样需要使用IERC20
接口。例如,要查询某个地址owner
在代币合约tokenAddress
中的余额,代码如下:
pragma solidity ^0.8.0;interface IERC20 { function balanceOf(address account) external view returns (uint256);}contract BalanceChecker { function getBalance(address tokenAddress, address owner) public view returns (uint256) { IERC20 token = IERC20(tokenAddress); return token.balanceOf(owner); }}
在这段代码中,BalanceChecker
合约定义了一个getBalance
函数,该函数接收代币合约地址和要查询余额的地址作为参数。通过IERC20
接口调用balanceOf
函数,返回指定地址的代币余额。view
关键字表示该函数不会修改区块链状态,只是读取数据,因此不需要消耗 Gas。
二、独一无二的数字资产:ERC721
(一)ERC721 是什么
ERC721 是以太坊上的非同质化代币(Non - Fungible Token,NFT)标准 ,它与 ERC20 有着本质的区别。如果说 ERC20 代币如同普通的货币,彼此之间毫无差异且可互换,那么 ERC721 代币就像是一件件独特的艺术品,每一个都独一无二,拥有自己独特的身份标识(Token ID),不可分割也不可互换 。例如,世界上只有一个蒙娜丽莎的画作,它是独一无二且不可替代的,ERC721 代币就类似于这样的存在。在 NFT 领域,ERC721 标准占据着举足轻重的地位。它使得数字资产的所有权能够被精准地定义和转移,为数字艺术品、虚拟房地产、游戏道具等地方带来了全新的发展机遇 。像著名的无聊猿游艇俱乐部(Bored Ape Yacht Club)、CryptoPunks 等 NFT 项目,都是基于 ERC721 标准创建的。这些项目中的每一个 NFT 都具有独特的属性和价值,吸引了众多收藏家和投资者的关注。以无聊猿游艇俱乐部为例,每个无聊猿 NFT 都有不同的外貌特征,包括发型、表情、服装等,这些独特的属性使得每个无聊猿在市场上都有不同的价格和价值 。
(二)ERC721 如何使用
1. 在 NFT 市场的操作
以 OpenSea 为例,这是全球最大的 NFT 市场之一,在这个平台上,ERC721 代币的相关操作十分便捷 。
铸造:创作者想要铸造一个 ERC721 代币的 NFT,首先需要在 OpenSea 上连接自己的以太坊钱包(如 MetaMask)。连接成功后,点击 “创建” 按钮,进入创建页面。在这个页面中,创作者需要上传 NFT 的相关文件,如图像、视频、音频等,同时填写 NFT 的名称、描述、属性等信息。这些信息将作为 NFT 的元数据,存储在区块链上,用于描述 NFT 的独特特征。例如,一个数字艺术家要铸造一幅数字画作的 NFT,他上传画作文件后,填写名称为 “梦幻星空”,描述为 “一幅充满奇幻色彩的星空画作,使用了独特的绘画技巧和色彩搭配”,属性可以包括画作的尺寸、创作时间、绘画风格等。填写完成后,点击 “创建” 按钮,支付一定的 Gas 费用,即可完成 NFT 的铸造 。
交易:对于买家和卖家来说,在 OpenSea 上交易 ERC721 代币的 NFT 也非常简单。卖家可以在自己的账户中找到要出售的 NFT,点击 “出售” 按钮,设置出售价格、出售方式(固定价格出售或拍卖)等信息,然后确认出售。买家则可以在 OpenSea 的市场页面中浏览各种 NFT,找到自己心仪的 NFT 后,点击进入 NFT 详情页面,点击 “购买” 按钮,确认支付金额和支付方式(使用以太坊或其他支持的代币支付),完成支付后,即可获得该 NFT 的所有权 。
所有权转移:当 NFT 的所有权发生转移时,无论是通过交易、赠送还是其他方式,区块链上都会记录下详细的转移信息,包括转移的时间、转移的双方地址以及 NFT 的 Token ID 等。例如,A 将自己拥有的一个无聊猿 NFT 赠送给 B,在 OpenSea 上操作完成赠送后,以太坊区块链上会生成一条新的交易记录,记录显示该 NFT 从 A 的地址转移到了 B 的地址,并且记录了转移的时间戳和 NFT 的唯一标识 Token ID 。
2. 智能合约实现
在智能合约层面,实现 ERC721 代币也有一套相对固定的模式。同样以 Solidity 语言为例,一个基本的 ERC721 智能合约示例如下:
pragma solidity ^0.8.0;import \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";import \"@openzeppelin/contracts/utils/Counters.sol\";contract MyNFT is ERC721 { using Counters for Counters.Counter; Counters.Counter private _tokenIdCounter; constructor() ERC721(\"MyNFT\", \"MNFT\") {} function mintNFT(address recipient) public returns (uint256) { uint256 newTokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _mint(recipient, newTokenId); return newTokenId; }}
在这个合约中,首先导入了 OpenZeppelin 提供的 ERC721 合约库和计数器工具库Counters
。MyNFT
合约继承自ERC721
,并在构造函数中初始化了 NFT 的名称和符号。_tokenIdCounter
是一个计数器,用于生成唯一的 Token ID。mintNFT
函数用于铸造新的 NFT,它首先获取当前的 Token ID,然后将计数器递增,最后使用_mint
函数将新的 NFT 铸造并分配给指定的接收者recipient
。
在实际应用中,还可以根据需求对合约进行扩展和优化。例如,添加元数据管理功能,使得每个 NFT 可以关联更丰富的信息;添加权限控制功能,限制只有特定的用户或角色才能进行铸造、转移等操作;添加事件监听功能,以便外部应用程序能够实时获取 NFT 的状态变化等 。
三、全能选手:ERC1155
(一)ERC1155 是什么
ERC1155 是以太坊征求意见提案 1155(Ethereum Request for Comments 1155)的缩写 ,它是一种极具创新性的代币标准,诞生于 2018 年 6 月 17 日,由 Enjin 团队提出并被以太坊基金会正式采纳 。ERC1155 的出现,打破了传统代币标准的界限,它允许在单个智能合约中创建和管理多种类型的代币,既可以是同质化代币,也可以是非同质化代币,甚至还能支持半同质化代币 。
在 ERC1155 之前,ERC20 和 ERC721 分别统治着同质化和非同质化代币领域,各自为营。但在实际应用中,很多场景需要同时处理多种类型的代币,比如在区块链游戏中,玩家既拥有可互换的游戏币(同质化代币),又拥有独一无二的稀有装备(非同质化代币)。如果使用传统的标准,就需要为每种代币创建单独的智能合约,这不仅繁琐,而且成本高昂 。
ERC1155 则巧妙地解决了这个问题。它通过唯一的 uint256 代币 ID 来区分不同的代币类型,为每个代币定义独特的属性,如总供应量、URI、名称、符号等 。例如,在一个区块链游戏中,代币 ID 为 1 的可能代表 100 个同质化的游戏金币,而代币 ID 为 2 的则可能代表一把独一无二的传奇宝剑(非同质化代币)。这种设计使得开发者可以在一个合约中轻松管理多种资产,大大提高了开发效率和灵活性 。
另外,ERC1155 还引入了批量操作的概念,允许在单笔交易中同时转移多种代币,大大提高了交易效率,降低了交易成本 。例如,玩家可以在一次操作中,将自己拥有的游戏金币、药水、武器等多种物品一次性转移给其他玩家,而无需像以前那样进行多次单独的交易 。
(二)ERC1155 如何使用
1. 区块链游戏场景
在区块链游戏《MyCryptoGame》中,ERC1155 代币的使用贯穿了游戏的各个环节 。玩家进入游戏后,首先会获得一定数量的游戏金币,这些金币就是 ERC1155 代币中的同质化代币。玩家可以使用这些金币在游戏商店中购买各种道具,如药水、装备等。而这些药水和装备,则可能是具有独特属性的非同质化代币 。
当玩家想要与其他玩家交易时,ERC1155 的批量转移功能就发挥了重要作用。比如,玩家 A 想要将自己的 50 个金币、2 瓶治疗药水和 1 把稀有宝剑出售给玩家 B,在 ERC1155 的支持下,玩家 A 可以通过一次交易操作,将这些不同类型的代币同时转移给玩家 B,玩家 B 则支付相应的费用 。
在游戏中的合成系统中,也大量运用了 ERC1155 代币。例如,玩家可以将 3 个低级的魔法宝石(非同质化代币)合成为 1 个高级的魔法宝石,合成过程中,智能合约会根据 ERC1155 的规则,对相关代币进行销毁和铸造操作 。
2. 智能合约实现
同样以 Solidity 语言为例,以下是一个简单的 ERC1155 智能合约示例:
pragma solidity ^0.8.0;import \"@openzeppelin/contracts/token/ERC1155/ERC1155.sol\";contract MyERC1155 is ERC1155 { constructor() ERC1155(\"https://example.com/tokens/{id}.json\") {} function mint(address to, uint256 id, uint256 amount) public { _mint(to, id, amount, \"\"); } function burn(address from, uint256 id, uint256 amount) public { _burn(from, id, amount); }}
在这个合约中,首先导入了 OpenZeppelin 提供的 ERC1155 合约库。MyERC1155
合约继承自ERC1155
,并在构造函数中初始化了代币的元数据 URI 模板,这里假设每个代币的元数据存储在https://example.com/tokens/{id}.json
地址,其中{id}
会被实际的代币 ID 替换 。
mint
函数用于铸造新的代币,它接收目标地址to
、代币 ID id
和铸造数量amount
作为参数,通过调用_mint
函数来完成铸造操作 。burn
函数则用于销毁代币,接收源地址from
、代币 ID id
和销毁数量amount
作为参数,调用_burn
函数实现代币销毁 。
实际应用中,还可以根据需求扩展合约功能,如添加权限控制,只有游戏管理员才能铸造特定的稀有道具;添加事件监听,记录重要的代币操作,如玩家获得稀有装备时触发通知等 。
四、智能合约的 “瑞士军刀”:OpenZeppelin
(一)OpenZeppelin 是什么
在 Web3 开发的浩瀚宇宙中,OpenZeppelin 无疑是一颗璀璨的明星,被誉为智能合约开发领域的 “瑞士军刀” 。它是一个开源的智能合约开发框架,犹如一座宝藏库,为开发者提供了一系列经过严格审计、高度可复用且安全可靠的智能合约代码库和工具 。
OpenZeppelin 的诞生,源于对智能合约安全性和开发效率的深刻洞察。在区块链技术发展的早期,智能合约的编写往往面临着诸多挑战,如安全漏洞频发、代码重复率高、开发周期长等 。OpenZeppelin 的出现,彻底改变了这一局面。它通过提供标准化的合约模板和功能模块,让开发者无需从头开始编写复杂的智能合约代码,大大节省了开发时间和精力 。同时,OpenZeppelin 的代码都经过了专业的安全审计和社区的严格审查,遵循了最佳的安全实践,能够有效避免常见的安全漏洞,如重入攻击、整数溢出和下溢等,为智能合约的安全性提供了坚实的保障 。
如今,OpenZeppelin 已广泛应用于各种区块链项目中,涵盖了去中心化金融(DeFi)、非同质化代币(NFT)、去中心化自治组织(DAO)等多个热门领域 。无论是初创的区块链项目,还是成熟的大型企业级应用,OpenZeppelin 都能为其提供强大的技术支持,助力项目快速、安全地落地 。
(二)OpenZeppelin 如何使用
1. 安装与引入
以使用 Hardhat 开发环境为例,首先确保你的项目中已经安装了 Node.js 和 Hardhat 。打开终端,进入项目目录,运行以下命令安装 OpenZeppelin Contracts 库:
npm install @openzeppelin/contracts
安装完成后,在 Solidity 智能合约中引入 OpenZeppelin 的合约就变得非常简单 。例如,如果你要创建一个 ERC20 代币合约,可以这样引入:
pragma solidity ^0.8.0;import \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";contract MyToken is ERC20 { constructor(uint256 initialSupply) ERC20(\"MyToken\", \"MTK\") { _mint(msg.sender, initialSupply * 10 ** decimals()); }}
在这段代码中,通过import \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";
语句引入了 OpenZeppelin 提供的 ERC20 合约,然后MyToken
合约继承自ERC20
,并在构造函数中初始化了代币的名称、符号和初始供应量 。
2. 基于 OpenZeppelin 实现 ERC20、721、1155 合约
- ERC20 合约:除了上述基本的 ERC20 合约创建示例,OpenZeppelin 还提供了许多扩展功能,如可暂停的 ERC20 合约
PausableERC20
、带有授权功能的ERC20Permit
等 。以PausableERC20
为例,使用时首先引入相关合约:
pragma solidity ^0.8.0;import \"@openzeppelin/contracts/token/ERC20/extensions/PausableERC20.sol\";contract PausableMyToken is PausableERC20 { constructor(uint256 initialSupply) PausableERC20(\"PausableMyToken\", \"PMTK\") { _mint(msg.sender, initialSupply * 10 ** decimals()); } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); }}
在这个合约中,PausableMyToken
继承自PausableERC20
,拥有了可暂停和恢复转账功能 。pause
函数和unpause
函数分别用于暂停和恢复代币的转账操作,并且通过onlyOwner
修饰器限制只有合约所有者才能调用这两个函数 。
- ERC721 合约:创建一个基于 OpenZeppelin 的 ERC721 合约示例如下:
pragma solidity ^0.8.0;import \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";import \"@openzeppelin/contracts/utils/Counters.sol\";contract MyNFT is ERC721 { using Counters for Counters.Counter; Counters.Counter private _tokenIdCounter; constructor() ERC721(\"MyNFT\", \"MNFT\") {} function mintNFT(address recipient) public returns (uint256) { uint256 newTokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _mint(recipient, newTokenId); return newTokenId; }}
这里使用了 OpenZeppelin 的ERC721
合约和Counters
工具库 。Counters
用于生成唯一的 Token ID,mintNFT
函数用于铸造新的 NFT 并分配给指定的接收者 。
- ERC1155 合约:实现一个 ERC1155 合约示例:
pragma solidity ^0.8.0;import \"@openzeppelin/contracts/token/ERC1155/ERC1155.sol\";contract MyERC1155 is ERC1155 { constructor() ERC1155(\"https://example.com/tokens/{id}.json\") {} function mint(address to, uint256 id, uint256 amount) public { _mint(to, id, amount, \"\"); } function burn(address from, uint256 id, uint256 amount) public { _burn(from, id, amount); }}
在这个 ERC1155 合约中,继承自 OpenZeppelin 的ERC1155
合约,并在构造函数中初始化了元数据 URI 模板 。mint
函数用于铸造代币,burn
函数用于销毁代币 。
注意事项
在使用 OpenZeppelin 时,有一些重要的注意事项 。首先,要始终保持 OpenZeppelin 库的版本更新,以获取最新的安全修复和功能改进 。不同版本的 OpenZeppelin 可能存在一些 API 的变化和兼容性问题,所以在升级版本时,需要仔细阅读官方文档,确保项目代码能够顺利迁移 。
其次,虽然 OpenZeppelin 提供了安全的合约模板,但在实际应用中,仍然需要根据项目的具体需求进行合理的定制和扩展 。在扩展合约功能时,要注意遵循 OpenZeppelin 的设计模式和最佳实践,避免引入新的安全漏洞 。
另外,对于一些涉及资金操作的敏感功能,如代币的铸造、销毁和转账等,要进行严格的权限控制和安全检查 。例如,在上述的PausableMyToken
合约中,通过onlyOwner
修饰器限制了暂停和恢复转账功能的调用权限,确保只有合约所有者才能执行这些操作 。同时,在进行数学运算时,要注意使用 OpenZeppelin 提供的安全数学库,如SafeMath
,以防止整数溢出和下溢等问题 。