{"content":{"title":"NFT 合约中优秀的开发模式","body":"最近开发了不少 NFT 合约， 之前一直想总结一篇文章介绍 [NFT ](https://learnblockchain.cn/article/2544)开发中的各种技巧，奈何总是各种事情没有动手，今天看到老外的这篇总结，非常全面，就翻译一下。以下是原文翻译：\r\n\r\n![img](https://img.learnblockchain.cn/pics/20220705201757.png!/scale/30)\r\n\r\n我读过很多NFT合约，以下是我在最好的合约中看到的最常见的开发模式：\r\n\r\n- 用计数器取代ERC721Enumerable，以节省Gas。\r\n- 使用ERC721A来实现高效的批量铸造。\r\n- 使用mint而不是safeMint\r\n- 使用Merkle树实现白名单机制\r\n- 可升级/可交换的元数据合约\r\n- 防范机器人\r\n- 防止NFT狙击手（针对性铸造稀有 NFT ）\r\n- 其他模式\r\n\r\n## 用计数器取代ERC721Enumerable，以节省Gas。\r\n\r\n首先，简单介绍一下背景，ERC-721标准由2个扩展组成：\r\n\r\n- ERC721Metadata\r\n- ERC721Enumerable\r\n\r\n核心721标准是非常简单的，你只需要实现以下函数，就能符合核心721标准：\r\n\r\n![ERC721](https://img.learnblockchain.cn/pics/20220705201651.png!/scale/30)\r\n\r\n2个扩展在上面添加了这些函数：\r\n\r\n![ERC721Metadata](https://img.learnblockchain.cn/pics/20220705201648.png!/scale/30)\r\n\r\n那么，ERC721Enumerable的问题在哪里？\r\n\r\nOpenZeppelin为所有这些接口提供了现成的实现。它们中没有一个是完美的，但2个实现（ERC721和ERC721Metadata）做得相当好。然而，ERC721Enumerable的实现是非常浪费gas。它耗费了大量的Gas并浪费了存储空间。看看他们是如何实现ERC721Enumerable接口的：\r\n\r\n![ERC721Enumerable](https://img.learnblockchain.cn/pics/20220705201653.png!/scale/30)\r\n\r\n你可以想象，跟踪这么多的映射和数组是多么的浪费。(顺便说一句，它们在转账前/后被更新)。上面的代码（错误地）针对读函数进行了优化，而它应该针对写函数进行优化（因为读函数大部分是免费的）。大多数合约开发人员都太懒了，只是从OpenZeppelin继承了所有3个接口。但你可以做得更好。\r\n\r\n**解决方案：**如果你从ERC721Enumerable接口中唯一需要的函数是`totalSupply`，那么你可以只使用一个整数，并把它作为一个计数器来记录NFT的铸币数量。你的合约将不再实现整个Enumerable接口，但你将节省大量的Gas。而好消息是，你只需要实现核心的ERC721接口，就能符合ERC721的要求。(也就是说，即使你不实现Enumerable接口，NFT 交易市场在解析你的合约时也不会有问题）。\r\n\r\n要注意事项是，你将不再有从tokenID到所有者的映射，反之亦然，但可以使用以太坊事件在链外跟踪这些信息。我认为OpenSea API已经提供了这一点。另外，使用The Graph可以简化与以太坊事件相关的web2基础设施。\r\n\r\n合约使用一个计数器，而不是继承OZ的ERC721Enumerable的有：\r\n\r\n- Crypto Coven\r\n- Azuki\r\n\r\n归功于[Shiny Object](https://learnblockchain.cn/article/4388)的探索。\r\n\r\n## 使用ERC721A进行高效的批量铸币\r\n\r\n大多数NFT合约都扩展了OpenZeppelin的实现。但它并没有为批量铸币进行优化。批量铸币与一次铸币相比，可以大大节省一些费用。\r\n\r\n例如，你可以不为每一次铸币触发一个转移事件，而只触发一个事件，并在事件中指定整个批次的铸币。另一个例子是每个批次只更新一次所有者余额，而不是在每一次铸币之后。\r\n\r\n意识到对批量铸币有一些可能的优化，Azuki团队创建了ERC721A--一个为批量铸币优化的ERC721的实现。以下是它与OZ的实现方式的比较：\r\n\r\n![img](https://img.learnblockchain.cn/pics/20220705201706.png!/scale/30)\r\n\r\nERC721A是如何实现这种节约的？主要是利用这些优化：\r\n\r\n- 摆脱了OZ的ERC721Enumerable\r\n- 每批只更新一次数据，而不是在每一次铸币后更新数据\r\n- 使用更有效的存储布局：如果连续的NFT有相同的所有者，不存储关于所有者的冗余信息（只为第一个拥有的NFT存储一次）。这个数据可以在运行时通过向左读直到找到所有者信息来推断。\r\n- 每批只触发一个转移事件。(这是更多相关的[最新变化](https://twitter.com/cygaar_dev/status/1538571744918884352?s=20&t=oJD6rSexY0eSWVbEiwyVVQ)，并不是原始ERC721A的一部分)\r\n\r\n你可以在[Azuki网站](https://www.azuki.com/erc721a)上阅读更多关于ERC721A的信息。\r\n\r\n如果你需要Enumerable接口的所有功能，你可以使用Azuki的[ERC721AQueryable](https://github.com/chiru-labs/ERC721A/blob/main/contracts/extensions/ERC721AQueryable.sol)接口，这是ERC721Enumerable的优化版本。\r\n\r\n使用ERC721A的合约：\r\n\r\n- Azuki\r\n- goblintown\r\n- wagdie\r\n- Moonbirds\r\n\r\n## 使用mint而不是safeMint\r\n\r\n`safeMint`最初是为了防止NFT丢失到合约中。如果NFT的接收者是一个合约，而它没有转移NFT方法，NFT将永远停留在合约内。\r\n\r\n所以，接收合约是需要实现ERC721Receiver接口，以便允许NFT合约检查NFT是否被接收方正确接收。如果一个合约实现了接收者接口，它就表明它知道收到NFT后知道如何处理：\r\n\r\n![safeMint](https://img.learnblockchain.cn/pics/20220705201716.png!/scale/30)\r\n\r\n如果你的接收方只是一个普通的账户，而不是一个合约，你就不需要使用`safeMint`。如果你100%确定接收方的合约可以处理NFT，你也不需要使用`safeMint`。\r\n\r\n通过使用mint而不是safeMint，你可以节省一些Gas。使用`transfer`而不是`safeTransfer`也是如此。\r\n\r\n这样做的合约有：\r\n\r\n- Crypto Coven\r\n\r\n## 使用Merkle树实现白名单机制\r\n\r\n如果你使用Merkle树来实现你的白名单，你可以节省大量的存储（和Gas）。Merkle树是一种高效的数据结构，它允许你以单个地址的代价存储一堆地址。其代价是，查找时间不是*O(1)*。不过*O(n)*也是相当不错的。\r\n\r\n你需要添加到合约中的是这些函数:\r\n\r\n![img](https://img.learnblockchain.cn/pics/20220705201723.png!/scale/30)\r\n\r\n你可以使用OpenZeppelin的MerkleProof[库](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)来完成验证步骤。\r\n\r\n然后你会像这样修改你的铸造函数：\r\n\r\n![img](https://img.learnblockchain.cn/pics/20220705201729.png!/scale/30)\r\n\r\n基本上，就是在mint函数中添加一个额外的参数：`merkleProof`。这是一个地址的哈希数组，构成了从可以铸币地址到根地址的路径。你可以在你的网站上为每个允许铸币地址计算这个路径。[这里](https://learnblockchain.cn/article/4521)可阅读更多相关信息。\r\n\r\n使用默克尔树的合约：\r\n\r\n- Crypto Coven\r\n- OKPC\r\n\r\n## 可升级/可替换的元数据合约\r\n\r\n如果你以后想升级你的NFT的展现，或者在链上和链下的渲染之间切换，你应该让元数据合约可被替换。像这样：\r\n\r\n![img](https://img.learnblockchain.cn/pics/20220705201738.png!/scale/30)\r\n\r\n使用这个方式的合约有：\r\n\r\n- OKPC\r\n- Watchfaces\r\n\r\n## 防止机器人铸币\r\n\r\n你可以采取2个保障措施来防止机器人将你的代币全部挖走。\r\n\r\n1. 限制每个钱包的可铸币量\r\n2. 检查`msg.sender == tx.origin`。当一个合约调用你的铸币功能时，`msg.sender`将是合约地址，但`tx.origin`将是调用该合约的人的地址。在[这里](https://ethereum.stackexchange.com/a/1892)可查看更多信息。\r\n\r\n## 防止NFT狙击手\r\n\r\nNFT狙击是指有人知道哪些代币是稀有的，并且知道代币被铸造的顺序。因此，他们选择适当的时间去铸造一堆NFT，目标获取稀有的NFT。\r\n\r\n你要避免NFT被狙击，以保证每个人都能公平地分配代币。让我们来谈谈如何防止NFT狙击（至少在某种程度上）。NFT狙击由2个问题组成：\r\n\r\n1. 暴露了代币元数据（让狙击手推断出代币的稀有性）\r\n2. 以确定的顺序铸造代币（让狙击手推断铸造稀有代币的正确时间）。\r\n\r\n你可以通过在代币被铸造后才披露元数据来解决第一个问题（更多信息[这里](https://medium.com/coinmonks/what-i-learned-from-building-cool-cats-nft-4057f279d400#f943)）。或者你可以使用分批渐进式披露。另外所有的链上数据都会被读取和利用。所以在铸币开始前不要验证你的合约。\r\n\r\n第二个问题可以通过随机化铸币顺序来解决。链上随机化是很难的。以太坊没有内置的随机数生成器，所以人们一直在使用各种技巧，如使用当前区块号作为种子和/或将其与矿工地址相结合以获得额外的随机性。由于这不是真正的随机性，这些类型的技巧很容易被高级狙击手识破。\r\n\r\n你可以使用一个随机化的预言机（Chainlink），但即使如此，高级狙击手也可以通过 `偷看 `NFT来绕过它，其可以实现：如果铸币的NFT被证明并不罕见，就回退交易（例子[这里](https://twitter.com/nazar_ilamanov/status/1536916110381043713?s=20&t=ulDz35wOno_6CjytBh3v7A)）。因此，不幸的是，没有100%的方法来解决第二个问题。你可以做的一件事是添加白名单机制，但这只有在整个NFT集合可以被限制在白名单的社区内时才有效。\r\n\r\n以太坊真的是一个黑暗的森林，如果你不小心，你可能就会被狙击。阅读更多关于NFT狙击攻击的信息可参看[这里](https://www.paradigm.xyz/2021/10/a-guide-to-designing-effective-nft-launches#examples-of-user-harm)。\r\n\r\n## 其他模式\r\n\r\n- **使合约可提取ERC-721和ERC-20：** 大多数合约只是实现了ETH的提取功能，而忘记了ERC-721和ERC-20的问题。但有时人们会错误地将代币发送到合约中，或者无法知道什么其他原因。添加一个提取功能，这样它们就不会被卡在你的合约中 (关于一个实现的例子，请查看Crypto Coven [合约](https://etherscan.io/address/0x5180db8F5c931aaE63c74266b211F580155ecac8#code))。\r\n- **使你的数据不可改变：** 要么创建链上NFT，如果使用链外渲染，则可以使用[证明哈希](https://medium.com/coinmonks/the-elegance-of-the-nft-provenance-hash-solution-823b39f99473)。\r\n- **预先授权OpenSea，以便0费用上架：**（自Seaport以来已经过时）。[之前](https://cryptocoven.mirror.xyz/A622VSRm8-9oLzc8l3oFGmfnFUZQmDQ3Wx3ObhSlhsc#gasless-listings-on-opensea)能够预先授权OpenSea合约，这样你的NFT持有人就不需要调用`setApproval`。但随着Seaport的引入，这不再是必要的(关于实施的例子，请查看Crypto Coven [合约](https://etherscan.io/address/0x5180db8F5c931aaE63c74266b211F580155ecac8#code))。\r\n\r\n---\r\n\r\n原文: https://www.solidnoob.com/blog/good-nft-contract-patterns"},"author":{"user":"https://learnblockchain.cn/people/15","address":"0x3aA0c5bDce1EBedcB3FaabF69CE159da72bdD0A7"},"history":null,"timestamp":1703068276,"version":1}