您现在的位置是:首页 >学无止境 >solidity笔记之数据位置&EVM网站首页学无止境

solidity笔记之数据位置&EVM

Jayo_o_0 2024-06-17 10:31:58
简介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) = 196

    608160020a字节码更短,但是没有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则采用了其他方法。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。