关于区块链:什么是智能合约存储布局

43次阅读

共计 3848 个字符,预计需要花费 10 分钟才能阅读完成。

本指南将解释智能合约中存储的数据。合约存储布局是指管制合约存储变量在长期内存中排布的规定。

读者先决条件常识

以下个别先决条件有助于了解本文:

  • 相熟面向对象的语言
  • 位和字节
  • 十六进制
  • 智能合约
  • 以太坊虚拟机(EVM)
  • 哈希
  • 无符号整数
  • 动态和动静数组
  • 映射
  • 其余变量类型(例如 int8,布尔,地址等)
  • 通过 Solidity 的 struct 关键字申明的用户定义类型
  • 动态大小变量和动静大小变量之间的区别
  • Solidity 中 Memory、Storage 和 Calldata 之间的区别

什么是合约存储布局,为什么它很重要?

  • 合约存储布局是指规定合约存储变量在长期内存中的排列形式的规定。简直所有的智能合约都有须要长期存储的状态变量。

了解合约存储布局对以下方面很重要:

  • 编写高效的燃气合约,因为在区块链上将数据存储在长期内存中是低廉的。在本文前面,咱们将具体介绍如何应用存储布局规定来最大化燃气节俭。
  • 解决应用代理或钻石模式或其余各种模式的合约。
  • 审计合约的安全性。不了解合约存储布局规定可能会使咱们的合约容易受到攻打。

除咱们定义的公共函数和变量之外,状态变量的布局也被认为是内部接口的一部分。

智能合约开发人员无奈间接管制合约内部接口的这个方面,它由编译器管制。然而,如果编译器版本更改并且合约存储布局的规定发生变化,开发人员须要理解这一点。

内存如何在 EVM 中应用?

智能合约是在区块链上运行的计算机程序。程序包含函数和数据(也称为变量或参数),这些函数操作数据。函数应用的数据须要存储在计算机的内存中。在这种状况下,计算机是 EVM。

Solidity 内存类型

在 Solidity 中,有 3 种不同的内存类型,开发人员能够应用它们来批示 EVM 存储其变量的地位:memory,calldata 和 storage。

还有对于变量存储地位的有效期限以及变量应用形式的规定。例如,变量是否能够被读取?变量是否能够被写入?

1. Memory

开发人员会在函数中应用“memory”关键字来定义变量和参数。这些类型的变量只存在于函数执行期间。当函数运行完结时,存储在内存区域中的变量和参数会隐没。

对于有编程背景的人来说,“memory”是最为相熟的内存类型。

2. Calldata

calldata memory类型与 memory 类型 十分类似,并且在申明组成函数签名的动静大小参数的内部函数时必须应用它。

memory变量和 calldata 变量之间的区别在于,calldata 变量援用的是只读内存区域。

3. Storage

Solidity 的最终类型是存储类型。存储内存 是合约的长期存储区域,在函数或事务执行实现后存储变量。

本文的重点是对于存储变量如何布局的 EVM 规定。

长期存储内存的概念与其余两种内存类型造成鲜明对比。合约的状态变量(即在合约内申明但不在函数内申明的变量)存储在存储内存区域中。

存储内存类型 的概念是区块链所特有的,因为在智能合约中工作时,通过区块链的加密封存属性,存储的数据是无奈篡改的。在其余编程环境中,如果咱们想要长期存储变量,通常会将这项工作转移到文件系统或数据库中。但在区块链上,智能合约的代码和数据都长期保留在区块链上。

什么是存储器?

每个合约都有本人的存储区域,这是一个长久的、可读写的内存区域。合约只能从本人的存储区读取和写入。合约的存储被分成 2²⁵⁶个 32 字节大小的槽位。槽位是间断的,由索引援用,从 0 开始,到 2²⁵⁶完结。所有槽位都初始化为 0。

EVM 存储器只能间接拜访这些 32 字节大小的槽位。

2²⁵⁶个槽位!

每个合约的存储区域具备比宇宙中所有星星都多的槽位。咱们在这里解决的是真正的天文数字。

因为存储容量微小,合约的存储能够被认为是虚构的。这意味着,如果您读取一个随机槽位,它很可能为空 / 未初始化。读取这样的槽位将返回一个值为 0。EVM 实际上并没有存储所有这些 0,但它会跟踪哪些槽位正在应用,哪些没有。当您拜访一个未应用的槽位时,EVM 晓得并将返回 0。

为什么 EVM 的设计者会给合约一个如此大的存储区域?

合约存储区域如此之大的起因与动静大小的状态变量以及哈希如何用于计算状态变量的存储槽无关。

状态变量如何存储在智能合约存储槽中?

Solidity 将主动将您合约定义的每个状态变量映射到存储槽中,依照申明状态变量的程序,从槽 0 开始。

这个想法的简略可视化如下所示:

状态变量映射到存储槽的图示。

这里咱们能够看到变量 a、b 和 c 如何从申明程序映射到它们的存储槽。要理解存储变量实际上如何被编码并存储在二进制级别的槽中,咱们须要深刻开掘并了解字节序、字节打包和字节填充的概念。

什么是字节序?

字节序是指计算机在内存中存储多字节值(例如:uint256、bytes32、address)的形式,有两种字节序:大端序和小端序。

大端序 → 数据类型的二进制示意的最初一个字节先存储

小端序 → 数据类型的二进制示意的第一个字节先存储

例如,取十六进制数 0x01e8f7a1,这个十六进制示意的十进制数是 32044961。这个值在内存中的存储形式是什么?视觉上,依据字节序的不同,它看起来像上面的其中一个图表。

计算机如何在内存中存储多字节值的图示。

Endian-ness 在以太坊中的应用形式是怎么的?

  • 以太坊应用大端和小端两种格局,应用的格局取决于变量类型。
  • 大端仅用于字节和字符串类型。这两种类型在合约存储槽中的行为与其余变量不同。
  • 小端用于其余任何类型的变量。一些例子是:uint8,uint32,uint256,int8,boolean,address 等等 …

状态变量在智能合约存储槽中如何填充和打包?

为了将须要少于 32 字节内存的变量存储在存储器中,EVM 将应用 0 填充值,直到应用了所有 32 字节的槽,而后存储填充值。

许多变量类型比 32 字节的槽大小要小,例如:bool,uint8,address。以下是当咱们想要存储须要少于 32 字节内存的类型的状态变量时它是什么样子的图示:

须要少于 32 字节内存的变量存储的图表。

因为 EVM 填充,开发者能够间接拜访状态变量 a 和 c,但会节约大量低廉的存储内存。EVM 以最小化读 / 写值的老本为代价来存储变量。

如果咱们认真思考合约状态变量的大小和申明程序,EVM 将把变量打包到存储槽中,以缩小应用的存储内存量。以上文中的 PaddedContract 示例为例,咱们能够重新排列状态变量的申明程序,让 EVM 严密地将变量打包到存储槽中。

上面是一个示例,即 PackedContract,它只是对 PaddedContract 示例中变量的从新排序:

在 EVM 中,变量将从存储槽的右侧开始打包,对于每个后续能够打包到同一槽中的状态变量,将向左挪动。在这里,咱们能够看到变量 a 和 c 曾经被打包到存储槽 0 中。因为变量 b 的大小不能适应槽 0 中残余的空间,EVM 将变量 b 调配给槽 1。

与填充变量相比,打包变量最大水平地缩小了存储使用量,然而读取 / 写入这些变量的代价更高。额定的读写老本来自于须要进行额定位操作以读取 a 和 c。例如,要读取 a,EVM 须要读取存储槽 0,而后掩码除属于变量 a 的所有位。

严密打包变量的存储气体节俭能够显着减少读取 / 写入它们的老本,如果打包的变量通常不一起应用。例如,如果咱们须要常常读取 c 而不读取 a,则最好不要严密打包变量。这是开发人员编写合同时必须思考的设计问题。

用户定义的类型(构造体)如何存储在合约内存中?

在咱们有一组逻辑上属于一起的变量并且常常作为一个单元进行读写的状况下,咱们能够通过 Solidity 的 struct 关键字定义一个用户定义的类型,并利用上述字节打包的常识,以取得最无效的燃气应用,以便在存储变量的应用和读写方面进行优化。

构造体在智能合约存储内存中如何打包和存储的图表。

当初咱们能够受害于严密字节打包和分组读写状态变量 someStruct。

动态大小的状态变量存储在它们对应的槽位中。如果一个动态大小的变量占用 2 个槽位(64 字节)并存储在槽位 s 中,那么下一个存储变量将存储在槽位 s + 2 处。

例如,在以下合约中,状态变量的存储布局如下:

动态大小状态变量如何存储在存储插槽中的图表。

动静大小的状态变量如何存储在智能合约内存中?

动静大小的状态变量与动态大小的状态变量调配的插槽雷同,但调配给动静状态变量的插槽只是标记插槽。也就是说,插槽标记了动静数组或映射存在的事实,但插槽不存储变量的数据。

对于动静大小的变量,为什么不能像动态大小的变量一样间接存储其数据到调配的插槽中?

因为如果向变量增加新项,将须要更多的插槽来存储其数据,这意味着后续的状态变量将被推到更远的插槽。

通过应用标记插槽的 keccak256 哈希,咱们能够利用微小的虚拟存储区域来存储变量,而不会呈现动静大小的变量增长并与其余状态变量重叠的危险。

对于动静大小的数组,标记插槽还存储数组的长度。标记插槽号的 keccak256 哈希是指向数组值在合同存储布局中寄存地位的“指针”。

例如:

动静大小状态变量如何应用标记槽和 keccak256 哈希存储在存储槽中的图表。

映射存储在智能合约存储中的形式是怎么的?

对于 mappings,标记槽只标记了有映射的事实。要找到给定键的值,应用公式 keccak256(h(k).p),其中:

  • 符号. 示意字符串连贯
  • p 是状态变量在智能合约中的申明地位
  • h()是利用于键的函数,取决于键的类型
  • 对于值类型,h()返回填充为 32 个字节的值
  • 对于字符串和字节数组,h()只返回未填充的数据

上面是一个形容映射如何存储在内存中的图示:

智能合约存储槽中映射如何存储的图表。

本文由 mdnice 多平台公布

正文完
 0