代码中学习NFT智能合约

代码中学习NFT智能合约

NFT 或不可替代的代币是 DeFi 之后的下一个趋势。就炒作程度和参与人数而言,NFT 远远领先于之前在给定文章中介绍的竞争对手。

在谷歌请求的数量方面,NFT 一词已经赶上了 2017 年“ICO”一词的指标。从 2021 年 2 月 4 日到 3 月 4 日,非同质代币在 12 个最大平台上的交易量达到创纪录的 4.8 亿美元。除了加密货币行业的代表外,许多企业家、政治家和创意精英都对这一趋势表示了兴趣。百事可乐在2021年12月投放了其创世的NFT集合 Pepsi Mic Drop,截止到今天,该项目的交易量为 2.8k项目地址。本文来仔细学习 Pepsi NFT 合约,了解是如何实施这个项目的。

NFT集合 Pepsi Mic Drop 主页

基础知识

如果了解 ERC-721 并有编码经验,则可以跳过本节。

  • Solidity:到目前为止,大多数 NFT 合约都使用 Solidity,无需了解 Solidity 即可理解该概念。但是如果以后想自己实现一个 NFT 合约,一些编码经验会很有帮助。要学习 Solidity,强烈建议阅读官方文档。

  • ERC-721:Pepsi NFT 建立在 Open Zeppelin 实施的 ERC-721 智能合约之上。简单来说,ERC-721 标准提供了 NFT 的基本功能,包括转账、查询账户余额、铸造等。

智能合约在哪里?

区块链中许多项目都是开源的,开源链、开源合约等,开源透明是区块链行业的主要特征之一。

百事可乐在官网公布了其 NFT 合约地址。可以简单地复制该地址并在 Etherscan 中找到,这是查看以太坊区块链信息的首选网站,点击查看 Pepsi Mic Drop

点击查看 Pepsi Mic Drop

通过单击按钮 Contract ,将看到 14 个文件。不过只需要查看第一个文件 PepsiMicDrop.sol。文件包含 Pepsi NFT 合约,其余的是Open Zeppelin 的 ERC-721 合约。

智能合约中的 NFT

在深入学习代码之前,先了解智能合约是如何定义 NFT 的。在底层,NFT 有两个主要信息:IdURI

ERC-721 标准下,每个 NFT 都是独一无二的。因此,需要合约为每个 NFT 分配一个唯一的 Id,从而实现唯一性。在基于 ERC-721 的合约中,不可能存在两个相同 ID 的不同 NFT。

接下来是图像在哪里(也可以是视频、文档等)?这就是 URI 发挥作用的地方。URI 代表通用资源标识符,更加详细的介绍参阅《图解URL、URI和URN 区别》。将其视为一个 URL,通过它可以获取与 NFT 相关的所有元数据。元数据可以是图像 URL、描述、属性等。当前最流行的 NFT 市场 OpenSea 有其元数据标准,在这里可以找到完整的元数据列表。

URI 和 Id 能保证 NFT 的唯一性吗?

理想情况下,基于 ERC-721 的 NFT 将拥有其独特的图像或元数据,这就是它们不可替代的原因。但是,理论上是可以将相同的 URI 分配给不同的 Id,从而创建两个从表面上看相同的 NFT。然而,这不是 ERC-721 的希望的,有另一个标准 ERC-1155,它支持半可替代 NFT,ERC-1155 文档

为什么元数据没有存储在合约中?

因为在区块链上存储数据非常昂贵,尤其是图像或视频。但是,仍然可以在链上存储元数据并支付高昂的 gas 费。一些项目使用 SVG 格式的图像,这将大大减少了数据大小,从而降低了 gas 费用。

智能合约结构

智能合约初看可能觉得很复杂,但它的结构设计的很好,下图为智能合约的结构,来看看每个组件。

智能合约的结构,来看看每个组件

  • SPDX许可证表达式(SPDX License Identifier):SPDX许可证信息,告诉其他人可以如何使用此代码。 (第 48 行)
  • 智能合约版本:让编译器正确编译代码,然后 EVM 才能理解。 (第 49 行)
  • 导入 ERC-721 合约:百事NFT合约将使用ERC-721合约作为其蓝图。
  • PepsiMicDrop:将在下一节进一步讨论的 NFT 合约,它包含三个部分:(1)状态变量(2)构造函数(3)函数。(第56行)
  • 状态变量:是其值永久存储在合约存储中的变量。
  • 构造函数:是一个特殊函数,仅在合约创建时执行,可以运行合约初始化代码。
  • 函数:大多数函数用于设置或获取状态值。

PepsiMicDrop 合约

现在来看看 PepsiMicDrop 合约,通过它的功能来解释合约,每个功能都与一些函数和状态变量相关。

构造函数

constructor() ERC721("Pepsi Mic Drop", "PEPSIMICDROP") {
    reserveMicDropsId = 1; // item 1-50
    micDropsId = 51; // item 51-1893
}

构造函数定义了 2 个输入:

  • Pepsi Mic Drop 是 NFT 代币名称
  • PEPSIMICDROP 是 token symbol

分配发生在 ERC721 合约代码中,该合约在第 51 行导入。这就是为什么找不到与此构造函数主体相关的代码逻辑的原因,这是继承来完成的不需要重做

在构造函数内部,可以看到 2 个状态变量 reserveMicDropsIdmicDropsId 获得了新值。为什么需要它们?在这次 NFT 中,百事将前 50 个 NFT 保留给自己,因此公开可用的 NFT 从 id 51开始。

NFT 不是图像吗?为什么在这里使用数字来表示 NFT?如果有这样的问题就需要研究什么是 NFT。

铸造

现在来看看铸造函数:

function mint(bytes32[] memory proof, bytes32 leaf) public returns (uint256) {
    // merkle tree
    if (merkleEnabled) {
        require(keccak256(abi.encodePacked(msg.sender)) == leaf, "This leaf does not belong to the sender");
        require(proof.verify(merkleRoot, leaf), "You are not in the list");
    }

    require(saleStarted == true, "The sale is paused");
    require(msg.sender != address(0x0), "Public address is not correct");
    require(alreadyMinted[msg.sender] == false, "Address already used");
    require(micDropsId <= maxMint, "Mint limit reached");

    _safeMint(msg.sender, micDropsId++);

    alreadyMinted[msg.sender] = true;

    return micDropsId;
}
第一部分:Merkle Proof

mint 函数有两个输入,证明 proofleaf ,它们用于第 3 行到第 7 行的 Merkle Proof。本质上,它检查用户是否有资格铸造 NFT,有一个概念叫白名单,通常白名单账号,才能在以后铸造 NFT。

第二部分:Prerequisites

第 8 到 11 行,定义了 mint 的 4 个先决条件,下面来逐一解释:

require(saleStarted == true, "The sale is paused");  // 第 8 行
require(msg.sender != address(0x0), "Public address is not correct");  // 第 9 行
require(alreadyMinted[msg.sender] == false, "Address already used"); // 第 10 行
require(micDropsId <= maxMint, "Mint limit reached");  // 第 11 行
  1. 在 NFT 开始销售之前,状态变量 saleStarted 的值为 false,所以它不会执行第 8 行。当销售开始时,合约所有者可以调用函数startSale() 来更改 saleStarted 的值。
  2. 第 9 行:检查确保用户地址不是 0x00x0 是以太坊创世地址,没有用户会使用它。
  3. 第 10 行:在 Pepsi Drop 中,每个地址只能铸造一个 NFT。 alreadyMinted 是 Map 类型的状态变量,跟踪所有铸造 NFT 的地址。
  4. 第 11 行:铸造数量有限,最多 1983 个 NFT,检查是否所有 NFT 都已被认领, micDropsId 是上面讨论的唯一令牌 ID。
第三部分:真正的铸造

这是铸造实际发生的地方,实际编码不需要自己实现它,因为 ERC-721 已经做好了。

_safeMint(msg.sender, micDropsId++);

为了铸造,使用用户的钱包地址和唯一的代币 ID 调用函数 _safemint(),在底层,有一个状态变量(如字典)来跟踪每个令牌的所有权。通过调用 _safemint() 函数来更新这个状态变量,分配或更改相应令牌的所有权。状态变量 micDropId 表示令牌 ID,并且在每次铸造后立即递增 1

第四部分:更新铸造地址

如第二部分所述,每个地址只能铸造一个 NFT,下面这行代码确保了成功铸造 NFT 的地址都会被记录下来。

alreadyMinted[msg.sender] = true;
返回

铸造成功后,返回 token id

更新URI

现在讨论的最后一部分是更新 URI 的函数。

function setBaseURI(string memory _baseUri) public onlyOwner {
    baseURI = _baseUri;
}

每个 NFT 都有其 URI,这是用于更改基本 URI 的函数。基础 URI 是每个 NFT 的 URI 之间的组成部分。默认情况下,URI 是 baseURI/tokenId,之所以设置 baseURI 是为了节省 gas 费用。

可以通过覆盖 ERC-721 合约中定义的函数 tokenURI 来更改 baseURItokenId 的组合方式。例如,如果 URI 是这样格式化的,可以执行 baseURI/tokenId + '.json' 之类的操作。

那么什么是 publiconlyOwner,这些是函数修饰符,它们定义了函数运行的条件。public 意味着公开的,所有人都可以调用。部署后,通过网站(更具体地说,JavaScript)与合约进行交互,交互方法或者变量就是使用 public 来定义。onlyOwner 表示只有合约所有者才能调用该函数,只希望合约所有者具有更改令牌 URI 的能力。

总结

上面对百事可乐 NFT 智能合约进行解读,其实大部分的 NFT 智能合约的结构都差不多。