不要將任何敏感數據存放在合約中,因為合約中的任何數據都可被讀取,包括private 定義私有數據。
在 solidity 中,有四種可見性關鍵字:external,public,internal 和 private。默認時函數可見性為 public。對狀態變量而言,除了不能用 external 來定義,其它三個都可以來定義變量,狀態變量默認的可見性為 internal。
- external 關鍵字
external 定義的外部函數可以被其它合約調用。用 external 修飾的外部函數 function() 不能作為內部函數直接調用,也就是說 function() 的調用方式必須用 this.function() 。
- public 關鍵字
public 定義的函數可以被內部函數或外部消息調用。對用 public 定義的狀態變量,系統會自動生成一個 getter 函數。
- internal 用關鍵字
internal 定義的函數和狀態變量只能在(當前合約或當前合約派生的合約)內部進行訪問。
- private 關鍵字
private 定義的函數和狀態變量只對定義它的合約可見,該合約派生的合約都不能調用和訪問該函數及狀態變量。
綜上可知,合約中修飾變量存儲的關鍵字僅僅限制了其調用的范圍,并沒有限制其是否可讀。所以我們今天就來帶大家了解如何讀取合約中的所有數據。
solidity 中的三種數據存儲方式:
- storage(存儲)
storage 中的數據被永久存儲。其以鍵值對的形式存儲在 slot 插槽中。
storage 中的數據會被寫在區塊鏈中(因此它們會更改狀態),這就是為什么使用存儲非常昂貴的原因。
storage 共有 2^256 個插槽,每個插槽 32 個字節數據按聲明順序依次存儲,數據將會從每個插槽的右邊開始存儲,如果相鄰變量適合單個 32 字節,然后它們被打包到同一個插槽中否則將會啟用新的插槽來存儲。
storage 中的數組的存儲方式就比較獨特了,首先,solidity 中的數組分為兩種:
a.定長數組(長度固定):
定長數組中的每個元素都會有一個獨立的插槽來存儲。以一個含有三個 uint64 元素的定長數組為例,下圖可以清楚的看出其存儲方式:
b.變長數組(長度隨元素的數量而改變):
變長數組的存儲方式就很奇特,在遇到變長數組時,會先啟用一個新的插槽 slotA 用來存儲數組的長度,其數據存儲在另外的編號為 slotV 的插槽中。slotA 表示變長數組聲明的位置,用 length 表示變長數組的長度,用 slotV 表示變長數組數據存儲的位置,用 value 表示變長數組某個數據的值,用 index 表示 value 對應的索引下標。
memory(內存)
memory 是一個字節數組,其插槽大小為 256 位(32 個字節)。數據僅在函數執行期間存儲,執行完之后,將會被刪除。它們不會保存到區塊鏈中。
讀或寫一個字節(256 位)需要 3 gas 。
為了避免給礦工帶來太多工作,在進行 22 次讀寫操作后,之后的讀寫成本開始上升。calldata(調用數據)
calldata 是一個不可修改的,非持久性的區域,用于存儲函數參數,并且其行為基本上類似于 memory。
調用外部函數的參數需要 calldata,也可用于其他變量。
它避免了復制,并確保了數據不能被修改。
帶有 calldata 數據位置的數組和結構體也可以從函數中返回,但是不可以為這種類型賦值。
由合約中可以看到 slot0 中只存儲了一個 uint 類型的數據,我們讀取出來看一下: