Solidity中数据的布局

news/2025/1/22 0:46:04/文章来源:https://www.cnblogs.com/WZM1230/p/18684806

1.Storage中

1.1-基本原则(基本的值类型)

在这里面的变量都是独立的,互相不影响,所以非常"安全";

均存储在slot中,slot有2^256个,每个长度256位;

0开始连续往后存储(除动态数组和映射),当有连续几个都小于256位时,会尝试将它们放在同一个slot中;

同时也遵循以下规则:

  • 比如有小于256位的数据需要存储,会存在低阶位置(右边)
  • Value types使用同样多的位数去存储(比如uint128,会使用128位去存储它,无论它何值)
  • 当前slot不够存,则会存在下一个slot
  • StructsArray会在一个新的slot开始存,并且它们的值会打包的很紧密
  • StructArray后面的数据,会单开一个slot

1.2-Dynamic Arrays & mapping

由于动态数组和映射的不可预测性,它们不像正常的状态变量一样紧跟着在后面存,而是占用一个新的slot,再根据它们自己的规则去存;

假设有一个动态数组或者映射,它占用的是slot的第p号位置;

对于动态数组而言:

slot p的位置存储着数组长度,在slot Keccak256(p)的位置开始存储数组元素;

Key Value
slot 0 其他数据
... ...
slot p 数组长度
... ...
slot Keccak256(p) 元素1
slot Keccak256(p) + 1 元素2
... ...
function getHash() public pure returns (bytes memory, bytes32) {return (abi.encodePacked(uint256(0)), keccak256(abi.encodePacked(uint256(0))));// bytes: 0x0000000000000000000000000000000000000000000000000000000000000000// bytes32: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
}
function getHash() public pure returns (bytes memory, bytes32) {return (abi.encodePacked(uint256(1)), keccak256(abi.encodePacked(uint256(1))));// bytes: 0x0000000000000000000000000000000000000000000000000000000000000001// bytes32: 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
}

image-20250119181650767

同样的,若是每个元素数据小于256位,和上面的规则一样,紧凑的往下写,从小端往大端写:

image-20250119213722968

对于映射而言:

slot p的位置会存储全0数据,代表着此处是一个映射的开始;

映射的key会存在一个经过计算的slotslot Keccak256(h(k) . p),映射的value则会被直接存在对应slotvalue中;

其中,h(k)中的h代表类型,addressuint256等等,k就是映射的key值;

.代表连接,solidity就可以用abi.encode(a, b)来实现相同功能;

p是当前映射首个元素的slot编号;

Key Value
slot 0 其他数据
... ...
slot p 全0(占用,代表这边是个映射的起始)
... ...
slot Keccak256(h(key1) . p) key1.value
... ...
slot Keccak256(h(key2) . p) key2.value
... ...
例子1(多个连续映射)

两个映射,分别存储以下数据:

contract Exp {uint256 public a = 0x1;mapping (address => uint256) public b;mapping (address => uint256) public c;function addMapping() public {b[address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4)] = 0xb11;c[address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB)] = 0xc11;b[address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)] = 0xb22;c[address(0x617F2E2fD72FD9D5503197092aC168c91465E7f2)] = 0xc22;b[address(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db)] = 0xb33;c[address(0x17F6AD8Ef982297579C203069C1DbfFE4348c372)] = 0xc33;}
}

这是该合约调试的结果:

  • 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
    • key:0x0000000000000000000000000000000000000000000000000000000000000000
    • value:0x01
  • 0x34a2b38493519efd2aea7c8727c9ed8774c96c96418d940632b22aa9df022106:Object
    • key:0x36306db541fd1551fd93a60031e8a8c89d69ddef41d6249f5fdc265dbc8fffa2
    • value:0x0000000000000000000000000000000000000000000000000000000000000b11
  • 0xf5e7424a6e4079071264af0bf2d5ca3530102dfccdbd193e7d54d0d4400332e0:Object
    • key:0x4f3049662ef87b9e630d98ff73343a6afe9cd9c07fbd5dfe02b76d8fef856cb2
    • value:0x0000000000000000000000000000000000000000000000000000000000000c11
  • 0x94b29c01ed483e694a7ecf386d384987d4d3e9d4e6c476f5b97302b23ff871c9:Object
    • key:0x9d4d959825f0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
    • value:0x0000000000000000000000000000000000000000000000000000000000000b22
  • 0x3a885bd4536bf506a6b539c1ecc761dabfeafcbdc0e1f368493815c2bebe5da6:Object
    • key:0xd9ea8f4d7f096f77b3e4169a2c25f1eb750f25bc40cad854f045cfb680dbd21d
    • value:0x0000000000000000000000000000000000000000000000000000000000000c22
  • 0x709c4b690342f2b3b4553bcc137e89412180007fbe4ac36592d4bb2c9003b39b:Object
    • key:0xe20f19dc6931eb9e42fe3f21abe1a9ef59942d8e586871d88564d0d0b63a5e5c
    • value:0x0000000000000000000000000000000000000000000000000000000000000b33
  • 0x16086c92369a425dfd790c80c90e5c1364cccb1a6ffc51553bc237bad3e37c09:Object
    • key:0xeb070bdf6b9b9e994516da4d935f3719e4f3890dd106aba68bfe82094e359b19
    • value:0x0000000000000000000000000000000000000000000000000000000000000c33

下面我们来验证,按照推断,映射b应该从slot 1开始,映射c应该从slot 2开始;

// 映射b的第一个元素,bytes32: 0x36306db541fd1551fd93a60031e8a8c89d69ddef41d6249f5fdc265dbc8fffa2
function getHash_b1() public pure returns (bytes32){return keccak256(abi.encode(address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), uint256(1)));
}
// 映射b的第二个元素,bytes32: 0x9d4d959825f0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
function getHash_b2() public pure returns (bytes32){return keccak256((abi.encode(address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2), uint256(1))));
}
// 映射c的第一个元素,bytes32: 0x4f3049662ef87b9e630d98ff73343a6afe9cd9c07fbd5dfe02b76d8fef856cb2
function getHash_c1() public pure returns (bytes32){return keccak256((abi.encode(address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB), uint256(2))));
}
// 映射c的第二个元素,bytes32: 0xd9ea8f4d7f096f77b3e4169a2c25f1eb750f25bc40cad854f045cfb680dbd21d
function getHash_c2() public pure returns (bytes32){return keccak256((abi.encode(address(0x617F2E2fD72FD9D5503197092aC168c91465E7f2), uint256(2))));
}
例子2(多重映射)

一个三重映射:address => uint256 => uint256

contract Exp {uint256 public a = 0x1;mapping (address => mapping (uint256 => uint256)) public data;function addMapping() public {data[address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4)][3] = 4;data[address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)][7] = 8;}
}

调试的结果:

  • 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
    • key:0x0000000000000000000000000000000000000000000000000000000000000000
    • value:0x01
  • 0x6d56a438fa5dd6d0bbc7c165fa370e93f31dc10c1348391ce17cdc6a22c94479:Object
    • key:0xf72ff45ebf51ff9fe6584c33c990ae8cc8de482bd70289044a93604d667fc9ad
    • value:0x0000000000000000000000000000000000000000000000000000000000000004
  • 0xc116603da12a240205c7c8b682cead30acae1a1f283e7a40647ef98139f92d02:Object
    • key:0x287bec35ef93239ef6c83981c0922f4e9288d308bc2bfa6a0d60780af18028b6
    • value:0x0000000000000000000000000000000000000000000000000000000000000008

验证,从前往后一个一个算,Keccak256(address(k) . p) --> Keccak256(uint256(k) . Keccak256(address(k) . p))

// 映射的第一个元素:bytes32: 0xf72ff45ebf51ff9fe6584c33c990ae8cc8de482bd70289044a93604d667fc9ad
function getHash_data1() public pure returns (bytes32){// 先计算[address(k), p]bytes32 temp = keccak256(abi.encode(address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), uint256(1)));// 再计算{uint256(k), [address(k), p]}return keccak256(abi.encode(uint256(3), temp));
}
// 映射的第二个元素:bytes32: 0x287bec35ef93239ef6c83981c0922f4e9288d308bc2bfa6a0d60780af18028b6
function getHash_data2() public pure returns (bytes32){bytes32 temp = keccak256(abi.encode(address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2), uint256(1)));return keccak256(abi.encode(uint256(7), temp));
}
例子3(string和bytes)

注意:

对于基本值类型,h都是将其扩充为256位;

对于stringbyte arrayh不将其扩充,保持原样;

示例合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// import "./Ethernaut.sol";contract Exp {uint256 public a = 0x1;mapping (string => uint256) public data;// key:0x3760e61363bd6582908f2b38ae298a6e2239ed14711faf5dace0ac9362a82e59// value:0x0000000000000000000000000000000000000000000000000000000000000002function addMapping() public {data["WZM"] = 2;}// 0:bytes: a 0x575a4d// 1:bytes: b 0x0000000000000000000000000000000000000000000000000000000000000001// 2:bytes: c 0x575a4d0000000000000000000000000000000000000000000000000000000000000001function getbytes() public pure returns (bytes memory a, bytes memory b, bytes memory c){a = (abi.encodePacked("WZM"));b = (abi.encodePacked(uint256(1)));c = abi.encodePacked("WZM", uint256(1));}// bytes32: 0x3760e61363bd6582908f2b38ae298a6e2239ed14711faf5dace0ac9362a82e59function getHash() public pure returns (bytes32){return keccak256(abi.encodePacked("WZM", uint256(1)));}
}

1.3-string & bytes

这两个类型编码的方式相同,就和bytes1[]类似,但不同长度的编码方式不一样;

长度不超过31字节

新起一个slot,编号不需要计算,前31个字节存放数据,最后一个字节存放数据长度 * 2(也就是有多少个hex)

例子:

contract Exp {uint256 public a = 0x1;bytes public b = "WZM";string public c = "WZM1230";
}

结果:

  • 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
    • key:0x0000000000000000000000000000000000000000000000000000000000000000
    • value:0x0000000000000000000000000000000000000000000000000000000000000001
  • 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6:Object
    • key:0x0000000000000000000000000000000000000000000000000000000000000001
    • value:0x575a4d0000000000000000000000000000000000000000000000000000000006
  • 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace:Object
    • key:0x0000000000000000000000000000000000000000000000000000000000000002
    • value:0x575a4d313233300000000000000000000000000000000000000000000000000e

长度超过31字节

和动态数组几乎一样,在slot p的位置存储着长度length * 2 + 1,在slot Keccak256(p)的位置开始存储数组元素;

例子:

contract Exp {uint256 public a = 0x1;// length = 33bytes public b = "WZMWZMWZMWZMWZMWZMWZMWZMWZMWZMWZM";// length = 35string public c = "WZM1230WZM1230WZM1230WZM1230WZM1230";
}

结果:

  • 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
    • key:0x0000000000000000000000000000000000000000000000000000000000000000
    • value:0x0000000000000000000000000000000000000000000000000000000000000001
  • 0xb5d9d894133a730aa651ef62d26b0ffa846233c74177a591a4a896adfda97d22:Object----2.slot Keccak(1),存储数据,和bytes1[]一样的存储方式
    • key:0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
    • value:0x575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a
  • 0xea7809e925a8989e20c901c4c1da82f0ba29b26797760d445a0ce4cf3c6fbd31:Object----3.slot Keccak(1)+1,继续存储数据
    • key:0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7
    • value:0x4d00000000000000000000000000000000000000000000000000000000000000
  • 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6:Object----1.slot 1,存储 长度(length * 2 + 1)
    • key:0x0000000000000000000000000000000000000000000000000000000000000001
    • value:0x0000000000000000000000000000000000000000000000000000000000000043
  • 0x1ab0c6948a275349ae45a06aad66a8bd65ac18074615d53676c09b67809099e0:Object
    • key:0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace
    • value:0x575a4d31323330575a4d31323330575a4d31323330575a4d31323330575a4d31
  • 0x2f2149d90beac0570c7f26368e4bc897ca24bba51b1a0f4960d358f764f11f31:Object
    • key:0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf
    • value:0x3233300000000000000000000000000000000000000000000000000000000000
  • 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace:Object
    • key:0x0000000000000000000000000000000000000000000000000000000000000002
    • value:0x0000000000000000000000000000000000000000000000000000000000000047

2.Memory中

仅有stringbytesarraystruct使用memory;

以太坊内存中保留了前四个slot,分别是:

  • 0x00 - 0x1f, 0x20 - 0x3f:两个slot,存储散列方法
  • 0x40 - 0x5f:当前分配的内存大小
  • 0x60 - 0x7f:zero slot(此部分不能被写入)

每个元素均占用一个slot,这点和在Storage中不同:

uint8[4] a;

Storage中,仅占用1个slot;而在Memory中,却占用4个slot

例子:

contract Exp {struct Wallet{address addr;uint16 num;}function Demo() public {bytes memory a = "123";bytes8[] memory b = new bytes8[](0x3);string memory c = "WZM1230";Wallet memory wallet = Wallet(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db, 0x123);}
}

image-20250122003517584

参考:Solidity Documents-INTERNALS

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探讨视图更新:提升数据库灵活性的关键技术

title: 深入探讨视图更新:提升数据库灵活性的关键技术 date: 2025/1/21 updated: 2025/1/21 author: cmdragon excerpt: 在现代数据库的管理中,视图作为一种高级的抽象机制,为数据的管理提供了多种便利。它不仅简化了复杂查询的过程,还能用来增强数据的安全性,限制用户对…

标准制修订信息管理系统:开启标准化管理的智能新时代

在当今快速发展的商业环境中,标准化工作对于企业的高效运营、质量提升以及行业竞争力的增强至关重要。然而,传统标准化管理方式往往面临着诸多痛点,如缺乏完善的动态管理体系、信息分散、查询与实施监督困难等。针对这些挑战,标准制修订信息管理系统应运而生,它以强大的技…

Catlike Coding Custom SRP笔记 - 平行光

原文链接:Directional Lights效果图 CustomRenderPipelineAsset.cs[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")] public class CustomRenderPipelineAsset : RenderPipelineAsset {public bool useDynamicBatching = true; //启用动态合批pu…

uart串口的低速通信基础知识及模块代码(来自正点原子P15)

正点原子P15在PL端的uart电路参考,PS端uart和PL端一致,这里不做重复,uart电路由电脑端进行供电,即uart和主芯片之间除利用uart_tx和uart_rx通信外是独立的。从上图中可以看到,FPGA芯片的PL_UART1_TX连接到CH340的RXD管脚,FPGA芯片的PL_UART1_RX连接到 CH340 的 TXD 管脚,…

I/O框架

流的概念、流的分类、字节流、字符编码、字符流、打印流、转换流和File类。流的概念概念:内存与存储设备之间传输数据的通道。流的分类按方向【重点】输入流:将<存储设备>中的内容读入到<内存>中。 输出流:将<内存>中的内容写入到<存储设备>中。按单…

网站向顾客发送电子邮件

首先说一下,针对顾客未登录就可下单这个功能,为了使用户可以实时知晓货品的物流状态,使用了advance shipment tracking这个插件,这个插件不仅可以显示货品的物流信息,还可以在货品物流状态更新时向顾客发送电子邮件,这样就实现了顾客在未登录时就可以知道自己购买的商品的…

2025.1.20——1300

2025.1.20——1300A 1300 You are given a binary string \(s\). A binary string is a string consisting of characters 0 and/or 1. You can perform the following operation on \(s\) any number of times (even zero):choose an integer \(i\) such that \(1 \le i \le |…

制作docker 镜像上传到docker hub仓库

注册docker hub账号 https://hub.docker.com/ 参照此篇:https://www.cnblogs.com/yjlch1016/p/8998479.htmldocker hub上创建仓库https://hub.docker.com/repositories 本地制作镜像并上传在本地登陆 docker hub 帐号docker login将容器commit 成镜像,可以先用docker …

虚拟现实国标解读系列(一)帧率

大家好,我是ij(我的网名),中文名叫林志宏。 遵循我一贯的年底必摸鱼的习惯,我打算开始摸鱼来水一些文章,安慰下自己过去的一年。 有关注过我,听我吹过牛的都知道,我在几年前,也不知道几年前, 反正long long ago,我参与起草过一份牛逼的测试标准,国标GB/T 38258《虚…

【Linux网络】深入理解linux内核网络性能优化

一、网络请求优化 1.1 减少不必要的网络IO 在系统设计与开发过程中,应尽量避免不必要的网络I/O操作,尤其是在可以通过本地进程或内存内完成的场景下,避免使用网络通信来实现。网络虽然是现代分布式系统中的核心组件,能够连接不同模块、简化开发流程,并支持大规模系统的构建…

【Java开发】简化Maven项目依赖:优雅去除未使用Jar包

一、为什么要做这件事? 自从我踏入职场,便历经了技术革新的数次浪潮。从最初的.Net Framework、Winform、WPF,到Asp.Net MVC、Asp.Net MVC WebApi,再到Asp.Net Core 2.x的广泛应用,我始终深耕于.net领域。 然而,随着技术的不断演进,我逐渐发现.net相关的工作机会变得稀少…

《操作系统真相还原》实验记录2.7——生产者与消费者问题

本节实现内容如下: ① 环形缓存区的结构体创建; ② 环形键盘缓冲区的创建; ③ 生产者消费者问题剖析;一、生产者与消费者问题简述我们知道,在计算机中可以并行多个线程,当它们之间相互合作时,必然会存在共享资源的问题,这是通过“线程同步”来解决的,而诠释“线程同步…