本文作者:Tiny 熊[1]
最近开发了不少 NFT 合约, 之前一直想总结一篇文章介绍 NFT 开发中的各种技巧,奈何总是各种事情没有动手,今天看到老外的这篇总结,非常全面,就翻译一下。以下是原文翻译:

我读过很多 NFT 合约,以下是我在最好的合约中看到的最常见的开发模式:
首先,简单介绍一下背景,ERC-721 标准由 2 个扩展组成:
核心 721 标准是非常简单的,你只需要实现以下函数,就能符合核心 721 标准:

2 个扩展在上面添加了这些函数:

那么,ERC721Enumerable 的问题在哪里?
OpenZeppelin 为所有这些接口提供了现成的实现。它们中没有一个是完美的,但 2 个实现(ERC721 和 ERC721Metadata)做得相当好。然而,ERC721Enumerable 的实现是非常浪费 gas。它耗费了大量的 Gas 并浪费了存储空间。看看他们是如何实现 ERC721Enumerable 接口的:

你可以想象,跟踪这么多的映射和数组是多么的浪费。(顺便说一句,它们在转账前/后被更新)。上面的代码(错误地)针对读函数进行了优化,而它应该针对写函数进行优化(因为读函数大部分是免费的)。大多数合约开发人员都太懒了,只是从 OpenZeppelin 继承了所有 3 个接口。但你可以做得更好。
解决方案: 如果你从 ERC721Enumerable 接口中唯一需要的函数是totalSupply,那么你可以只使用一个整数,并把它作为一个计数器来记录 NFT 的铸币数量。你的合约将不再实现整个 Enumerable 接口,但你将节省大量的 Gas。而好消息是,你只需要实现核心的 ERC721 接口,就能符合 ERC721 的要求。(也就是说,即使你不实现 Enumerable 接口,NFT 交易市场在解析你的合约时也不会有问题)。
要注意事项是,你将不再有从 tokenID 到所有者的映射,反之亦然,但可以使用以太坊事件在链外跟踪这些信息。我认为 OpenSea API 已经提供了这一点。另外,使用 The Graph 可以简化与以太坊事件相关的 web2 基础设施。
合约使用一个计数器,而不是继承 OZ 的 ERC721Enumerable 的有:
归功于Shiny Object[2]的探索。
大多数 NFT 合约都扩展了 OpenZeppelin 的实现。但它并没有为批量铸币进行优化。批量铸币与一次铸币相比,可以大大节省一些费用。
例如,你可以不为每一次铸币触发一个转移事件,而只触发一个事件,并在事件中指定整个批次的铸币。另一个例子是每个批次只更新一次所有者余额,而不是在每一次铸币之后。
意识到对批量铸币有一些可能的优化,Azuki 团队创建了 ERC721A--一个为批量铸币优化的 ERC721 的实现。以下是它与 OZ 的实现方式的比较:

ERC721A 是如何实现这种节约的?主要是利用这些优化:
你可以在Azuki 网站[4]上阅读更多关于 ERC721A 的信息。
如果你需要 Enumerable 接口的所有功能,你可以使用 Azuki 的ERC721AQueryable[5]接口,这是 ERC721Enumerable 的优化版本。
使用 ERC721A 的合约:
safeMint最初是为了防止 NFT 丢失到合约中。如果 NFT 的接收者是一个合约,而它没有转移 NFT 方法,NFT 将永远停留在合约内。
所以,接收合约是需要实现 ERC721Receiver 接口,以便允许 NFT 合约检查 NFT 是否被接收方正确接收。如果一个合约实现了接收者接口,它就表明它知道收到 NFT 后知道如何处理:

如果你的接收方只是一个普通的账户,而不是一个合约,你就不需要使用safeMint。如果你 100%确定接收方的合约可以处理 NFT,你也不需要使用safeMint。
通过使用 mint 而不是 safeMint,你可以节省一些 Gas。使用transfer而不是safeTransfer也是如此。
这样做的合约有:
如果你使用 Merkle 树来实现你的白名单,你可以节省大量的存储(和 Gas)。Merkle 树是一种高效的数据结构,它允许你以单个地址的代价存储一堆地址。其代价是,查找时间不是*O(1)。不过O(n)*也是相当不错的。
你需要添加到合约中的是这些函数:

你可以使用 OpenZeppelin 的 MerkleProof库[6]来完成验证步骤。
然后你会像这样修改你的铸造函数:

基本上,就是在 mint 函数中添加一个额外的参数:merkleProof。这是一个地址的哈希数组,构成了从可以铸币地址到根地址的路径。你可以在你的网站上为每个允许铸币地址计算这个路径。这里[7]可阅读更多相关信息。
使用默克尔树的合约:
如果你以后想升级你的 NFT 的展现,或者在链上和链下的渲染之间切换,你应该让元数据合约可被替换。像这样:

使用这个方式的合约有:
你可以采取 2 个保障措施来防止机器人将你的代币全部挖走。
msg.sender == tx.origin。当一个合约调用你的铸币功能时,msg.sender将是合约地址,但tx.origin将是调用该合约的人的地址。在这里[8]可查看更多信息。NFT 狙击是指有人知道哪些代币是稀有的,并且知道代币被铸造的顺序。因此,他们选择适当的时间去铸造一堆 NFT,目标获取稀有的 NFT。
你要避免 NFT 被狙击,以保证每个人都能公平地分配代币。让我们来谈谈如何防止 NFT 狙击(至少在某种程度上)。NFT 狙击由 2 个问题组成:
你可以通过在代币被铸造后才披露元数据来解决第一个问题(更多信息这里[9])。或者你可以使用分批渐进式披露。另外所有的链上数据都会被读取和利用。所以在铸币开始前不要验证你的合约。
第二个问题可以通过随机化铸币顺序来解决。链上随机化是很难的。以太坊没有内置的随机数生成器,所以人们一直在使用各种技巧,如使用当前区块号作为种子和/或将其与矿工地址相结合以获得额外的随机性。由于这不是真正的随机性,这些类型的技巧很容易被高级狙击手识破。
你可以使用一个随机化的预言机(Chainlink),但即使如此,高级狙击手也可以通过 偷看NFT 来绕过它,其可以实现:如果铸币的 NFT 被证明并不罕见,就回退交易(例子这里[10])。因此,不幸的是,没有 100%的方法来解决第二个问题。你可以做的一件事是添加白名单机制,但这只有在整个 NFT 集合可以被限制在白名单的社区内时才有效。
以太坊真的是一个黑暗的森林,如果你不小心,你可能就会被狙击。阅读更多关于 NFT 狙击攻击的信息可参看这里[11]。
setApproval。但随着 Seaport 的引入,这不再是必要的(关于实施的例子,请查看 Crypto Coven 合约[15])。原文: https://www.solidnoob.com/blog/good-nft-contract-patterns
[1]
Tiny熊: https://learnblockchain.cn/people/15
[2]
Shiny Object: https://shiny.mirror.xyz/OUampBbIz9ebEicfGnQf5At_ReMHlZy0tB4glb9xQ0E
[3]
最新变化: https://twitter.com/cygaar_dev/status/1538571744918884352?s=20&t=oJD6rSexY0eSWVbEiwyVVQ
[4]
Azuki网站: https://www.azuki.com/erc721a
[5]
ERC721AQueryable: https://github.com/chiru-labs/ERC721A/blob/main/contracts/extensions/ERC721AQueryable.sol
[6]
库: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol
[7]
这里: https://medium.com/@ItsCuzzo/using-merkle-trees-for-nft-whitelists-523b58ada3f9
[8]
这里: https://ethereum.stackexchange.com/a/1892
[9]
这里: https://medium.com/coinmonks/what-i-learned-from-building-cool-cats-nft-4057f279d400#f943
[10]
这里: https://twitter.com/nazar_ilamanov/status/1536916110381043713?s=20&t=ulDz35wOno_6CjytBh3v7A
[11]
这里: https://www.paradigm.xyz/2021/10/a-guide-to-designing-effective-nft-launches#examples-of-user-harm
[12]
合约: https://etherscan.io/address/0x5180db8F5c931aaE63c74266b211F580155ecac8#code
[13]
证明哈希: https://medium.com/coinmonks/the-elegance-of-the-nft-provenance-hash-solution-823b39f99473
[14]
之前: https://cryptocoven.mirror.xyz/A622VSRm8-9oLzc8l3oFGmfnFUZQmDQ3Wx3ObhSlhsc#gasless-listings-on-opensea
[15]
合约: https://etherscan.io/address/0x5180db8F5c931aaE63c74266b211F580155ecac8#code