Icon HelpCircleForumIcon Link

⌘K

Icon HelpCircleForumIcon Link

Icon LinkStorage

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 that 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.

Icon LinkStorage Accesses Via the storage Keyword

Declaring variables in storage requires a storage block that contains a list of all your variables, their types, and their initial values. The initial value can be any expression that can be evaluated to a constant during compilation, as follows:

storage {
    var1: u64 = 1,
    var2: b256 = b256::zero(),
    var3: Address = Address::zero(),
    var4: Option<u8> = None,
}

To write into a storage variable, you need to use the storage keyword as follows:

storage.var1.write(42);
storage
    .var2
    .write(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
    .var3
    .write(Address::from(0x1111111111111111111111111111111111111111111111111111111111111111));
storage.var4.write(Some(2u8));

To read a storage variable, you also need to use the storage keyword. You may use read() or try_read(), however we recommend using try_read() for additional safety.

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);

Icon LinkStoring Structs

To store a struct in storage, each variable must be assigned in the storage block. This can be either my assigning the fields individually or using a public constructor that can be evaluated to a constant during compilation.

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(),
}

You may write to both fields of a struct and the entire struct as follows:

// 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);

The same applies to reading structs from storage, where both the individual and struct as a whole may be read as follows:

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());

Icon LinkCommon Storage Collections

We support the following common storage collections:

  • StorageMap<K, V>
  • StorageVec<T>
  • StorageBytes
  • StorageString

Please note that these types are not initialized during compilation. This means that if you try to access a key from a storage map before the storage has been set, for example, the call will revert.

Declaring these variables in storage requires a storage block as follows:

storage {
    storage_map: StorageMap<u64, bool> = StorageMap {},
    storage_vec: StorageVec<b256> = StorageVec {},
    storage_string: StorageString = StorageString {},
    storage_bytes: StorageBytes = StorageBytes {},
}

Icon LinkStorageMaps<K, V>

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>.

Warning While the StorageMap<K, V> is currently included in the prelude, to use it the Hash trait must still be imported. This is a known issue and will be resolved.

use std::hash::Hash;

To write to a storage map, call either the insert() or try_insert() functions as follows:

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());

The following demonstrates how to read from a storage map:

// 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();

Icon LinkStorageVec<T>

Generic storage vectors are available in the standard library as StorageVec<T> which have to be defined inside a storage block and allow you to call push() and pop() to push and pop values from a vector respectively. Refer to Storage Vector for more information about StorageVec<T>.

The following demonstrates how to import StorageVec<T>:

use std::storage::storage_vec::*;
Icon InfoCircle

NOTE: When importing the StorageVec<T>, please be sure to use the glob operator: use std::storage::storage_vec::*.

The following demonstrates how to write to a 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());

The following demonstrates how to read from a 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());

Icon LinkStorageBytes

Storage of Bytes is available in the standard library as StorageBytes which have to be defined inside a storage block. StorageBytes cannot be manipulated in the same way a StorageVec<T> or StorageMap<K, V> can but stores bytes more efficiently thus reducing gas. Only the entirety of a Bytes may be read/written to storage. This means any changes would require loading the entire Bytes to the heap, making changes, and then storing it once again. If frequent changes are needed, a StorageVec<u8> is recommended.

The following demonstrates how to import StorageBytes:

use std::storage::storage_bytes::*;
Icon InfoCircle

NOTE: When importing the StorageBytes, please be sure to use the glob operator: use std::storage::storage_bytes::*.

The following demonstrates how to write to a 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);

The following demonstrates how to read from a StorageBytes:

let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();

Icon LinkStorageString

Storage of String is available in the standard library as StorageString which have to be defined inside a storage block. StorageString cannot be manipulated in the same way a StorageVec<T> or StorageMap<K, V>. Only the entirety of a String may be read/written to storage.

The following demonstrates how to import StorageString:

use std::storage::storage_string::*;
Icon InfoCircle

NOTE: When importing the StorageString, please be sure to use the glob operator: use std::storage::storage_string::*.

The following demonstrates how to write to a StorageString:

let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);

The following demonstrates how to read from a StorageString:

let stored_string: String = storage.storage_string.read_slice().unwrap();

Icon LinkAdvanced Storage

For more advanced storage techniques please refer to the Advanced Storage page.