以太坊系列 - Solidity减少gas消耗与字节码大小的小技巧

Solidity减少gas消耗与字节码大小的小技巧

Solidity是一种特殊的语言,有许多小怪癖。Solidity中的许多事情与大多数其他语言的行为不同,因为创建Solidity以使用其有限的功能集来处理EVM。关于在Solidity中节省gas的技巧,与大家分享。

函数修饰符可能效率低下

添加函数修饰符时,将拾取该函数的代码并将其放在函数修饰符中以代替_符号。这也可以理解为“函数修饰符被内联”。在普通的编程语言中,内联小代码更有效,没有任何实际缺点,但Solidity不是普通的语言。在Solidity中,EIP 170将contract的最大大小限制为24 KB 。如果多次内联相同的代码,则会增加大小,并且可以轻松地限制大小限制。

另一方面,内部函数不是内联函数,而是作为单独的函数调用。这意味着它们在运行时的成本略高,但在部署中节省了大量冗余字节码。内部函数还可以帮助避免可怕的“堆栈太深错误”,因为在内部函数中创建的变量不与原始函数共享相同的受限堆栈,但是在修改器中创建的变量共享相同的堆栈限制。

布尔运算使用8位,而您只需要1位

在Solidity的引导下,Booleans(bool)uint8意味着他们使用8位存储。布尔值只能有两个值:True或False。这意味着您只能在一个位中存储布尔值。您可以在一个单词中打包256个布尔值。最简单的方法是获取一个uint256变量并使用它的全部256位来表示单个布尔值。要从a获取单个布尔值uint256 ,请使用以下函数:

function getBoolean(uint256 _packedBools,uint256 _boolNumber)
    public view returns(bool)
{ 
    uint256 flag =(_ packageBools >> _boolNumber)&uint256(1; 
    return(flag == 1truefalse; 
}

要设置或清除bool,请使用:

function setBoolean(
    uint256 _packedBools,
    uint256 _boolNumber,
    bool _value 
)public view returns(uint256){ 
    if(_value)
        return _packedBools | uint256(1<< _ boolNumber; 
    else 
        return _packedBools &〜(uint256(1<< _ boolNumber); 
}

使用此技术,您可以在一个存储槽中存储256个布尔值。如果您尝试bool正常打包(如在结构中),那么您将只能在一个插槽中容纳32个bool。仅在您要存储超过32个布尔值时才使用此选项。

使用库来保存一些字节码

当您调用库的公共函数时,该函数的字节码不会成为您的contract的一部分,因此您可以将复杂的逻辑放在库中,同时保持contract规模较小。请记住,调用库需要一些gas并且还需要一些字节码。对库的调用是通过委托调用进行的,这意味着库可以访问与contract相同的数据以及相同的权限。这意味着它不值得做简单的任务。另一件需要记住的事情是solc内联函数库的内部函数。内联具有自己的优点,但需要字节码空间。

无需使用默认值初始化变量

如果未设置/初始化变量,则假定其具有默认值(0,false,0x0等,具体取决于数据类型)。如果您用它的默认值显式初始化它,您只是在浪费gas。

uint256 hello = 0; //bad, expensive
uint256 world; //good, cheap

使用简短的原因字符串

您可以(并且应该)将错误原因字符串与require语句一起附加,以便更容易理解contract调用还原的原因。但是,这些字符串在部署的字节码中占用空间。每个原因字符串至少需要32个字节,因此请确保您的字符串符合32个字节,否则会变得更加昂贵。

require(balance >= amount, "Insufficient balance"); //good
require(balance >= amount, "To whomsoever it may concern. I am writing this error message to let you know that the amount you are trying to transfer is unfortunately more than your current balance. Perhaps you made a typo or you are just trying to be a hacker boi. In any case, this transaction is going to revert. Please try again with a lower amount. Warm regards, EVM"; //bad

避免重复检查

不需要以不同的形式一次又一次地检查相同的条件。最常见的冗余检查是由SafeMath库引起的。SafeMath库本身检查下溢和溢出,因此您不需要自己检查变量。

require(balance >= amount); 
//This check is redundant because the safemath subtract function used below already includes this check.
balance = balance.sub(amount);

利用单线交换

Solidity提供了一种相对不常见的功能,允许您在单个语句中交换变量值。使用它而不是使用临时变量/ xor / arithmetic函数来交换值。以下示例显示如何交换不同变量的值:

(hello, world) = (world, hello)

使用事件存储链上不需要的数据

使用事件来存储数据比将它们存储在变量中要便宜得多。但您不能在事件链上使用数据。此外,正在修剪旧事件,因此您可能必须在将来托管自己的节点以从旧事件中获取数据。

正确使用优化器

除了允许您打开和关闭优化器之外,solc还允许您自定义优化器运行。runs不是优化器运行的次数,而是您希望在该智能合约中调用函数的次数。如果智能合约只是一次性用作归属或锁定令牌的智能合约,则可以将runs值设置为1使编译器生成尽可能小的字节码,但调用该函数可能需要更多的gas。如果要部署一个将被大量使用的contract(如ERC20令牌),则应将其设置runs为一个1337较大的数字,以便初始字节码稍微大一点,但对该contract的调用会更便宜。像转移这样的常用功能会更便宜。

使用较少的functions会很有帮助

通常,使用具有单个任务的较小单例functions是一种很好的编码实践。实际上,使用多个较小的functions需要更多的gas并且需要更多的字节码。使用更大的复杂functions又可能会使测试和审核变得困难,不推荐这么做,除非您真的想挤出gas些节省,您就可以利用它们。

调用内部函数更便宜

从智能合约内部调用其内部函数比调用其公共函数便宜,因为当您调用公共函数时,所有参数将再次复制到内存中并传递给该函数。相反,当您调用内部函数时,将传递这些参数的引用,并且它们不会再次复制到内存中。这节省了一点gas,特别是当参数很大时。

使用代理模式进行大规模部署

如果您希望部署同一contract的多个副本,则考虑仅部署一个实现contract和多个代理contract,将其逻辑委派给实现contract。这将允许这些contract共享相同的逻辑但不同的数据。

使用嵌套if(s)和multiple require(s)

这可能会让您感到意外,但使用嵌套if和multiple需要比使用&&多个检查组合更加便宜。还有更多优点,例如更容易阅读代码和更好的覆盖率报告。以下是使用的示例和gas:

//bad function uses 302 gas in execution
    function bad(uint a, uint b) public returns(uint256) {
        require(a>5 && b==0);
        return a;
    }
/good function uses only 288 gas in execution
    function good(uint a, uint b) public returns(uint256) {
        require(a>5);
        require(b==0);
        return a;
    }

最后的想法

大多数一般良好的编程原理和优化也适用于Solidity,但在Solidity中有一些奇怪之处,就像上面提到的那些使得优化Solidity代码更难(但有趣)。随着您越来越多地使用实体,您将学到更多技巧。但是,无论您使用多少技巧,在创建复杂代码时,您仍可能面临24 KB的代码大小限制。您可以使用代理或其他技巧将contract拆分为各种contract,但限制仍然很痛苦。


往期精彩回顾:
区块链知识系列
密码学系列
共识系列
公链调研系列
以太坊系列
EOS系列
智能合约系列
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖魁首

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值