Skip to content

storage_slots_allocator - [mainnet]

Abstraction to having “addressable” storage slots (i.e. items) in global storage. Addresses are local u64 values (unique within a single StorageSlotsAllocator instance, but can and do overlap across instances).

Allows optionally to initialize slots (and pay for them upfront), and then reuse them, providing predictable storage costs.

If we need to mutate multiple slots at the same time, we can workaround borrow_mut preventing us from that, via provided pair of remove_and_reserve and fill_reserved_slot methods, to do so in non-conflicting manner.

Similarly allows getting an address upfront via reserve_slot, for a slot created later (i.e. if we need address to initialize the value itself).

In the future, more sophisticated strategies can be added, without breaking/modifying callers, for example:

  • inlining some nodes
  • having a fee-payer for any storage creation operations
use 0x1::error;
use 0x1::option;
use 0x1::table_with_length;

Data stored in an individual slot

enum Link<T: store> has store
Variants
Occupied
Fields
value: T
Vacant
Fields
next: u64

Enum StorageSlotsAllocator

enum StorageSlotsAllocator<T: store> has store
Variants
V1
Fields
slots: option::Option<table_with_length::TableWithLength<u64, storage_slots_allocator::Link<T>>>
new_slot_index: u64
should_reuse: bool
reuse_head_index: u64
reuse_spare_count: u32

Constants

const ECANNOT_HAVE_SPARES_WITHOUT_REUSE: u64 = 2;
const EINTERNAL_INVARIANT_BROKEN: u64 = 7;
const EINVALID_ARGUMENT: u64 = 1;
const FIRST_INDEX: u64 = 10;
const NULL_INDEX: u64 = 0;

Structs

ReservedSlot

Handle to a reserved slot within a transaction. Not copy/drop/store-able, to guarantee reservation is used or released within the transaction.

struct ReservedSlot
Fields
slot_index: u64

StoredSlot

Ownership handle to a slot. Not copy/drop-able to make sure slots are released when not needed, and there is unique owner for each slot.

struct StoredSlot has store
Fields
slot_index: u64

Functions

new

public fun new<T: store>(should_reuse: bool): storage_slots_allocator::StorageSlotsAllocator<T>
Implementation
public fun new<T: store>(should_reuse: bool): StorageSlotsAllocator<T> {
StorageSlotsAllocator::V1 {
slots: option::none(),
new_slot_index: FIRST_INDEX,
should_reuse,
reuse_head_index: NULL_INDEX,
reuse_spare_count: 0,
}
}

allocate_spare_slots

public fun allocate_spare_slots<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, num_to_allocate: u64)
Implementation
public fun allocate_spare_slots<T: store>(self: &mut StorageSlotsAllocator<T>, num_to_allocate: u64) {
assert!(self.should_reuse, error::invalid_argument(ECANNOT_HAVE_SPARES_WITHOUT_REUSE));
for (i in 0..num_to_allocate) {
let slot_index = self.next_slot_index();
self.maybe_push_to_reuse_queue(slot_index);
};
}

get_num_spare_slot_count

public fun get_num_spare_slot_count<T: store>(self: &storage_slots_allocator::StorageSlotsAllocator<T>): u32
Implementation
public fun get_num_spare_slot_count<T: store>(self: &StorageSlotsAllocator<T>): u32 {
assert!(self.should_reuse, error::invalid_argument(ECANNOT_HAVE_SPARES_WITHOUT_REUSE));
self.reuse_spare_count
}

add

public fun add<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, val: T): storage_slots_allocator::StoredSlot
Implementation
public fun add<T: store>(self: &mut StorageSlotsAllocator<T>, val: T): StoredSlot {
let (stored_slot, reserved_slot) = self.reserve_slot();
self.fill_reserved_slot(reserved_slot, val);
stored_slot
}

remove

public fun remove<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, slot: storage_slots_allocator::StoredSlot): T
Implementation
public fun remove<T: store>(self: &mut StorageSlotsAllocator<T>, slot: StoredSlot): T {
let (reserved_slot, value) = self.remove_and_reserve(slot.stored_to_index());
self.free_reserved_slot(reserved_slot, slot);
value
}

destroy_empty

public fun destroy_empty<T: store>(self: storage_slots_allocator::StorageSlotsAllocator<T>)
Implementation
public fun destroy_empty<T: store>(self: StorageSlotsAllocator<T>) {
loop {
let reuse_index = self.maybe_pop_from_reuse_queue();
if (reuse_index == NULL_INDEX) {
break;
};
};
match (self) {
V1 {
slots,
new_slot_index: _,
should_reuse: _,
reuse_head_index,
reuse_spare_count: _,
} => {
assert!(reuse_head_index == NULL_INDEX, EINTERNAL_INVARIANT_BROKEN);
if (slots.is_some()) {
slots.destroy_some().destroy_empty();
} else {
slots.destroy_none();
}
},
};
}

borrow

public fun borrow<T: store>(self: &storage_slots_allocator::StorageSlotsAllocator<T>, slot_index: u64): &T
Implementation
public fun borrow<T: store>(self: &StorageSlotsAllocator<T>, slot_index: u64): &T {
&self.slots.borrow().borrow(slot_index).value
}

borrow_mut

public fun borrow_mut<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, slot_index: u64): &mut T
Implementation
public fun borrow_mut<T: store>(self: &mut StorageSlotsAllocator<T>, slot_index: u64): &mut T {
&mut self.slots.borrow_mut().borrow_mut(slot_index).value
}

reserve_slot

public fun reserve_slot<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>): (storage_slots_allocator::StoredSlot, storage_slots_allocator::ReservedSlot)
Implementation
public fun reserve_slot<T: store>(self: &mut StorageSlotsAllocator<T>): (StoredSlot, ReservedSlot) {
let slot_index = self.maybe_pop_from_reuse_queue();
if (slot_index == NULL_INDEX) {
slot_index = self.next_slot_index();
};
(
StoredSlot { slot_index },
ReservedSlot { slot_index },
)
}

fill_reserved_slot

public fun fill_reserved_slot<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, slot: storage_slots_allocator::ReservedSlot, val: T)
Implementation
public fun fill_reserved_slot<T: store>(self: &mut StorageSlotsAllocator<T>, slot: ReservedSlot, val: T) {
let ReservedSlot { slot_index } = slot;
self.add_link(slot_index, Link::Occupied { value: val });
}

remove_and_reserve

Remove storage slot, but reserve it for later.

public fun remove_and_reserve<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, slot_index: u64): (storage_slots_allocator::ReservedSlot, T)
Implementation
public fun remove_and_reserve<T: store>(self: &mut StorageSlotsAllocator<T>, slot_index: u64): (ReservedSlot, T) {
let Link::Occupied { value } = self.remove_link(slot_index);
(ReservedSlot { slot_index }, value)
}

free_reserved_slot

public fun free_reserved_slot<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, reserved_slot: storage_slots_allocator::ReservedSlot, stored_slot: storage_slots_allocator::StoredSlot)
Implementation
public fun free_reserved_slot<T: store>(self: &mut StorageSlotsAllocator<T>, reserved_slot: ReservedSlot, stored_slot: StoredSlot) {
let ReservedSlot { slot_index } = reserved_slot;
assert!(slot_index == stored_slot.slot_index, EINVALID_ARGUMENT);
let StoredSlot { slot_index: _ } = stored_slot;
self.maybe_push_to_reuse_queue(slot_index);
}

reserved_to_index

public fun reserved_to_index(self: &storage_slots_allocator::ReservedSlot): u64
Implementation
public fun reserved_to_index(self: &ReservedSlot): u64 {
self.slot_index
}

stored_to_index

public fun stored_to_index(self: &storage_slots_allocator::StoredSlot): u64
Implementation
public fun stored_to_index(self: &StoredSlot): u64 {
self.slot_index
}

is_null_index

public fun is_null_index(slot_index: u64): bool
Implementation
public fun is_null_index(slot_index: u64): bool {
slot_index == NULL_INDEX
}

is_special_unused_index

public fun is_special_unused_index(slot_index: u64): bool
Implementation
public fun is_special_unused_index(slot_index: u64): bool {
slot_index != NULL_INDEX && slot_index < FIRST_INDEX
}

maybe_pop_from_reuse_queue

fun maybe_pop_from_reuse_queue<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>): u64
Implementation
fun maybe_pop_from_reuse_queue<T: store>(self: &mut StorageSlotsAllocator<T>): u64 {
let slot_index = self.reuse_head_index;
if (slot_index != NULL_INDEX) {
let Link::Vacant { next } = self.remove_link(slot_index);
self.reuse_head_index = next;
self.reuse_spare_count -= 1;
};
slot_index
}

maybe_push_to_reuse_queue

fun maybe_push_to_reuse_queue<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, slot_index: u64)
Implementation
fun maybe_push_to_reuse_queue<T: store>(self: &mut StorageSlotsAllocator<T>, slot_index: u64) {
if (self.should_reuse) {
let link = Link::Vacant { next: self.reuse_head_index };
self.add_link(slot_index, link);
self.reuse_head_index = slot_index;
self.reuse_spare_count += 1;
};
}

next_slot_index

fun next_slot_index<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>): u64
Implementation
fun next_slot_index<T: store>(self: &mut StorageSlotsAllocator<T>): u64 {
let slot_index = self.new_slot_index;
self.new_slot_index += 1;
if (self.slots.is_none()) {
self.slots.fill(table_with_length::new<u64, Link<T>>());
};
slot_index
}
fun add_link<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, slot_index: u64, link: storage_slots_allocator::Link<T>)
Implementation
fun add_link<T: store>(self: &mut StorageSlotsAllocator<T>, slot_index: u64, link: Link<T>) {
self.slots.borrow_mut().add(slot_index, link);
}
fun remove_link<T: store>(self: &mut storage_slots_allocator::StorageSlotsAllocator<T>, slot_index: u64): storage_slots_allocator::Link<T>
Implementation
fun remove_link<T: store>(self: &mut StorageSlotsAllocator<T>, slot_index: u64): Link<T> {
self.slots.borrow_mut().remove(slot_index)
}

Specification

pragma verify = false;