跳至内容
0%

如何在 Yul 中读写打包存储变量

发布于 2023年8月1日1 分钟阅读

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 中 MemoryStorageSmart Contract Calls 的工作原理。

Background gradient

构建区块链应用

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