Solidity Storage底层管理

1. 引言

前序博客有:

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值