在 Solidity 中使用位图,可以为你节省大量的 Gas 费
- 原文链接: https://soliditydeveloper.com/bitmaps
- 译文出自:登链翻译计划
- 译者:翻译小组 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
有过合约开发经验的同学都可能知道的,以太坊中最昂贵的操作是存储数据(SSTORE)。所以大家也应该一直寻找方法来减少存储需求。让我们来探讨一个特别有用的方法:位图
注:在 Uniswap 的代码中,有很多使用位图来优化 gas 的技巧。
假设我们想存储10个布尔值。通常,我们会用一个简单的布尔数组来实现这一点,例如:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;
contract BitmapTest {
bool[10] implementationWithBool;
function setDataWithBoolArray(bool[10] memory data) external {
implementationWithBool = data;
}
function readWithBoolArray(uint256 index) external returns (bool) {
return implementationWithBool[index];
}
}
而使用 Bitmap 位图,可以用一个 uint10
代替bool数组来实现。uint10将在存储中用10 位(bits 比特位)表示。
例如,这里有一些用比特(bit)表示的十进制数字:
我们可以用一些额外的数学方法来利用这种位表示法。为了得到这个整数的第n位,我们可以使用位运算。
让我们来看看729这个数字,在常规方式下,用一个bool数组来读取第4个bool值,它只是一个array[4]
。对于位图,我们可以通过使用左移运算符<<
将1向左移,来代替创建第二个数字。
现在使用位和运算符&,我们可以得到第n位的值(从0开始计算)。
其结果是
只要这个结果 大于0 ,原数的第n位就是1,所以现在我们可以实现位图:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;
contract BitmapTest {
uint256 implementationWithBitmap;
function setDataWithBitmap(uint256 data) external {
implementationWithBitmap = data;
}
function readWithBitmap(uint256 indexFromRight) external returns (bool) {
uint256 bitAtIndex = implementationWithBitmap & (1 << indexFromRight);
return bitAtIndex > 0;
}
}
你可能已经注意到,我们在上面的实现中选择了uint256
。虽然uint10
在技术上是足够的,但这实际上会导致比使用uint256
更高的Gas成本。这是因为EVM在32个字节的寄存器(256位)上操作,任何低于这个数字的都需要额外的转换。
所以你应该总是选择 uint256
吗?
也不是,这取决于你的使用情况。用一个uint256
,你可以表示256位。那么你想存储的数据是否适合一个256位的布尔数组?如果是,那么就继续使用单个uint256
。
如果不能,例如布尔数组可以任意增长,那么就把位图本身打包成一个数组。我将在最后用一个例子来探讨这两种选择。
让我们先来看看10位例子中的Gas成本差异。用原来的布尔数组,交易的执行成本是:
setDataWithBoolArray
: 140,583 gasReadWithBoolArray
: 1,281 gas现在有了位图,我们可以大大改善这个情况:
setDataWithBitmap
: 78,043 gasreadWithBitmap
: 1,129 gas现在来看看第一个使用场景: 布尔开关通常被用来激活系统中的某些选项。
比方说,你建立了一个像Uniswap一样的DEX,你可以自动触发的交易。你可以根据交易的来源来激活某些设置。例如,你可能有如下开关
NO_FEES
(无交易费)
...
SENDING_FEES_TO_GOVERNANCE
(发送费用到治理)
DELAY_TRADE_EXECUTION
(延迟交易执行)
这些选项可能不会超过256个,所以你可以很容易地将这些选项存储在一个uint256
中。
你可能想向任何参与过你的合约的人支付奖励。这可能是一个任意的大列表。你可以在一个映射中保存每个参与者,或者用一个uint256数组来代替位图。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;
contract ParticipatedWithBitmap {
uint256[] public participantsBitmap;
function setParticipants(uint256[] memory participantsBitmap_) external onlyOwner {
participantsBitmap = participantsBitmap_;
}
function hasParticipated(uint256 bitmapIndex, uint256 indexFromRight) external view returns (bool) {
uint256 bitAtIndex = participantsBitmap[bitmapIndex] & (1 << indexFromRight);
return bitAtIndex > 0;
}
}
欢迎订阅专栏 学习更多 Solidity 高阶优化技巧。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!