Storage

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.

Storage 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 = ZERO_B256,
    var3: Address = Address::from(ZERO_B256),
    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(ZERO_B256);
        let var3: Address = storage.var3.try_read().unwrap_or(Address::from(ZERO_B256));
        let var4: Option<u8> = storage.var4.try_read().unwrap_or(None);

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

Common 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 {},
}

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

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

StorageVec<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 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, ZERO_B256);

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

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

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

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

StorageString

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

Advanced Storage

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