mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-22 22:01:08 +00:00
[contracts] Implement transparent hashing for contract storage (#11501)
* save * builds and old tests pass save: temporary value dropped while borrowed save: finally builds test updated but still fails * type names enhanced * VarSizedKey bounded to new Config param * improved wasm runtime updated funcs * unstable-interface tests fixed * benchmarks fixed * Apply suggestions from code review Co-authored-by: Alexander Theißen <alex.theissen@me.com> * fixes on feedback * fixes on feedback applied + make it build * benchmarks build but fail (old) * "Original code too large" * seal_clear_storage bench fixed (code size workaround hack removal tbd) * bench_seal_clear_storage pass * bench_seal_take_storage ... ok * added new seal_set_storage + updated benchmarks * added new seal_get_storage + updated benchmarks * added new seal_contains_storage + updated benchmarks * added tests for _transparent exec functions * wasm test for clear_storage * wasm test for take_storage * wasm test for new set_storage * wasm test for new get_storage * wasm test for new contains_storage * CI fix * ci fix * ci fix * ci fix * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * fixes according to the review feedback * tests & benchmarks fixed * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * refactoring * fix to runtime api * ci fix * ctx.get_storage() factored out * ctx.contains_storage() factored out * number of batches reduced for transparent hashing storage benchmarks * contracts RPC & pallet::get_storage to use transparent hashing * node and rpc updated to use get_storage with VarSizedKey * refactored (more concize) * refactored contains_storage (DRYed) * refactored contains_storage (DRYed) * fix rpc * fmt fix * more fixes in rpc * rollback `Pallet:get_storage` to Vec<u8> and rpc and node parts related to it * added `KeyDecodingFailed` error * Revert weird "fmt fix" This reverts commit c582cfff4b5cb2c9929fd5e3b45519bb24aeb657. * node-executor basic test update * fix node-executor basic test * benchmarks fix * more benchmarks fix * FixedSizedKey is hidden from pub, VarSizedKey is exported as StorageKey * ci fix * set_storage benchmark fix * ci fix * ci fix * comments improved * new error code to rpc: KEY_DECODING_FAILED * Put `rusty-cachier` before PR merge into `master` for `cargo-check-benches` job * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * minor optimization Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Parity Bot <admin@parity.io> Co-authored-by: Vladimir Istyufeev <vladimir@parity.io> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
7d2ecc8995
commit
68ea2ab039
@@ -764,7 +764,11 @@ fn deploying_wasm_contract_should_work() {
|
|||||||
t.execute_with(|| {
|
t.execute_with(|| {
|
||||||
// Verify that the contract does exist by querying some of its storage items
|
// Verify that the contract does exist by querying some of its storage items
|
||||||
// It does not matter that the storage item itself does not exist.
|
// It does not matter that the storage item itself does not exist.
|
||||||
assert!(&pallet_contracts::Pallet::<Runtime>::get_storage(addr, Default::default()).is_ok());
|
assert!(&pallet_contracts::Pallet::<Runtime>::get_storage(
|
||||||
|
addr,
|
||||||
|
pallet_contracts::StorageKey::<Runtime>::default().to_vec()
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1151,6 +1151,7 @@ impl pallet_contracts::Config for Runtime {
|
|||||||
type ContractAccessWeight = pallet_contracts::DefaultContractAccessWeight<RuntimeBlockWeights>;
|
type ContractAccessWeight = pallet_contracts::DefaultContractAccessWeight<RuntimeBlockWeights>;
|
||||||
type MaxCodeLen = ConstU32<{ 128 * 1024 }>;
|
type MaxCodeLen = ConstU32<{ 128 * 1024 }>;
|
||||||
type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>;
|
type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>;
|
||||||
|
type MaxStorageKeyLen = ConstU32<128>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_sudo::Config for Runtime {
|
impl pallet_sudo::Config for Runtime {
|
||||||
@@ -1936,7 +1937,7 @@ impl_runtime_apis! {
|
|||||||
|
|
||||||
fn get_storage(
|
fn get_storage(
|
||||||
address: AccountId,
|
address: AccountId,
|
||||||
key: [u8; 32],
|
key: Vec<u8>,
|
||||||
) -> pallet_contracts_primitives::GetStorageResult {
|
) -> pallet_contracts_primitives::GetStorageResult {
|
||||||
Contracts::get_storage(address, key)
|
Contracts::get_storage(address, key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
|
|||||||
pub enum ContractAccessError {
|
pub enum ContractAccessError {
|
||||||
/// The given address doesn't point to a contract.
|
/// The given address doesn't point to a contract.
|
||||||
DoesntExist,
|
DoesntExist,
|
||||||
|
/// Storage key cannot be decoded from the provided input data.
|
||||||
|
KeyDecodingFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ sp_api::decl_runtime_apis! {
|
|||||||
/// doesn't exist, or doesn't have a contract then `Err` is returned.
|
/// doesn't exist, or doesn't have a contract then `Err` is returned.
|
||||||
fn get_storage(
|
fn get_storage(
|
||||||
address: AccountId,
|
address: AccountId,
|
||||||
key: [u8; 32],
|
key: Vec<u8>,
|
||||||
) -> GetStorageResult;
|
) -> GetStorageResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use pallet_contracts_primitives::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sp_api::ProvideRuntimeApi;
|
use sp_api::ProvideRuntimeApi;
|
||||||
use sp_blockchain::HeaderBackend;
|
use sp_blockchain::HeaderBackend;
|
||||||
use sp_core::{Bytes, H256};
|
use sp_core::Bytes;
|
||||||
use sp_rpc::number::NumberOrHex;
|
use sp_rpc::number::NumberOrHex;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
generic::BlockId,
|
generic::BlockId,
|
||||||
@@ -44,6 +44,7 @@ pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi;
|
|||||||
|
|
||||||
const RUNTIME_ERROR: i32 = 1;
|
const RUNTIME_ERROR: i32 = 1;
|
||||||
const CONTRACT_DOESNT_EXIST: i32 = 2;
|
const CONTRACT_DOESNT_EXIST: i32 = 2;
|
||||||
|
const KEY_DECODING_FAILED: i32 = 3;
|
||||||
|
|
||||||
pub type Weight = u64;
|
pub type Weight = u64;
|
||||||
|
|
||||||
@@ -74,6 +75,12 @@ impl From<ContractAccessError> for JsonRpseeError {
|
|||||||
None::<()>,
|
None::<()>,
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
|
KeyDecodingFailed => CallError::Custom(ErrorObject::owned(
|
||||||
|
KEY_DECODING_FAILED,
|
||||||
|
"Failed to decode the specified storage key.",
|
||||||
|
None::<()>,
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +174,7 @@ where
|
|||||||
fn get_storage(
|
fn get_storage(
|
||||||
&self,
|
&self,
|
||||||
address: AccountId,
|
address: AccountId,
|
||||||
key: H256,
|
key: Bytes,
|
||||||
at: Option<BlockHash>,
|
at: Option<BlockHash>,
|
||||||
) -> RpcResult<Option<Bytes>>;
|
) -> RpcResult<Option<Bytes>>;
|
||||||
}
|
}
|
||||||
@@ -292,13 +299,13 @@ where
|
|||||||
fn get_storage(
|
fn get_storage(
|
||||||
&self,
|
&self,
|
||||||
address: AccountId,
|
address: AccountId,
|
||||||
key: H256,
|
key: Bytes,
|
||||||
at: Option<<Block as BlockT>::Hash>,
|
at: Option<<Block as BlockT>::Hash>,
|
||||||
) -> RpcResult<Option<Bytes>> {
|
) -> RpcResult<Option<Bytes>> {
|
||||||
let api = self.client.runtime_api();
|
let api = self.client.runtime_api();
|
||||||
let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
|
let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
|
||||||
let result = api
|
let result = api
|
||||||
.get_storage(&at, address, key.into())
|
.get_storage(&at, address, key.to_vec())
|
||||||
.map_err(runtime_error_into_rpc_err)?
|
.map_err(runtime_error_into_rpc_err)?
|
||||||
.map_err(ContractAccessError)?
|
.map_err(ContractAccessError)?
|
||||||
.map(Bytes);
|
.map(Bytes);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use self::{
|
|||||||
sandbox::Sandbox,
|
sandbox::Sandbox,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
exec::{AccountIdOf, StorageKey},
|
exec::{AccountIdOf, FixSizedKey, VarSizedKey},
|
||||||
schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
|
schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
|
||||||
storage::Storage,
|
storage::Storage,
|
||||||
wasm::CallFlags,
|
wasm::CallFlags,
|
||||||
@@ -132,11 +132,17 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Store the supplied storage items into this contracts storage.
|
/// Store the supplied storage items into this contracts storage.
|
||||||
fn store(&self, items: &Vec<(StorageKey, Vec<u8>)>) -> Result<(), &'static str> {
|
fn store(&self, items: &Vec<(FixSizedKey, Vec<u8>)>) -> Result<(), &'static str> {
|
||||||
let info = self.info()?;
|
let info = self.info()?;
|
||||||
for item in items {
|
for item in items {
|
||||||
Storage::<T>::write(&info.trie_id, &item.0, Some(item.1.clone()), None, false)
|
Storage::<T>::write(
|
||||||
.map_err(|_| "Failed to write storage to restoration dest")?;
|
&info.trie_id,
|
||||||
|
&item.0 as &FixSizedKey,
|
||||||
|
Some(item.1.clone()),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(|_| "Failed to write storage to restoration dest")?;
|
||||||
}
|
}
|
||||||
<ContractInfoOf<T>>::insert(&self.account_id, info);
|
<ContractInfoOf<T>>::insert(&self.account_id, info);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -896,33 +902,37 @@ benchmarks! {
|
|||||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||||
|
|
||||||
// Only the overhead of calling the function itself with minimal arguments.
|
// Only the overhead of calling the function itself with minimal arguments.
|
||||||
// The contract is a bit more complex because I needs to use different keys in order
|
// The contract is a bit more complex because it needs to use different keys in order
|
||||||
// to generate unique storage accesses. However, it is still dominated by the storage
|
// to generate unique storage accesses. However, it is still dominated by the storage
|
||||||
// accesses.
|
// accesses. We store all the keys that we are about to write at beforehand
|
||||||
|
// because re-writing at an existing key is always more expensive than writing
|
||||||
|
// it at a virgin key.
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_set_storage {
|
seal_set_storage {
|
||||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
let r in 0 .. API_BENCHMARK_BATCHES/2;
|
||||||
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
.collect::<Vec<_>>();
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let keys_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "seal1",
|
module: "__unstable__",
|
||||||
name: "seal_set_storage",
|
name: "seal_set_storage",
|
||||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
DataSegment {
|
DataSegment {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
value: key_bytes,
|
value: keys_bytes,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::I32Const(0)), // value_ptr
|
Regular(Instruction::I32Const(0)), // value_ptr
|
||||||
Regular(Instruction::I32Const(0)), // value_len
|
Regular(Instruction::I32Const(0)), // value_len
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
@@ -935,7 +945,7 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -947,18 +957,19 @@ benchmarks! {
|
|||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_set_storage_per_new_kb {
|
seal_set_storage_per_new_kb {
|
||||||
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
|
let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb
|
||||||
let keys = (0 .. API_BENCHMARK_BATCH_SIZE)
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE)
|
||||||
.collect::<Vec<_>>();
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "seal1",
|
module: "__unstable__",
|
||||||
name: "seal_set_storage",
|
name: "seal_set_storage",
|
||||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -968,9 +979,10 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::I32Const(0)), // value_ptr
|
Regular(Instruction::I32Const(0)), // value_ptr
|
||||||
Regular(Instruction::I32Const((n * 1024) as i32)), // value_len
|
Regular(Instruction::I32Const((n * 2048) as i32)), // value_len increments by 2kb up to max payload_len
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
Regular(Instruction::Drop),
|
Regular(Instruction::Drop),
|
||||||
])),
|
])),
|
||||||
@@ -981,7 +993,7 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -993,18 +1005,19 @@ benchmarks! {
|
|||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_set_storage_per_old_kb {
|
seal_set_storage_per_old_kb {
|
||||||
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
|
let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb
|
||||||
let keys = (0 .. API_BENCHMARK_BATCH_SIZE)
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE)
|
||||||
.collect::<Vec<_>>();
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "seal1",
|
module: "__unstable__",
|
||||||
name: "seal_set_storage",
|
name: "seal_set_storage",
|
||||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1014,9 +1027,10 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::I32Const(0)), // value_ptr
|
Regular(Instruction::I32Const(0)), // value_ptr
|
||||||
Regular(Instruction::I32Const(0)), // value_len
|
Regular(Instruction::I32Const(0)), // value_len is 0 as testing vs pre-existing value len
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
Regular(Instruction::Drop),
|
Regular(Instruction::Drop),
|
||||||
])),
|
])),
|
||||||
@@ -1027,8 +1041,8 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 1024) as usize]),
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -1037,23 +1051,25 @@ benchmarks! {
|
|||||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||||
|
|
||||||
// Similar to seal_set_storage. However, we store all the keys that we are about to
|
// Similar to seal_set_storage. We store all the keys that we are about to
|
||||||
// delete beforehand in order to prevent any optimizations that could occur when
|
// delete beforehand in order to prevent any optimizations that could occur when
|
||||||
// deleting a non existing key.
|
// deleting a non existing key. We generate keys of a maximum length, and have to
|
||||||
|
// reduce batch size in order to make resulting contract code size less than MaxCodeLen.
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_clear_storage {
|
seal_clear_storage {
|
||||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
let r in 0 .. API_BENCHMARK_BATCHES/2;
|
||||||
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
.collect::<Vec<_>>();
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "__unstable__",
|
module: "__unstable__",
|
||||||
name: "seal_clear_storage",
|
name: "seal_clear_storage",
|
||||||
params: vec![ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1063,7 +1079,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32),
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
Regular(Instruction::Drop),
|
Regular(Instruction::Drop),
|
||||||
])),
|
])),
|
||||||
@@ -1074,7 +1091,7 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -1087,18 +1104,19 @@ benchmarks! {
|
|||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_clear_storage_per_kb {
|
seal_clear_storage_per_kb {
|
||||||
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
|
let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb
|
||||||
let keys = (0 .. API_BENCHMARK_BATCH_SIZE)
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE)
|
||||||
.collect::<Vec<_>>();
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "__unstable__",
|
module: "__unstable__",
|
||||||
name: "seal_clear_storage",
|
name: "seal_clear_storage",
|
||||||
params: vec![ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1108,7 +1126,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
Regular(Instruction::Drop),
|
Regular(Instruction::Drop),
|
||||||
])),
|
])),
|
||||||
@@ -1119,8 +1138,8 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 1024) as usize]),
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -1132,19 +1151,20 @@ benchmarks! {
|
|||||||
// We make sure that all storage accesses are to unique keys.
|
// We make sure that all storage accesses are to unique keys.
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_get_storage {
|
seal_get_storage {
|
||||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
let r in 0 .. API_BENCHMARK_BATCHES/2;
|
||||||
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
.collect::<Vec<_>>();
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let key_bytes_len = key_bytes.len();
|
let key_bytes_len = key_bytes.len();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "seal0",
|
module: "__unstable__",
|
||||||
name: "seal_get_storage",
|
name: "seal_get_storage",
|
||||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1158,7 +1178,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_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::I32Const(key_bytes_len as i32)), // out_len_ptr
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
@@ -1171,7 +1192,7 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -1184,19 +1205,20 @@ benchmarks! {
|
|||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_get_storage_per_kb {
|
seal_get_storage_per_kb {
|
||||||
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
|
let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb
|
||||||
let keys = (0 .. API_BENCHMARK_BATCH_SIZE)
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE)
|
||||||
.collect::<Vec<_>>();
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let key_bytes_len = key_bytes.len();
|
let key_bytes_len = key_bytes.len();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "seal0",
|
module: "__unstable__",
|
||||||
name: "seal_get_storage",
|
name: "seal_get_storage",
|
||||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1210,7 +1232,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_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::I32Const(key_bytes_len as i32)), // out_len_ptr
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
@@ -1223,8 +1246,8 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 1024) as usize]),
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -1237,19 +1260,20 @@ benchmarks! {
|
|||||||
// We make sure that all storage accesses are to unique keys.
|
// We make sure that all storage accesses are to unique keys.
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_contains_storage {
|
seal_contains_storage {
|
||||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
let r in 0 .. API_BENCHMARK_BATCHES/2;
|
||||||
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
.collect::<Vec<_>>();
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let key_bytes_len = key_bytes.len();
|
let key_bytes_len = key_bytes.len();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "seal0",
|
module: "__unstable__",
|
||||||
name: "seal_contains_storage",
|
name: "seal_contains_storage",
|
||||||
params: vec![ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1259,7 +1283,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
Regular(Instruction::Drop),
|
Regular(Instruction::Drop),
|
||||||
])),
|
])),
|
||||||
@@ -1270,7 +1295,7 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -1283,18 +1308,19 @@ benchmarks! {
|
|||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_contains_storage_per_kb {
|
seal_contains_storage_per_kb {
|
||||||
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
|
let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb
|
||||||
let keys = (0 .. API_BENCHMARK_BATCH_SIZE)
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE)
|
||||||
.collect::<Vec<_>>();
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
memory: Some(ImportedMemory::max::<T>()),
|
memory: Some(ImportedMemory::max::<T>()),
|
||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "seal0",
|
module: "__unstable__",
|
||||||
name: "seal_contains_storage",
|
name: "seal_contains_storage",
|
||||||
params: vec![ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1304,7 +1330,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
Regular(Instruction::Drop),
|
Regular(Instruction::Drop),
|
||||||
])),
|
])),
|
||||||
@@ -1315,8 +1342,8 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 1024) as usize]),
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -1328,11 +1355,12 @@ benchmarks! {
|
|||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_take_storage {
|
seal_take_storage {
|
||||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
let r in 0 .. API_BENCHMARK_BATCHES/2;
|
||||||
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
.collect::<Vec<_>>();
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let key_bytes_len = key_bytes.len();
|
let key_bytes_len = key_bytes.len();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
@@ -1340,7 +1368,7 @@ benchmarks! {
|
|||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "__unstable__",
|
module: "__unstable__",
|
||||||
name: "seal_take_storage",
|
name: "seal_take_storage",
|
||||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1354,7 +1382,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_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::I32Const(key_bytes_len as i32)), // out_len_ptr
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
@@ -1367,7 +1396,7 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -1380,11 +1409,12 @@ benchmarks! {
|
|||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
seal_take_storage_per_kb {
|
seal_take_storage_per_kb {
|
||||||
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
|
let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb
|
||||||
let keys = (0 .. API_BENCHMARK_BATCH_SIZE)
|
let max_key_len = T::MaxStorageKeyLen::get();
|
||||||
.map(|n| T::Hashing::hash_of(&n).as_ref().to_vec())
|
let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE)
|
||||||
.collect::<Vec<_>>();
|
.map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec();
|
||||||
let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0);
|
h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
|
||||||
let key_bytes_len = key_bytes.len();
|
let key_bytes_len = key_bytes.len();
|
||||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||||
@@ -1392,7 +1422,7 @@ benchmarks! {
|
|||||||
imported_functions: vec![ImportedFunction {
|
imported_functions: vec![ImportedFunction {
|
||||||
module: "__unstable__",
|
module: "__unstable__",
|
||||||
name: "seal_take_storage",
|
name: "seal_take_storage",
|
||||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||||
return_type: Some(ValueType::I32),
|
return_type: Some(ValueType::I32),
|
||||||
}],
|
}],
|
||||||
data_segments: vec![
|
data_segments: vec![
|
||||||
@@ -1406,7 +1436,8 @@ benchmarks! {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
|
||||||
Counter(0, key_len as u32), // key_ptr
|
Counter(0, max_key_len as u32), // key_ptr
|
||||||
|
Regular(Instruction::I32Const(max_key_len as i32)), // key_len
|
||||||
Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_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::I32Const(key_bytes_len as i32)), // out_len_ptr
|
||||||
Regular(Instruction::Call(0)),
|
Regular(Instruction::Call(0)),
|
||||||
@@ -1419,8 +1450,8 @@ benchmarks! {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&info.trie_id,
|
&info.trie_id,
|
||||||
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 1024) as usize]),
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,12 +27,13 @@ use frame_support::{
|
|||||||
storage::{with_transaction, TransactionOutcome},
|
storage::{with_transaction, TransactionOutcome},
|
||||||
traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time},
|
traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time},
|
||||||
weights::Weight,
|
weights::Weight,
|
||||||
|
Blake2_128Concat, BoundedVec, StorageHasher,
|
||||||
};
|
};
|
||||||
use frame_system::RawOrigin;
|
use frame_system::RawOrigin;
|
||||||
use pallet_contracts_primitives::ExecReturnValue;
|
use pallet_contracts_primitives::ExecReturnValue;
|
||||||
use smallvec::{Array, SmallVec};
|
use smallvec::{Array, SmallVec};
|
||||||
use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic};
|
use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic};
|
||||||
use sp_io::crypto::secp256k1_ecdsa_recover_compressed;
|
use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256};
|
||||||
use sp_runtime::traits::Convert;
|
use sp_runtime::traits::Convert;
|
||||||
use sp_std::{marker::PhantomData, mem, prelude::*};
|
use sp_std::{marker::PhantomData, mem, prelude::*};
|
||||||
|
|
||||||
@@ -40,12 +41,40 @@ pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
|
|||||||
pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
|
pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
|
||||||
pub type SeedOf<T> = <T as frame_system::Config>::Hash;
|
pub type SeedOf<T> = <T as frame_system::Config>::Hash;
|
||||||
pub type BlockNumberOf<T> = <T as frame_system::Config>::BlockNumber;
|
pub type BlockNumberOf<T> = <T as frame_system::Config>::BlockNumber;
|
||||||
pub type StorageKey = [u8; 32];
|
|
||||||
pub type ExecResult = Result<ExecReturnValue, ExecError>;
|
pub type ExecResult = Result<ExecReturnValue, ExecError>;
|
||||||
|
|
||||||
/// A type that represents a topic of an event. At the moment a hash is used.
|
/// A type that represents a topic of an event. At the moment a hash is used.
|
||||||
pub type TopicOf<T> = <T as frame_system::Config>::Hash;
|
pub type TopicOf<T> = <T as frame_system::Config>::Hash;
|
||||||
|
|
||||||
|
/// Type for fix sized storage key.
|
||||||
|
pub type FixSizedKey = [u8; 32];
|
||||||
|
|
||||||
|
/// Type for variable sized storage key. Used for transparent hashing.
|
||||||
|
pub type VarSizedKey<T> = BoundedVec<u8, <T as Config>::MaxStorageKeyLen>;
|
||||||
|
|
||||||
|
/// Trait for hashing storage keys.
|
||||||
|
pub trait StorageKey<T>
|
||||||
|
where
|
||||||
|
T: Config,
|
||||||
|
{
|
||||||
|
fn hash(&self) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> StorageKey<T> for FixSizedKey {
|
||||||
|
fn hash(&self) -> Vec<u8> {
|
||||||
|
blake2_256(self.as_slice()).to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StorageKey<T> for VarSizedKey<T>
|
||||||
|
where
|
||||||
|
T: Config,
|
||||||
|
{
|
||||||
|
fn hash(&self) -> Vec<u8> {
|
||||||
|
Blake2_128Concat::hash(self.as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Origin of the error.
|
/// Origin of the error.
|
||||||
///
|
///
|
||||||
/// Call or instantiate both called into other contracts and pass through errors happening
|
/// Call or instantiate both called into other contracts and pass through errors happening
|
||||||
@@ -140,19 +169,44 @@ pub trait Ext: sealing::Sealed {
|
|||||||
///
|
///
|
||||||
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
||||||
/// was deleted.
|
/// was deleted.
|
||||||
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>>;
|
fn get_storage(&mut self, key: &FixSizedKey) -> Option<Vec<u8>>;
|
||||||
|
|
||||||
|
/// This is a variation of `get_storage()` to be used with transparent hashing.
|
||||||
|
/// These two will be merged into a single function after some refactoring is done.
|
||||||
|
/// Returns the storage entry of the executing account by the given `key`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
||||||
|
/// was deleted.
|
||||||
|
fn get_storage_transparent(&mut self, key: &VarSizedKey<Self::T>) -> Option<Vec<u8>>;
|
||||||
|
|
||||||
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
||||||
/// was deleted.
|
/// was deleted.
|
||||||
fn get_storage_size(&mut self, key: &StorageKey) -> Option<u32>;
|
fn get_storage_size(&mut self, key: &FixSizedKey) -> Option<u32>;
|
||||||
|
|
||||||
|
/// This is the variation of `get_storage_size()` to be used with transparent hashing.
|
||||||
|
/// These two will be merged into a single function after some refactoring is done.
|
||||||
|
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
||||||
|
/// was deleted.
|
||||||
|
fn get_storage_size_transparent(&mut self, key: &VarSizedKey<Self::T>) -> Option<u32>;
|
||||||
|
|
||||||
/// Sets the storage entry by the given key to the specified value. If `value` is `None` then
|
/// Sets the storage entry by the given key to the specified value. If `value` is `None` then
|
||||||
/// the storage entry is deleted.
|
/// the storage entry is deleted.
|
||||||
fn set_storage(
|
fn set_storage(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: StorageKey,
|
key: &FixSizedKey,
|
||||||
|
value: Option<Vec<u8>>,
|
||||||
|
take_old: bool,
|
||||||
|
) -> Result<WriteOutcome, DispatchError>;
|
||||||
|
|
||||||
|
/// This is the variation of `set_storage()` to be used with transparent hashing.
|
||||||
|
/// These two will be merged into a single function after some refactoring is done.
|
||||||
|
fn set_storage_transparent(
|
||||||
|
&mut self,
|
||||||
|
key: &VarSizedKey<Self::T>,
|
||||||
value: Option<Vec<u8>>,
|
value: Option<Vec<u8>>,
|
||||||
take_old: bool,
|
take_old: bool,
|
||||||
) -> Result<WriteOutcome, DispatchError>;
|
) -> Result<WriteOutcome, DispatchError>;
|
||||||
@@ -1085,24 +1139,48 @@ where
|
|||||||
Self::transfer(ExistenceRequirement::KeepAlive, &self.top_frame().account_id, to, value)
|
Self::transfer(ExistenceRequirement::KeepAlive, &self.top_frame().account_id, to, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
|
fn get_storage(&mut self, key: &FixSizedKey) -> Option<Vec<u8>> {
|
||||||
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
|
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_storage_size(&mut self, key: &StorageKey) -> Option<u32> {
|
fn get_storage_transparent(&mut self, key: &VarSizedKey<T>) -> Option<Vec<u8>> {
|
||||||
|
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage_size(&mut self, key: &FixSizedKey) -> Option<u32> {
|
||||||
|
Storage::<T>::size(&self.top_frame_mut().contract_info().trie_id, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage_size_transparent(&mut self, key: &VarSizedKey<T>) -> Option<u32> {
|
||||||
Storage::<T>::size(&self.top_frame_mut().contract_info().trie_id, key)
|
Storage::<T>::size(&self.top_frame_mut().contract_info().trie_id, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_storage(
|
fn set_storage(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: StorageKey,
|
key: &FixSizedKey,
|
||||||
value: Option<Vec<u8>>,
|
value: Option<Vec<u8>>,
|
||||||
take_old: bool,
|
take_old: bool,
|
||||||
) -> Result<WriteOutcome, DispatchError> {
|
) -> Result<WriteOutcome, DispatchError> {
|
||||||
let frame = self.top_frame_mut();
|
let frame = self.top_frame_mut();
|
||||||
Storage::<T>::write(
|
Storage::<T>::write(
|
||||||
&frame.contract_info.get(&frame.account_id).trie_id,
|
&frame.contract_info.get(&frame.account_id).trie_id,
|
||||||
&key,
|
key,
|
||||||
|
value,
|
||||||
|
Some(&mut frame.nested_storage),
|
||||||
|
take_old,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_storage_transparent(
|
||||||
|
&mut self,
|
||||||
|
key: &VarSizedKey<T>,
|
||||||
|
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,
|
value,
|
||||||
Some(&mut frame.nested_storage),
|
Some(&mut frame.nested_storage),
|
||||||
take_old,
|
take_old,
|
||||||
@@ -2635,35 +2713,35 @@ mod tests {
|
|||||||
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
||||||
// Write
|
// Write
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.set_storage([1; 32], Some(vec![1, 2, 3]), false),
|
ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false),
|
||||||
Ok(WriteOutcome::New)
|
Ok(WriteOutcome::New)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.set_storage([2; 32], Some(vec![4, 5, 6]), true),
|
ctx.ext.set_storage(&[2; 32], Some(vec![4, 5, 6]), true),
|
||||||
Ok(WriteOutcome::New)
|
Ok(WriteOutcome::New)
|
||||||
);
|
);
|
||||||
assert_eq!(ctx.ext.set_storage([3; 32], None, false), 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(&[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(&[5; 32], Some(vec![]), false), Ok(WriteOutcome::New));
|
||||||
assert_eq!(ctx.ext.set_storage([6; 32], Some(vec![]), true), Ok(WriteOutcome::New));
|
assert_eq!(ctx.ext.set_storage(&[6; 32], Some(vec![]), true), Ok(WriteOutcome::New));
|
||||||
|
|
||||||
// Overwrite
|
// Overwrite
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.set_storage([1; 32], Some(vec![42]), false),
|
ctx.ext.set_storage(&[1; 32], Some(vec![42]), false),
|
||||||
Ok(WriteOutcome::Overwritten(3))
|
Ok(WriteOutcome::Overwritten(3))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.set_storage([2; 32], Some(vec![48]), true),
|
ctx.ext.set_storage(&[2; 32], Some(vec![48]), true),
|
||||||
Ok(WriteOutcome::Taken(vec![4, 5, 6]))
|
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(&[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(&[4; 32], None, true), Ok(WriteOutcome::New));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.set_storage([5; 32], Some(vec![]), false),
|
ctx.ext.set_storage(&[5; 32], Some(vec![]), false),
|
||||||
Ok(WriteOutcome::Overwritten(0))
|
Ok(WriteOutcome::Overwritten(0))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.set_storage([6; 32], Some(vec![]), true),
|
ctx.ext.set_storage(&[6; 32], Some(vec![]), true),
|
||||||
Ok(WriteOutcome::Taken(vec![]))
|
Ok(WriteOutcome::Taken(vec![]))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2690,14 +2768,175 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_storage_transparent_works() {
|
||||||
|
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
||||||
|
// Write
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1; 64].to_vec()).unwrap(),
|
||||||
|
Some(vec![1, 2, 3]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![4, 5, 6]),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([3; 19].to_vec()).unwrap(),
|
||||||
|
None,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([4; 64].to_vec()).unwrap(),
|
||||||
|
None,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([5; 30].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([6; 128].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overwrite
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1; 64].to_vec()).unwrap(),
|
||||||
|
Some(vec![42, 43, 44]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::Overwritten(3))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![48]),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::Taken(vec![4, 5, 6]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([3; 19].to_vec()).unwrap(),
|
||||||
|
None,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([4; 64].to_vec()).unwrap(),
|
||||||
|
None,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([5; 30].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::Overwritten(0))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([6; 128].to_vec()).unwrap(),
|
||||||
|
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 get_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.get_storage(&[1; 32]), Some(vec![1, 2, 3]));
|
||||||
|
assert_eq!(ctx.ext.get_storage(&[2; 32]), Some(vec![]));
|
||||||
|
assert_eq!(ctx.ext.get_storage(&[3; 32]), None);
|
||||||
|
|
||||||
|
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]
|
#[test]
|
||||||
fn get_storage_size_works() {
|
fn get_storage_size_works() {
|
||||||
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.set_storage([1; 32], Some(vec![1, 2, 3]), false),
|
ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false),
|
||||||
Ok(WriteOutcome::New)
|
Ok(WriteOutcome::New)
|
||||||
);
|
);
|
||||||
assert_eq!(ctx.ext.set_storage([2; 32], Some(vec![]), false), Ok(WriteOutcome::New));
|
assert_eq!(ctx.ext.set_storage(&[2; 32], Some(vec![]), false), Ok(WriteOutcome::New));
|
||||||
assert_eq!(ctx.ext.get_storage_size(&[1; 32]), Some(3));
|
assert_eq!(ctx.ext.get_storage_size(&[1; 32]), Some(3));
|
||||||
assert_eq!(ctx.ext.get_storage_size(&[2; 32]), Some(0));
|
assert_eq!(ctx.ext.get_storage_size(&[2; 32]), Some(0));
|
||||||
assert_eq!(ctx.ext.get_storage_size(&[3; 32]), None);
|
assert_eq!(ctx.ext.get_storage_size(&[3; 32]), None);
|
||||||
@@ -2724,6 +2963,129 @@ mod tests {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_storage_transparent_works() {
|
||||||
|
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![1, 2, 3]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2; 16].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.get_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1; 19].to_vec()).unwrap()
|
||||||
|
),
|
||||||
|
Some(vec![1, 2, 3])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.get_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2; 16].to_vec()).unwrap()
|
||||||
|
),
|
||||||
|
Some(vec![])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.get_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([3; 8].to_vec()).unwrap()
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
|
||||||
|
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 get_storage_size_transparent_works() {
|
||||||
|
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![1, 2, 3]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2; 16].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
Ok(WriteOutcome::New)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.get_storage_size_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1; 19].to_vec()).unwrap()
|
||||||
|
),
|
||||||
|
Some(3)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.get_storage_size_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2; 16].to_vec()).unwrap()
|
||||||
|
),
|
||||||
|
Some(0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctx.ext.get_storage_size_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([3; 8].to_vec()).unwrap()
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
|
||||||
|
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]
|
#[test]
|
||||||
fn ecdsa_to_eth_address_returns_proper_value() {
|
fn ecdsa_to_eth_address_returns_proper_value() {
|
||||||
let bob_ch = MockLoader::insert(Call, |ctx, _| {
|
let bob_ch = MockLoader::insert(Call, |ctx, _| {
|
||||||
|
|||||||
@@ -98,11 +98,6 @@ pub mod weights;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub use crate::{
|
|
||||||
exec::Frame,
|
|
||||||
pallet::*,
|
|
||||||
schedule::{HostFnWeights, InstructionWeights, Limits, Schedule},
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack},
|
exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack},
|
||||||
gas::GasMeter,
|
gas::GasMeter,
|
||||||
@@ -129,6 +124,12 @@ use sp_core::{crypto::UncheckedFrom, Bytes};
|
|||||||
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup};
|
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup};
|
||||||
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
||||||
|
|
||||||
|
pub use crate::{
|
||||||
|
exec::{Frame, VarSizedKey as StorageKey},
|
||||||
|
pallet::*,
|
||||||
|
schedule::{HostFnWeights, InstructionWeights, Limits, Schedule},
|
||||||
|
};
|
||||||
|
|
||||||
type CodeHash<T> = <T as frame_system::Config>::Hash;
|
type CodeHash<T> = <T as frame_system::Config>::Hash;
|
||||||
type TrieId = BoundedVec<u8, ConstU32<128>>;
|
type TrieId = BoundedVec<u8, ConstU32<128>>;
|
||||||
type BalanceOf<T> =
|
type BalanceOf<T> =
|
||||||
@@ -372,6 +373,9 @@ pub mod pallet {
|
|||||||
/// new instrumentation increases the size beyond the limit it would make that contract
|
/// new instrumentation increases the size beyond the limit it would make that contract
|
||||||
/// inaccessible until rectified by another runtime upgrade.
|
/// inaccessible until rectified by another runtime upgrade.
|
||||||
type RelaxedMaxCodeLen: Get<u32>;
|
type RelaxedMaxCodeLen: Get<u32>;
|
||||||
|
|
||||||
|
/// The maximum allowable length in bytes for storage keys.
|
||||||
|
type MaxStorageKeyLen: Get<u32>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::hooks]
|
#[pallet::hooks]
|
||||||
@@ -942,11 +946,14 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Query storage of a specified contract under a specified key.
|
/// Query storage of a specified contract under a specified key.
|
||||||
pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult {
|
pub fn get_storage(address: T::AccountId, key: Vec<u8>) -> GetStorageResult {
|
||||||
let contract_info =
|
let contract_info =
|
||||||
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
|
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
|
||||||
|
|
||||||
let maybe_value = Storage::<T>::read(&contract_info.trie_id, &key);
|
let maybe_value = Storage::<T>::read(
|
||||||
|
&contract_info.trie_id,
|
||||||
|
&StorageKey::<T>::try_from(key).map_err(|_| ContractAccessError::KeyDecodingFailed)?,
|
||||||
|
);
|
||||||
Ok(maybe_value)
|
Ok(maybe_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ use frame_support::{
|
|||||||
};
|
};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
use sp_core::crypto::UncheckedFrom;
|
use sp_core::crypto::UncheckedFrom;
|
||||||
use sp_io::{hashing::blake2_256, KillStorageResult};
|
use sp_io::KillStorageResult;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
traits::{Hash, Zero},
|
traits::{Hash, Zero},
|
||||||
RuntimeDebug,
|
RuntimeDebug,
|
||||||
@@ -124,16 +124,16 @@ where
|
|||||||
///
|
///
|
||||||
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
|
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
|
||||||
/// contract doesn't store under the given `key` `None` is returned.
|
/// contract doesn't store under the given `key` `None` is returned.
|
||||||
pub fn read(trie_id: &TrieId, key: &StorageKey) -> Option<Vec<u8>> {
|
pub fn read<K: StorageKey<T>>(trie_id: &TrieId, key: &K) -> Option<Vec<u8>> {
|
||||||
child::get_raw(&child_trie_info(trie_id), &blake2_256(key))
|
child::get_raw(&child_trie_info(trie_id), key.hash().as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
||||||
/// was deleted.
|
/// was deleted.
|
||||||
pub fn size(trie_id: &TrieId, key: &StorageKey) -> Option<u32> {
|
pub fn size<K: StorageKey<T>>(trie_id: &TrieId, key: &K) -> Option<u32> {
|
||||||
child::len(&child_trie_info(trie_id), &blake2_256(key))
|
child::len(&child_trie_info(trie_id), key.hash().as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a storage entry into a contract's kv storage.
|
/// Update a storage entry into a contract's kv storage.
|
||||||
@@ -143,15 +143,15 @@ where
|
|||||||
///
|
///
|
||||||
/// This function also records how much storage was created or removed if a `storage_meter`
|
/// This function also records how much storage was created or removed if a `storage_meter`
|
||||||
/// is supplied. It should only be absent for testing or benchmarking code.
|
/// is supplied. It should only be absent for testing or benchmarking code.
|
||||||
pub fn write(
|
pub fn write<K: StorageKey<T>>(
|
||||||
trie_id: &TrieId,
|
trie_id: &TrieId,
|
||||||
key: &StorageKey,
|
key: &K,
|
||||||
new_value: Option<Vec<u8>>,
|
new_value: Option<Vec<u8>>,
|
||||||
storage_meter: Option<&mut meter::NestedMeter<T>>,
|
storage_meter: Option<&mut meter::NestedMeter<T>>,
|
||||||
take: bool,
|
take: bool,
|
||||||
) -> Result<WriteOutcome, DispatchError> {
|
) -> Result<WriteOutcome, DispatchError> {
|
||||||
let hashed_key = blake2_256(key);
|
|
||||||
let child_trie_info = &child_trie_info(trie_id);
|
let child_trie_info = &child_trie_info(trie_id);
|
||||||
|
let hashed_key = key.hash();
|
||||||
let (old_len, old_value) = if take {
|
let (old_len, old_value) = if take {
|
||||||
let val = child::get_raw(child_trie_info, &hashed_key);
|
let val = child::get_raw(child_trie_info, &hashed_key);
|
||||||
(val.as_ref().map(|v| v.len() as u32), val)
|
(val.as_ref().map(|v| v.len() as u32), val)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::{
|
|||||||
ChainExtension, Environment, Ext, InitState, Result as ExtensionResult, RetVal,
|
ChainExtension, Environment, Ext, InitState, Result as ExtensionResult, RetVal,
|
||||||
ReturnFlags, SysConfig, UncheckedFrom,
|
ReturnFlags, SysConfig, UncheckedFrom,
|
||||||
},
|
},
|
||||||
exec::Frame,
|
exec::{FixSizedKey, Frame},
|
||||||
storage::Storage,
|
storage::Storage,
|
||||||
wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode},
|
wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode},
|
||||||
weights::WeightInfo,
|
weights::WeightInfo,
|
||||||
@@ -291,6 +291,7 @@ impl Config for Test {
|
|||||||
type ContractAccessWeight = DefaultContractAccessWeight<BlockWeights>;
|
type ContractAccessWeight = DefaultContractAccessWeight<BlockWeights>;
|
||||||
type MaxCodeLen = ConstU32<{ 128 * 1024 }>;
|
type MaxCodeLen = ConstU32<{ 128 * 1024 }>;
|
||||||
type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>;
|
type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>;
|
||||||
|
type MaxStorageKeyLen = ConstU32<128>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
|
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
|
||||||
@@ -1723,8 +1724,14 @@ fn lazy_removal_partial_remove_works() {
|
|||||||
|
|
||||||
// Put value into the contracts child trie
|
// Put value into the contracts child trie
|
||||||
for val in &vals {
|
for val in &vals {
|
||||||
Storage::<Test>::write(&info.trie_id, &val.0, Some(val.2.clone()), None, false)
|
Storage::<Test>::write(
|
||||||
.unwrap();
|
&info.trie_id,
|
||||||
|
&val.0 as &FixSizedKey,
|
||||||
|
Some(val.2.clone()),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
||||||
|
|
||||||
@@ -1903,8 +1910,14 @@ fn lazy_removal_does_not_use_all_weight() {
|
|||||||
|
|
||||||
// Put value into the contracts child trie
|
// Put value into the contracts child trie
|
||||||
for val in &vals {
|
for val in &vals {
|
||||||
Storage::<Test>::write(&info.trie_id, &val.0, Some(val.2.clone()), None, false)
|
Storage::<Test>::write(
|
||||||
.unwrap();
|
&info.trie_id,
|
||||||
|
&val.0 as &FixSizedKey,
|
||||||
|
Some(val.2.clone()),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
exec::{
|
exec::{
|
||||||
AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, SeedOf, StorageKey,
|
AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, FixSizedKey,
|
||||||
|
SeedOf, VarSizedKey,
|
||||||
},
|
},
|
||||||
gas::GasMeter,
|
gas::GasMeter,
|
||||||
storage::WriteOutcome,
|
storage::WriteOutcome,
|
||||||
@@ -330,7 +331,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct MockExt {
|
pub struct MockExt {
|
||||||
storage: HashMap<StorageKey, Vec<u8>>,
|
storage: HashMap<Vec<u8>, Vec<u8>>,
|
||||||
instantiates: Vec<InstantiateEntry>,
|
instantiates: Vec<InstantiateEntry>,
|
||||||
terminations: Vec<TerminationEntry>,
|
terminations: Vec<TerminationEntry>,
|
||||||
calls: Vec<CallEntry>,
|
calls: Vec<CallEntry>,
|
||||||
@@ -425,19 +426,45 @@ mod tests {
|
|||||||
self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() });
|
self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
|
fn get_storage(&mut self, key: &FixSizedKey) -> Option<Vec<u8>> {
|
||||||
self.storage.get(key).cloned()
|
self.storage.get(&key.to_vec()).cloned()
|
||||||
}
|
}
|
||||||
fn get_storage_size(&mut self, key: &StorageKey) -> Option<u32> {
|
fn get_storage_transparent(&mut self, key: &VarSizedKey<Self::T>) -> Option<Vec<u8>> {
|
||||||
self.storage.get(key).map(|val| val.len() as u32)
|
self.storage.get(&key.to_vec()).cloned()
|
||||||
|
}
|
||||||
|
fn get_storage_size(&mut self, key: &FixSizedKey) -> Option<u32> {
|
||||||
|
self.storage.get(&key.to_vec()).map(|val| val.len() as u32)
|
||||||
|
}
|
||||||
|
fn get_storage_size_transparent(&mut self, key: &VarSizedKey<Self::T>) -> Option<u32> {
|
||||||
|
self.storage.get(&key.to_vec()).map(|val| val.len() as u32)
|
||||||
}
|
}
|
||||||
fn set_storage(
|
fn set_storage(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: StorageKey,
|
key: &FixSizedKey,
|
||||||
value: Option<Vec<u8>>,
|
value: Option<Vec<u8>>,
|
||||||
take_old: bool,
|
take_old: bool,
|
||||||
) -> Result<WriteOutcome, DispatchError> {
|
) -> Result<WriteOutcome, DispatchError> {
|
||||||
let entry = self.storage.entry(key);
|
let key = key.to_vec();
|
||||||
|
let entry = self.storage.entry(key.clone());
|
||||||
|
let result = match (entry, take_old) {
|
||||||
|
(Entry::Vacant(_), _) => WriteOutcome::New,
|
||||||
|
(Entry::Occupied(entry), false) =>
|
||||||
|
WriteOutcome::Overwritten(entry.remove().len() as u32),
|
||||||
|
(Entry::Occupied(entry), true) => WriteOutcome::Taken(entry.remove()),
|
||||||
|
};
|
||||||
|
if let Some(value) = value {
|
||||||
|
self.storage.insert(key, value);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
fn set_storage_transparent(
|
||||||
|
&mut self,
|
||||||
|
key: &VarSizedKey<Self::T>,
|
||||||
|
value: Option<Vec<u8>>,
|
||||||
|
take_old: bool,
|
||||||
|
) -> Result<WriteOutcome, DispatchError> {
|
||||||
|
let key = key.to_vec();
|
||||||
|
let entry = self.storage.entry(key.clone());
|
||||||
let result = match (entry, take_old) {
|
let result = match (entry, take_old) {
|
||||||
(Entry::Vacant(_), _) => WriteOutcome::New,
|
(Entry::Vacant(_), _) => WriteOutcome::New,
|
||||||
(Entry::Occupied(entry), false) =>
|
(Entry::Occupied(entry), false) =>
|
||||||
@@ -836,6 +863,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "unstable-interface"))]
|
||||||
fn contains_storage_works() {
|
fn contains_storage_works() {
|
||||||
const CODE: &str = r#"
|
const CODE: &str = r#"
|
||||||
(module
|
(module
|
||||||
@@ -844,11 +872,10 @@ mod tests {
|
|||||||
(import "seal0" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32)))
|
(import "seal0" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32)))
|
||||||
(import "env" "memory" (memory 1 1))
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
;; [0, 4) size of input buffer (32 byte as we copy the key here)
|
;; [0, 4) size of input buffer (32 bytes as we copy the key here)
|
||||||
(data (i32.const 0) "\20")
|
(data (i32.const 0) "\20")
|
||||||
|
|
||||||
;; [4, 36) input buffer
|
;; [4, 36) input buffer
|
||||||
|
|
||||||
;; [36, inf) output buffer
|
;; [36, inf) output buffer
|
||||||
|
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
@@ -879,8 +906,8 @@ mod tests {
|
|||||||
|
|
||||||
let mut ext = MockExt::default();
|
let mut ext = MockExt::default();
|
||||||
|
|
||||||
ext.storage.insert([1u8; 32], vec![42u8]);
|
ext.storage.insert(vec![1u8; 32], vec![42u8]);
|
||||||
ext.storage.insert([2u8; 32], vec![]);
|
ext.storage.insert(vec![2u8; 32], vec![]);
|
||||||
|
|
||||||
// value does not exist -> sentinel value returned
|
// value does not exist -> sentinel value returned
|
||||||
let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap();
|
let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap();
|
||||||
@@ -895,6 +922,84 @@ mod tests {
|
|||||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0,);
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0,);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-interface")]
|
||||||
|
fn contains_storage_works() {
|
||||||
|
const CODE: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||||
|
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||||
|
(import "__unstable__" "seal_contains_storage" (func $seal_contains_storage (param i32 i32) (result i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
|
||||||
|
;; size of input buffer
|
||||||
|
;; [0, 4) size of input buffer (128+32 = 160 bytes = 0xA0)
|
||||||
|
(data (i32.const 0) "\A0")
|
||||||
|
|
||||||
|
;; [4, 164) input buffer
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
;; Receive key
|
||||||
|
(call $seal_input
|
||||||
|
(i32.const 4) ;; Where we take input and store it
|
||||||
|
(i32.const 0) ;; Where we take and store the length of the data
|
||||||
|
)
|
||||||
|
;; Call seal_clear_storage and save what it returns at 0
|
||||||
|
(i32.store (i32.const 0)
|
||||||
|
(call $seal_contains_storage
|
||||||
|
(i32.const 8) ;; key_ptr
|
||||||
|
(i32.load (i32.const 4)) ;; key_len
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call $seal_return
|
||||||
|
(i32.const 0) ;; flags
|
||||||
|
(i32.const 0) ;; returned value
|
||||||
|
(i32.const 4) ;; length of returned value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut ext = MockExt::default();
|
||||||
|
ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1u8; 64].to_vec()).unwrap(),
|
||||||
|
Some(vec![42u8]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2u8; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//value does not exist (wrong key length)
|
||||||
|
let input = (63, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
// sentinel returned
|
||||||
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
||||||
|
|
||||||
|
// value exists
|
||||||
|
let input = (64, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
// true as u32 returned
|
||||||
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 1);
|
||||||
|
// getter does not remove the value from storage
|
||||||
|
assert_eq!(ext.storage.get(&[1u8; 64].to_vec()).unwrap(), &[42u8]);
|
||||||
|
|
||||||
|
// value exists (test for 0 sized)
|
||||||
|
let input = (19, [2u8; 19]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
// true as u32 returned
|
||||||
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
||||||
|
// getter does not remove the value from storage
|
||||||
|
assert_eq!(ext.storage.get(&[2u8; 19].to_vec()).unwrap(), &([] as [u8; 0]));
|
||||||
|
}
|
||||||
|
|
||||||
const CODE_INSTANTIATE: &str = r#"
|
const CODE_INSTANTIATE: &str = r#"
|
||||||
(module
|
(module
|
||||||
;; seal_instantiate(
|
;; seal_instantiate(
|
||||||
@@ -1204,7 +1309,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn get_storage_puts_data_into_buf() {
|
fn get_storage_puts_data_into_buf() {
|
||||||
let mut mock_ext = MockExt::default();
|
let mut mock_ext = MockExt::default();
|
||||||
mock_ext.storage.insert([0x11; 32], [0x22; 32].to_vec());
|
mock_ext.storage.insert([0x11; 32].to_vec(), [0x22; 32].to_vec());
|
||||||
|
|
||||||
let output = execute(CODE_GET_STORAGE, vec![], mock_ext).unwrap();
|
let output = execute(CODE_GET_STORAGE, vec![], mock_ext).unwrap();
|
||||||
|
|
||||||
@@ -2176,6 +2281,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "unstable-interface"))]
|
||||||
fn set_storage_works() {
|
fn set_storage_works() {
|
||||||
const CODE: &str = r#"
|
const CODE: &str = r#"
|
||||||
(module
|
(module
|
||||||
@@ -2199,7 +2305,7 @@ mod tests {
|
|||||||
(call $seal_set_storage
|
(call $seal_set_storage
|
||||||
(i32.const 4) ;; key_ptr
|
(i32.const 4) ;; key_ptr
|
||||||
(i32.const 36) ;; value_ptr
|
(i32.const 36) ;; value_ptr
|
||||||
(i32.sub ;; value_len (input_size - key_size)
|
(i32.sub ;; value_len (input_size - key_size)
|
||||||
(i32.load (i32.const 0))
|
(i32.load (i32.const 0))
|
||||||
(i32.const 32)
|
(i32.const 32)
|
||||||
)
|
)
|
||||||
@@ -2207,7 +2313,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
(call $seal_return
|
(call $seal_return
|
||||||
(i32.const 0) ;; flags
|
(i32.const 0) ;; flags
|
||||||
(i32.const 0) ;; returned value
|
(i32.const 0) ;; pointer to returned value
|
||||||
(i32.const 4) ;; length of returned value
|
(i32.const 4) ;; length of returned value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2222,19 +2328,184 @@ mod tests {
|
|||||||
let input = ([1u8; 32], [42u8, 48]).encode();
|
let input = ([1u8; 32], [42u8, 48]).encode();
|
||||||
let result = execute(CODE, input, &mut ext).unwrap();
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
||||||
assert_eq!(ext.storage.get(&[1u8; 32]).unwrap(), &[42u8, 48]);
|
assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[42u8, 48]);
|
||||||
|
|
||||||
// value do exist -> length of old value returned
|
// value do exist -> length of old value returned
|
||||||
let input = ([1u8; 32], [0u8; 0]).encode();
|
let input = ([1u8; 32], [0u8; 0]).encode();
|
||||||
let result = execute(CODE, input, &mut ext).unwrap();
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 2);
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 2);
|
||||||
assert_eq!(ext.storage.get(&[1u8; 32]).unwrap(), &[0u8; 0]);
|
assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[0u8; 0]);
|
||||||
|
|
||||||
// value do exist -> length of old value returned (test for zero sized val)
|
// value do exist -> length of old value returned (test for zero sized val)
|
||||||
let input = ([1u8; 32], [99u8]).encode();
|
let input = ([1u8; 32], [99u8]).encode();
|
||||||
let result = execute(CODE, input, &mut ext).unwrap();
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
||||||
assert_eq!(ext.storage.get(&[1u8; 32]).unwrap(), &[99u8]);
|
assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[99u8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-interface")]
|
||||||
|
fn set_storage_works() {
|
||||||
|
const CODE: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||||
|
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||||
|
(import "__unstable__" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
;; [0, 4) size of input buffer
|
||||||
|
;; 4k in little endian
|
||||||
|
(data (i32.const 0) "\00\10")
|
||||||
|
|
||||||
|
;; [4, 4100) input buffer
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
;; Receive (key ++ value_to_write)
|
||||||
|
(call $seal_input
|
||||||
|
(i32.const 4) ;; Pointer to the input buffer
|
||||||
|
(i32.const 0) ;; Size of the input buffer
|
||||||
|
)
|
||||||
|
;; Store the passed value to the passed key and store result to memory
|
||||||
|
(i32.store (i32.const 168)
|
||||||
|
(call $seal_set_storage
|
||||||
|
(i32.const 8) ;; key_ptr
|
||||||
|
(i32.load (i32.const 4)) ;; key_len
|
||||||
|
(i32.add ;; value_ptr = 8 + key_len
|
||||||
|
(i32.const 8)
|
||||||
|
(i32.load (i32.const 4)))
|
||||||
|
(i32.sub ;; value_len (input_size - (key_len + key_len_len))
|
||||||
|
(i32.load (i32.const 0))
|
||||||
|
(i32.add
|
||||||
|
(i32.load (i32.const 4))
|
||||||
|
(i32.const 4)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call $seal_return
|
||||||
|
(i32.const 0) ;; flags
|
||||||
|
(i32.const 168) ;; ptr to returned value
|
||||||
|
(i32.const 4) ;; length of returned value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut ext = MockExt::default();
|
||||||
|
|
||||||
|
// value did not exist before -> sentinel returned
|
||||||
|
let input = (32, [1u8; 32], [42u8, 48]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
||||||
|
assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[42u8, 48]);
|
||||||
|
|
||||||
|
// value do exist -> length of old value returned
|
||||||
|
let input = (32, [1u8; 32], [0u8; 0]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 2);
|
||||||
|
assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[0u8; 0]);
|
||||||
|
|
||||||
|
// value do exist -> length of old value returned (test for zero sized val)
|
||||||
|
let input = (32, [1u8; 32], [99u8]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
||||||
|
assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[99u8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-interface")]
|
||||||
|
fn get_storage_works() {
|
||||||
|
const CODE: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||||
|
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||||
|
(import "__unstable__" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
;; [0, 4) size of input buffer (160 bytes as we copy the key+len here)
|
||||||
|
(data (i32.const 0) "\A0")
|
||||||
|
|
||||||
|
;; [4, 8) size of output buffer
|
||||||
|
;; 4k in little endian
|
||||||
|
(data (i32.const 4) "\00\10")
|
||||||
|
|
||||||
|
;; [8, 168) input buffer
|
||||||
|
;; [168, 4264) output buffer
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
;; Receive (key ++ value_to_write)
|
||||||
|
(call $seal_input
|
||||||
|
(i32.const 8) ;; Pointer to the input buffer
|
||||||
|
(i32.const 0) ;; Size of the input buffer
|
||||||
|
)
|
||||||
|
;; Load a storage value and result of this call into the output buffer
|
||||||
|
(i32.store (i32.const 168)
|
||||||
|
(call $seal_get_storage
|
||||||
|
(i32.const 12) ;; key_ptr
|
||||||
|
(i32.load (i32.const 8)) ;; key_len
|
||||||
|
(i32.const 172) ;; Pointer to the output buffer
|
||||||
|
(i32.const 4) ;; Pointer to the size of the buffer
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call $seal_return
|
||||||
|
(i32.const 0) ;; flags
|
||||||
|
(i32.const 168) ;; output buffer ptr
|
||||||
|
(i32.add ;; length: output size + 4 (retval)
|
||||||
|
(i32.load (i32.const 4))
|
||||||
|
(i32.const 4)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut ext = MockExt::default();
|
||||||
|
|
||||||
|
ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([1u8; 64].to_vec()).unwrap(),
|
||||||
|
Some(vec![42u8]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2u8; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// value does not exist
|
||||||
|
let input = (63, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
||||||
|
ReturnCode::KeyNotFound as u32
|
||||||
|
);
|
||||||
|
|
||||||
|
// value exists
|
||||||
|
let input = (64, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
||||||
|
ReturnCode::Success as u32
|
||||||
|
);
|
||||||
|
assert_eq!(ext.storage.get(&[1u8; 64].to_vec()).unwrap(), &[42u8]);
|
||||||
|
assert_eq!(&result.data.0[4..], &[42u8]);
|
||||||
|
|
||||||
|
// value exists (test for 0 sized)
|
||||||
|
let input = (19, [2u8; 19]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
||||||
|
ReturnCode::Success as u32
|
||||||
|
);
|
||||||
|
assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), Some(&vec![]));
|
||||||
|
assert_eq!(&result.data.0[4..], &([] as [u8; 0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2244,23 +2515,26 @@ mod tests {
|
|||||||
(module
|
(module
|
||||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||||
(import "__unstable__" "seal_clear_storage" (func $seal_clear_storage (param i32) (result i32)))
|
(import "__unstable__" "seal_clear_storage" (func $seal_clear_storage (param i32 i32) (result i32)))
|
||||||
(import "env" "memory" (memory 1 1))
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
;; 0x1000 = 4k in little endian
|
|
||||||
;; size of input buffer
|
;; size of input buffer
|
||||||
(data (i32.const 0) "\00\10")
|
;; [0, 4) size of input buffer (128+32 = 160 bytes = 0xA0)
|
||||||
|
(data (i32.const 0) "\A0")
|
||||||
|
|
||||||
|
;; [4, 164) input buffer
|
||||||
|
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
;; Receive key
|
;; Receive key
|
||||||
(call $seal_input
|
(call $seal_input
|
||||||
(i32.const 4) ;; Pointer to the input buffer
|
(i32.const 4) ;; Where we take input and store it
|
||||||
(i32.const 0) ;; Size of the length buffer
|
(i32.const 0) ;; Where we take and store the length of thedata
|
||||||
)
|
)
|
||||||
;; Store the passed value to the passed key and store result to memory
|
;; Call seal_clear_storage and save what it returns at 0
|
||||||
(i32.store (i32.const 0)
|
(i32.store (i32.const 0)
|
||||||
(call $seal_clear_storage
|
(call $seal_clear_storage
|
||||||
(i32.const 4) ;; key_ptr
|
(i32.const 8) ;; key_ptr
|
||||||
|
(i32.load (i32.const 4)) ;; key_len
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(call $seal_return
|
(call $seal_return
|
||||||
@@ -2276,23 +2550,48 @@ mod tests {
|
|||||||
|
|
||||||
let mut ext = MockExt::default();
|
let mut ext = MockExt::default();
|
||||||
|
|
||||||
ext.storage.insert([1u8; 32], vec![42u8]);
|
ext.set_storage_transparent(
|
||||||
ext.storage.insert([2u8; 32], vec![]);
|
&VarSizedKey::<Test>::try_from([1u8; 64].to_vec()).unwrap(),
|
||||||
|
Some(vec![42u8]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2u8; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// value does not exist -> sentinel returned
|
// value did not exist
|
||||||
let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap();
|
let input = (32, [3u8; 32]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
// sentinel returned
|
||||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
||||||
assert_eq!(ext.storage.get(&[3u8; 32]), None);
|
assert_eq!(ext.storage.get(&[3u8; 32].to_vec()), None);
|
||||||
|
|
||||||
// value did exist -> length returned
|
// value did exist
|
||||||
let result = execute(CODE, [1u8; 32].encode(), &mut ext).unwrap();
|
let input = (64, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
// length returned
|
||||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 1);
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 1);
|
||||||
assert_eq!(ext.storage.get(&[1u8; 32]), None);
|
// value cleared
|
||||||
|
assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None);
|
||||||
|
|
||||||
// value did exist -> length returned (test for 0 sized)
|
//value did not exist (wrong key length)
|
||||||
let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap();
|
let input = (63, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
// sentinel returned
|
||||||
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
||||||
|
assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None);
|
||||||
|
|
||||||
|
// value exists
|
||||||
|
let input = (19, [2u8; 19]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
|
// length returned (test for 0 sized)
|
||||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
||||||
assert_eq!(ext.storage.get(&[2u8; 32]), None);
|
// value cleared
|
||||||
|
assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2302,42 +2601,42 @@ mod tests {
|
|||||||
(module
|
(module
|
||||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||||
(import "__unstable__" "seal_take_storage" (func $seal_take_storage (param i32 i32 i32) (result i32)))
|
(import "__unstable__" "seal_take_storage" (func $seal_take_storage (param i32 i32 i32 i32) (result i32)))
|
||||||
(import "env" "memory" (memory 1 1))
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
;; [0, 32) size of input buffer (32 byte as we copy the key here)
|
;; [0, 4) size of input buffer (160 bytes as we copy the key+len here)
|
||||||
(data (i32.const 0) "\20")
|
(data (i32.const 0) "\A0")
|
||||||
|
|
||||||
;; [32, 64) size of output buffer
|
;; [4, 8) size of output buffer
|
||||||
;; 4k in little endian
|
;; 4k in little endian
|
||||||
(data (i32.const 32) "\00\10")
|
(data (i32.const 4) "\00\10")
|
||||||
|
|
||||||
;; [64, 96) input buffer
|
;; [8, 168) input buffer
|
||||||
|
;; [168, 4264) output buffer
|
||||||
;; [96, inf) output buffer
|
|
||||||
|
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
;; Receive key
|
;; Receive key
|
||||||
(call $seal_input
|
(call $seal_input
|
||||||
(i32.const 64) ;; Pointer to the input buffer
|
(i32.const 8) ;; Pointer to the input buffer
|
||||||
(i32.const 0) ;; Size of the length buffer
|
(i32.const 0) ;; Size of the length buffer
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Load a storage value and result of this call into the output buffer
|
;; Load a storage value and result of this call into the output buffer
|
||||||
(i32.store (i32.const 96)
|
(i32.store (i32.const 168)
|
||||||
(call $seal_take_storage
|
(call $seal_take_storage
|
||||||
(i32.const 64) ;; The pointer to the storage key to fetch
|
(i32.const 12) ;; key_ptr
|
||||||
(i32.const 100) ;; Pointer to the output buffer
|
(i32.load (i32.const 8)) ;; key_len
|
||||||
(i32.const 32) ;; Pointer to the size of the buffer
|
(i32.const 172) ;; Pointer to the output buffer
|
||||||
|
(i32.const 4) ;; Pointer to the size of the buffer
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Return the contents of the buffer
|
;; Return the contents of the buffer
|
||||||
(call $seal_return
|
(call $seal_return
|
||||||
(i32.const 0) ;; flags
|
(i32.const 0) ;; flags
|
||||||
(i32.const 96) ;; output buffer ptr
|
(i32.const 168) ;; output buffer ptr
|
||||||
(i32.add ;; length: storage size + 4 (retval)
|
(i32.add ;; length: storage size + 4 (retval)
|
||||||
(i32.load (i32.const 32))
|
(i32.load (i32.const 4))
|
||||||
(i32.const 4)
|
(i32.const 4)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2349,32 +2648,46 @@ mod tests {
|
|||||||
|
|
||||||
let mut ext = MockExt::default();
|
let mut ext = MockExt::default();
|
||||||
|
|
||||||
ext.storage.insert([1u8; 32], vec![42u8]);
|
ext.set_storage_transparent(
|
||||||
ext.storage.insert([2u8; 32], vec![]);
|
&VarSizedKey::<Test>::try_from([1u8; 64].to_vec()).unwrap(),
|
||||||
|
Some(vec![42u8]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<Test>::try_from([2u8; 19].to_vec()).unwrap(),
|
||||||
|
Some(vec![]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// value does not exist -> error returned
|
// value does not exist -> error returned
|
||||||
let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap();
|
let input = (63, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
||||||
ReturnCode::KeyNotFound as u32
|
ReturnCode::KeyNotFound as u32
|
||||||
);
|
);
|
||||||
|
|
||||||
// value did exist -> value returned
|
// value did exist -> value returned
|
||||||
let result = execute(CODE, [1u8; 32].encode(), &mut ext).unwrap();
|
let input = (64, [1u8; 64]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
||||||
ReturnCode::Success as u32
|
ReturnCode::Success as u32
|
||||||
);
|
);
|
||||||
assert_eq!(ext.storage.get(&[1u8; 32]), None);
|
assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None);
|
||||||
assert_eq!(&result.data.0[4..], &[42u8]);
|
assert_eq!(&result.data.0[4..], &[42u8]);
|
||||||
|
|
||||||
// value did exist -> length returned (test for 0 sized)
|
// value did exist -> length returned (test for 0 sized)
|
||||||
let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap();
|
let input = (19, [2u8; 19]).encode();
|
||||||
|
let result = execute(CODE, input, &mut ext).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()),
|
||||||
ReturnCode::Success as u32
|
ReturnCode::Success as u32
|
||||||
);
|
);
|
||||||
assert_eq!(ext.storage.get(&[2u8; 32]), None);
|
assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), None);
|
||||||
assert_eq!(&result.data.0[4..], &[0u8; 0]);
|
assert_eq!(&result.data.0[4..], &[0u8; 0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,16 @@
|
|||||||
//! Environment definition of the wasm smart-contract runtime.
|
//! Environment definition of the wasm smart-contract runtime.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
exec::{ExecError, ExecResult, Ext, StorageKey, TopicOf},
|
exec::{ExecError, ExecResult, Ext, FixSizedKey, TopicOf, VarSizedKey},
|
||||||
gas::{ChargedAmount, Token},
|
gas::{ChargedAmount, Token},
|
||||||
schedule::HostFnWeights,
|
schedule::HostFnWeights,
|
||||||
wasm::env_def::ConvertibleToWasm,
|
wasm::env_def::ConvertibleToWasm,
|
||||||
BalanceOf, CodeHash, Config, Error, SENTINEL,
|
BalanceOf, CodeHash, Config, Error, SENTINEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use codec::{Decode, DecodeAll, Encode, MaxEncodedLen};
|
use codec::{Decode, DecodeAll, Encode, MaxEncodedLen};
|
||||||
use frame_support::{dispatch::DispatchError, ensure, weights::Weight};
|
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight};
|
||||||
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
|
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
|
||||||
use sp_core::{crypto::UncheckedFrom, Bytes};
|
use sp_core::{crypto::UncheckedFrom, Bytes};
|
||||||
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
|
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
|
||||||
@@ -35,6 +36,28 @@ use sp_sandbox::SandboxMemory;
|
|||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
use wasm_instrument::parity_wasm::elements::ValueType;
|
use wasm_instrument::parity_wasm::elements::ValueType;
|
||||||
|
|
||||||
|
/// Type of a storage key.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum KeyType {
|
||||||
|
/// Deprecated fix sized key [0;32].
|
||||||
|
Fix,
|
||||||
|
/// Variable sized key used in transparent hashing,
|
||||||
|
/// cannot be larger than MaxStorageKeyLen.
|
||||||
|
Variable(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyType {
|
||||||
|
fn len<T: Config>(&self) -> Result<u32, TrapReason> {
|
||||||
|
match self {
|
||||||
|
KeyType::Fix => Ok(32u32),
|
||||||
|
KeyType::Variable(len) => {
|
||||||
|
ensure!(len <= &<T>::MaxStorageKeyLen::get(), Error::<T>::DecodingFailed);
|
||||||
|
Ok(*len)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Every error that can be returned to a contract when it calls any of the host functions.
|
/// Every error that can be returned to a contract when it calls any of the host functions.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
@@ -695,6 +718,7 @@ where
|
|||||||
|
|
||||||
fn set_storage(
|
fn set_storage(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
key_type: KeyType,
|
||||||
key_ptr: u32,
|
key_ptr: u32,
|
||||||
value_ptr: u32,
|
value_ptr: u32,
|
||||||
value_len: u32,
|
value_len: u32,
|
||||||
@@ -705,10 +729,21 @@ where
|
|||||||
if value_len > max_size {
|
if value_len > max_size {
|
||||||
return Err(Error::<E::T>::ValueTooLarge.into())
|
return Err(Error::<E::T>::ValueTooLarge.into())
|
||||||
}
|
}
|
||||||
let mut key: StorageKey = [0; 32];
|
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||||
self.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
|
||||||
let value = Some(self.read_sandbox_memory(value_ptr, value_len)?);
|
let value = Some(self.read_sandbox_memory(value_ptr, value_len)?);
|
||||||
let write_outcome = self.ext.set_storage(key, value, false)?;
|
let write_outcome = match key_type {
|
||||||
|
KeyType::Fix => self.ext.set_storage(
|
||||||
|
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
value,
|
||||||
|
false,
|
||||||
|
)?,
|
||||||
|
KeyType::Variable(_) => self.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
value,
|
||||||
|
false,
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
|
||||||
self.adjust_gas(
|
self.adjust_gas(
|
||||||
charged,
|
charged,
|
||||||
RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: write_outcome.old_len() },
|
RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: write_outcome.old_len() },
|
||||||
@@ -716,15 +751,70 @@ where
|
|||||||
Ok(write_outcome.old_len_with_sentinel())
|
Ok(write_outcome.old_len_with_sentinel())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_storage(&mut self, key_ptr: u32) -> Result<u32, TrapReason> {
|
fn clear_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result<u32, TrapReason> {
|
||||||
let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?;
|
let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?;
|
||||||
let mut key: StorageKey = [0; 32];
|
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||||
self.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
let outcome = match key_type {
|
||||||
let outcome = self.ext.set_storage(key, None, false)?;
|
KeyType::Fix => self.ext.set_storage(
|
||||||
|
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)?,
|
||||||
|
KeyType::Variable(_) => self.ext.set_storage_transparent(
|
||||||
|
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
|
||||||
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len()));
|
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len()));
|
||||||
Ok(outcome.old_len_with_sentinel())
|
Ok(outcome.old_len_with_sentinel())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_storage(
|
||||||
|
&mut self,
|
||||||
|
key_type: KeyType,
|
||||||
|
key_ptr: u32,
|
||||||
|
out_ptr: u32,
|
||||||
|
out_len_ptr: u32,
|
||||||
|
) -> Result<ReturnCode, TrapReason> {
|
||||||
|
let charged = self.charge_gas(RuntimeCosts::GetStorage(self.ext.max_value_size()))?;
|
||||||
|
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||||
|
let outcome = match key_type {
|
||||||
|
KeyType::Fix => self.ext.get_storage(
|
||||||
|
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
),
|
||||||
|
KeyType::Variable(_) => self.ext.get_storage_transparent(
|
||||||
|
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(value) = outcome {
|
||||||
|
self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32));
|
||||||
|
self.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
|
||||||
|
Ok(ReturnCode::Success)
|
||||||
|
} else {
|
||||||
|
self.adjust_gas(charged, RuntimeCosts::GetStorage(0));
|
||||||
|
Ok(ReturnCode::KeyNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result<u32, TrapReason> {
|
||||||
|
let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?;
|
||||||
|
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||||
|
let outcome = match key_type {
|
||||||
|
KeyType::Fix => self.ext.get_storage_size(
|
||||||
|
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
),
|
||||||
|
KeyType::Variable(_) => self.ext.get_storage_size_transparent(
|
||||||
|
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.unwrap_or(0)));
|
||||||
|
Ok(outcome.unwrap_or(SENTINEL))
|
||||||
|
}
|
||||||
|
|
||||||
fn call(
|
fn call(
|
||||||
&mut self,
|
&mut self,
|
||||||
flags: CallFlags,
|
flags: CallFlags,
|
||||||
@@ -863,11 +953,14 @@ define_env!(Env, <E: Ext>,
|
|||||||
// Equivalent to the newer version of `seal_set_storage` with the exception of the return
|
// Equivalent to the newer version of `seal_set_storage` with the exception of the return
|
||||||
// type. Still a valid thing to call when not interested in the return value.
|
// type. Still a valid thing to call when not interested in the return value.
|
||||||
[seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => {
|
[seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => {
|
||||||
ctx.set_storage(key_ptr, value_ptr, value_len).map(|_| ())
|
ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set the value at the given key in the contract storage.
|
// Set the value at the given key in the contract storage.
|
||||||
//
|
//
|
||||||
|
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||||
|
// hashing, please use the newer version of this function.
|
||||||
|
//
|
||||||
// The value length must not exceed the maximum defined by the contracts module parameters.
|
// The value length must not exceed the maximum defined by the contracts module parameters.
|
||||||
// Specifying a `value_len` of zero will store an empty value.
|
// Specifying a `value_len` of zero will store an empty value.
|
||||||
//
|
//
|
||||||
@@ -882,7 +975,27 @@ define_env!(Env, <E: Ext>,
|
|||||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||||
// `SENTINEL` is returned as a sentinel value.
|
// `SENTINEL` is returned as a sentinel value.
|
||||||
[seal1] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => {
|
[seal1] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => {
|
||||||
ctx.set_storage(key_ptr, value_ptr, value_len)
|
ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set the value at the given key in the contract storage.
|
||||||
|
//
|
||||||
|
// The key and value lengths must not exceed the maximums defined by the contracts module parameters.
|
||||||
|
// Specifying a `value_len` of zero will store an empty value.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - `key_ptr`: pointer into the linear memory where the location to store the value is placed.
|
||||||
|
// - `key_len`: the length of the key in bytes.
|
||||||
|
// - `value_ptr`: pointer into the linear memory where the value to set is placed.
|
||||||
|
// - `value_len`: the length of the value in bytes.
|
||||||
|
//
|
||||||
|
// # Return Value
|
||||||
|
//
|
||||||
|
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||||
|
// `SENTINEL` is returned as a sentinel value.
|
||||||
|
[__unstable__] seal_set_storage(ctx, key_ptr: u32, key_len: u32, value_ptr: u32, value_len: u32) -> u32 => {
|
||||||
|
ctx.set_storage(KeyType::Variable(key_len), key_ptr, value_ptr, value_len)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Clear the value at the given key in the contract storage.
|
// Clear the value at the given key in the contract storage.
|
||||||
@@ -890,25 +1003,29 @@ define_env!(Env, <E: Ext>,
|
|||||||
// Equivalent to the newer version of `seal_clear_storage` with the exception of the return
|
// Equivalent to the newer version of `seal_clear_storage` with the exception of the return
|
||||||
// type. Still a valid thing to call when not interested in the return value.
|
// type. Still a valid thing to call when not interested in the return value.
|
||||||
[seal0] seal_clear_storage(ctx, key_ptr: u32) => {
|
[seal0] seal_clear_storage(ctx, key_ptr: u32) => {
|
||||||
ctx.clear_storage(key_ptr).map(|_| ()).map_err(Into::into)
|
ctx.clear_storage(KeyType::Fix, key_ptr).map(|_| ())
|
||||||
},
|
},
|
||||||
|
|
||||||
// Clear the value at the given key in the contract storage.
|
// Clear the value at the given key in the contract storage.
|
||||||
//
|
//
|
||||||
// # Parameters
|
// # Parameters
|
||||||
//
|
//
|
||||||
// - `key_ptr`: pointer into the linear memory where the location to clear the value is placed.
|
// - `key_ptr`: pointer into the linear memory where the key is placed.
|
||||||
|
// - `key_len`: the length of the key in bytes.
|
||||||
//
|
//
|
||||||
// # Return Value
|
// # Return Value
|
||||||
//
|
//
|
||||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||||
// `SENTINEL` is returned as a sentinel value.
|
// `SENTINEL` is returned as a sentinel value.
|
||||||
[__unstable__] seal_clear_storage(ctx, key_ptr: u32) -> u32 => {
|
[__unstable__] seal_clear_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => {
|
||||||
ctx.clear_storage(key_ptr).map_err(Into::into)
|
ctx.clear_storage(KeyType::Variable(key_len), key_ptr)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Retrieve the value under the given key from storage.
|
// Retrieve the value under the given key from storage.
|
||||||
//
|
//
|
||||||
|
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||||
|
// hashing, please use the newer version of this function.
|
||||||
|
//
|
||||||
// # Parameters
|
// # Parameters
|
||||||
//
|
//
|
||||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||||
@@ -920,21 +1037,36 @@ define_env!(Env, <E: Ext>,
|
|||||||
//
|
//
|
||||||
// `ReturnCode::KeyNotFound`
|
// `ReturnCode::KeyNotFound`
|
||||||
[seal0] seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
[seal0] seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||||
let charged = ctx.charge_gas(RuntimeCosts::GetStorage(ctx.ext.max_value_size()))?;
|
ctx.get_storage(KeyType::Fix, key_ptr, out_ptr, out_len_ptr)
|
||||||
let mut key: StorageKey = [0; 32];
|
},
|
||||||
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
|
||||||
if let Some(value) = ctx.ext.get_storage(&key) {
|
// Retrieve the value under the given key from storage.
|
||||||
ctx.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32));
|
//
|
||||||
ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
|
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||||
Ok(ReturnCode::Success)
|
// hashing, please use the newer version of this function.
|
||||||
} else {
|
//
|
||||||
ctx.adjust_gas(charged, RuntimeCosts::GetStorage(0));
|
// The key length must not exceed the maximum defined by the contracts module parameter.
|
||||||
Ok(ReturnCode::KeyNotFound)
|
//
|
||||||
}
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||||
|
// - `key_len`: the length of the key in bytes.
|
||||||
|
// - `out_ptr`: pointer to the linear memory where the value is written to.
|
||||||
|
// - `out_len_ptr`: in-out pointer into linear memory where the buffer length
|
||||||
|
// is read from and the value length is written to.
|
||||||
|
//
|
||||||
|
// # Errors
|
||||||
|
//
|
||||||
|
// `ReturnCode::KeyNotFound`
|
||||||
|
[__unstable__] seal_get_storage(ctx, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||||
|
ctx.get_storage(KeyType::Variable(key_len), key_ptr, out_ptr, out_len_ptr)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Checks whether there is a value stored under the given key.
|
// Checks whether there is a value stored under the given key.
|
||||||
//
|
//
|
||||||
|
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||||
|
// hashing, please use the newer version of this function.
|
||||||
|
//
|
||||||
// # Parameters
|
// # Parameters
|
||||||
//
|
//
|
||||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||||
@@ -944,16 +1076,24 @@ define_env!(Env, <E: Ext>,
|
|||||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||||
// `SENTINEL` is returned as a sentinel value.
|
// `SENTINEL` is returned as a sentinel value.
|
||||||
[seal0] seal_contains_storage(ctx, key_ptr: u32) -> u32 => {
|
[seal0] seal_contains_storage(ctx, key_ptr: u32) -> u32 => {
|
||||||
let charged = ctx.charge_gas(RuntimeCosts::ContainsStorage(ctx.ext.max_value_size()))?;
|
ctx.contains_storage(KeyType::Fix, key_ptr)
|
||||||
let mut key: StorageKey = [0; 32];
|
},
|
||||||
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
|
||||||
if let Some(len) = ctx.ext.get_storage_size(&key) {
|
// Checks whether there is a value stored under the given key.
|
||||||
ctx.adjust_gas(charged, RuntimeCosts::ContainsStorage(len));
|
//
|
||||||
Ok(len)
|
// The key length must not exceed the maximum defined by the contracts module parameter.
|
||||||
} else {
|
//
|
||||||
ctx.adjust_gas(charged, RuntimeCosts::ContainsStorage(0));
|
// # Parameters
|
||||||
Ok(SENTINEL)
|
//
|
||||||
}
|
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||||
|
// - `key_len`: the length of the key in bytes.
|
||||||
|
//
|
||||||
|
// # Return Value
|
||||||
|
//
|
||||||
|
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||||
|
// `SENTINEL` is returned as a sentinel value.
|
||||||
|
[__unstable__] seal_contains_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => {
|
||||||
|
ctx.contains_storage(KeyType::Variable(key_len), key_ptr)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Retrieve and remove the value under the given key from storage.
|
// Retrieve and remove the value under the given key from storage.
|
||||||
@@ -961,6 +1101,7 @@ define_env!(Env, <E: Ext>,
|
|||||||
// # Parameters
|
// # Parameters
|
||||||
//
|
//
|
||||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||||
|
// - `key_len`: the length of the key in bytes.
|
||||||
// - `out_ptr`: pointer to the linear memory where the value is written to.
|
// - `out_ptr`: pointer to the linear memory where the value is written to.
|
||||||
// - `out_len_ptr`: in-out pointer into linear memory where the buffer length
|
// - `out_len_ptr`: in-out pointer into linear memory where the buffer length
|
||||||
// is read from and the value length is written to.
|
// is read from and the value length is written to.
|
||||||
@@ -968,11 +1109,10 @@ define_env!(Env, <E: Ext>,
|
|||||||
// # Errors
|
// # Errors
|
||||||
//
|
//
|
||||||
// `ReturnCode::KeyNotFound`
|
// `ReturnCode::KeyNotFound`
|
||||||
[__unstable__] seal_take_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
[__unstable__] seal_take_storage(ctx, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||||
let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?;
|
let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?;
|
||||||
let mut key: StorageKey = [0; 32];
|
let key = ctx.read_sandbox_memory(key_ptr, key_len)?;
|
||||||
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage_transparent(&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?, None, true)? {
|
||||||
if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage(key, None, true)? {
|
|
||||||
ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(value.len() as u32));
|
ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(value.len() as u32));
|
||||||
ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
|
ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
|
||||||
Ok(ReturnCode::Success)
|
Ok(ReturnCode::Success)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user