Solidity Data Collision

Wias Liaw
Taipei Ethereum Meetup
5 min readAug 24, 2021

這是一篇關於 Proxy Contract 和 delegatecall 的注意事項。

Delegatecall

當 A 合約對 B 合約執行 delegatecall 時,B 合約的函式會被執行,但是對 storage 的操作都會作用在 A 合約上。舉例如下:

但是假如多加了一個 other 欄位在 _value 之前,執行合約之後反而是 other 欄位被更改了。

Storage Layout

了解上面的合約之前要先了解 Solidity 怎麼儲存 State Variables。Solidity Storage 以 Slot 為單位,每個 Slot 可以儲存 32 bytes 的資訊,一個 Contract 擁有 2**256 個 Slot。上述可以寫成一個映射關係 mapping(uint256 => bytes32) slots

Solidity 會從 Slot Index 為零開始分配給 State Variable。

除了 mappingdynamically-sized array,其他的 State Variable 會從 index 為零的 slot 開始被分配。

沒有宣告確切大小的 Array 會以 Slot Index 計算出一個雜湊值並將其作為 Slot Index。透過計算 keccak256(slot) 可以得知 _arr[0] 被存在哪裡,如果要取得 _arr[1] 則將計算出來的雜湊加上 Array 的 index 即可。

Mapping 則是以 Slot Index 和 Key 計算出一個雜湊值並將其作為 Slot Index。透過計算 keccak256(key, slot) 可以得到 mapping(key => value) 被存在哪。

Storage Collision

回到 DelegateExample_v2 的合約,對 B 來說, add 最後儲存加法的 Slot Index 為零,所以使用 A 的 Storage 執行 B 的函式結果自然會儲存在 A 的 other 裡,其 Slot Index 為 0。

這個問題也發生在 Proxy Contract,Layout 如下,當有需要更改 _owner 的操作,就會順帶把 _implementation 也更改了。

|Proxy                     |Implementation           |
|--------------------------|-------------------------|
|address _implementation |address _owner | <= collision
|... |mapping _balances |
| |uint256 _supply |
| |... |

OpenZeppelin 處理的手法也很簡單,就是將 _implementation 換地方擺。以特定字串的雜湊值作為 Slot Index,儲存 Implementation 的地址。

|Proxy                     |Implementation           |
|--------------------------|-------------------------|
|... |address _owner |
|... |mapping _balances |
|... |uint256 _supply |
|... |... |
|address _implementation | | <= specified
|... | |

hardhat-storage-layout

如何知道合約的 Storage Layout 呢?這邊推薦一個 Hardhat Plugin,按照文件就能得到合約的 Storage Layout。

Reference

--

--