在 Solidity中使用值数组以降低 gas 消耗

  • Tiny熊
  • 更新于 2020-08-20 18:53
  • 阅读 1826

本文讨论如何使用值数组(Value Array)替换引用数组(Reference Array)来减少 Solidity 智能合约的gas 消耗。

背景

我们Datona Labs在开发和测试Solidity数据访问合约(S-DAC:Smart-Data-Access-Contract)模板过程中,经常需要使用只有很小数值的小数组(数组元素个数少)。在本示例中,研究了使用值数组(Value Array)是否比引用数组(Reference Array)更高效。

讨论

Solidity支持内存(memory)中的分配数组,这些数组会很浪费空间(参考 文档),而存储(storage)中的数组则会消耗大量的gas来分配和访问存储。但是Solidity所运行的以太坊虚拟机(EVM)有一个256位(32字节)机器字长。正是后一个特性使我们能够考虑使用值数组(Value Array)。在机器字长的语言中,例如32位(4字节),值数组(Value Array)不太可能实用。

我们可以使用值数组(Value Array)减少存储空间和gas消耗吗?

译者注:机器字长 是指每一个指令处理的数据长度。

比较值数组与引用数组

引用数组(Reference Array)

在 Solidity 中,数组通常是引用类型。这意味着每当在程序中遇到变量符号时,都会使用指向数组的指针,不过也有一些例外情况会生成一个拷贝(参考文档-引用类型)。在以下代码中,将10个元素的 8位uint users 的数组传递给setUser函数,该函数设置users数组中的一个元素:

contract TestReferenceArray {
    function test() public pure {
        uint8[10] memory users;

        setUser(users, 5, 123);
        require(users[5] == 123);
    }

    function setUser(uint8[10] memory users, uint index, uint8 ev) 
    public pure {
        users[index] = ev;
    }
}

函数返回后,users数组元素将被更改。

值数组(Value Arrays)

值数组是以值类型保存的数组。这意味着在程序中遇到变量符号,就会使用其值。

contract TestValueArray {
    function test() public pure {
        uint users;

        users = setUser(users, 5, 12345);
        require(users == ...);
    }

    function setUser(uint users, uint index, uint ev) public pure 
    returns (uint) {
        return ...;
    }
}

请注意,在函数返回之后,函数的users参数将保持不变,因为它是通过值传递的,为了获得更改后的值,需要将函数返回值赋值给users变量。

Solidity bytes32 值数组

Solidity 在 bytesX(X=1..32)类型中提供了一个部分值数组。这些字节元素可以使用数组方式访问单独读取,例如:

    ...
    bytes32 bs = "hello";
    byte b = bs[0];
    require(bs[0] == 'h');
    ...

但不幸的是,在Solidity 目前的版本中,我们无法使用数组访问方式写入某个字节:

    ...
    bytes32 bs = "hello";
    bs[0] = 'c'; // 不可以实现
    ...

让我们使用Solidity的 using for 导入库的方式为bytes32类型添加新能力:

library bytes32lib {
    uint constant bits = 8;
    uint constant elements = 32;

    function set(bytes32 va, uint index, byte ev) internal pure 
    returns (bytes32) {
        require(index < elements);
        index = (elements - 1 - index) * bits;
        return bytes32((uint(va) & ~(0x0FF << index)) | 
                        (uint(uint8(ev)) << index));
    }
}

这个库提供了set()函数,它允许调用者将bytes32变量中的任何字节设置为想要的字节值。根据你的需求,你可能希望为你使用的其他bytesX类型生成类似的库。

测试一把

让我们导入该库并测试它:

import "bytes32lib.sol";

contract TestBytes32 {
    using bytes32lib for bytes32;

    function test1() public pure {
        bytes32 va = "hello";
        require(va[0] == 'h');
        // 类似 va[0] = 'c'; 的功能
        va = va.set(0, 'c');
        require(va[0] == 'c');
    }
}

在这里,你可以清楚地看到set()函数的返回值被分配回参数变量。如果缺少赋值,则变量将保持不变,require()就是来验证它。

可能的固定长度值数组

在Solidity机器字长为256位(32字节),我们可以考虑以下可能的值数组。

固定长度值数组

这些是以些Solidity可用整型匹配的固定长度的值数组:


                         固定长度值数组
类型          类型名       描述
uint128[2]   uint128a2   2个128位元素的值数组
uint64[4]    uint64a4    4个64位元素的值数组
uint32[8]    u...

剩余50%的内容订阅专栏后可查看

点赞 1
收藏 0
分享

0 条评论

请先 登录 后评论
Tiny熊
Tiny熊
0x1231...6564
登链社区发起人 登链团队对 DEFI 应用有深刻的理解和丰富的开发经验,如果你有开发、审计、培训合作等需求, 加我微信:xlbxiong 。 咨询问题在问答区提问即可,微信好友太多,不看问题,请凉解~