以太坊虛擬機
儲存槽
儲存槽是合約永久儲存中一個可定址的格子:一個由 256 位元鍵標識、32 位元組(256 位元)的盒子。合約的儲存不過是從這些鍵到這些 32 位元組值的對映,而合約保有的每個變數,歸根究柢都存在一個或多個槽裡。理解高階變數如何對映到槽,就是「能讀懂一份合約的原始儲存」與「被它搞得一頭霧水」之間的分野。
Solidity 以確定性佈局指派槽。固定大小的狀態變數,依宣告順序從第 0 槽起依序擺放。為節省空間,多個小於 32 位元組的變數在塞得下時會被打包進同一個槽——例如一個 uint128、一個 uint64 和一個 uint64 能共用一個槽,於是把三者一併更新只需單一 SSTORE。這種打包是最可靠的 gas 最佳化手段,但唯有當這些變數被一起寫入時才有幫助。
動態與帶鍵的資料無法落在固定偏移處,因為它們的大小在編譯期未知,所以 Solidity 用雜湊推導它們的槽。對宣告於第 p 槽的對映而言,鍵 k 的值存在 keccak256(h(k) . p)(鍵經補位後與槽號串接再雜湊)。對位於第 p 槽的動態陣列而言,長度存在第 p 槽,元素則從 keccak256(p) 起連續排列。巢狀對映則反覆雜湊。這種以雜湊四散的安排,也是你無法在鏈上列舉一個對映之鍵的原因——這些槽偽隨機地散佈在 2²⁵⁶ 的空間中。
由於佈局是依位置決定的,它對可升級合約成了一道硬性限制:透過 delegatecall 觸及的新實作,必須維持完全相同的槽順序,否則它會以錯誤的變數讀寫代理合約既有的資料。這類儲存碰撞臭蟲曾造成真實的攻擊,這正是 ERC-7201「命名空間儲存」之類的模式刻意把結構體擺在以雜湊得出、抗碰撞的基底槽的原因。
slot(mapping[k] at base p) = keccak256(h(k) . p)
對映的值是透過把鍵與槽號雜湊,散佈在儲存空間各處。
另见