什么是智能合约存储布局?

news2025/1/11 12:52:27

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

读者先决条件知识

以下一般先决条件有助于理解本文:

  • 熟悉面向对象的语言
  • 位和字节
  • 十六进制
  • 智能合约
  • 以太坊虚拟机(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字节内存的变量存储的图表。*
*需要少于32字节内存的变量存储的图表。*

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

由于EVM填充,开发者可以直接访问状态变量a和c,但会浪费大量昂贵的存储内存。EVM以最小化读/写值的成本为代价来存储变量。

如果我们仔细考虑合约状态变量的大小和声明顺序,EVM将把变量打包到存储槽中,以减少使用的存储内存量。以上文中的PaddedContract示例为例,我们可以重新排列状态变量的声明顺序,让EVM紧密地将变量打包到存储槽中。

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

https://img-blog.csdnimg.cn/img_convert/c518da2b602bdfc3363fd54074fb3a56.jpeg
https://img-blog.csdnimg.cn/img_convert/c518da2b602bdfc3363fd54074fb3a56.jpeg

在 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哈希存储在存储槽中的图表。
动态大小状态变量如何使用标记槽和keccak256哈希存储在存储槽中的图表。

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

映射存储在智能合约存储中的方式是怎样的?

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

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

下面是一个描述映射如何存储在内存中的图示:

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

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

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/561494.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图像噪声类别

什么是图像噪声? 图像噪声是图像在获取或是传输过程中受到随机信号干扰,妨碍人们对图像理解及分析处理 的信号。 图像噪声的产生来自图像获取中的环境条件和传感元器件自身的质量,图像在传输过程中产 生图像噪声的主要因素是所用的传输信道…

chatgpt赋能Python-python_quad

Python quad是什么? 在Python编程中,Quad是指四元组的缩写。它是一个包含四个元素的有序组。Quad通常在图形学和计算机图像处理中广泛应用。 在Quad中,每个元素都可以是数字或点的组合。 在Python编程中,quad被广泛用于三维计算机…

我最近的练习一些全栈项目

嘿,大家好!作为一个程序员,我突然出现在这里,就像程序里的一个Bug一样突兀。我知道我很久没有发博客了,你们一定在想,这家伙是被代码迷宫困住了还是被Bug们抓走了?实际上,我一直忙于…

一文读懂:什么是数组

大家好,我是三叔,很高兴这期又和大家见面了,一个奋斗在互联网的打工人。 什么是数组 Java是一种面向对象的编程语言,提供了许多数据结构来处理和组织数据。其中,数组是一种常见且强大的数据结构,是存放在…

python+mysql电影推荐系统 影院售票选座系统vue

随着互联网的蓬勃发展,现代社会进入了以计算机为中心的信息时代,计算机技术正以一种前所未有的持久方式改变着世界的面貌。应用网络技术电影推荐系统受到许多用户的重视。网站的开发可以对人们的交流起到重要的作用,因此,为了满足…

印象笔记导出HTML再转markdown的方法

前言 我已经使用6年印象笔记了,越来越依赖它了,现在已经有6000多条笔记了,我就想着如果某一天印象笔记没了,那我这些心血就都没了,所以我想要把笔记全部转为markdown格式,然后自己存储起来。可以选择用百度…

chatgpt赋能Python-python_pendulum

Python Pendulum: 了解更便捷的时间操作 在我们的日常生活中,对于时间的操作极为频繁,不仅仅是时钟和日历,还包括计划、调度等等。Python pendulum正是一个极为优秀的工具,它为我们的时间操作提供了更为灵活且方便的使用体验。 …

chatgpt赋能Python-python_plt_坐标轴

Python plt 坐标轴详解 介绍 在数据可视化领域中,matplotlib.pyplot是一款十分流行的python库。它支持绘制各种类型的图表,例如散点图、折线图、柱状图、饼图等。在绘制各种图表时,一个重要的因素就是如何调整和修改坐标轴以展示数据。本文…

chatgpt赋能Python-python_put

Python PUT:了解PUT请求 介绍 在RESTful API中,PUT请求用于更新资源,它是一种向服务器提交指定资源的请求方法。Python是一种流行的编程语言,其标准库中包含了支持基本HTTP协议的模块。Python的requests库是使用最广泛的HTTP库之…

小航编程题库机器人等级考试理论一级(2023年03月) (含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统(含题库答题软件账号)_程序猿下山的博客-CSDN博客 单选题2.0分 删除编辑 答案:B 第1题关于使用动滑轮说法正确的是?( ) A、是否省力与动滑轮的颜色…

Java --- 期末复习卷

一、单选题 1.所有Java应用程序主类必须有一个名叫( )的方法。[ ] A.method B.main() C.java() D.hello 2.编写并保存了一个Java程序文件之后,( )它。[ …

NTLM认证协议

NTLM(Windows NT LAN Manager)是一种用于身份验证和安全通信的协议。它最初由微软开发,用于早期版本的Windows操作系统。NTLM协议在Windows网络环境中广泛使用,特别是在企业网络中。 NTLM身份验证步骤 NTLM验证是一种Challenge/R…

Java-API简析_java.lang.Class类(基于JDK1.8)(浅析源码)

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/130838927 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

【连续介质力学】涉及积分的定理

涉及积分的定理 分部积分 分部积分: ∫ a b u ( x ) v ′ ( x ) d x u ( x ) v ( x ) ∣ a b − ∫ a b v ( x ) u ′ ( x ) d x \int_a^bu(x)v(x)dxu(x)v(x)|_a^b-\int_a^bv(x)u(x)dx ∫ab​u(x)v′(x)dxu(x)v(x)∣ab​−∫ab​v(x)u′(x)dx 其中,…

Packet Tracer - 综合技能练习(配置 VLAN 间路由、配置静态路由以及默认路由)

Packet Tracer - 综合技能练习 地址分配表 设备 接口 IP 地址 子网掩码 默认网关 VLAN R1 S0/0/0 172.31.1.2 255.255.255.0 不适用 不适用 G0/0.10 172.31.10.1 255.255.255.0 不适用 10 G0/0.20 172.31.20.1 255.255.255.0 不适用 20 G0/0.30 172.31.…

chatgpt赋能Python-python_punctuation

了解Python中的标点符号:了解Punctuation,打造Python高效编程 Python是一种精简的高级编程语言,它可以通过简洁的语法和强大的工具集来处理各种编程任务。在Python中,标点符号是非常重要的组成部分。了解Python中的标点符号将有助…

凹函数和凸函数

凹函数英文concave,凸函数英文concave。 在有些参考资料中,凸函数又称为下凹(concave down)函数,凹函数称为上凹(concave up)函数。 凹函数和凸函数根据判定方法的不同,分为以下两类: 一元函…

Vulkan Tutorial 1 实例和物理设备

目录 0 基本代码 1 Instance 2 验证层 3 物理设备和队列系列 4 逻辑设备和队列 0 基本代码 首先包括LunarG SDK的Vulkan头,它提供了函数、结构和枚举。stdexcept和iostream’头文件被包括在内,用于报告和传播错误 函数将被initVulkan函数调用进入…

C语言生成随机数

目录 概念: 具体运用 实战 1.只使用rand()函数 2.使用rand()函数和srand() 函数 概念: 在实践中,找到真正的随机数字是困难的。数字计算机只能在⼀个限定的范围内和有限的精度下去处理数字。在⼤多数情况下,最好的⽅法是产⽣伪随机数&am…

【AI 孙燕姿】歌声转换技术原理浅析

最近打开B站,首页会推荐很多以【AI 孙燕姿】开头的视频,内容是用孙燕姿的音色去唱其他歌手的歌。出于好(ceng)奇(re)心(du),作者去了解下歌声转换(Singing Voice Conversion,SVC)这个任务。不看不知道&…