以太坊合约中数据的底层存储

本文深入探讨了以太坊智能合约中的数据如何在底层存储。解释了所有数据存储在一个巨大数组中,详细分析了固定长度数据如结构体、定长数组的存储方式,以及动态数组和mapping这类不定长数据的存储机制。通过示例代码,展示了如何计算动态数组和mapping元素的具体位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C/C++程序员在进行Solidity开发的时候往往会想:Solidity中的结构体是怎么存储的呢?会不会进行数据对齐呢?怎样定义结构体会使访问效率改一些呢?
我们来探索下以太坊合约的数据在底层的存取机制。参考文章:这个以及它的翻译

巨大的数组

合约中的所有的数据都存在这个巨大的数组中,以下简称为“大数组”

  • 数组长度为2^256 - 1
  • 数组每个元素32Byte。
  • 数组是稀疏的——不会一下子生成整个数组存在链上
  • 值为0的元素不需要存储
    在这里插入图片描述

本文中我们使用如下合约代码作为例子:

contract StorageTest {
    uint256 a;     // slot 0
    uint256[2] b;  // slots 1-2

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;       // slots 3-4
    Entry[] d;     // slot 5 for length, keccak256(5)+ for data

    mapping(uint256 => uint256) e;    // slot 6, data at h(k . 6)
    mapping(uint256 => uint256) f;    // slot 7, data at h(k . 7)

    mapping(uint256 => uint256[]) g;  // slot 8
    mapping(uint256 => uint256)[] h;  // slot 9
}
固定长度的数据结构

普通内置类型(uint8, uint32,bool 等等)、定长数组、结构体变量,都属于固定长度的数据。

  • 固定长度的数据结构使用声明顺序(原文称之为slot)做数组的索引(下标),存储在大数组中
  • 结构体占用的数组元素个数取决于成员的个数。
  • 编译时刻即确定了这些数据存储的位置
  • 示例代码中,a、b、c为固定长度的数据,他们在大数组中的位置如下图所示
    在这里插入图片描述
不定长度的数据结构

动态数组、mapping属于不定长数据结构,这两类结构在编译期无法确定空间大小。

  • 动态数组的长度存在大数组中,位置为该动态数组声明的序号(原文称之为slot)。
  • 动态数组的第一个元素存储在 hash(序号)处,序号即为该数组被声明的序号。
  • mapping成员在大数组中的位置,由 “该变量的成员的key” + “该变量声明的位置” 合起来的hash确定。
    举例:
  • 动态数组d的slot为5(c占用了两个slot)。故大数组的下标5处存放d的长度。
  • 动态数组的第一个元素的下标为: keccak256(5)
  • mapping e 的slot为6,但大数组的下标6处存放的值恒为0、
  • mapping内成员 e[“hello”] 存放的位置为 keccak256(“hello”,6)
  • 下图为 e[123] 以及 e[42]在大数组中的位置,分别为 keccak256(123,6)与 keccak256(42,6)
    在这里插入图片描述
复合数据结构

通过上述分析,我们可以写出定位动态数组元素位置的函数与定位mapping元素位置的函数

function arrLocation(uint256 slot, uint256 index, uint256 elementSize)public pure returns (uint256){
    return uint256(keccak256(slot)) + (index * elementSize);
}
function mapLocation(uint256 slot, uint256 key) public pure returns (uint256) {
    return uint256(keccak256(key, slot));
}

示例代码中的g、h便是典型的复合数据结构。

  • g[“haha”][5]元素的位置这样计算: index = arrLocation(mapLocation(8, “haha”), 5, 1);
  • h[4][“hehe”]元素的位置这样计算: index = mapLocation(attLocation(9, 4, 1), “hehe”);
  • 规则总结:先计算复合结构本身在大数组中的位置(slotFather)、再根据slotFather计算子结构的位置,一直递推到最终的基本元素。

合约数据最终会存储在哪里?

首先澄清一下,合约数据指的是合约内部的数据,而不是指合约本身的字节码。
答案是:全节点的EVM中

合约内的数据的存数本质上是一次代码的执行,EVM便是负责执行代码的。块只记录最终的Merkel根。事实上,不光合约内的数据没有直接记录在链上的块中,连最基本的账户的余额这个信息也没有直接记录在链上的块中。块上只保存TransactionToot、StateRoot、ReceiptToot 三个Merkel root便可验证合约数据、账户余额等信息。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值