存儲(chǔ)中狀態(tài)變量的布局

2022-05-24 11:38 更新

合約的狀態(tài)變量以緊湊的方式存儲(chǔ)在存儲(chǔ)中,以便多個(gè)值有時(shí)使用相同的存儲(chǔ)槽。除了動(dòng)態(tài)大小的數(shù)組和映射(見下文)之外,數(shù)據(jù)以連續(xù)的方式存儲(chǔ)在以第一個(gè)狀態(tài)變量開頭的項(xiàng)接一項(xiàng)地存儲(chǔ),該狀態(tài)變量存儲(chǔ)在插槽中。對(duì)于每個(gè)變量,大?。ㄒ宰止?jié)為單位)根據(jù)其類型確定。如果可能,需要少于 32 個(gè)字節(jié)的多個(gè)連續(xù)項(xiàng)目將打包到單個(gè)存儲(chǔ)槽中,具體取決于以下規(guī)則:0

  • 存儲(chǔ)槽中的第一個(gè)項(xiàng)目以較低的順序?qū)R存儲(chǔ)。
  • 值類型僅使用存儲(chǔ)它們所需的字節(jié)數(shù)。
  • 如果值類型不適合存儲(chǔ)槽的剩余部分,則該值類型將存儲(chǔ)在下一個(gè)存儲(chǔ)槽中。
  • 結(jié)構(gòu)和陣列數(shù)據(jù)總是啟動(dòng)一個(gè)新的槽,并且根據(jù)這些規(guī)則,它們的物品被緊密地包裝起來。
  • 結(jié)構(gòu)或數(shù)組數(shù)據(jù)后面的項(xiàng)始終啟動(dòng)新的存儲(chǔ)槽。

對(duì)于使用繼承的協(xié)定,狀態(tài)變量的順序由從最基本守合同開始的協(xié)定的 C3 線性化順序確定。如果上述規(guī)則允許,來自不同合約的狀態(tài)變量確實(shí)共享相同的存儲(chǔ)槽。

結(jié)構(gòu)和數(shù)組的元素彼此相鄰存儲(chǔ),就像它們作為單個(gè)值給出一樣。

警告

當(dāng)使用小于 32 字節(jié)的元素時(shí),合約的氣體使用量可能會(huì)更高。這是因?yàn)?EVM 一次在 32 個(gè)字節(jié)上運(yùn)行。因此,如果元素小于此值,則 EVM 必須使用更多操作,以便將元素的大小從 32 個(gè)字節(jié)減少到所需的大小。

如果要處理存儲(chǔ)值,則使用減小大小的類型可能會(huì)有所幫助,因?yàn)榫幾g器會(huì)將多個(gè)元素打包到一個(gè)存儲(chǔ)槽中,從而將多個(gè)讀取或?qū)懭牒喜⒌絾蝹€(gè)操作中。但是,如果不同時(shí)讀取或?qū)懭氩壑械乃兄?,則可能會(huì)產(chǎn)生相反的效果:當(dāng)將一個(gè)值寫入多值存儲(chǔ)槽時(shí),必須首先讀取該存儲(chǔ)槽,然后將其與新值組合,以便不會(huì)破壞同一槽中的其他數(shù)據(jù)。

在處理函數(shù)參數(shù)或內(nèi)存值時(shí),沒有固有的好處,因?yàn)榫幾g器不打包這些值。

最后,為了允許 EVM 對(duì)此進(jìn)行優(yōu)化,請(qǐng)確保嘗試對(duì)存儲(chǔ)變量和成員進(jìn)行排序,以便它們可以緊密地打包。例如,按 的順序聲明存儲(chǔ)變量,而不是 ,因?yàn)榍罢邇H占用兩個(gè)存儲(chǔ)槽,而后者將占用三個(gè)。structuint128, uint128, uint256uint128, uint256, uint128

注意

存儲(chǔ)中狀態(tài)變量的布局被認(rèn)為是 Solidity 外部接口的一部分,因?yàn)榇鎯?chǔ)指針可以傳遞到庫。這意味著對(duì)本節(jié)中概述的規(guī)則的任何更改都被視為語言的重大更改,并且由于其關(guān)鍵性,在執(zhí)行之前應(yīng)非常仔細(xì)地考慮。如果發(fā)生這樣的重大更改,我們希望發(fā)布一種兼容模式,在該模式下,編譯器將生成支持舊布局的字節(jié)碼。

映射和動(dòng)態(tài)數(shù)組

由于映射和動(dòng)態(tài)大小的數(shù)組類型不可預(yù)測(cè),因此不能存儲(chǔ)在它們之前和之后的狀態(tài)變量“之間”。相反,根據(jù)上述規(guī)則,它們被認(rèn)為僅占用32個(gè)字節(jié),并且它們包含的元素從使用Keccak-256哈希計(jì)算的不同存儲(chǔ)槽開始存儲(chǔ)。

假設(shè)映射或陣列的存儲(chǔ)位置在應(yīng)用存儲(chǔ)布局規(guī)則后最終成為插槽。對(duì)于動(dòng)態(tài)數(shù)組,此槽存儲(chǔ)數(shù)組中的元素?cái)?shù)(字節(jié)數(shù)組和字符串是例外,請(qǐng)參閱下文)。對(duì)于映射,該槽保持為空,但仍需要確保即使有兩個(gè)映射彼此相鄰,其內(nèi)容最終也會(huì)位于不同的存儲(chǔ)位置。p

數(shù)組數(shù)據(jù)從 開始,其布局方式與靜態(tài)大小的數(shù)組數(shù)據(jù)相同:一個(gè)元素接一個(gè),如果元素的長(zhǎng)度不超過 16 個(gè)字節(jié),則可能共享存儲(chǔ)槽。動(dòng)態(tài)數(shù)組的動(dòng)態(tài)數(shù)組以遞歸方式應(yīng)用此規(guī)則。元素的位置,其中類型為 ,計(jì)算如下(再次假設(shè)其本身存儲(chǔ)在插槽中):插槽是,并且可以使用從插槽數(shù)據(jù)中獲取元素。keccak256(p)x[i][j]xuint24[][]xpkeccak256(keccak256(p) + i) + floor(j / floor(256 / 24))v(v >> ((j % floor(256 / 24)) * 24)) & type(uint24).max

與映射鍵對(duì)應(yīng)的值位于串聯(lián)位置,并且是根據(jù)其類型應(yīng)用于鍵的函數(shù):kkeccak256(h(k) . p).h

  • 對(duì)于值類型,將值填充到 32 個(gè)字節(jié),其方式與將值存儲(chǔ)在內(nèi)存中時(shí)相同。h

  • 對(duì)于字符串和字節(jié)數(shù)組,只是未填充的數(shù)據(jù)。h(k)

如果映射值是非值類型,則計(jì)算的槽將標(biāo)記數(shù)據(jù)的開始。例如,如果值為結(jié)構(gòu)類型,則必須添加與結(jié)構(gòu)成員對(duì)應(yīng)的偏移量才能到達(dá)該成員。

例如,請(qǐng)考慮以下合同:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;


contract C {
    struct S { uint16 a; uint16 b; uint256 c; }
    uint x;
    mapping(uint => mapping(uint => S)) data;
}

我們計(jì)算 的存儲(chǔ)位置。映射本身的位置是(前面有 32 個(gè)字節(jié)的變量)。這意味著 存儲(chǔ)在 。的類型再次是映射,并且 的數(shù)據(jù)從插槽 開始。結(jié)構(gòu)內(nèi)成員的槽偏移量是因?yàn)?并且被打包在單個(gè)槽中。這意味著 的插槽是 。該值的類型是 ,因此它使用單個(gè)插槽。data[4][9].c1xdata[4]keccak256(uint256(4) . uint256(1))data[4]data[4][9]keccak256(uint256(9) . keccak256(uint256(4) . uint256(1)))cS1abdata[4][9].ckeccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1uint256

bytesstring

bytes并且編碼相同。通常,編碼類似于 ,因?yàn)閿?shù)組本身有一個(gè)插槽,而數(shù)據(jù)區(qū)域是使用該插槽位置的哈希值計(jì)算的。但是,對(duì)于短值(短于 32 個(gè)字節(jié)),數(shù)組元素將與長(zhǎng)度一起存儲(chǔ)在同一個(gè)插槽中。stringbytes1[]keccak256

特別是:如果數(shù)據(jù)最多是字節(jié)長(zhǎng),則元素存儲(chǔ)在高階字節(jié)(左對(duì)齊)中,最低階字節(jié)存儲(chǔ)值。對(duì)于存儲(chǔ)長(zhǎng)度為或更多字節(jié)的數(shù)據(jù)的字節(jié)數(shù)組,主槽將存儲(chǔ),并且數(shù)據(jù)將照常存儲(chǔ)在 中。這意味著您可以通過檢查是否設(shè)置了最低位來區(qū)分短數(shù)組和長(zhǎng)數(shù)組:短位(未設(shè)置)和長(zhǎng)位(設(shè)置)。31length * 232plength * 2 + 1keccak256(p)

注意

目前不支持處理無效編碼的插槽,但將來可能會(huì)添加。如果通過 IR 進(jìn)行編譯,則讀取無效編碼的插槽會(huì)導(dǎo)致錯(cuò)誤。Panic(0x22)

JSON 輸出

可以通過標(biāo)準(zhǔn) JSON 接口請(qǐng)求合約的存儲(chǔ)布局。輸出是一個(gè) JSON 對(duì)象,其中包含兩個(gè)鍵和 。該對(duì)象是一個(gè)數(shù)組,其中每個(gè)元素具有以下形式:storagetypesstorage

{
    "astId": 2,
    "contract": "fileA:A",
    "label": "x",
    "offset": 0,
    "slot": "0",
    "type": "t_uint256"
}

上面的示例是源單元的存儲(chǔ)布局,并且contract A { uint x; }fileA

  • astId是狀態(tài)變量聲明的 AST 節(jié)點(diǎn)的 ID

  • contract是合約的名稱,包括其路徑作為前綴

  • label是狀態(tài)變量的名稱

  • offset是存儲(chǔ)槽內(nèi)根據(jù)編碼的偏移量(以字節(jié)為單位)

  • slot是狀態(tài)變量駐留或啟動(dòng)的存儲(chǔ)槽。此數(shù)字可能非常大,因此其 JSON 值表示為字符串。

  • type是用作變量類型信息鍵的標(biāo)識(shí)符(如下所述)

給定的 ,在本例中表示 中的一個(gè)元素,其形式為:typet_uint256types

{
    "encoding": "inplace",
    "label": "uint256",
    "numberOfBytes": "32",
}

哪里

  • encoding數(shù)據(jù)在存儲(chǔ)中的編碼方式,其中可能的值為:

    • inplace:數(shù)據(jù)在存儲(chǔ)中連續(xù)排列(見上文)。

    • mapping:基于Keccak-256哈希的方法(見上文)。

    • dynamic_array:基于Keccak-256哈希的方法(見上文)。

    • bytes:?jiǎn)尾宀刍蚧贙eccak-256哈希,具體取決于數(shù)據(jù)大?。ㄒ?a rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" >上文)。

  • label是規(guī)范類型名稱。

  • numberOfBytes是已用字節(jié)數(shù)(作為十進(jìn)制字符串)。請(qǐng)注意,如果這意味著使用了多個(gè)插槽。numberOfBytes > 32

除了上述四種類型之外,某些類型還有額外的信息。映射包含其和類型(再次引用此類型映射中的條目),數(shù)組具有其類型,結(jié)構(gòu)以與頂級(jí)相同的格式列出它們(見上文)。keyvaluebasemembersstorage

注意

合約存儲(chǔ)布局的 JSON 輸出格式仍被視為實(shí)驗(yàn)性格式,并且可能會(huì)在 Solidity 的非中斷版本中發(fā)生變化。

下面的示例演示協(xié)定及其存儲(chǔ)布局,其中包含值和引用類型、編碼打包的類型以及嵌套類型。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract A {
    struct S {
        uint128 a;
        uint128 b;
        uint[2] staticArray;
        uint[] dynArray;
    }

    uint x;
    uint y;
    S s;
    address addr;
    mapping (uint => mapping (address => bool)) map;
    uint[] array;
    string s1;
    bytes b1;
}
{
  "storage": [
    {
      "astId": 15,
      "contract": "fileA:A",
      "label": "x",
      "offset": 0,
      "slot": "0",
      "type": "t_uint256"
    },
    {
      "astId": 17,
      "contract": "fileA:A",
      "label": "y",
      "offset": 0,
      "slot": "1",
      "type": "t_uint256"
    },
    {
      "astId": 20,
      "contract": "fileA:A",
      "label": "s",
      "offset": 0,
      "slot": "2",
      "type": "t_struct(S)13_storage"
    },
    {
      "astId": 22,
      "contract": "fileA:A",
      "label": "addr",
      "offset": 0,
      "slot": "6",
      "type": "t_address"
    },
    {
      "astId": 28,
      "contract": "fileA:A",
      "label": "map",
      "offset": 0,
      "slot": "7",
      "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
    },
    {
      "astId": 31,
      "contract": "fileA:A",
      "label": "array",
      "offset": 0,
      "slot": "8",
      "type": "t_array(t_uint256)dyn_storage"
    },
    {
      "astId": 33,
      "contract": "fileA:A",
      "label": "s1",
      "offset": 0,
      "slot": "9",
      "type": "t_string_storage"
    },
    {
      "astId": 35,
      "contract": "fileA:A",
      "label": "b1",
      "offset": 0,
      "slot": "10",
      "type": "t_bytes_storage"
    }
  ],
  "types": {
    "t_address": {
      "encoding": "inplace",
      "label": "address",
      "numberOfBytes": "20"
    },
    "t_array(t_uint256)2_storage": {
      "base": "t_uint256",
      "encoding": "inplace",
      "label": "uint256[2]",
      "numberOfBytes": "64"
    },
    "t_array(t_uint256)dyn_storage": {
      "base": "t_uint256",
      "encoding": "dynamic_array",
      "label": "uint256[]",
      "numberOfBytes": "32"
    },
    "t_bool": {
      "encoding": "inplace",
      "label": "bool",
      "numberOfBytes": "1"
    },
    "t_bytes_storage": {
      "encoding": "bytes",
      "label": "bytes",
      "numberOfBytes": "32"
    },
    "t_mapping(t_address,t_bool)": {
      "encoding": "mapping",
      "key": "t_address",
      "label": "mapping(address => bool)",
      "numberOfBytes": "32",
      "value": "t_bool"
    },
    "t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
      "encoding": "mapping",
      "key": "t_uint256",
      "label": "mapping(uint256 => mapping(address => bool))",
      "numberOfBytes": "32",
      "value": "t_mapping(t_address,t_bool)"
    },
    "t_string_storage": {
      "encoding": "bytes",
      "label": "string",
      "numberOfBytes": "32"
    },
    "t_struct(S)13_storage": {
      "encoding": "inplace",
      "label": "struct A.S",
      "members": [
        {
          "astId": 3,
          "contract": "fileA:A",
          "label": "a",
          "offset": 0,
          "slot": "0",
          "type": "t_uint128"
        },
        {
          "astId": 5,
          "contract": "fileA:A",
          "label": "b",
          "offset": 16,
          "slot": "0",
          "type": "t_uint128"
        },
        {
          "astId": 9,
          "contract": "fileA:A",
          "label": "staticArray",
          "offset": 0,
          "slot": "1",
          "type": "t_array(t_uint256)2_storage"
        },
        {
          "astId": 12,
          "contract": "fileA:A",
          "label": "dynArray",
          "offset": 0,
          "slot": "3",
          "type": "t_array(t_uint256)dyn_storage"
        }
      ],
      "numberOfBytes": "128"
    },
    "t_uint128": {
      "encoding": "inplace",
      "label": "uint128",
      "numberOfBytes": "16"
    },
    "t_uint256": {
      "encoding": "inplace",
      "label": "uint256",
      "numberOfBytes": "32"
    }
  }
}
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)