如何在 Yul 中读写打包存储变量
Yul 是一种中间编程语言,可以用来在智能合约内部编写一种形式的汇编语言。在了解了 Yul 的语法以及存储的工作原理之后,接下来需要理解如何在 Yul 中读写打包存储变量。
如何在 Yul 中读写打包存储变量
假设你想把 var5 改为 4。我们知道 var5 位于 slot 3,所以你可能会尝试这样写:
用 getValInHex(3) 查看,我们发现 slot 3 被重写成了:
0x0000000000000000000000000000000000000000000000000000000000000004
这就有问题了,因为现在 var4 被重写成了 0。在这一部分我们会讲解如何读写打包变量,但在此之前需要先多了解一些 Yul 的语法。
如果你还不熟悉这些操作也不用担心,接下来我们会通过示例逐一讲解。
先从 and() 开始。我们拿两个 bytes32 来试一下 and() 操作符,看看它会返回什么。
看输出结果我们会看到:
0x0000000000000000000000009acc1d6aa9b846083e8a497a661853aae07f0f00
原因是 and() 会分别比较两个输入中每一位(bit)的值。
如果两个 bit 都是 1(可以理解为二进制中的激活或未激活状态),那么该 bit 保持原值。
否则就被置为 0。
现在来看 or() 的代码。
这次的输出是:
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
这是因为它只要有一个 bit 是激活状态就会保留。
我们再看看把 mask 变量改成下面这样会发生什么:
0x00ffffffffffffffffffffff0000000000000000000000000000000000000000
可以看到输出变成了:
0x00ffffffffffffffffffffff9acc1d6aa9b846083e8a497a661853aae07f0f00
注意第一个字节是 0x00,因为两个输入在第一个字节上都没有激活的 bit。
xor() 稍有不同。它要求一个 bit 是激活状态(1),另一个 bit 是未激活状态(0)。
下面用代码来演示:
输出是:
0xffffffffffffffffffffffff0000000000000000000000000000000000000000
关键区别在于,只有当 0x00 和 0xff 对齐时,输出中的对应位才是激活的。
shl() 和 shr() 的操作方式非常相似。两者都是将输入值按指定的 bit 数进行移位。shl() 向左移位,shr() 向右移位。
来看一段代码!
输出:
ans1: 0x0000ffff00000000000000000000000000000000000000000000000000000000
ans2: 0x00000000000000000000000000000000000000000000000000000000ffff0000
先看 ans1。我们对它执行了 16 位(2 字节)的 shr()。可以看到最后两个字节从 0xffff 变成了 0x0000,而前两个字节整体向右移了两个字节。理解了这一点,ans2 就很好理解了:只是把移位方向从右改成了左。
在写入 var5 之前,我们先写一个函数来读取 var4 和 var5。
输出分别是预期中的 1 和 2。
要取出 var4,只需用一个 mask 把值设为:
0x0000000000000000000000000000000000000000000000000000000000000001
然后返回一个等于 1 的 uint128。
在读取 var5 时,需要把 var4 的部分右移掉。
处理后得到的就是可以返回的值:
0x0000000000000000000000000000000000000000000000000000000000000002
需要注意的是,有时候一个存储槽(slot)中打包了两个以上的变量,这种情况下需要同时使用移位和掩码才能正确读取某个值。
好了,终于可以把 var5 的值改成 4 了!
第一步是加载存储槽 slot 3。
接下来,我们需要创建一个 mask。
和读取 var4 时类似,我们要把值隔离到:
0x0000000000000000000000000000000000000000000000000000000000000001
下一步是把新值格式化到 var5 在槽中的位置,使其变成:
0x0000000000000000000000000000000400000000000000000000000000000000
和读取 var5 时不同,这次我们要把值向左移位。
最后,我们用 or() 把两个值合并成 32 字节的十六进制数,并把这个值存回 slot 3。
我们可以调用 getValInHex(3) 来检查结果。
它会返回下面这个值,正是我们期望看到的:
0x0000000000000000000000000000000400000000000000000000000000000001
很好,现在你已经知道如何读写打包存储槽了!接下来,去了解一下 Yul 中 Memory、Storage 和 Smart Contract Calls 的工作原理。
相关概览

构建区块链应用
Alchemy 将最强大的 Web3 开发者产品和工具与资源、社区及专业支持结合在一起。


