When developing a smart contract, you will typically need some sort of persistent storage. In this case, persistent storage, often just called storage in this context, is a place where you can store values that are persisted inside the contract itself. This is in contrast to a regular value in memory, which disappears after the contract exits.
Put in conventional programming terms, contract storage is like saving data to a hard drive. That data is saved even after the program which saved it exits. That data is persistent. Using memory is like declaring a variable in a program: it exists for the duration of the program and is non-persistent.
Some basic use cases of storage include declaring an owner address for a contract and saving balances in a wallet.
storage
Keyword Declaring variables in storage requires a storage
declaration that contains a list of all your variables, their types, and their initial values as follows:
struct Type1 {
x: u64,
y: u64,
}
struct Type2 {
w: b256,
z: bool,
}
storage {
var1: Type1 = Type1 { x: 0, y: 0 },
var2: Type2 = Type2 {
w: 0x0000000000000000000000000000000000000000000000000000000000000000,
z: false,
},
}
To write into a storage variable, you need to use the storage
keyword as follows:
#[storage(write)]
fn store_something() {
storage.var1.x.write(42);
storage.var1.y.write(77);
storage.var2.w.write(0x1111111111111111111111111111111111111111111111111111111111111111);
storage.var2.z.write(true);
}
To read a storage variable, you also need to use the storage
keyword as follows:
#[storage(read)]
fn get_something() -> (u64, u64, b256, bool) {
(
storage.var1.x.read(),
storage.var1.y.read(),
storage.var2.w.read(),
storage.var2.z.read(),
)
}
Generic storage maps are available in the standard library as StorageMap<K, V>
which have to be defined inside a storage
block and allow you to call insert()
and get()
to insert values at specific keys and get those values respectively. Refer to Storage Maps for more information about StorageMap<K, V>
.
It is possible to leverage FuelVM storage operations directly using the std::storage::storage_api::write
and std::storage::storage_api::read
functions provided in the standard library. With this approach you will have to manually assign the internal key used for storage. An example is as follows:
contract;
use std::storage::storage_api::{read, write};
abi StorageExample {
#[storage(write)]
fn store_something(amount: u64);
#[storage(read)]
fn get_something() -> u64;
}
const STORAGE_KEY: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;
impl StorageExample for Contract {
#[storage(write)]
fn store_something(amount: u64) {
write(STORAGE_KEY, 0, amount);
}
#[storage(read)]
fn get_something() -> u64 {
let value: Option<u64> = read::<u64>(STORAGE_KEY, 0);
value.unwrap_or(0)
}
}
Note: Though these functions can be used for any data type, they should mostly be used for arrays because arrays are not yet supported in
storage
blocks. Note, however, that all data types can be used as types for keys and/or values inStorageMap<K, V>
without any restrictions.