EVM里有5个地方涉及存储
- 存储中的数据是永久存在的。存储是一个key/value库- 存储中的数据写入区块链,因此会修改状态,这也是存储使用成本高的原因。
- 占用一个256位的槽需要消耗20000 gas
- 修改一个已经使用的存储槽的值,需要消耗5000 gas
- 当清零一个存储槽时,会返还一定数量的gas
- 存储按256位的槽位分配,即使没有完全使用一个槽位,也需要支付其开销
底层指令为 SSTORE
andSLOAD
- 内存是一个字节数组,槽大小位256位(32字节)
- 数据仅在函数执行期间存在,执行完毕后就被销毁
- 读或写一个内存槽都会消耗3gas
- 为了避免矿工的工作量过大,22个操作之后的单操作成本会上涨
- 底层代码
MLOAD
,MSTORE
, andMSTORE8
.CALL
,DELEGATECALL
orSTATICCALL
操作也会通过参数消耗内存
- 调用数据是不可修改、非持久化的区域,用来保存函数参数,其行为类似于内存
- 外部函数的参数必须使用calldata,但是也可用于其他变量
- 调用数据避免了数据拷贝,并确保数据不被修改
- 函数也可以返回使用calldata声明的数组和结果,但是不可能分配这些类型
- 底层代码
CALLDATALOAD
,CALLDATASIZE
andCALLDATACOPY
.
- stack存储函数里的本地变量
- 大小限制:
- 底层指令
USH
,POP
,SWAP
andDUP
- 合约代码,bytecode编码
- 只能读,不可写
- 底层代码,读取代码
CODESIZE
andCODECOPY
. 操作代码EXTERNALCODESIZE
andEXTERNALCODECOPY
.
根据变量在代码里定义的位置,solidity会默认给出存储位置
- # 变量定义为 constant = 相当于合约代码 (= bytecode).
-
-
- # state 变量(在函数外面定义) = in storage by default.
-
-
- # 本地变量local variables (函数体内定义) = in the stack.
通常情况下,代码里无需声明变量类型,solidty会根据定义变量的位置来确定类型
不过,像函数里的struct 和 arrays, 需要声明变量存储类型
- 在存储storage和内存memory(或调用数据calldata)间的赋值将创建一个新的独立拷贝
- 内存memory之间的赋值仅创建引用,这意味着对一个内存memory变量的修改会
同时反应在其他引用相同数据的内存memory变量上- 从存储storage到局部存储变量的赋值,实际上只会给一个引用
- 所有其他赋值通常导致产生新的数据拷贝。例如赋值给状态变量
或位于存储storage的结构类型的局部变量成员时,即使局部变量只是一个
对于数组(无论边长还是定长,诸如 unit256[]),bytes, strings,struct 和 mapping ,需要严格声明存储位置(storage
, memory
or calldata)
以下3种情况,需要声明存储位置
函数参数(函数定义时)
函数内部定义的局部变量
返回类型始终都存储在memory
函数内部,所有数据存储类型都可以定义,无论函数是否可见
不同类型的变量
Inside functions, all three data locations can be specified, no matter the function visibility.
However, assignments between reference types are bound to specific rules. (Here is where it gets complicated and “slightly tongue twisting!”).
- // SPDX-License-Identifier: Apache-2
- pragma solidity ^0.8.0;
-
- contract StorageReferences {
-
- bytes someData;
-
- function storageReferences() public {
- bytes storage a = someData;
- bytes memory b;
- bytes calldata c;
-
- // storage 变量可以引用另外一个 storage 变量(必须初始化过)
- bytes storage d = a;
-
-
- // 如果 storage 的引用没有初始化,将会报错
- // 错误信息 "This variable (refering to a) is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour."
- // f -> e -> (nothing) ??? 不能创建storage 引用指向了未知(nothing)
- /// bytes storage e;
- /// bytes storage f = e;
-
- // storage 指针不能指向 memory 指针(不管memory指针是否已经初始化)
- /// bytes storage x = b;
- /// bytes memory r = new bytes(3);
- /// bytes storage s = r;
-
- // storage 指针不能指向 calldata 指针(不论calldata指针是否已经初始化)
- /// bytes storage y = c;
- /// bytes calldata m = msg.data;
- /// bytes storage n = m;
- }
-
- }
参考