NFT的gas优化终极指南

NFT的gas优化终极指南

未标题-3

img

在我们尝试着创造一个新的收藏品的时候,发现gas费比NFT本身还要贵!

本文旨在解决上面的问题。

接下来我们将看到的是,NFT智能合约的工程团队去寻找降低gas费用的方法时,会发生什么。

本指南是我们精心研究和实验的结果。

在本文中,我们将通过不同的方法来提高铸造的成本效益:

  • 我们真的需要ERC721Enumerable吗?
  • 使用映射而不是数组
  • ERC721A标准
  • 从代币 Id 1开始
  • 白名单的Merkle树
  • 打包变量
  • 使用未经检查的
  • 为什么第一个铸造更贵,我们能做什么?
  • 使用优化器
  • 将’ if语句’转换为单独的函数

本文中提到的所有代码都可以在我们的Github上找到:

https://github.com/WallStFam/gas-optimization

我们真的需要ERC721Enumerable吗?

在编写mint函数时,需要确保该函数使用了所需的最少代码。

有时,在合约中添加更多的函数以备将来使用,或者使链下查询更容易。问题是,我们添加的任何额外函数都会增加gas成本。

使用昂贵的Mint函数最常见的情况之一是让我们的合约继承自 ERC721Enumerable。

这个扩展的问题是,它为转账增加了大量的开销。

ERC721Enumerable使用4个映射和一个数组来跟踪每个用户的代币id。在每次转账中写入这些结构要花费大量的gas。

以下是两个智能合约制造一个代币的gas成本的比较。一个继承自ERC721Enumerable,另一个不继承:

img

ERC721Enumerable的价格是普通ERC721的2倍!

如果我们看看排在第一个铸造之后的铸造,我们就会发现两者的差异更明显:

img

ERC721Enumerable在第一次铸造后的成本几乎比 ERC721 高出 3 倍!

注意:在solidity中,将变量从0设置为非0比从非0设置为非0更昂贵。这就是为什么在ERC721中的第一个铸造会更昂贵,因为用户的余额从0变化到1。

但有趣的是,ERC721中的第一次铸造更贵,ERC721Enumerable中的第一次铸造更便宜。如果我们有兴趣并且想知道为什么会这样,请查看Open Zepplin的ERC721Enumerable的98行。

第一个铸造是更昂贵的,因为_addtokentownerenumeration(address,uint256)在第一个铸造将映射中的值设置为零,并将值从0设置为0是没有成本。

因此,在添加ERC721Enumerable之前,请自问:“我的合约中真的需要这个函数吗?”

如果我们只打算从合约之外查询每个用户的代币id,那么有一些方法可以做到这一点,而无需使用ERC721Enumerable。

这里有两种方法:

  • 为每个代币调用ownerOf(uint tokenId)。
  • 查询来自ERC721的Transfer事件并处理它们以获得每个代币的所有者

可以在我们的Github库中找到这两种方法的脚本:

  • https://github.com/WallStFam/gas-optimization/blob/master/scripts/getAllOwners/getAllOwners_ownerOf.js
  • https://github.com/WallStFam/gas-optimization/blob/master/scripts/getAllOwners/getAllOwners_Transfer_event.js

这些脚本查询区块链以获得ERC721合约的每个代币的所有者。

我们在这些脚本中使用了Wall Street Dads合约作为示例,但是我们可以自由地将该代码用于任何其他合约。我们只需要替换abi和合约地址。

使用映射而不是数组

有时可以用映射替代数组的函数。映射的优点是,我们可以访问任何值,而不必像通常使用数组那样进行迭代。

例如,在NFT集合中使用白名单是很常见的。被添加到白名单的用户有优先权,通常可以得到比公开销售更低的价格。

我们可以做一个白名单使用数组如下:

address[] whitelistedUsers;
function mintPublicSale() external payable {
    require(msg.value >= 0.2 ether, "Not enough ether");
    _mint(msg.sender, currTokenId++);
}
function mintWhitelist() external payable {
    require(isWhitelisted(msg.sender), "You are not whitelisted");
    require(msg.value >= 0.1 ether, "Not enough ether");
    _mint(msg.sender, currTokenId++);
}
function addToWhitelist(address user) external onlyOwner {
    require(!isWhitelisted(user), "User is already whitelisted");
    whitelistedUsers.push(user);
}
function isWhitelisted(address _user) public view returns (bool) {
    for(uint i=0; i<whitelistedUsers.length; i++){
        if(whitelistedUsers[i] == _user){
            return true;
        }
    }
    return false;
}

尽管这段代码有用,但它有一个大问题:随着越来越多的用户被添加到whitelistedUsers数组,调用mintWhitelist()的变得越来越昂贵。这是因为数组越大,需要迭代的次数就越多,以确定是否添加了用户。

通常情况下,Solidity中的循环可能不是正确的解决方案。在某些情况下使用数组是可以的,但要确保循环是

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值