Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 29 additions & 84 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use crate::{
exec::{AccountIdOf, StorageKey},
schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
storage::Storage,
wasm::CallFlags,
Pallet as Contracts, *,
};
use codec::{Encode, MaxEncodedLen};
Expand Down Expand Up @@ -1526,44 +1527,21 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![])

seal_call_per_transfer_input_output_kb {
seal_call_per_transfer_clone_kb {
let t in 0 .. 1;
let i in 0 .. code::max_pages::<T>() * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
let callee_code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "seal_return",
params: vec![
ValueType::I32,
ValueType::I32,
ValueType::I32,
],
return_type: None,
}],
call_body: Some(body::plain(vec![
Instruction::I32Const(0), // flags
Instruction::I32Const(0), // data_ptr
Instruction::I32Const((o * 1024) as i32), // data_len
Instruction::Call(0),
Instruction::End,
])),
.. Default::default()
});
let c in 0 .. code::max_pages::<T>() * 64;
let callees = (0..API_BENCHMARK_BATCH_SIZE)
.map(|i| Contract::with_index(i + 1, callee_code.clone(), vec![]))
.map(|i| Contract::with_index(i + 1, <WasmModule<T>>::dummy(), vec![]))
.collect::<Result<Vec<_>, _>>()?;
let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0);
let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect::<Vec<_>>();
let callees_len = callee_bytes.len();
let value: BalanceOf<T> = t.into();
let value_bytes = value.encode();
let value_len = value_bytes.len();
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
module: "seal1",
name: "seal_call",
params: vec![
ValueType::I32,
Expand All @@ -1574,7 +1552,6 @@ benchmarks! {
ValueType::I32,
ValueType::I32,
ValueType::I32,
ValueType::I32,
],
return_type: Some(ValueType::I32),
}],
Expand All @@ -1587,29 +1564,25 @@ benchmarks! {
offset: value_len as u32,
value: callee_bytes,
},
DataSegment {
offset: (value_len + callees_len) as u32,
value: (o * 1024).to_le_bytes().into(),
},
],
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
Regular(Instruction::I32Const(CallFlags::CLONE_INPUT.bits() as i32)), // flags
Counter(value_len as u32, callee_len as u32), // callee_ptr
Regular(Instruction::I32Const(callee_len as i32)), // callee_len
Regular(Instruction::I64Const(0)), // gas
Regular(Instruction::I32Const(0)), // value_ptr
Regular(Instruction::I32Const(value_len as i32)), // value_len
Regular(Instruction::I32Const(0)), // input_data_ptr
Regular(Instruction::I32Const((i * 1024) as i32)), // input_data_len
Regular(Instruction::I32Const((value_len + callees_len + 4) as i32)), // output_ptr
Regular(Instruction::I32Const((value_len + callees_len) as i32)), // output_len_ptr
Regular(Instruction::I32Const(0)), // input_data_len
Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr
Regular(Instruction::I32Const(0)), // output_len_ptr
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
let bytes = vec![42; (c * 1024) as usize];
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, bytes)

// We assume that every instantiate sends at least the minimum balance.
seal_instantiate {
Expand Down Expand Up @@ -1725,52 +1698,28 @@ benchmarks! {
}
}

seal_instantiate_per_input_output_salt_kb {
let i in 0 .. (code::max_pages::<T>() - 1) * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
seal_instantiate_per_transfer_salt_kb {
let t in 0 .. 1;
let s in 0 .. (code::max_pages::<T>() - 1) * 64;
let callee_code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "seal_return",
params: vec![
ValueType::I32,
ValueType::I32,
ValueType::I32,
],
return_type: None,
}],
deploy_body: Some(body::plain(vec![
Instruction::I32Const(0), // flags
Instruction::I32Const(0), // data_ptr
Instruction::I32Const((o * 1024) as i32), // data_len
Instruction::Call(0),
Instruction::End,
])),
.. Default::default()
});
let callee_code = WasmModule::<T>::dummy();
let hash = callee_code.hash.clone();
let hash_bytes = callee_code.hash.encode();
let hash_len = hash_bytes.len();
Contracts::<T>::store_code_raw(callee_code.code, whitelisted_caller())?;
let inputs = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::<Vec<_>>();
let input_len = inputs.get(0).map(|x| x.len()).unwrap_or(0);
let input_bytes = inputs.iter().cloned().flatten().collect::<Vec<_>>();
let inputs_len = input_bytes.len();
let value = T::Currency::minimum_balance();
assert!(value > 0u32.into());
let salts = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::<Vec<_>>();
let salt_len = salts.get(0).map(|x| x.len()).unwrap_or(0);
let salt_bytes = salts.iter().cloned().flatten().collect::<Vec<_>>();
let salts_len = salt_bytes.len();
let value: BalanceOf<T> = t.into();
let value_bytes = value.encode();
let value_len = value_bytes.len();
let addr_len = T::AccountId::max_encoded_len();

// offsets where to place static data in contract memory
let input_offset = 0;
let value_offset = inputs_len;
let salt_offset = 0;
let value_offset = salts_len;
let hash_offset = value_offset + value_len;
let addr_len_offset = hash_offset + hash_len;
let output_len_offset = addr_len_offset + 4;
let output_offset = output_len_offset + 4;

let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
Expand All @@ -1796,8 +1745,8 @@ benchmarks! {
}],
data_segments: vec![
DataSegment {
offset: input_offset as u32,
value: input_bytes,
offset: salt_offset as u32,
value: salt_bytes,
},
DataSegment {
offset: value_offset as u32,
Expand All @@ -1811,25 +1760,21 @@ benchmarks! {
offset: addr_len_offset as u32,
value: (addr_len as u32).to_le_bytes().into(),
},
DataSegment {
offset: output_len_offset as u32,
value: (o * 1024).to_le_bytes().into(),
},
],
call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![
Regular(Instruction::I32Const(hash_offset as i32)), // code_hash_ptr
Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len
Regular(Instruction::I64Const(0)), // gas
Regular(Instruction::I32Const(value_offset as i32)), // value_ptr
Regular(Instruction::I32Const(value_len as i32)), // value_len
Counter(input_offset as u32, input_len as u32), // input_data_ptr
Regular(Instruction::I32Const((i * 1024).max(input_len as u32) as i32)), // input_data_len
Regular(Instruction::I32Const(0)), // input_data_ptr
Regular(Instruction::I32Const(0)), // input_data_len
Regular(Instruction::I32Const((addr_len_offset + addr_len) as i32)), // address_ptr
Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr
Regular(Instruction::I32Const(output_offset as i32)), // output_ptr
Regular(Instruction::I32Const(output_len_offset as i32)), // output_len_ptr
Counter(input_offset as u32, input_len as u32), // salt_ptr
Regular(Instruction::I32Const((s * 1024).max(input_len as u32) as i32)), // salt_len
Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr
Regular(Instruction::I32Const(0)), // output_len_ptr
Counter(salt_offset as u32, salt_len as u32), // salt_ptr
Regular(Instruction::I32Const((s * 1024).max(salt_len as u32) as i32)), // salt_len
Regular(Instruction::Call(0)),
Regular(Instruction::I32Eqz),
Regular(Instruction::If(BlockType::NoResult)),
Expand Down
61 changes: 30 additions & 31 deletions frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use crate::{
gas::GasMeter,
storage::{self, Storage, WriteOutcome},
wasm::{decrement_refcount, increment_refcount},
AccountCounter, BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event,
Pallet as Contracts, Schedule,
};
Expand Down Expand Up @@ -92,10 +91,6 @@ pub trait Ext: sealing::Sealed {
/// Call (possibly transferring some amount of funds) into the specified account.
///
/// Returns the original code size of the called contract.
///
/// # Return Value
///
/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
fn call(
&mut self,
gas_limit: Weight,
Expand All @@ -108,10 +103,6 @@ pub trait Ext: sealing::Sealed {
/// Execute code in the current frame.
///
/// Returns the original code size of the called contract.
///
/// # Return Value
///
/// Result<ExecReturnValue, ExecError>
fn delegate_call(
&mut self,
code: CodeHash<Self::T>,
Expand All @@ -123,10 +114,6 @@ pub trait Ext: sealing::Sealed {
/// Returns the original code size of the called contract.
/// The newly created account will be associated with `code`. `value` specifies the amount of
/// value transferred from this to the newly created account.
///
/// # Return Value
///
/// Result<(AccountId, ExecReturnValue, CodeSize), (ExecError, CodeSize)>
fn instantiate(
&mut self,
gas_limit: Weight,
Expand Down Expand Up @@ -269,12 +256,17 @@ pub trait Executable<T: Config>: Sized {
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError>;

/// Decrement the refcount by one if the code exists.
/// Increment the refcount of a code in-storage by one.
///
/// # Note
/// This is needed when the code is not set via instantiate but `seal_set_code_hash`.
///
/// Charges weight proportional to the code size from the gas meter.
fn remove_user(code_hash: CodeHash<T>) -> Result<(), DispatchError>;
/// # Errors
///
/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist.
fn add_user(code_hash: CodeHash<T>) -> Result<(), DispatchError>;

/// Decrement the refcount by one if the code exists.
fn remove_user(code_hash: CodeHash<T>);

/// Execute the specified exported function and return the result.
///
Expand Down Expand Up @@ -1058,7 +1050,7 @@ where
T::Currency::free_balance(&frame.account_id),
)?;
ContractInfoOf::<T>::remove(&frame.account_id);
E::remove_user(info.code_hash)?;
E::remove_user(info.code_hash);
Contracts::<T>::deposit_event(Event::Terminated {
contract: frame.account_id.clone(),
beneficiary: beneficiary.clone(),
Expand Down Expand Up @@ -1188,10 +1180,10 @@ where
}

fn set_code_hash(&mut self, hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
increment_refcount::<Self::T>(hash)?;
E::add_user(hash)?;
let top_frame = self.top_frame_mut();
let prev_hash = top_frame.contract_info().code_hash.clone();
decrement_refcount::<Self::T>(prev_hash.clone())?;
E::remove_user(prev_hash.clone());
top_frame.contract_info().code_hash = hash;
Contracts::<Self::T>::deposit_event(Event::ContractCodeUpdated {
contract: top_frame.account_id.clone(),
Expand Down Expand Up @@ -1249,7 +1241,11 @@ mod tests {
use pretty_assertions::assert_eq;
use sp_core::Bytes;
use sp_runtime::{traits::Hash, DispatchError};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use std::{
cell::RefCell,
collections::hash_map::{Entry, HashMap},
rc::Rc,
};

type System = frame_system::Pallet<Test>;

Expand Down Expand Up @@ -1311,15 +1307,15 @@ mod tests {
})
}

fn increment_refcount(code_hash: CodeHash<Test>) {
fn increment_refcount(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
LOADER.with(|loader| {
let mut loader = loader.borrow_mut();
loader
.map
.entry(code_hash)
.and_modify(|executable| executable.refcount += 1)
.or_insert_with(|| panic!("code_hash does not exist"));
});
match loader.map.entry(code_hash) {
Entry::Vacant(_) => Err(<Error<Test>>::CodeNotFound)?,
Entry::Occupied(mut entry) => entry.get_mut().refcount += 1,
}
Ok(())
})
}

fn decrement_refcount(code_hash: CodeHash<Test>) {
Expand Down Expand Up @@ -1355,9 +1351,12 @@ mod tests {
})
}

fn remove_user(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
fn add_user(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
MockLoader::increment_refcount(code_hash)
}

fn remove_user(code_hash: CodeHash<Test>) {
MockLoader::decrement_refcount(code_hash);
Ok(())
}

fn execute<E: Ext<T = Test>>(
Expand All @@ -1367,7 +1366,7 @@ mod tests {
input_data: Vec<u8>,
) -> ExecResult {
if let &Constructor = function {
MockLoader::increment_refcount(self.code_hash);
Self::add_user(self.code_hash).unwrap();
}
if function == &self.func_type {
(self.func)(MockCtx { ext, input_data }, &self)
Expand Down
Loading