合約的狀態(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
對(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ù)組類型不可預(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]
x
uint24[][]
x
p
keccak256(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ù):k
keccak256(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].c
1
x
data[4]
keccak256(uint256(4) . uint256(1))
data[4]
data[4][9]
keccak256(uint256(9) . keccak256(uint256(4) . uint256(1)))
c
S
1
a
b
data[4][9].c
keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1
uint256
bytes
和string
bytes
并且編碼相同。通常,編碼類似于 ,因?yàn)閿?shù)組本身有一個(gè)插槽,而數(shù)據(jù)區(qū)域是使用該插槽位置的哈希值計(jì)算的。但是,對(duì)于短值(短于 32 個(gè)字節(jié)),數(shù)組元素將與長(zhǎng)度一起存儲(chǔ)在同一個(gè)插槽中。string
bytes1[]
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è)置)。31
length * 2
32
p
length * 2 + 1
keccak256(p)
注意
目前不支持處理無效編碼的插槽,但將來可能會(huì)添加。如果通過 IR 進(jìn)行編譯,則讀取無效編碼的插槽會(huì)導(dǎo)致錯(cuò)誤。Panic(0x22)
可以通過標(biāo)準(zhǔn) JSON 接口請(qǐng)求合約的存儲(chǔ)布局。輸出是一個(gè) JSON 對(duì)象,其中包含兩個(gè)鍵和 。該對(duì)象是一個(gè)數(shù)組,其中每個(gè)元素具有以下形式:storage
types
storage
{ "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è)元素,其形式為:type
t_uint256
types
{ "encoding": "inplace", "label": "uint256", "numberOfBytes": "32", }
哪里
encoding
數(shù)據(jù)在存儲(chǔ)中的編碼方式,其中可能的值為:
label
是規(guī)范類型名稱。
numberOfBytes
是已用字節(jié)數(shù)(作為十進(jìn)制字符串)。請(qǐng)注意,如果這意味著使用了多個(gè)插槽。numberOfBytes > 32
除了上述四種類型之外,某些類型還有額外的信息。映射包含其和類型(再次引用此類型映射中的條目),數(shù)組具有其類型,結(jié)構(gòu)以與頂級(jí)相同的格式列出它們(見上文)。key
value
base
members
storage
注意
合約存儲(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" } } }
更多建議: