在开发智能合约 时,通常需要某种形式的持久性存储。在这种情况下,持久性存储,通常在此上下文中称为 存储,是一个可以存储在合约内部并持久存在的值的地方。这与 内存 中的常规值形成对比,后者在合约退出后消失。
从传统编程角度来看,合约存储就像将数据保存到硬盘上一样。这些数据在保存它的程序退出后仍然存在。这些数据是持久的。使用内存就像在程序中声明一个变量:它存在于程序的整个运行期间,是非持久的。
一些存储的基本用例包括为合约声明所有者地址和在钱包中保存余额。
storage
关键字访问存储 在存储中声明变量需要一个 storage
块,其中包含所有变量、它们的类型和它们的初始值的列表。初始值可以是在编译期间可以计算为常量的任何表达式,如下所示:
storage {
var1: u64 = 1,
var2: b256 = b256::zero(),
var3: Address = Address::zero(),
var4: Option<u8> = None,
}
要写入存储变量,您需要使用 storage
关键字,如下所示:
storage.var1.write(42);
storage
.var2
.write(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.var3
.write(Address::from(0x1111111111111111111111111111111111111111111111111111111111111111));
storage.var4.write(Some(2u8));
要读取存储变量,您还需要使用 storage
关键字。您可以使用 read()
或 try_read()
,但我们建议使用 try_read()
进行额外的安全性。
let var1: u64 = storage.var1.read();
let var2: b256 = storage.var2.try_read().unwrap_or(b256::zero());
let var3: Address = storage.var3.try_read().unwrap_or(Address::zero());
let var4: Option<u8> = storage.var4.try_read().unwrap_or(None);
要在存储中存储结构体,必须在 storage
块中为每个变量分配值。这可以通过分配字段或使用可以在编译期间评估为常量的公共构造函数 来实现。
struct Type1 {
x: u64,
y: u64,
}
struct Type2 {
w: b256,
z: bool,
}
impl Type2 {
// a constructor that evaluates to a constant during compilation
fn default() -> Self {
Self {
w: 0x0000000000000000000000000000000000000000000000000000000000000000,
z: true,
}
}
}
storage {
var1: Type1 = Type1 { x: 0, y: 0 },
var2: Type2 = Type2::default(),
}
您可以同时写入结构体的两个字段和整个结构体,如下所示:
// Store individual fields
storage.var1.x.write(42);
storage.var1.y.write(77);
// Store an entire struct
let new_struct = Type2 {
w: 0x1111111111111111111111111111111111111111111111111111111111111111,
z: false,
};
storage.var2.write(new_struct);
同样的方法也适用于从存储中读取结构体,可以读取单个字段和整个结构体,如下所示:
let var1_x: u64 = storage.var1.x.try_read().unwrap_or(0);
let var1_y: u64 = storage.var1.y.try_read().unwrap_or(0);
let var2: Type2 = storage.var2.try_read().unwrap_or(Type2::default());
我们支持以下常见的存储集合:
StorageMap<K, V>
StorageVec<T>
StorageBytes
StorageString
请注意,这些类型在编译期间不会被初始化。这意味着如果您尝试在设置存储之前从存储映射中访问键,例如,该调用将导致回滚。
在存储中声明这些变量需要一个 storage
块,如下所示:
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
StorageMaps<K, V>
标准库中提供了通用的存储映射,称为 StorageMap<K, V>
,必须在 storage
块中定义,允许您调用 insert()
和 get()
分别在特定键处插入值和获取这些值。有关 StorageMap<K, V>
的更多信息,请参阅存储映射 。
要写入存储映射,请调用 insert()
或 try_insert()
函数,如下所示:
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
以下示例演示了如何从存储映射中读取:
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
StorageVec<T>
标准库中提供了通用的存储向量,称为 StorageVec<T>
,必须在 storage
块中定义,允许您调用 push()
和 pop()
来分别从向量中推送和弹出值。有关 StorageVec<T>
的更多信息,请参阅存储向量 。
以下示例演示了如何写入 StorageVec<T>
:
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
以下示例演示了如何从 StorageVec<T>
中读取:
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
StorageBytes
StorageBytes
是标准库中提供的一种存储 Bytes
的类型,必须在 storage
块中定义。StorageBytes
无法像 StorageVec<T>
或 StorageMap<K, V>
那样进行操纵,但以更有效的方式存储字节,从而减少 gas 消耗。只能读取/写入 Bytes
的全部内容。这意味着任何更改都需要将整个 Bytes
加载到堆中,进行更改,然后再次存储它。如果需要频繁更改,建议使用 StorageVec<u8>
。
以下演示了如何写入 StorageBytes
:
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
以下演示了如何从 StorageBytes
中读取:
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
StorageString
标准库中提供了 String
的存储类型 StorageString
,必须在 storage
块中定义。StorageString
无法像 StorageVec<T>
或 StorageMap<K, V>
那样进行操纵。只能读取/写入 String
的全部内容。
以下演示了如何写入 StorageString
:
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
以下演示了如何从 StorageString
中读取:
let stored_string: String = storage.storage_string.read_slice().unwrap();
有关更高级的存储技术,请参阅高级存储 页面。