Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Add new versions for storage access host functions
Browse files Browse the repository at this point in the history
  • Loading branch information
athei committed Dec 17, 2021
1 parent 5bd5b84 commit d9ac33f
Show file tree
Hide file tree
Showing 6 changed files with 703 additions and 46 deletions.
128 changes: 121 additions & 7 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ where
fn store(&self, items: &Vec<(StorageKey, Vec<u8>)>) -> Result<(), &'static str> {
let info = self.info()?;
for item in items {
Storage::<T>::write(&info.trie_id, &item.0, Some(item.1.clone()), None)
Storage::<T>::write(&info.trie_id, &item.0, Some(item.1.clone()), None, false)
.map_err(|_| "Failed to write storage to restoration dest")?;
}
<ContractInfoOf<T>>::insert(&self.account_id, info.clone());
Expand Down Expand Up @@ -784,10 +784,10 @@ benchmarks! {
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
module: "__unstable__",
name: "seal_set_storage",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
return_type: None,
return_type: Some(ValueType::I32),
}],
data_segments: vec![
DataSegment {
Expand All @@ -800,6 +800,7 @@ benchmarks! {
Regular(Instruction::I32Const(0)), // value_ptr
Regular(Instruction::I32Const(0)), // value_len
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
.. Default::default()
});
Expand All @@ -814,10 +815,10 @@ benchmarks! {
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
module: "__unstable__",
name: "seal_set_storage",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
return_type: None,
return_type: Some(ValueType::I32),
}],
data_segments: vec![
DataSegment {
Expand All @@ -830,6 +831,7 @@ benchmarks! {
Instruction::I32Const(0), // value_ptr
Instruction::I32Const((n * 1024) as i32), // value_len
Instruction::Call(0),
Instruction::Drop,
])),
.. Default::default()
});
Expand All @@ -851,10 +853,10 @@ benchmarks! {
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
module: "__unstable__",
name: "seal_clear_storage",
params: vec![ValueType::I32],
return_type: None,
return_type: Some(ValueType::I32),
}],
data_segments: vec![
DataSegment {
Expand All @@ -865,6 +867,7 @@ benchmarks! {
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
Counter(0, key_len as u32),
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
.. Default::default()
});
Expand All @@ -876,6 +879,7 @@ benchmarks! {
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
Some(vec![42; T::Schedule::get().limits.payload_len as usize]),
None,
false,
)
.map_err(|_| "Failed to write to storage during setup.")?;
}
Expand Down Expand Up @@ -906,6 +910,10 @@ benchmarks! {
offset: 0,
value: key_bytes,
},
DataSegment {
offset: key_bytes_len as u32,
value: T::Schedule::get().limits.payload_len.to_le_bytes().into(),
},
],
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
Counter(0, key_len as u32), // key_ptr
Expand All @@ -924,6 +932,7 @@ benchmarks! {
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
Some(vec![]),
None,
false,
)
.map_err(|_| "Failed to write to storage during setup.")?;
}
Expand Down Expand Up @@ -970,12 +979,117 @@ benchmarks! {
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
Some(vec![42u8; (n * 1024) as usize]),
None,
false,
)
.map_err(|_| "Failed to write to storage during setup.")?;
<ContractInfoOf<T>>::insert(&instance.account_id, info.clone());
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])

#[skip_meta]
seal_take_storage {
let r in 0 .. API_BENCHMARK_BATCHES;
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
.collect::<Vec<_>>();
let key_len = sp_std::mem::size_of::<<T::Hashing as sp_runtime::traits::Hash>::Output>();
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
let key_bytes_len = key_bytes.len();
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "__unstable__",
name: "seal_take_storage",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
return_type: Some(ValueType::I32),
}],
data_segments: vec![
DataSegment {
offset: 0,
value: key_bytes,
},
DataSegment {
offset: key_bytes_len as u32,
value: T::Schedule::get().limits.payload_len.to_le_bytes().into(),
},
],
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
Counter(0, key_len as u32), // key_ptr
Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr
Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![])?;
let info = instance.info()?;
for key in keys {
Storage::<T>::write(
&info.trie_id,
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
Some(vec![]),
None,
false,
)
.map_err(|_| "Failed to write to storage during setup.")?;
}
<ContractInfoOf<T>>::insert(&instance.account_id, info.clone());
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])

#[skip_meta]
seal_take_storage_per_kb {
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
let keys = (0 .. API_BENCHMARK_BATCH_SIZE)
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
.collect::<Vec<_>>();
let key_len = sp_std::mem::size_of::<<T::Hashing as sp_runtime::traits::Hash>::Output>();
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
let key_bytes_len = key_bytes.len();
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "__unstable__",
name: "seal_take_storage",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
return_type: Some(ValueType::I32),
}],
data_segments: vec![
DataSegment {
offset: 0,
value: key_bytes,
},
DataSegment {
offset: key_bytes_len as u32,
value: T::Schedule::get().limits.payload_len.to_le_bytes().into(),
},
],
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
Counter(0, key_len as u32), // key_ptr
Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr
Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![])?;
let info = instance.info()?;
for key in keys {
Storage::<T>::write(
&info.trie_id,
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
Some(vec![42u8; (n * 1024) as usize]),
None,
false,
)
.map_err(|_| "Failed to write to storage during setup.")?;
}
<ContractInfoOf<T>>::insert(&instance.account_id, info.clone());
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])

// We transfer to unique accounts.
seal_transfer {
let r in 0 .. API_BENCHMARK_BATCHES;
Expand Down
122 changes: 119 additions & 3 deletions frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

use crate::{
gas::GasMeter,
storage::{self, Storage},
storage::{self, Storage, WriteOutcome},
AccountCounter, BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event,
Pallet as Contracts, Schedule,
};
Expand Down Expand Up @@ -140,9 +140,20 @@ pub trait Ext: sealing::Sealed {
/// was deleted.
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>>;

/// Returns true iff some storage entry exists under the supplied `key`
///
/// Returns `false` if the `key` wasn't previously set by `set_storage` or
/// was deleted.
fn contains_storage(&mut self, key: &StorageKey) -> bool;

/// Sets the storage entry by the given key to the specified value. If `value` is `None` then
/// the storage entry is deleted.
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult;
fn set_storage(
&mut self,
key: StorageKey,
value: Option<Vec<u8>>,
take_old: bool,
) -> Result<WriteOutcome, DispatchError>;

/// Returns a reference to the account id of the caller.
fn caller(&self) -> &AccountIdOf<Self::T>;
Expand Down Expand Up @@ -985,13 +996,23 @@ where
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
}

fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult {
fn contains_storage(&mut self, key: &StorageKey) -> bool {
Storage::<T>::contains(&self.top_frame_mut().contract_info().trie_id, key)
}

fn set_storage(
&mut self,
key: StorageKey,
value: Option<Vec<u8>>,
take_old: bool,
) -> Result<WriteOutcome, DispatchError> {
let frame = self.top_frame_mut();
Storage::<T>::write(
&frame.contract_info.get(&frame.account_id).trie_id,
&key,
value,
Some(&mut frame.nested_storage),
take_old,
)
}

Expand Down Expand Up @@ -2349,4 +2370,99 @@ mod tests {
assert_eq!(<AccountCounter<Test>>::get(), 4);
});
}

#[test]
fn set_storage_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
// Write
assert_eq!(
ctx.ext.set_storage([1; 32], Some(vec![1, 2, 3]), false),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage([2; 32], Some(vec![4, 5, 6]), true),
Ok(WriteOutcome::New)
);
assert_eq!(ctx.ext.set_storage([3; 32], None, false), Ok(WriteOutcome::New));
assert_eq!(ctx.ext.set_storage([4; 32], None, true), Ok(WriteOutcome::New));
assert_eq!(ctx.ext.set_storage([5; 32], Some(vec![]), false), Ok(WriteOutcome::New));
assert_eq!(ctx.ext.set_storage([6; 32], Some(vec![]), true), Ok(WriteOutcome::New));

// Overwrite
assert_eq!(
ctx.ext.set_storage([1; 32], Some(vec![42]), false),
Ok(WriteOutcome::Overwritten(3))
);
assert_eq!(
ctx.ext.set_storage([2; 32], Some(vec![48]), true),
Ok(WriteOutcome::Taken(vec![4, 5, 6]))
);
assert_eq!(ctx.ext.set_storage([3; 32], None, false), Ok(WriteOutcome::New));
assert_eq!(ctx.ext.set_storage([4; 32], None, true), Ok(WriteOutcome::New));
assert_eq!(
ctx.ext.set_storage([5; 32], Some(vec![]), false),
Ok(WriteOutcome::Overwritten(0))
);
assert_eq!(
ctx.ext.set_storage([6; 32], Some(vec![]), true),
Ok(WriteOutcome::Taken(vec![]))
);

exec_success()
});

ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
));
});
}

#[test]
fn contains_storage_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.set_storage([1; 32], Some(vec![1, 2, 3]), false),
Ok(WriteOutcome::New)
);
assert_eq!(ctx.ext.set_storage([2; 32], Some(vec![]), false), Ok(WriteOutcome::New));
assert_eq!(ctx.ext.contains_storage(&[1; 32]), true);
assert_eq!(ctx.ext.contains_storage(&[1; 32]), true);
assert_eq!(ctx.ext.contains_storage(&[3; 32]), false);

exec_success()
});

ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
));
});
}
}
Loading

0 comments on commit d9ac33f

Please sign in to comment.