Skip to content

Commit

Permalink
feat: Use Vec of Bytes for Contract State (#671)
Browse files Browse the repository at this point in the history
* Change ContractsState::Value to Vec<u8>

* WIP

* WIP

* WIP

* WIP

* Update

* Update CHANGELOG.md

* Fix test

* Update blockchain.rs

* no_std vec

* WIP

* Test Fix

* WIP

* Maintain serialization/deserialization format

* Remove dbg

* Fix includes for no_std

* Use StorageRead/StorageWrite

* Clippeehee

* Uncomment take

* Update tests

* Fix test

* Rename StorageData to ContractsStateData for clarity

* Revert changes to StorageSlot

* Refactor ContractsState into separate file

* Minor refactor

* Update use statements

* Remove commented out code

* Fix feature flag

* Use iterator instead of the vector

---------

Co-authored-by: xgreenx <xgreenx9999@gmail.com>
  • Loading branch information
Brandon Vrooman and xgreenx authored Feb 27, 2024
1 parent a7dc1c0 commit ecf5b24
Show file tree
Hide file tree
Showing 22 changed files with 550 additions and 218 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed
#### Breaking

- [#671](https://github.com/FuelLabs/fuel-vm/pull/671): Support dynamically sized values in the ContractsState table by using a vector data type (`Vec<u8>`).
- [#682](https://github.com/FuelLabs/fuel-vm/pull/682): Include `Tip` policy in fee calculation
- [#683](https://github.com/FuelLabs/fuel-vm/pull/683): Simplify `InterpreterStorage` by removing dependency on `MerkleRootStorage` and removing `merkle_` prefix from method names.
- [#678](https://github.com/FuelLabs/fuel-vm/pull/678): Zero malleable fields before execution. Remove some now-obsolete GTF getters. Don't update `tx.receiptsRoot` after pushing receipts, and do it after execution instead.
Expand Down
24 changes: 22 additions & 2 deletions fuel-storage/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,26 @@ impl<'a, T: StorageRead<Type> + StorageSize<Type> + ?Sized, Type: Mappable>
}
}

impl<'a, T: StorageWrite<Type> + ?Sized, Type: Mappable> StorageWrite<Type>
for &'a mut T
{
fn write(&mut self, key: &Type::Key, buf: &[u8]) -> Result<usize, Self::Error> {
<T as StorageWrite<Type>>::write(self, key, buf)
}

fn replace(
&mut self,
key: &Type::Key,
buf: &[u8],
) -> Result<(usize, Option<Vec<u8>>), Self::Error> {
<T as StorageWrite<Type>>::replace(self, key, buf)
}

fn take(&mut self, key: &Type::Key) -> Result<Option<Vec<u8>>, Self::Error> {
<T as StorageWrite<Type>>::take(self, key)
}
}

impl<'a, T: MerkleRootStorage<Key, Type> + ?Sized, Key, Type: Mappable>
MerkleRootStorage<Key, Type> for &'a mut T
{
Expand Down Expand Up @@ -221,15 +241,15 @@ impl<'a, T, Type: Mappable> StorageMut<'a, T, Type> {

impl<'a, T: StorageWrite<Type>, Type: Mappable> StorageMut<'a, T, Type> {
#[inline(always)]
pub fn write(&mut self, key: &Type::Key, buf: Vec<u8>) -> Result<usize, T::Error> {
pub fn write(&mut self, key: &Type::Key, buf: &[u8]) -> Result<usize, T::Error> {
self.0.write(key, buf)
}

#[inline(always)]
pub fn replace(
&mut self,
key: &Type::Key,
buf: Vec<u8>,
buf: &[u8],
) -> Result<(usize, Option<Vec<u8>>), T::Error>
where
T: StorageSize<Type>,
Expand Down
8 changes: 3 additions & 5 deletions fuel-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ pub trait StorageWrite<Type: Mappable>: StorageMutate<Type> {
/// Does not perform any serialization.
///
/// Returns the number of bytes written.
fn write(&mut self, key: &Type::Key, buf: Vec<u8>) -> Result<usize, Self::Error>;
fn write(&mut self, key: &Type::Key, buf: &[u8]) -> Result<usize, Self::Error>;

/// Write the value to the given key from the provided buffer and
/// return the previous value if it existed.
Expand All @@ -143,10 +143,8 @@ pub trait StorageWrite<Type: Mappable>: StorageMutate<Type> {
fn replace(
&mut self,
key: &Type::Key,
buf: Vec<u8>,
) -> Result<(usize, Option<Vec<u8>>), Self::Error>
where
Self: StorageSize<Type>;
buf: &[u8],
) -> Result<(usize, Option<Vec<u8>>), Self::Error>;

/// Removes a value from the storage and returning it without deserializing it.
fn take(&mut self, key: &Type::Key) -> Result<Option<Vec<u8>>, Self::Error>;
Expand Down
16 changes: 5 additions & 11 deletions fuel-tx/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ use fuel_types::{
};

use alloc::vec::Vec;
use core::{
iter,
ops::Deref,
};
use core::iter;

/// The target size of Merkle tree leaves in bytes. Contract code will will be divided
/// into chunks of this size and pushed to the Merkle tree.
Expand Down Expand Up @@ -105,13 +102,10 @@ impl Contract {
where
I: Iterator<Item = &'a StorageSlot>,
{
let root = SparseMerkleTree::root_from_set(storage_slots.map(|slot| {
(
MerkleTreeKey::new(*slot.key().deref()),
*slot.value().deref(),
)
}));

let storage_slots = storage_slots
.map(|slot| (*slot.key(), slot.value()))
.map(|(key, data)| (MerkleTreeKey::new(key), data));
let root = SparseMerkleTree::root_from_set(storage_slots);
root.into()
}

Expand Down
5 changes: 1 addition & 4 deletions fuel-tx/src/transaction/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,7 @@ mod tests {
}

fn invert_storage_slot(storage_slot: &mut StorageSlot) {
let mut data = [0u8; 64];
storage_slot
.encode(&mut &mut data[..])
.expect("Failed to encode storage slot");
let mut data = storage_slot.to_bytes();
invert(&mut data);
*storage_slot =
StorageSlot::from_bytes(&data).expect("Failed to decode storage slot");
Expand Down
1 change: 0 additions & 1 deletion fuel-tx/src/transaction/types/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use fuel_types::{
Bytes32,
Bytes64,
};

#[cfg(feature = "random")]
use rand::{
distributions::{
Expand Down
28 changes: 13 additions & 15 deletions fuel-vm/src/interpreter/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ use crate::{
storage::{
ContractsAssetsStorage,
ContractsRawCode,
ContractsStateData,
InterpreterStorage,
},
};
Expand Down Expand Up @@ -1013,7 +1014,7 @@ pub(crate) fn state_read_word<S: InterpreterStorage>(
.map_err(RuntimeError::Storage)?
.map(|bytes| {
Word::from_be_bytes(
bytes[..8]
bytes.as_ref().as_ref()[..8]
.try_into()
.expect("8 bytes can be converted to a Word"),
)
Expand Down Expand Up @@ -1065,17 +1066,16 @@ pub(crate) fn state_write_word<S: InterpreterStorage>(
let contract = ContractId::from_bytes_ref(contract.read(memory));
let key = Bytes32::from_bytes_ref(key.read(memory));

let mut value = Bytes32::default();
let mut value = Bytes32::zeroed();
value.as_mut()[..WORD_SIZE].copy_from_slice(&c.to_be_bytes());

value[..WORD_SIZE].copy_from_slice(&c.to_be_bytes());

let result = storage
.contract_state_insert(contract, key, &value)
let (_size, prev) = storage
.contract_state_insert(contract, key, value.as_ref())
.map_err(RuntimeError::Storage)?;

*created_new = result.is_none() as Word;
*created_new = prev.is_none() as Word;

if result.is_none() {
if prev.is_none() {
// New data was written, charge gas for it
let profiler = ProfileGas {
pc: pc.as_ref(),
Expand Down Expand Up @@ -1239,10 +1239,10 @@ fn state_read_qword<S: InterpreterStorage>(
.map_err(RuntimeError::Storage)?
.into_iter()
.flat_map(|bytes| match bytes {
Some(bytes) => **bytes,
Some(bytes) => bytes.into_owned(),
None => {
all_set = false;
*Bytes32::zeroed()
ContractsStateData::from(Bytes32::zeroed().as_ref())
}
})
.collect();
Expand Down Expand Up @@ -1304,13 +1304,11 @@ fn state_write_qword<'vm, S: InterpreterStorage>(
let destination_key =
Bytes32::from_bytes_ref(input.starting_storage_key_memory_range.read(memory));

let values: Vec<_> = memory[input.source_address_memory_range.usizes()]
.chunks_exact(Bytes32::LEN)
.flat_map(|chunk| Some(Bytes32::from(<[u8; 32]>::try_from(chunk).ok()?)))
.collect();
let values =
memory[input.source_address_memory_range.usizes()].chunks_exact(Bytes32::LEN);

let unset_count = storage
.contract_state_insert_range(contract_id, destination_key, &values)
.contract_state_insert_range(contract_id, destination_key, values)
.map_err(RuntimeError::Storage)?;
*result_register = unset_count as Word;

Expand Down
2 changes: 1 addition & 1 deletion fuel-vm/src/interpreter/blockchain/other_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ fn test_code_size() {
let mut memory: Memory<MEM_SIZE> = vec![1u8; MEM_SIZE].try_into().unwrap();
memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice());
StorageAsMut::storage::<ContractsRawCode>(&mut storage)
.write(&ContractId::from([3u8; 32]), vec![1u8; 100])
.write(&ContractId::from([3u8; 32]), &[1u8; 100])
.unwrap();
let mut pc = 4;
let is = 0;
Expand Down
9 changes: 8 additions & 1 deletion fuel-vm/src/interpreter/blockchain/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use core::{
use crate::{
context::Context,
interpreter::memory::Memory,
storage::MemoryStorage,
storage::{
ContractsStateData,
MemoryStorage,
},
};
use test_case::test_case;

Expand All @@ -34,6 +37,10 @@ const fn key(k: u8) -> [u8; 32] {
]
}

fn data(value: &[u8]) -> ContractsStateData {
ContractsStateData::from(value)
}

impl OwnershipRegisters {
pub fn test(stack: Range<u64>, heap: Range<u64>, context: Context) -> Self {
Self {
Expand Down
23 changes: 13 additions & 10 deletions fuel-vm/src/interpreter/blockchain/test/scwq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use alloc::{

use crate::storage::{
ContractsState,
ContractsStateData,
MemoryStorage,
};

Expand All @@ -16,22 +17,22 @@ use test_case::test_case;

struct SCWQInput {
input: StateClearQWord,
storage_slots: Vec<([u8; 32], [u8; 32])>,
storage_slots: Vec<([u8; 32], ContractsStateData)>,
memory: Memory<MEM_SIZE>,
}

#[test_case(
SCWQInput{
input: StateClearQWord::new(0, 1).unwrap(),
storage_slots: vec![(key(27), [8; 32])],
storage_slots: vec![(key(27), data(&[8; 32]))],
memory: mem(&[&key(27)]),
} => (vec![], true)
; "Clear single storage slot"
)]
#[test_case(
SCWQInput{
input: StateClearQWord::new(0, 2).unwrap(),
storage_slots: vec![(key(27), [8; 32]), (key(28), [9; 32])],
storage_slots: vec![(key(27), data(&[8; 32])), (key(28), data(&[9; 32]))],
memory: mem(&[&key(27)]),
} => (vec![], true)
; "Clear multiple existing storage slots"
Expand All @@ -55,20 +56,22 @@ struct SCWQInput {
#[test_case(
SCWQInput{
input: StateClearQWord::new(0, 2).unwrap(),
storage_slots: vec![(key(27), [8; 32]), (key(29), [8; 32])],
storage_slots: vec![(key(27), data(&[8; 32])), (key(29), data(&[8; 32]))],
memory: mem(&[&key(27)]),
} => (vec![(key(29), [8; 32])], false)
} => (vec![(key(29), vec![8; 32].into())], false)
; "Clear storage slots with some previously set"
)]
#[test_case(
SCWQInput{
input: StateClearQWord::new(0, 2).unwrap(),
storage_slots: vec![(key(27), [8; 32]), (key(26), [8; 32])],
storage_slots: vec![(key(27), data(&[8; 32])), (key(26), data(&[8; 32]))],
memory: mem(&[&key(27)]),
} => (vec![(key(26), [8; 32])], false)
} => (vec![(key(26), vec![8; 32].into())], false)
; "Clear storage slots with some previously set before the key"
)]
fn test_state_clear_qword(input: SCWQInput) -> (Vec<([u8; 32], [u8; 32])>, bool) {
fn test_state_clear_qword(
input: SCWQInput,
) -> (Vec<([u8; 32], ContractsStateData)>, bool) {
let SCWQInput {
input,
storage_slots,
Expand All @@ -81,7 +84,7 @@ fn test_state_clear_qword(input: SCWQInput) -> (Vec<([u8; 32], [u8; 32])>, bool)
.storage::<ContractsState>()
.insert(
&(&ContractId::default(), &Bytes32::new(k)).into(),
&Bytes32::new(v),
v.as_ref(),
)
.unwrap();
}
Expand All @@ -100,7 +103,7 @@ fn test_state_clear_qword(input: SCWQInput) -> (Vec<([u8; 32], [u8; 32])>, bool)

let results = storage
.all_contract_state()
.map(|(key, v)| (**key.state_key(), **v))
.map(|(key, v)| (**key.state_key(), v.clone()))
.collect();
(results, result_register != 0)
}
Expand Down
21 changes: 11 additions & 10 deletions fuel-vm/src/interpreter/blockchain/test/srwq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
context::Context,
storage::{
ContractsState,
ContractsStateData,
MemoryStorage,
},
};
Expand All @@ -12,7 +13,7 @@ use test_case::test_case;

struct SRWQInput {
input: StateReadQWord,
storage_slots: Vec<([u8; 32], [u8; 32])>,
storage_slots: Vec<([u8; 32], ContractsStateData)>,
memory: Memory<MEM_SIZE>,
}

Expand Down Expand Up @@ -43,56 +44,56 @@ impl StateReadQWord {
#[test_case(
SRWQInput{
input: StateReadQWord::test(1, 2, 1).unwrap(),
storage_slots: vec![(key(27), [5; 32])],
storage_slots: vec![(key(27), data(&[5; 32]))],
memory: mem(&[&[0; 2], &key(27)]),
} => (mem(&[&[0], &[5; 32], &[27]]), true)
)]
#[test_case(
SRWQInput{
input: StateReadQWord::test(0, 0, 2).unwrap(),
storage_slots: vec![(key(27), [5; 32]), (key(28), [6; 32])],
storage_slots: vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32]))],
memory: mem(&[&key(27)]),
} => (mem(&[&[5; 32], &[6; 32]]), true)
)]
#[test_case(
SRWQInput{
input: StateReadQWord::test(0, 0, 3).unwrap(),
storage_slots: vec![(key(27), [5; 32]), (key(28), [6; 32]), (key(29), [7; 32])],
storage_slots: vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32]))],
memory: mem(&[&key(27)]),
} => (mem(&[&[5; 32], &[6; 32], &[7; 32]]), true)
)]
#[test_case(
SRWQInput{
input: StateReadQWord::test(0, 0, 2).unwrap(),
storage_slots: vec![(key(27), [5; 32]), (key(28), [6; 32]), (key(29), [7; 32])],
storage_slots: vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32]))],
memory: mem(&[&key(27)]),
} => (mem(&[&[5; 32], &[6; 32]]), true)
)]
#[test_case(
SRWQInput{
input: StateReadQWord::test(0, 0, 3).unwrap(),
storage_slots: vec![(key(27), [5; 32]), (key(30), [6; 32]), (key(29), [7; 32])],
storage_slots: vec![(key(27), data(&[5; 32])), (key(30), data(&[6; 32])), (key(29), data(&[7; 32]))],
memory: mem(&[&key(27)]),
} => (mem(&[&[5; 32], &[0; 32], &[7; 32]]), false)
)]
#[test_case(
SRWQInput{
input: StateReadQWord::test(0, 0, 3).unwrap(),
storage_slots: vec![(key(27), [5; 32]), (key(28), [7; 32])],
storage_slots: vec![(key(27), data(&[5; 32])), (key(28), data(&[7; 32]))],
memory: mem(&[&key(27)]),
} => (mem(&[&[5; 32], &[7; 32], &[0; 32]]), false)
)]
#[test_case(
SRWQInput{
input: StateReadQWord::test(0, 0, 3).unwrap(),
storage_slots: vec![(key(26), [5; 32]), (key(28), [6; 32]), (key(29), [7; 32])],
storage_slots: vec![(key(26), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32]))],
memory: mem(&[&key(27)]),
} => (mem(&[&[0; 32], &[6; 32], &[7; 32]]), false)
)]
#[test_case(
SRWQInput{
input: StateReadQWord::test(0, 0, 3).unwrap(),
storage_slots: vec![(key(28), [6; 32]), (key(29), [7; 32])],
storage_slots: vec![(key(28), data(&[6; 32])), (key(29), data(&[7; 32]))],
memory: mem(&[&key(27)]),
} => (mem(&[&[0; 32], &[6; 32], &[7; 32]]), false)
)]
Expand All @@ -108,7 +109,7 @@ fn test_state_read_qword(input: SRWQInput) -> (Memory<MEM_SIZE>, bool) {
.storage::<ContractsState>()
.insert(
&(&ContractId::default(), &Bytes32::new(k)).into(),
&Bytes32::new(v),
v.as_ref(),
)
.unwrap();
}
Expand Down
Loading

0 comments on commit ecf5b24

Please sign in to comment.