分析百事可乐NFT的智能合约

974 阅读9分钟

NFT初学者教程

百事可乐的NFT系列

百事可乐在2021年12月投放了其创世的NFT集合(来源),直到今天,这个项目的交易量为2.3k ETH(560万美元)。我仔细研究了百事可乐的NFT合约,以了解他们是如何实施这个项目的。在本教程中,我将逐条解释该智能合约。

谁是目标受众?

如果你是NFT的新手,想学习如何自己开发一个NFT智能合约,这篇文章就适合你。如果你好奇NFT背后的技术是如何运作的,这篇文章也会有帮助。

前提条件

如果你了解ERC-721并且有编码经验,那么你可以跳过这一部分。

  • Solidity。到目前为止,大多数NFT合约都使用Solidity。你不需要知道Solidity来理解这个概念。但是如果你想在之后自己实现一个NFT合同,一些编码经验将是非常有帮助的。要学习Solidity,我强烈建议阅读官方文件
  • ERC-721:百事通NFT是建立在Open Zeppelin实现的ERC-721智能合约上。简而言之,ERC-721标准提供了NFT的基本功能,包括转账、检查账户余额、造币等。这篇文章给出了一个很好的概述。ERC-721不可伪造代币标准

免责声明

像很多人一样,我是这个行业的新手。我试图仔细检查内容的正确性,但可能会出现错误。如果你发现任何错误,请让我知道,我将非常感谢你的帮助和更新本教程。

智能合约在哪里?

许多项目会将他们的合约开源,人们可以在做出任何(投资)决定之前得到教育。透明度也是区块链行业中我最喜欢的功能之一。

百事可乐在官网上公布了它的NFT合约地址。你可以简单地复制该地址并在Etherscan中搜索,Etherscan是查询以太坊区块链信息的首选之地。然后你会看到以下页面。

图1 Etherscan合约页面的屏幕截图

点击Contract 按钮,你会看到14个文件。然而,我们只需要第一个文件,PepsiMicDrop.sol 。这个文件包含了Pepsi NFT合约。其余的是Open Zeppelin的ERC-721合约,我们将不涉及。

为了你的方便,我把代码放在GitHub上,你可以在这里找到它。我建议你把代码和这篇文章并排打开。

合同中的NFT

在深入研究代码之前,首先让我们了解一下智能合约是如何定义NFT的。在引擎盖下,一个NFT有两部分信息。(1) Id (2) URI

根据ERC-721标准,每个NFT都是唯一的。如何做到的?合同为每个NFT分配了一个唯一的Id,从而实现了唯一性。在一个基于ERC-721的合同中,你不会发现两个不同的NFT有相同的Id。

那么,图像(也可以是视频、文件等)在哪里?这就是URI发挥作用的地方。URI是通用资源标识符的意思。把它想象成一个URL,通过它你可以得到与NFT相关的所有元数据。元数据可以是图像URL、描述、属性等。目前最流行的NFT市场OpenSea有其元数据标准。你可以找到一个全面的元数据列表。

现在,你可能会问:URI和Id能确保NFT的唯一性吗?

理想情况下,基于ERC-721的NFT将有其独特的图像或元数据,这就是为什么它们是不可伪造的。然而,你可以将相同的URI分配给不同的Id,从而创造出两个从表面上看起来相同的NFT。然而,这不是ERC-721的目的。有另一个标准,ERC-1155,它支持半风化的NFT。你可以在这里了解更多。

此外,你可能想知道。为什么元数据不存储在合同中?因为在区块链上存储数据是超级昂贵的,尤其是图片或视频。然而,你仍然可以在链上存储元数据,并支付高额的气体费用。一些项目使用SVG格式的图片,这大大减少了数据的大小,从而降低了气体费用。然而,这已经超出了本教程的范围。如果你想了解更多关于基于SVG的NFT,请告诉我。

合同结构

合同乍看之下可能很复杂,但它组织得很好。图2直观地展示了这个合同的结构。让我们来看看每个组成部分。

图2 百事可乐NFT合同的结构

  • SPDX许可标识符 是注释后的第一行。它表明其他人可以如何使用这段代码。(第48行)
  • Solidity Version让编译器正确翻译代码,然后EVM可以理解。(第49行)
  • 导入ERC-721合约 ,那么Pepsi NFT合约将使用ERC-721合约作为其蓝图。
  • PepsiMicDrop是我们将在下一节进一步讨论的NFT合约。它有三个子部分。(1)状态变量 (2)构造器 (3)函数
  • 状态变量是变量,其值被永久地保存在合约存储中。
  • 构造器是一个特殊的函数,只在合约创建时执行。你可以运行合约的初始化代码。
  • 函数是不言自明的。大多数函数是用来设置或获取状态值的。

PepsiMicDrop合约

让我们来看看PepsiMicDrop 契约。我将按照合同的功能来解释,每个功能都与一些函数和状态变量有关。

(1) 构造函数

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

构造函数需要2个输入。

  • "Pepsi Mic Drop "是NFT代币的名称
  • "PEPSIMICDROP "是代币符号。

赋值发生在ERC721合约代码中,该合约在第51行导入。这就是为什么你找不到与这个构造函数体有关的东西。这就是继承的魅力,我们不需要重做它。

在构造函数内部,我们可以看到2个状态变量得到了新的值,reserveMicDropsId ,和micDropsId 。为什么需要它们?在这个NFT投放中,百事可乐把前50个NFT留给了自己,所以公开的NFT从id 51开始。

等等,NFT不是一个图像吗?为什么他们在这里用数字来代表NFT?如果你有这些问题,我是和你一起的。为了理解这一点,我们需要研究一下什么是NFT。

(2) Mint

现在让我们来谈谈造币厂的问题。

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;
}

第一部分 - 默克尔证明

造币厂函数有两个输入,证明,和叶子。它们被用于第3至第7行的Merkle Proof。本质上,它检查一个用户是否有资格为NFT造币。有一个概念叫**白名单。**有时你必须进入这个白名单,以便以后能够铸造NFT。我不会解释这一部分,因为有人已经做得很好了。请查看这篇文章,它对Merkle Proof进行了全面的解释。

第二部分 - 先决条件

下一部分,第8至11行,定义了造币的4个先决条件。让我们逐一看一下。

(1) 在NFT开始销售之前,状态变量saleStarted 被设置为false。所以它将不会通过第8行的检查。当销售开始时,合同所有者可以调用函数startSale() 来改变saleStarted 的值。

require(saleStarted == true, "The sale is paused");

(2) 这个检查确保了用户地址不是0x0。0x0是Ethereum的创世地址,没有用户会使用它。我也不确定为什么这个检查是必要的。如果你理解的话,请告诉我。

require(msg.sender != address(0x0), "Public address is not correct");

(3) 在Pepsi Drop中,每个地址只能铸造一个NFT。alreadyMinted 是一个映射类型的状态变量,就像一个字典,它记录了所有铸造NFT的地址。

require(alreadyMinted[msg.sender] == false, "Address already used");

(4) 供应有限,最多1983个NFTs。这将检查是否所有的NFT都已被认领。micDropsId 是我们上面讨论的唯一的token id。

require(micDropsId <= maxMint, "Mint limit reached");

很简单,对吗?而我们几乎已经完成了,挂在那里。

第三部分。实际的造币。

这是造币实际发生的地方。好消息是我们不需要实现它,因为ERC-721已经做到了这一点。

_safeMint(msg.sender, micDropsId++);

为了造币,我们用用户的钱包地址和唯一的代币ID调用函数_safemint() ,就这样。在引擎盖下,有一个状态变量(像一个字典)来跟踪每个代币的所有权。通过调用_safemint() 函数,我们更新这个状态变量,分配或改变相应代币的所有权。

这里有一点要注意,状态变量micDropId 代表代币的ID,它 ,在每次造币前都会递增1。

第四部分:更新铸币地址

正如第二部分中提到的,每个地址只能铸造一个NFT。

alreadyMinted[msg.sender] = true;

这一行确保成功铸造NFT的人被记录下来。

第5部分:返回

铸币后,代币ID被返回到前端。

(3) 更新URI

我们需要讨论的最后一部分是更新URI的函数。

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

正如你已经知道的,每个NFT都有其URI。而这是用来改变基础URI的函数。

基本URI是每个NFT的URI之间的相互部分。默认情况下,URI是baseURI/tokenId 。我们设置baseURI的原因是,它可以节省汽油费。想象一下,如果你为每个NFT设置URI,那是非常昂贵的。

你可以改变如何结合baseURItokenId ,通过覆盖函数 **tokenURI** 定义在ERC-721合同中。例如,如果URI的格式是这样的,你可以做一些类似baseURI/tokenId + ‘.json’

你可能想知道,什么是 **public****onlyOwner**.这些是函数修饰符,它定义了一个函数运行的条件。public 意味着 "在合同之外"。部署后,我们通过网站(更确切地说,是JavaScript)与合同互动,这就是public的意思。onlyOwner 意味着只有合同所有者可以调用这个函数。当然,我们只希望合同拥有者有能力改变令牌URI。

(4) 其他部分

揭开的部分只是获取或设置状态变量的函数,我相信你不需要我的帮助。

结束

那么,就这样了。谢谢你阅读这篇文章。我希望你觉得它对你有帮助。