您现在的位置是:首页 >学无止境 >solidity笔记之数据位置&EVM网站首页学无止境
solidity笔记之数据位置&EVM
数据储存位置
solidity数据位置
所有复杂类型都必须给出明确的数据存储位置,memory,storage,calldata。
storage
storage的数据永久存储,键值的形式写在区块链中,256位的插槽gas成本20000,修改值花费5000,清理插槽退还一定gas,不完全占用256位大小的存储字段也又成本
memory
字节数组,256位32个字节。数据在函数执行期间存在,执行结束删除,不保存。读或者写都需要3gas。22次读写后成本上升。
calldata
calldata不可修改,非持久性,用于存储函数参数,行为类似memory,外部函数参数需要calldata。不能复制 修改,可与从函数中返回但不能赋值。
赋值的工作方式
- storage 和 memory之间的赋值总是独立创建副本
- 从m到m的赋值 仅创建引用
- 从s到本地s变量的赋值也仅赋值引用
- 所有其他到s的赋值总是被复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;
contract DataLocationTest {
uint[] stateVar = [1,4,5];
function foo() public{
// case 1 : 从存储中加载到内存
uint[] memory y = stateVar; // 复制 stateVar 到 y
// case 2 : from memory to storage
y[0] = 12;
y[1] = 20;
y[2] = 24;
stateVar = y; // copy the content of y to stateVar
// case 3 : from storage to storage
uint[] storage z = stateVar; // z is a pointer to stateVar
z[0] = 38;
z[1] = 89;
z[2] = 72;
}
}
从storage赋值到memory中
uint[] stateVar = [1,4,5]
是storage位置,uint[] memory y = stateVar;
是从storage赋值到memory中,在编译器dubug step by step可以看到需要sload到栈中三次,每一次的SLOAD gas都是在2100左右,成本很高
从memory赋值到storage中
修改完存储在memory中的副本再保存回storage时,同样消耗大量gas,四个SSTORE操作符大约13000。
从storage赋值到storage中
在此方式中将会创建新的局部变量,直接引用storage的数据而不是创建新副本,引用然后更新状态,总体花费小很多
优化策略
-
sstore指令第一次写入一个新位置需要花费20000 gas
-
sstore指令后续写入一个已存在的位置需要花费5000 gas
-
sload指令的成本是500 gas
-
大多数的指令成本是3~10 gas
-
通过使用相同的存储位置,Solidity为存储第二个变量支付5000 gas,而不是20000 gas,节约了15000 gas
-
应该可以将两个128位的数打包成一个数放入内存中,然后使用一个’sstore’指令进行存储操作,而不是使用两个单独的sstore命令来存储变量a和b,这样就额外的又省了5000 gas。
-
一笔交易的每个零字节的数据或代码费用为 4 gas
-
一笔交易的每个非零字节的数据或代码的费用为 68 gas
-
来计算下两个表示方式所花费的gas成本:
0x200000000000000000000000000000000字节码包含了很多的0,更加的便宜。
(1 * 68) + (32 * 4) = 196608160020a字节码更短,但是没有0。
5 * 68 = 340更长的字节码序列有很多的0,所以实际上更加的便宜
EVM架构
code存储contract代码的二进制源码,部署合约的时候data字段也就是合约内容会存储在EVM的code中;
storage为不易失的链上数据,而stack,args,memory里存储的数据是易失的。
Stack
整理几个EVM操作栈的指令,分析合约的时候会用到:
- Pop指令(操作码0x50)用来从栈顶弹出一个元素;
- PushX指令用来把紧跟在后面的1-32字节元素推入栈顶,Push指令一共32条,从Push1(0x60)到- - Push32(0x7A),因为栈的一个字是256bit,一个字节8bit,所以Push指令最多可以把其后32字节 的元素放入栈中而不溢出。
- DupX指令用来复制从栈顶开始的第1-16个元素,复制后把元素在推入栈顶,Dup指令一共16条,从Dup1(0x80)到Dup16(0x8A)。
- SwapX指令把栈顶元素和从栈顶开始数的第1-16个元素进行交换,Swap指令一共16条,从Swap1(0x01)一直到Swap16(0x9A)。
Args
也叫做calldata,是一段只读的可寻址的保存函数调用参数的空间,与栈不同的地方的是,如果要使用calldata里面的数据,必须手动指定偏移量和读取的字节数。
EVM提供的用于操作calldata的指令有三个:
- calldatasize返回calldata的大小。
- calldataload从calldata中加载32bytes到stack中。
- calldatacopy拷贝一些字节到内存中。
Memory
Memory是一个易失性的可以读写修改的空间,主要是在运行期间存储数据,将参数传递给内部函数。内存可以在字节级别寻址,一次可以读取32字节。
EVM提供的用于操作memory的指令有三个:
- Mload加载一个字从stack到内存;
- Mstore存储一个值到指定的内存地址,格式mstore(p,v),存储v到地址p;
- Mstore8存储一个byte到指定地址 ;
当操作内存的时候,总是需要加载0x40,因为这个地址保存了空闲内存的指针,避免了覆盖已有的数据。
Storage
Storage是一个可以读写修改的持久存储的空间,也是每个合约持久化存储数据的地方。Storage是一个巨大的map,一共2^256个插槽,一个插糟有32byte。
EVM提供的用于操作storage的指令有两个:
- Sload用于加载一个字到stack中;
- Sstore用于存储一个字到storage中;
solidity将定义的状态变量,映射到插糟内,对于静态大小的变量从0开始连续布局,对于动态数组和map则采用了其他方法。