前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NFT 合约中优秀的开发模式

NFT 合约中优秀的开发模式

作者头像
Tiny熊
发布2022-11-07 10:12:46
8610
发布2022-11-07 10:12:46
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:Tiny 熊[1]

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

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

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

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

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

  • ERC721Metadata
  • ERC721Enumerable

核心 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 的有:

  • Crypto Coven
  • Azuki

归功于Shiny Object[2]的探索。

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

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

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

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

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

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

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

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

使用 ERC721A 的合约:

  • Azuki
  • goblintown
  • wagdie
  • Moonbirds

使用 mint 而不是 safeMint

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

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

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

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

这样做的合约有:

  • Crypto Coven

使用 Merkle 树实现白名单机制

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

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

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

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

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

使用默克尔树的合约:

  • Crypto Coven
  • OKPC

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

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

使用这个方式的合约有:

  • OKPC
  • Watchfaces

防止机器人铸币

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

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

防止 NFT 狙击手

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

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

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

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

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

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

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

其他模式

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用计数器取代 ERC721Enumerable,以节省 Gas。
  • 使用 ERC721A 进行高效的批量铸币
  • 使用 mint 而不是 safeMint
  • 使用 Merkle 树实现白名单机制
  • 可升级/可替换的元数据合约
  • 防止机器人铸币
  • 防止 NFT 狙击手
  • 其他模式
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档