Skip to content

Commit

Permalink
Update std::storage::get and std::storage::StorageMap::get to ret…
Browse files Browse the repository at this point in the history
…urn an `Option` (FuelLabs#3862)

API change:
Previous:
```rust
pub fn get<T>(key: b256) -> T;
pub fn get(self, key: K) -> V;
```
Now:
```rust
pub fn get<T>(key: b256) -> Option<T>;
pub fn get(self, key: K) -> Option<V>;
```
`Option::None` indicates that the storage slot requested in not
available.

- Updated all storage intrinsics to return a `bool` except for
`__state_load_word` because it already returns the value loaded. The
`bool` returned represents the previous state of the storage slots as
described in the specs. I was not able to update `__state_load_word` to
return both the value loaded and the `bool` unfortunately because I hit
various hiccups along the way, particularly in IR/codegen.
- Updated `get` and `StorageMap::get` in the standard library to return
an `Option` based on the Boolean mentioned above. In the copy type case,
I had to use `asm` blocks instead of `__state_load_word` until we're
able to return a struct from an intrinsic.
- I did not update `store` and `StorageMap::insert` to return an
`Option`, for now, because that would involve an extra storage read to
return the previous value, if available. We can think about whether this
is worth it at some point.
- I added `unwrap` and `unwrap_or` where it makes sense in `StoargeVec`.
- Update various docs, examples, and tests.

Closes FuelLabs#3318

I will open separate issues for updating `__state_load_word` and
thinking about making `store` and `insert` also return an `Option`.
  • Loading branch information
mohammadfawaz authored Jan 24, 2023
1 parent a0ef103 commit 0a3beae
Show file tree
Hide file tree
Showing 28 changed files with 586 additions and 310 deletions.
10 changes: 1 addition & 9 deletions docs/book/src/common-collections/storage_map.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,7 @@ We can get a value out of the storage map by providing its `key` to the `get` me
{{#include ../../../../examples/storage_map/src/main.sw:storage_map_get}}
```

Here, `value1` will have the value that's associated with the first address, and the result will be `42`. You might expect `get` to return an `Option<V>` where the return value would be `None` if the value does not exist. However, that is not case for `StorageMap`. In fact, storage maps have no way of knowing whether `insert` has been called with a given key or not as it would be too expensive to keep track of that information. Instead, a default value whose byte-representation is all zeros is returned if `get` is called with a key that has no value in the map. Note that each type interprets that default value differently:

* The default value for a `bool` is `false`.
* The default value for a integers is `0`.
* The default value for a `b256` is `0x0000000000000000000000000000000000000000000000000000000000000000`.
* The default value for a `str[n]` is a string of `Null` characters.
* The default value for a tuple is a tuple of the default values of its components.
* The default value for a struct is a struct of the default values of its components.
* The default value for an enum is an instance of its first variant containing the default for its associated value.
Here, `value1` will have the value that's associated with the first address, and the result will be `42`. The `get` method returns an `Option<V>`; if there’s no value for that key in the storage map, `get` will return `Option::None`. This program handles the `Option` by calling `unwrap_or` to set `value1` to zero if `map` doesn't have an entry for the key.

## Storage maps with multiple keys

Expand Down
12 changes: 6 additions & 6 deletions docs/book/src/reference/compiler_intrinsics.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,30 +75,30 @@ __state_load_word(key: b256) -> u64
___

```sway
__state_load_quad(key: b256, ptr: raw_ptr, slots: u64)
__state_load_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool
```

**Description:** Reads `slots` number of slots (`b256` each) from storage starting at key `key` and stores them in memory starting at address `ptr`
**Description:** Reads `slots` number of slots (`b256` each) from storage starting at key `key` and stores them in memory starting at address `ptr`. Returns a Boolean describing whether all the storage slots were previously set.

**Constraints:** None.

___

```sway
__state_store_word(key: b256, val: u64)
__state_store_word(key: b256, val: u64) -> bool
```

**Description:** Stores a single word `val` into storage at key `key`.
**Description:** Stores a single word `val` into storage at key `key`. Returns a Boolean describing whether the store slot was previously set.

**Constraints:** None.

___

```sway
__state_store_quad(key: b256, ptr: raw_ptr, slots: u64)
__state_store_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool
```

**Description:** Stores `slots` number of slots (`b256` each) starting at address `ptr` in memory into storage starting at key `key`.
**Description:** Stores `slots` number of slots (`b256` each) starting at address `ptr` in memory into storage starting at key `key`. Returns a Boolean describing whether the first storage slot was previously set.

**Constraints:** None.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ storage {
// ANCHOR: reading_from_storage
#[storage(read)]
fn reading_from_storage(id: u64) {
let user = storage.user.get((msg_sender().unwrap(), id));
let user = storage.user.get((msg_sender().unwrap(), id)).unwrap_or(0);
}
// ANCHOR_END: reading_from_storage
// ANCHOR: writing_to_storage
#[storage(read, write)]
fn writing_to_storage() {
let balance = storage.balance.get(msg_sender().unwrap());
let balance = storage.balance.get(msg_sender().unwrap()).unwrap_or(0);
storage.balance.insert(msg_sender().unwrap(), balance + 1);
}
// ANCHOR_END: writing_to_storage
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Missing Features
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ There are two `storage` variables: `balance` & `user`. `balance` takes a single

## Reading from Storage

Retrieving data from a storage variable is done through the `.get(key)` method. That is to say that we state which storage variable we would like to read from and append `.get()` to the end while providing the key for the data that we want to retrieve.
Retrieving data from a storage variable is done through the `.get(key)` method. That is to say that we state which storage variable we would like to read from and append `.get()` to the end while providing the key for the data that we want to retrieve. The method `get` returns an `Option`; if there is no value for `key` in the map, `get` will return `Option::None`.

In this example we wrap the [`Identity`](../../namespace/identity.md) of the caller with their provided `id` into a [tuple](../../../language/built-ins/tuples.md) and use that as the key.

```sway
{{#include ../../../../code/operations/storage/storage_map/src/main.sw:reading_from_storage}}
```

This contract method handles the returned `Option` by calling `unwrap_or` to set `user` to zero if the map `user` doesn't have an entry for the key.

## Writing to Storage

Writing to storage is similar to [reading](#reading-from-storage). The difference is that we use a different method `.insert(key, value)`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ In this example we retrieve some `u64` at the position of `key`.
```sway
{{#include ../../../../code/operations/storage/store_get/src/main.sw:get}}
```

The function `get` returns an `Option`; if the storage slots requested have not been set before, `get` will return `Option::None`.
2 changes: 1 addition & 1 deletion examples/cei_analysis/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl MyContract for Contract {
#[storage(read, write)]
fn withdraw(external_contract_id: ContractId) {
let sender = msg_sender().unwrap();
let bal = storage.balances.get(sender);
let bal = storage.balances.get(sender).unwrap_or(0);

assert(bal > 0);

Expand Down
4 changes: 2 additions & 2 deletions examples/storage_example/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl StorageExample for Contract {

#[storage(read)]
fn get_something() -> u64 {
let value = get::<u64>(STORAGE_KEY);
value
let value: Option<u64> = get::<u64>(STORAGE_KEY);
value.unwrap_or(0)
}
}
2 changes: 1 addition & 1 deletion examples/storage_map/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl StorageMapExample for Contract {
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);

let value1 = storage.map.get(addr1);
let value1 = storage.map.get(addr1).unwrap_or(0);
}
// ANCHOR_END: storage_map_get
}
6 changes: 3 additions & 3 deletions examples/subcurrency/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl Token for Contract {
};

// Increase the balance of receiver
storage.balances.insert(receiver, storage.balances.get(receiver) + amount);
storage.balances.insert(receiver, storage.balances.get(receiver).unwrap_or(0) + amount);
}

#[storage(read, write)]
Expand All @@ -80,12 +80,12 @@ impl Token for Contract {
};

// Reduce the balance of sender
let sender_amount = storage.balances.get(sender);
let sender_amount = storage.balances.get(sender).unwrap_or(0);
assert(sender_amount > amount);
storage.balances.insert(sender, sender_amount - amount);

// Increase the balance of receiver
storage.balances.insert(receiver, storage.balances.get(receiver) + amount);
storage.balances.insert(receiver, storage.balances.get(receiver).unwrap_or(0) + amount);

log(Sent {
from: sender,
Expand Down
28 changes: 21 additions & 7 deletions sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1620,17 +1620,25 @@ impl<'ir> FuelAsmBuilder<'ir> {

self.cur_bytecode.push(Op {
opcode: Either::Left(match access_type {
StateAccessType::Read => {
VirtualOp::SRWQ(val_reg, was_slot_set_reg, key_reg, number_of_slots_reg)
}
StateAccessType::Write => {
VirtualOp::SWWQ(key_reg, was_slot_set_reg, val_reg, number_of_slots_reg)
}
StateAccessType::Read => VirtualOp::SRWQ(
val_reg,
was_slot_set_reg.clone(),
key_reg,
number_of_slots_reg,
),
StateAccessType::Write => VirtualOp::SWWQ(
key_reg,
was_slot_set_reg.clone(),
val_reg,
number_of_slots_reg,
),
}),
comment: "quad word state access".into(),
owning_span,
});

self.reg_map.insert(*instr_val, was_slot_set_reg);

ok((), Vec::new(), Vec::new())
}

Expand Down Expand Up @@ -1716,14 +1724,20 @@ impl<'ir> FuelAsmBuilder<'ir> {
let key_reg = self.offset_reg(&base_reg, key_offset_in_bytes, owning_span.clone());

self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::SWW(key_reg, was_slot_set_reg, store_reg)),
opcode: Either::Left(VirtualOp::SWW(
key_reg,
was_slot_set_reg.clone(),
store_reg,
)),
comment: "single word state access".into(),
owning_span,
});
}
_ => unreachable!("Unexpected storage locations for key and store_val"),
}

self.reg_map.insert(*instr_val, was_slot_set_reg);

ok((), Vec::new(), Vec::new())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,8 @@ fn type_check_state_load_word(
}

/// Signature: `__state_store_word(key: b256, val: u64) -> bool`
/// Description: Stores a single word `val` into storage at key `key`.
/// Description: Stores a single word `val` into storage at key `key`. Returns a Boolean describing
/// whether the store slot was previously set.
/// Constraints: None.
fn type_check_state_store_word(
ctx: TypeCheckContext,
Expand Down Expand Up @@ -718,18 +719,20 @@ fn type_check_state_store_word(
type_arguments: type_argument.map_or(vec![], |ta| vec![ta]),
span,
};
let return_type = type_engine.insert(decl_engine, TypeInfo::Tuple(vec![]));
let return_type = type_engine.insert(decl_engine, TypeInfo::Boolean);
ok((intrinsic_function, return_type), warnings, errors)
}

/// Signature: `__state_load_quad(key: b256, ptr: raw_ptr, slots: u64)`
/// Description: Reads `slots` number of slots (`b256` each) from storage starting at key `key` and
/// stores them in memory starting at address `ptr`
/// stores them in memory starting at address `ptr`. Returns a Boolean describing
/// whether all the storage slots were previously set.
/// Constraints: None.
///
/// Signature: `__state_store_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool`
/// Description: Stores `slots` number of slots (`b256` each) starting at address `ptr` in memory
/// into storage starting at key `key`.
/// into storage starting at key `key`. Returns a Boolean describing
/// whether the first storage slot was previously set.
/// Constraints: None.
fn type_check_state_quad(
ctx: TypeCheckContext,
Expand Down Expand Up @@ -840,7 +843,7 @@ fn type_check_state_quad(
type_arguments: type_argument.map_or(vec![], |ta| vec![ta]),
span,
};
let return_type = type_engine.insert(decl_engine, TypeInfo::Tuple(vec![]));
let return_type = type_engine.insert(decl_engine, TypeInfo::Boolean);
ok((intrinsic_function, return_type), warnings, errors)
}

Expand Down
6 changes: 3 additions & 3 deletions sway-ir/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,13 @@ impl Instruction {

Instruction::FuelVm(FuelVmInstruction::Smo { .. }) => Some(Type::get_unit(context)),
Instruction::FuelVm(FuelVmInstruction::StateLoadQuadWord { .. }) => {
Some(Type::get_unit(context))
Some(Type::get_bool(context))
}
Instruction::FuelVm(FuelVmInstruction::StateStoreQuadWord { .. }) => {
Some(Type::get_unit(context))
Some(Type::get_bool(context))
}
Instruction::FuelVm(FuelVmInstruction::StateStoreWord { .. }) => {
Some(Type::get_unit(context))
Some(Type::get_bool(context))
}
Instruction::MemCopy { .. } => Some(Type::get_unit(context)),
Instruction::Store { .. } => Some(Type::get_unit(context)),
Expand Down
Loading

0 comments on commit 0a3beae

Please sign in to comment.