NFT 合约中优秀的开发模式

本文整理出了,NFT 合约开发过程中,优秀且常见的开发模式

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

img

我读过很多NFT合约,以下是我在最好的合约中看到的最常见的开发模式:

  • 用计数器取代ERC721Enumerable,以节省Gas。
  • 使用ERC721A来实现高效的批量铸造。
  • 使用mint而不是safeMint
  • 使用Merkle树实现白名单机制
  • 可升级/可交换的元数据合约
  • 防范机器人
  • 防止NFT狙击手(针对性铸造稀有 NFT )
  • 其他模式

用计数器取代ERC721Enumerable,以节省Gas。

首先,简单介绍一下背景,ERC-721标准由2个扩展组成:

  • ERC721Metadata
  • ERC721Enumerable

核心721标准是非常简单的,你只需要实现以下函数,就能符合核心721标准:

ERC721

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

ERC721Metadata

那么,ERC721Enumerable的问题在哪里?

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

ERC721Enumerable

你可以想象,跟踪这么多的映射和数组是多么的浪费。(顺便说一句,它们在转账前/后被更新)。上面的代码(错误地)针对读函数进行了优化,而它应该针对写函数进行优化(因为读函数大部分是免费的)。大多数合约开发人员都太懒了,只是从OpenZeppelin继承了所有3个接口。但你可以做得更好。

解决方案:如果你从ERC721Enumerable接口中唯一需要的函数是totalSupply,那么你可以只使用一个整数,并把它作为一个计数器来记录NFT的铸币数量。你的合约将不再实现整个Enumerable接口,但你将节省大量的Gas。而好消息是,你只需要实现核心的ERC721接口,就能符合ERC721的要求。(也就是说,即使你不实现Enumerable接口,NFT 交易市场在解析你的合约时也不会有问题)。

要注意事项是,你将不再有从tokenID到所有者的映射,反之亦然,但可以使用以太坊事件在链外跟踪这些信息。我认为OpenSea API已经提供了这一点。另外,使用The Graph可以简化与以太坊事件相关的web2基础设施。

合约使用一个计数器,而不是继承OZ的ERC721Enumerable的有:

  • Crypto Coven
  • Azuki

归功于Shiny Object的探索。

使用ERC721A进行高效的批量铸币

大多数NFT合约都扩展了OpenZeppelin的实现。但它并没有为批量铸币进行优化。批量铸币与一次铸币相比,可以大大节省一些费用。

例如,你可以不为每一次铸币触发一个转移事件,而只触发一个事件,并在事件中指定整个批次的铸币。另一个例子是每个批次只更新一次所有者余额,而不是在每一次铸币之后。

意识到对批量铸币有一些可能的优化,Azuki团队创建了ERC721A--一个为批量铸币优化的ERC721的实现。以下是它与OZ的实现方式的比较:

img

ERC721A是如何实现这种节约的?主要是利用这些优化:

  • 摆脱了OZ的ERC721Enumerable
  • 每批只更新一次数据,而不是在每一次铸币后更新数据
  • 使用更有效的存储布局:如果连续的NFT有相同的所有者,不存储关于所有者的冗余信息(只为第一个拥有的NFT存储一次)。这个数据可以在运行时通过向左读直到找到所有者信息来推断。
  • 每批只触发一个转移事件。(这是更多相关的最新变化,并不是原始ERC721A的一部分)

你可以在Azuki网站上阅读更多关于ERC721A的信息。

如果你需要Enumerable接口的所有功能,你可以使用Azuki的ERC721AQueryable接口,这是ERC721Enumerable的优化版本。

使用ERC721A的合约:

  • Azuki
  • goblintown
  • wagdie
  • Moonbirds

使用mint而不是safeMint

safeMint最初是为了防止NFT丢失到合约中。如果NFT的接收者是一个合约,而它没有转移NFT方法,NFT将永远停留在合约内。

所以,接收合约是需要实现ERC721Receiver接口,以便允许NFT合约检查NFT是否被接收方正确接收。如果一个合约实现了接收者接口,它就表明它知道收到NFT后知道如何处理:

safeMint

如果你的接收方只是一个普通的账户,而不是一个合约,你就不需要使用safeMint。如果你100%确定接收方的合约可以处理NFT,你也不需要使用safeMint

通过使用mint而不是safeMint,你可以节省一些Gas。使用transfer而不是safeTransfer也是如此。

这样做的合约有:

  • Crypto Coven

使用Merkle树实现白名单机制

如果你使用Merkle树来实现你的白名单,你可以节省大量的存储(和Gas)。Merkle树是一种高效的数据结构,它允许你以单个地址的代价存储一堆地址。其代价是,查找时间不是O(1)。不过O(n)也是相当不错的。

你需要添加到合约中的是这些函数:

img

你可以使用OpenZeppelin的MerkleProof来完成验证步骤。

然后你会像这样修改你的铸造函数:

img

基本上,就是在mint函数中添加一个额外的参数:merkleProof。这是一个地址的哈希数组,构成了从可以铸币地址到根地址的路径。你可以在你的网站上为每个允许铸币地址计算这个路径。这里可阅读更多相关信息。

使用默克尔树的合约:

  • Crypto Coven
  • OKPC

可升级/可替换的元数据合约

如果你以后想升级你的NFT的展现,或者在链上和链下的渲染之间切换,你应该让元数据合约可被替换。像这样:

img

使用这个方式的合约有:

  • OKPC
  • Watchfaces

防止机器人铸币

你可以采取2个保障措施来防止机器人将你的代币全部挖走。

  1. 限制每个钱包的可铸币量
  2. 检查msg.sender == tx.origin。当一个合约调用你的铸币功能时,msg.sender将是合约地址,但tx.origin将是调用该合约的人的地址。在这里可查看更多信息。

防止NFT狙击手

NFT狙击是指有人知道哪些代币是稀有的,并且知道代币被铸造的顺序。因此,他们选择适当的时间去铸造一堆NFT,目标获取稀有的NFT。

你要避免NFT被狙击,以保证每个人都能公平地分配代币。让我们来谈谈如何防止NFT狙击(至少在某种程度上)。NFT狙击由2个问题组成:

  1. 暴露了代币元数据(让狙击手推断出代币的稀有性)
  2. 以确定的顺序铸造代币(让狙击手推断铸造稀有代币的正确时间)。

你可以通过在代币被铸造后才披露元数据来解决第一个问题(更多信息这里)。或者你可以使用分批渐进式披露。另外所有的链上数据都会被读取和利用。所以在铸币开始前不要验证你的合约。

第二个问题可以通过随机化铸币顺序来解决。链上随机化是很难的。以太坊没有内置的随机数生成器,所以人们一直在使用各种技巧,如使用当前区块号作为种子和/或将其与矿工地址相结合以获得额外的随机性。由于这不是真正的随机性,这些类型的技巧很容易被高级狙击手识破。

你可以使用一个随机化的预言机(Chainlink),但即使如此,高级狙击手也可以通过 偷看NFT来绕过它,其可以实现:如果铸币的NFT被证明并不罕见,就回退交易(例子这里)。因此,不幸的是,没有100%的方法来解决第二个问题。你可以做的一件事是添加白名单机制,但这只有在整个NFT集合可以被限制在白名单的社区内时才有效。

以太坊真的是一个黑暗的森林,如果你不小心,你可能就会被狙击。阅读更多关于NFT狙击攻击的信息可参看这里

其他模式

  • 使合约可提取ERC-721和ERC-20: 大多数合约只是实现了ETH的提取功能,而忘记了ERC-721和ERC-20的问题。但有时人们会错误地将代币发送到合约中,或者无法知道什么其他原因。添加一个提取功能,这样它们就不会被卡在你的合约中 (关于一个实现的例子,请查看Crypto Coven 合约)。
  • 使你的数据不可改变: 要么创建链上NFT,如果使用链外渲染,则可以使用证明哈希
  • 预先授权OpenSea,以便0费用上架:(自Seaport以来已经过时)。之前能够预先授权OpenSea合约,这样你的NFT持有人就不需要调用setApproval。但随着Seaport的引入,这不再是必要的(关于实施的例子,请查看Crypto Coven 合约)。

原文: https://www.solidnoob.com/blog/good-nft-contract-patterns

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2022-07-05 21:45
  • 阅读 ( 1872 )
  • 学分 ( 302 )
  • 分类:NFT

2 条评论

请先 登录 后评论
Tiny熊
Tiny熊

布道者

150 篇文章, 278695 学分