Solidity Storage底层管理

这篇具有很好参考价值的文章主要介绍了Solidity Storage底层管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 引言

前序博客有:

  • Ethereum EVM简介
  • 揭秘EVM Opcodes
  • 剖析Solidity合约创建EVM bytecode

Solidity底层通过SLOAD和SSTORE opcode来控制EVM storage。

2. 何为Storage?

Storage为每个合约的持久mapping,具有 2 256 − 1 2^{256}-1 22561个32 byte words。当在合约中设置某状态变量值时,其会存储在指定的slot中,其将持续在EVM中,除非被相同类型的其它值覆盖。

3. 何时用Storage?何时用Memory?

当首次加载某storage slot时,其是cold的,意味着需要2100 gas,后续再调用该slot时,其是warm的,仅需100 gas。而Memory更便宜,其低至3 gas(当有memory expansion时,将更贵点)。

举例如下,未优化合约:

  contract C {
    struct S {
        uint256 a;
        uint256 b;
        address c;
    }

    S public s;

    function foo(uint256 input) external {
        // `s.b` is loaded from storage once: warming up the storage!
        if (input < s.b) revert;
        // Second `s.b` SLOAD with warm storage.
        if (s.b > 50) revert;
    }

其中s,b从storage中加载了2次。可优化为:创建内存变量来存储s.b值,后续使用该内存变量。原因在于MLOAD比SLOAD便宜。即优化为:

function foo(uint256 input) external {
    // Initial storage load to store in memory.
    uint256 b = s.b;
    // Using MLOAD in comparison operations!
    if (input < b) revert;
    if (b > 50) revert;
}

4. 手工分配Storage

// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

contract C {
    struct S {
        uint16 a;  // 2 bytes,  2 bytes total
        uint24 b;  // 3 bytes,  5 bytes total
        address c; // 20 bytes, 25 bytes total + end of slot 0x01
        address d; // 20 bytes, slot 0x02
    }

    // I've noted the storage slots each state is located at.
    // A single slot is 32 bytes :)
    uint256 boring;              // 0x00
    S s_struct;                  // 0x01, 0x02
    S[] s_array;                 // 0x03
    mapping(uint256 => S) s_map; // 0x04

    constructor() {
        boring = 0x288;
        s_struct = S({
            a: 10,
            b: 20,
            c: 0xdcD49C36E69bF85FA9c5a25dEA9455602C0B289e,
            d: 0x4675C7e5BaAFBFFbca748158bEcBA61ef3b0a263
        });
        s_array.push(s_struct);
        s_array.push(s_struct);
    }

    function view_boring() external view returns (bytes32) {
        bytes32 x;
        assembly {
            x := sload(0x00)
        }
        return x;
    }

    function view_slot(uint256 slot) external view returns(bytes32) {
        bytes32 x;
        assembly {
            x := sload(slot)
        }
        return x;
    }

    function view_b() external view returns (uint256) {
      bytes32 x;
      assembly {

        // before: 00000000000000 dcd49c36e69bf85fa9c5a25dea9455602c0b289e 000014 000a
        //                                                                         ^
        // after:  0000 00000000000000 dcd49c36e69bf85fa9c5a25dea9455602c0b289e 000014
        //          ^
        let v := shr(0x10, sload(0x01))

        // If both characters aren't 0, keep the bit (1). Otherwise, set to 0.
        // mask:   0000000000000000000000000000000000000000000000000000000000 FFFFFF
        // v:      000000000000000000dcd49c36e69bf85fa9c5a25dea9455602c0b289e 000014
        // result: 0000000000000000000000000000000000000000000000000000000000 000014
        v := and(0xffffff, v)

        // Store in memory bc return uses memory.
        mstore(0x40, v)

        // Return reads left to right.
        // Since our value is far right we can just return 32 bytes from the 64th byte in memory.
        x := mload(0x40)
      }
      return uint256(x);
    }
    
    //          unused bytes                     c                        b    a
    // before: 00000000000000 dcd49c36e69bf85fa9c5a25dea9455602c0b289e 000014 000a
    //          unused bytes                     c                        b    a                                                                         
    // after:  00000000000000 dcd49c36e69bf85fa9c5a25dea9455602c0b289e 0001F4 000a
  function set_b(uint24 b) external {
    assembly {
        // Removing the `uint16` from the right.
        // before: 00000000000000 dcd49c36e69bf85fa9c5a25dea9455602c0b289e 000014 000a
        //                                                                         ^
        // after:  0000 00000000000000 dcd49c36e69bf85fa9c5a25dea9455602c0b289e 000014
        //          ^
        let new_v := shr(0x10, sload(0x01))

        // Create our mask.
        new_v := and(0xffffff, new_v)

        // Input our value into the mask.
        new_v := xor(b, new_v)

        // Add back the removed `a` value bits.
        new_v := shl(0x10, new_v)

        // Replace original 32 bytes' `000014` with `0001F4`.
        new_v := xor(new_v, sload(0x01))

        // Store our new value.
        sstore(0x01, new_v)
    }
  }
  
  function view_d() external view returns (address) {
        return s_array[0].d;
    }
  
  // keccak256(array_slot) + var_slot
  // keccak256(0x03) + 1
  // Remember how `s_struct` takes up 2 slots?
  // The `+ 1` indicates the second slot allocation in S
  // For the bitpacked slot in S we use don't need the add
  // The next element's slot would be `+ 2`
  function get_element() external view returns(bytes32) {
    bytes32 x;
    assembly {
        // Store array slot in memory.
        mstore(0x0, 0x03)
        // Keccak does the MLOAD internally so we give the memory location.
        let hash := add(keccak256(0x0, 0x20), 1)
        // Store the return value.
        mstore(0x0, sload(hash))
        // Return `d`.
        x := mload(0x0)
    }
    return x;
  }

	// 返回s_array数组的长度
    function s_arrayLength () public view returns (uint r) {
        assembly {
            r := sload (3)
        }
    }
	// 从storage中取s_array数组的第i个32字节内容。
    function s_arrayElement (uint i) public view returns (bytes32 r) {
        assembly {
            mstore (0, 3)
            r := sload (add (keccak256 (0, 32), i))
        }
    }
}

4.1 基础类型访问

当想要访问uint256 boring时,访问方式可为:

assembly {
    let x := sload(0x00)
}

4.2 访问Bitpacked结构体

所谓bitpacked,是指在单个slot(32 bytes)内存储了多个变量。在本例中,slot 0x01中pack了共25字节内容:

  • uint16 a (2 bytes).
  • uint24 b (3 bytes).
  • address c (20 bytes).

对应s_struct的slots为:

// 0x01 0x00000000000000dcd49c36e69bf85fa9c5a25dea9455602c0b289e000014000a
// 0x02 0x0000000000000000000000004675c7e5baafbffbca748158becba61ef3b0a263

Bitpacked结构体的查询和设置,可参看上面合约中的view_bset_b

4.3 访问数组结构

pragma solidity >=0.7.0 <0.9.0;

contract Foo {
    uint internal x; // Storage slot #0
    mapping (uint => uint) internal y; // Storage slot #1
    uint [] internal z; // Storage slot #2

    constructor() {
        z.push(8);
        z.push(9);
    }
	// 动态数组的长度
    function zLength () public view returns (uint r) {
        assembly {
            r := sload (2)
        }
    }
	// 动态数组中第i个元素的值
    function zElement (uint i) public view returns (uint r) {
        assembly {
            mstore (0, 2)
            r := sload (add (keccak256 (0, 32), i))
        }
    }
}

4.4 访问mapping结构

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Foo {
  uint internal x; // Storage slot #0
  mapping (uint => address) internal y; // Storage slot #1
  uint [] internal z; // Storage slot #2

    constructor() {
       y[10] = 0xdcD49C36E69bF85FA9c5a25dEA9455602C0B289e;
       y[3] = 0x4675C7e5BaAFBFFbca748158bEcBA61ef3b0a263;
    }

  // 本例中,参数取(10, 1)或(3, 1)可分别获得y[10]以及y[3]的值
  function getStorageValue(uint num, uint slot) public view returns (address result) {
    assembly {
        // Store num in memory scratch space (note: lookup "free memory pointer" if you need to allocate space)
        mstore(0, num)
        // Store slot number in scratch space after num
        mstore(32, slot)
        // Create hash from previously stored num and slot
        let hash := keccak256(0, 64)
        // Load mapping value using the just calculated hash
        result := sload(hash)
    } 
  }
}

4.5 访问String和Bytes结构

bytes和string中的元素在storage中均以ASCII码值表示。

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Foo {
  uint internal x; // Storage slot #0
  mapping (uint => address) internal y; // Storage slot #1
  uint [] internal z; // Storage slot #2
  bytes internal b; // Storage slot #3
  string internal s; // Storage slot #4


    constructor() {
       b = "0123456789012345678901234567890123456789";
       s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    }

    // bytes和string中的元素在storage中均以ASCII码值表示。
    // bytes长度,本例中,返回值为81
    function bLength () public view returns (uint r) {
        assembly {
            r := sload (3)
        }
    }
	// 返回bytes中第i个32字节值
    //i=0,对应返回:0x3031323334353637383930313233343536373839303132333435363738393031
    //i=1,对应返回:0x3233343536373839000000000000000000000000000000000000000000000000
    function bElement (uint i) public view returns (bytes32 r) {
        assembly {
            mstore (0, 3)
            r := sload (add (keccak256 (0, 32), i))
        }
    }

    // string长度,本例中,返回值为105
    function sLength () public view returns (uint r) {
        assembly {
            r := sload (4)
        }
    }

    // 返回string中的第i个32字节值
    //i=0,对应返回:0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546
    //i=1,对应返回:0x4748494a4b4c4d4e4f505152535455565758595a000000000000000000000000
    function sElement (uint i) public view returns (bytes32 r) {
        assembly {
            mstore (0, 4)
            r := sload (add (keccak256 (0, 32), i))
        }
    }
}

参考资料

[1] A Low-Level Guide To Solidity’s Storage Management
[2] Layout of State Variables in Storage
[3] How to get access to the storage array through the solidity assembler?
[4] How to get access to the storage mapping through the solidity assembler?
[5] Storage and memory layout of strings文章来源地址https://www.toymoban.com/news/detail-810101.html

到了这里,关于Solidity Storage底层管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • solidity开发智能合约

    起源于以太坊(Ethereum),设计的目的是能在以太坊虚拟机(EVM)上运行。Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。所以先从智能合约开始。 参考文档 Solidity文档:区块链技术-智能合约Solidity编程语言 solidity官方文档: https://solidity-cn.readthedocs.io/

    2023年04月08日
    浏览(81)
  • 智能合约 Solidity – 构造函数

    构造函数是任何面向对象的编程语言中的一种特殊方法,每当初始化类的对象时都会调用它。Solidity 则完全不同,Solidity 在智能合约内部提供了一个构造函数声明,它只在合约部署时调用一次,用于初始化合约状态。如果没有明确定义的构造函数,则编译器会创建默认构造函

    2024年02月11日
    浏览(40)
  • solidity:智能合约结构介绍

    合约结构介绍 1.SPDX 版权声明 bytecode metadata 介绍 2.pragma solidity 版本限制 3.contract 4.import 导入声明 5.interface: 接口 6.library:库合约 第 1 行 // SPDX-License-Identifier: MIT 就是合约的版权声明。其中 SPDX-License-Identifier (SPDX 许可标示) 是标注当前的智能合约采用什么样的对外开放标

    2023年04月08日
    浏览(44)
  • Solidity智能合约开发 — 3.4-抽象智能合约和接口

    假如一个智能合约中至少一个函数缺省实现时,即某个函数缺少{}中的内容,这个智能合约就当做抽象智能合约。 当我们有一个函数没想好怎么写时,必须将该合约标为 abstract ,不然编译会报错;另外,未实现的函数需要加 virtual ,以便子合约重写。抽象智能合约是将智能合

    2024年02月12日
    浏览(45)
  • 【Solidity】智能合约案例——③版权保护合约

    目录 一、合约源码分析: 二、合约整体流程:         1.部署合约:         2.添加实体:          3.查询实体         4.审核版权:         5.版权转让         Copyright.sol:主合约,定义了版权局的实体,功能为:审核版权         Opus.sol:定义两个实体:作者和作

    2024年02月04日
    浏览(47)
  • Solidity实现简单的智能合约

    Solidity是以太坊中编写智能合约的语言,编译成字节码之后可以运行在以太坊虚拟机上。solidity语法与JavaScript很相似,有编程基础的开发者可以轻松上手,智能合约一旦部署就无法修改。 首先介绍我们的编译工具: Remix remix是一款非常好用的在线编译工具,我们通过这个工具

    2023年04月09日
    浏览(36)
  • Solidity contract智能合约概览

    Contracts in Solidity are similar to classes in object-oriented languages. They contain persistent data in state variables, and functions that can modify these variables. Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables in the calling contract are inaccessible. A con

    2024年02月11日
    浏览(43)
  • Solidity,智能合约的学习(1)

    Solidity是一种智能合约高级语言,运行在Ethereum虚拟机(EVM)上,Solidity是面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态内的账户行为的程序。 BitPen认为,作为Web3的链上玩家,那么能够看懂Solidity代码将会是一项必备的技能,因为大多区块链项目都是在

    2024年01月23日
    浏览(53)
  • 以太坊智能合约开发(五):Solidity成绩录入智能合约实验

    每个学生分别部署合约Student.sol ,保证只有自己可以修改姓名。老师部署合约StudentScore.sol,用于录入学生成绩,查询学生信息。查询学生信息时,需要调用学生部署的合约Student.sol。 student.sol合约,用于学生对自己信息进行管理。 学生的基本信息作为状态变量: 声明构造函

    2024年02月07日
    浏览(47)
  • 【Solidity】智能合约案例——②供应链金融合约

    目录 一、合约源码分析: 二、合约整体流程:         1.部署合约:         2.添加实体         3.发送交易存证            ①.银行向公司交易(公司向银行提供交易存证)            ②.公司向银行交易(银行向公司提供交易存证)            ③.公司向公司交易

    2024年02月06日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包