-
Notifications
You must be signed in to change notification settings - Fork 5.4k
/
storage_api.sw
183 lines (163 loc) · 6.14 KB
/
storage_api.sw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
library;
use ::alloc::alloc;
use ::option::Option::{self, *};
/// Stores a stack value in storage. Will not work for heap values.
///
/// # Additional Information
///
/// If the value crosses the boundary of a storage slot, writing continues at the following slot.
///
/// # Arguments
///
/// * `slot`: [b256] - The storage slot at which the variable will be stored.
/// * `offset`: [u64] - An offset starting at the beginning of `slot` at which `value` should be stored.
/// * `value`: [T] - The value to be stored.
///
/// # Number of Storage Accesses
///
/// * Reads: `1`
/// * Writes: `1`
///
/// # Examples
///
/// ```sway
/// use std::{storage::storage_api::{read, write}, constants::ZERO_B256};
///
/// fn foo() {
/// let five = 5_u64;
/// write(ZERO_B256, 2, five);
/// let stored_five = read::<u64>(ZERO_B256, 2).unwrap();
/// assert(five == stored_five);
/// }
/// ```
#[storage(read, write)]
pub fn write<T>(slot: b256, offset: u64, value: T) {
if __size_of::<T>() == 0 {
return;
}
// Determine how many slots and where the value is to be stored.
let (offset_slot, number_of_slots, place_in_slot) = slot_calculator::<T>(slot, offset);
// Allocate enough memory on the heap for `value` as well as any potential padding required due
// to `offset`.
let padded_value = alloc::<u64>(number_of_slots * 32);
// Read the values that currently exist in the affected storage slots.
let _ = __state_load_quad(offset_slot, padded_value, number_of_slots);
// Copy the value to be stored to `padded_value + offset`.
padded_value.add::<u64>(place_in_slot).write::<T>(value);
// Now store back the data at `padded_value` which now contains the old data but partially
// overwritten by the new data in the desired locations.
let _ = __state_store_quad(offset_slot, padded_value, number_of_slots);
}
/// Reads a value of type `T` starting at the location specified by `slot` and `offset`. If the
/// value crosses the boundary of a storage slot, reading continues at the following slot.
///
/// # Arguments
///
/// * `slot`: [b256] - The storage slot to load the value from.
/// * `offset`: [u64] - An offset, in words, from the start of `slot`, from which the value should be read.
///
/// # Returns
///
/// * [Option<T>] - `Option(value)` if the storage slots read were valid and contain `value`. Otherwise,
/// returns `None`.
///
/// # Number of Storage Accesses
///
/// * Reads: `1`
///
/// # Examples
///
/// ```sway
/// use std::{storage::storage_api::{read, write}, constants::ZERO_B256};
///
/// fn foo() {
/// let five = 5_u64;
/// write(ZERO_B256, 2, five);
/// let stored_five = read::<u64>(ZERO_B256, 2);
/// assert(five == stored_five.unwrap());
/// }
/// ```
#[storage(read)]
pub fn read<T>(slot: b256, offset: u64) -> Option<T> {
if __size_of::<T>() == 0 {
return None;
}
// Determine how many slots and where the value is to be read.
let (offset_slot, number_of_slots, place_in_slot) = slot_calculator::<T>(slot, offset);
// Allocate a buffer for the result. Its size needs to be a multiple of 32 bytes so we can
// make the 'quad' storage instruction read without overflowing.
let result_ptr = alloc::<u64>(number_of_slots * 32);
// Read `number_of_slots * 32` bytes starting at storage slot `slot` and return an `Option`
// wrapping the value stored at `result_ptr + offset` if all the slots are valid. Otherwise,
// return `None`.
if __state_load_quad(offset_slot, result_ptr, number_of_slots) {
Some(result_ptr.add::<u64>(place_in_slot).read::<T>())
} else {
None
}
}
/// Clear a value starting at some slot with an offset.
///
/// # Arguments
///
/// * `slot` - The key of the stored value that will be cleared
/// * `offset` - An offset, in words, from the start of `slot`, from which the value should be cleared.
///
/// # Number of Storage Accesses
///
/// * Clears: `1`
///
/// # Examples
///
/// ```sway
/// use std::{storage::storage_api::{read, write, clear}, constants::ZERO_B256};
///
/// fn foo() {
/// let five = 5_u64;
/// write(ZERO_B256, 0, five);
/// let cleared = clear::<u64>(ZERO_B256);
/// assert(cleared);
/// assert(read::<u64>(ZERO_B256, 0).is_none());
/// }
/// ```
#[storage(write)]
pub fn clear<T>(slot: b256, offset: u64) -> bool {
if __size_of::<T>() == 0 {
return true;
}
// Determine how many slots and where the value is to be cleared.
let (offset_slot, number_of_slots, _place_in_slot) = slot_calculator::<T>(slot, offset);
// Clear `number_of_slots * 32` bytes starting at storage slot `slot`.
__state_clear(offset_slot, number_of_slots)
}
/// Given a slot, offset, and type this function determines where something should be stored.
///
/// # Arguments
///
/// * `slot`: [b256] - The starting address at which something should be stored.
/// * `offset`: [u64] - The offset from `slot` to store the value.
///
/// # Returns
///
/// [b256] - The calculated offset slot to store the value.
/// [u64] - The number of slots the value will occupy in storage.
/// [u64] - The word in the slot where the value will start.
fn slot_calculator<T>(slot: b256, offset: u64) -> (b256, u64, u64) {
let size_of_t = __size_of::<T>();
// Get the last storage slot needed based on the size of `T`.
// ((offset * bytes_in_word) + bytes + (bytes_in_slot - 1)) >> align_to_slot = last slot
let last_slot = ((offset * 8) + size_of_t + 31) >> 5;
// Where in the storage slot to align `T` in order to pack word-aligned.
// offset % number_words_in_slot = word_place_in_slot
let place_in_slot = offset % 4;
// Get the number of slots `T` spans based on its packed position.
// ((place_in_slot * bytes_in_word) + bytes + (bytes_in_slot - 1)) >> align_to_slot = number_of_slots
let number_of_slots = match __is_reference_type::<T>() {
true => ((place_in_slot * 8) + size_of_t + 31) >> 5,
false => 1,
};
// Determine which starting slot `T` will be stored based on the offset.
let mut offset_slot = slot.as_u256();
offset_slot += last_slot.as_u256() - number_of_slots.as_u256();
(offset_slot.as_b256(), number_of_slots, place_in_slot)
}