mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 05:17:58 +00:00
contracts: Consider contract size in weights (#8086)
* contracts: Consider contract size in weights * Bump spec version * Whitespace fix Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Correct pre-charged code weight even in the error case * Use the instrumented code size in weight calculation * Charge the cost of re-instrumentation from the gas meter * Fix benchmark * cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --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 * Better documentation of return types Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
This commit is contained in:
committed by
GitHub
parent
fbd3148bba
commit
84071d6d49
@@ -72,8 +72,13 @@ pub trait Ext {
|
||||
|
||||
/// Instantiate a contract from the given code.
|
||||
///
|
||||
/// 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 (also known as endowment).
|
||||
///
|
||||
/// # Return Value
|
||||
///
|
||||
/// Result<(AccountId, ExecReturnValue, CodeSize), (ExecError, CodeSize)>
|
||||
fn instantiate(
|
||||
&mut self,
|
||||
code: CodeHash<Self::T>,
|
||||
@@ -81,7 +86,7 @@ pub trait Ext {
|
||||
gas_meter: &mut GasMeter<Self::T>,
|
||||
input_data: Vec<u8>,
|
||||
salt: &[u8],
|
||||
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
|
||||
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)>;
|
||||
|
||||
/// Transfer some amount of funds into the specified account.
|
||||
fn transfer(
|
||||
@@ -92,24 +97,35 @@ pub trait Ext {
|
||||
|
||||
/// Transfer all funds to `beneficiary` and delete the contract.
|
||||
///
|
||||
/// Returns the original code size of the terminated contract.
|
||||
/// Since this function removes the self contract eagerly, if succeeded, no further actions should
|
||||
/// be performed on this `Ext` instance.
|
||||
///
|
||||
/// This function will fail if the same contract is present on the contract
|
||||
/// call stack.
|
||||
///
|
||||
/// # Return Value
|
||||
///
|
||||
/// Result<CodeSize, (DispatchError, CodeSize)>
|
||||
fn terminate(
|
||||
&mut self,
|
||||
beneficiary: &AccountIdOf<Self::T>,
|
||||
) -> DispatchResult;
|
||||
) -> Result<u32, (DispatchError, u32)>;
|
||||
|
||||
/// 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,
|
||||
to: &AccountIdOf<Self::T>,
|
||||
value: BalanceOf<Self::T>,
|
||||
gas_meter: &mut GasMeter<Self::T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult;
|
||||
) -> Result<(ExecReturnValue, u32), (ExecError, u32)>;
|
||||
|
||||
/// Restores the given destination contract sacrificing the current one.
|
||||
///
|
||||
@@ -118,13 +134,17 @@ pub trait Ext {
|
||||
///
|
||||
/// This function will fail if the same contract is present
|
||||
/// on the contract call stack.
|
||||
///
|
||||
/// # Return Value
|
||||
///
|
||||
/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
|
||||
fn restore_to(
|
||||
&mut self,
|
||||
dest: AccountIdOf<Self::T>,
|
||||
code_hash: CodeHash<Self::T>,
|
||||
rent_allowance: BalanceOf<Self::T>,
|
||||
delta: Vec<StorageKey>,
|
||||
) -> DispatchResult;
|
||||
) -> Result<(u32, u32), (DispatchError, u32, u32)>;
|
||||
|
||||
/// Returns a reference to the account id of the caller.
|
||||
fn caller(&self) -> &AccountIdOf<Self::T>;
|
||||
@@ -190,7 +210,11 @@ pub enum ExportedFunction {
|
||||
/// order to be able to mock the wasm logic for testing.
|
||||
pub trait Executable<T: Config>: Sized {
|
||||
/// Load the executable from storage.
|
||||
fn from_storage(code_hash: CodeHash<T>, schedule: &Schedule<T>) -> Result<Self, DispatchError>;
|
||||
fn from_storage(
|
||||
code_hash: CodeHash<T>,
|
||||
schedule: &Schedule<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
) -> Result<Self, DispatchError>;
|
||||
|
||||
/// Load the module from storage without re-instrumenting it.
|
||||
///
|
||||
@@ -203,10 +227,14 @@ pub trait Executable<T: Config>: Sized {
|
||||
fn drop_from_storage(self);
|
||||
|
||||
/// Increment the refcount by one. Fails if the code does not exist on-chain.
|
||||
fn add_user(code_hash: CodeHash<T>) -> DispatchResult;
|
||||
///
|
||||
/// Returns the size of the original code.
|
||||
fn add_user(code_hash: CodeHash<T>) -> Result<u32, DispatchError>;
|
||||
|
||||
/// Decrement the refcount by one and remove the code when it drops to zero.
|
||||
fn remove_user(code_hash: CodeHash<T>);
|
||||
///
|
||||
/// Returns the size of the original code.
|
||||
fn remove_user(code_hash: CodeHash<T>) -> u32;
|
||||
|
||||
/// Execute the specified exported function and return the result.
|
||||
///
|
||||
@@ -238,6 +266,9 @@ pub trait Executable<T: Config>: Sized {
|
||||
/// without refetching this from storage the result can be inaccurate as it might be
|
||||
/// working with a stale value. Usually this inaccuracy is tolerable.
|
||||
fn occupied_storage(&self) -> u32;
|
||||
|
||||
/// Size of the instrumented code in bytes.
|
||||
fn code_len(&self) -> u32;
|
||||
}
|
||||
|
||||
pub struct ExecutionContext<'a, T: Config + 'a, E> {
|
||||
@@ -290,35 +321,42 @@ where
|
||||
}
|
||||
|
||||
/// Make a call to the specified address, optionally transferring some funds.
|
||||
///
|
||||
/// # Return Value
|
||||
///
|
||||
/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
|
||||
pub fn call(
|
||||
&mut self,
|
||||
dest: T::AccountId,
|
||||
value: BalanceOf<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
|
||||
if self.depth == T::MaxDepth::get() as usize {
|
||||
Err(Error::<T>::MaxCallDepthReached)?
|
||||
return Err((Error::<T>::MaxCallDepthReached.into(), 0));
|
||||
}
|
||||
|
||||
let contract = <ContractInfoOf<T>>::get(&dest)
|
||||
.and_then(|contract| contract.get_alive())
|
||||
.ok_or(Error::<T>::NotCallable)?;
|
||||
.ok_or((Error::<T>::NotCallable.into(), 0))?;
|
||||
|
||||
let executable = E::from_storage(contract.code_hash, &self.schedule)?;
|
||||
let executable = E::from_storage(contract.code_hash, &self.schedule, gas_meter)
|
||||
.map_err(|e| (e.into(), 0))?;
|
||||
let code_len = executable.code_len();
|
||||
|
||||
// This charges the rent and denies access to a contract that is in need of
|
||||
// eviction by returning `None`. We cannot evict eagerly here because those
|
||||
// changes would be rolled back in case this contract is called by another
|
||||
// contract.
|
||||
// See: https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324
|
||||
let contract = Rent::<T, E>::charge(&dest, contract, executable.occupied_storage())?
|
||||
.ok_or(Error::<T>::NotCallable)?;
|
||||
let contract = Rent::<T, E>::charge(&dest, contract, executable.occupied_storage())
|
||||
.map_err(|e| (e.into(), code_len))?
|
||||
.ok_or((Error::<T>::NotCallable.into(), code_len))?;
|
||||
|
||||
let transactor_kind = self.transactor_kind();
|
||||
let caller = self.self_account.clone();
|
||||
|
||||
self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| {
|
||||
let result = self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| {
|
||||
if value > BalanceOf::<T>::zero() {
|
||||
transfer::<T>(
|
||||
TransferCause::Call,
|
||||
@@ -336,7 +374,8 @@ where
|
||||
gas_meter,
|
||||
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
|
||||
Ok(output)
|
||||
})
|
||||
}).map_err(|e| (e, code_len))?;
|
||||
Ok((result, code_len))
|
||||
}
|
||||
|
||||
pub fn instantiate(
|
||||
@@ -581,10 +620,13 @@ where
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
input_data: Vec<u8>,
|
||||
salt: &[u8],
|
||||
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
|
||||
let executable = E::from_storage(code_hash, &self.ctx.schedule)?;
|
||||
let result = self.ctx.instantiate(endowment, gas_meter, executable, input_data, salt)?;
|
||||
Ok(result)
|
||||
) -> Result<(AccountIdOf<T>, ExecReturnValue, u32), (ExecError, u32)> {
|
||||
let executable = E::from_storage(code_hash, &self.ctx.schedule, gas_meter)
|
||||
.map_err(|e| (e.into(), 0))?;
|
||||
let code_len = executable.code_len();
|
||||
self.ctx.instantiate(endowment, gas_meter, executable, input_data, salt)
|
||||
.map(|r| (r.0, r.1, code_len))
|
||||
.map_err(|e| (e, code_len))
|
||||
}
|
||||
|
||||
fn transfer(
|
||||
@@ -604,12 +646,12 @@ where
|
||||
fn terminate(
|
||||
&mut self,
|
||||
beneficiary: &AccountIdOf<Self::T>,
|
||||
) -> DispatchResult {
|
||||
) -> Result<u32, (DispatchError, u32)> {
|
||||
let self_id = self.ctx.self_account.clone();
|
||||
let value = T::Currency::free_balance(&self_id);
|
||||
if let Some(caller_ctx) = self.ctx.caller {
|
||||
if caller_ctx.is_live(&self_id) {
|
||||
return Err(Error::<T>::ReentranceDenied.into());
|
||||
return Err((Error::<T>::ReentranceDenied.into(), 0));
|
||||
}
|
||||
}
|
||||
transfer::<T>(
|
||||
@@ -618,12 +660,12 @@ where
|
||||
&self_id,
|
||||
beneficiary,
|
||||
value,
|
||||
)?;
|
||||
).map_err(|e| (e, 0))?;
|
||||
if let Some(ContractInfo::Alive(info)) = ContractInfoOf::<T>::take(&self_id) {
|
||||
Storage::<T>::queue_trie_for_deletion(&info)?;
|
||||
E::remove_user(info.code_hash);
|
||||
Storage::<T>::queue_trie_for_deletion(&info).map_err(|e| (e, 0))?;
|
||||
let code_len = E::remove_user(info.code_hash);
|
||||
Contracts::<T>::deposit_event(RawEvent::Terminated(self_id, beneficiary.clone()));
|
||||
Ok(())
|
||||
Ok(code_len)
|
||||
} else {
|
||||
panic!(
|
||||
"this function is only invoked by in the context of a contract;\
|
||||
@@ -639,7 +681,7 @@ where
|
||||
value: BalanceOf<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
|
||||
self.ctx.call(to.clone(), value, gas_meter, input_data)
|
||||
}
|
||||
|
||||
@@ -649,10 +691,10 @@ where
|
||||
code_hash: CodeHash<Self::T>,
|
||||
rent_allowance: BalanceOf<Self::T>,
|
||||
delta: Vec<StorageKey>,
|
||||
) -> DispatchResult {
|
||||
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
|
||||
if let Some(caller_ctx) = self.ctx.caller {
|
||||
if caller_ctx.is_live(&self.ctx.self_account) {
|
||||
return Err(Error::<T>::ReentranceDenied.into());
|
||||
return Err((Error::<T>::ReentranceDenied.into(), 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,7 +870,8 @@ mod tests {
|
||||
impl Executable<Test> for MockExecutable {
|
||||
fn from_storage(
|
||||
code_hash: CodeHash<Test>,
|
||||
_schedule: &Schedule<Test>
|
||||
_schedule: &Schedule<Test>,
|
||||
_gas_meter: &mut GasMeter<Test>,
|
||||
) -> Result<Self, DispatchError> {
|
||||
Self::from_storage_noinstr(code_hash)
|
||||
}
|
||||
@@ -845,11 +888,11 @@ mod tests {
|
||||
|
||||
fn drop_from_storage(self) {}
|
||||
|
||||
fn add_user(_code_hash: CodeHash<Test>) -> DispatchResult {
|
||||
Ok(())
|
||||
fn add_user(_code_hash: CodeHash<Test>) -> Result<u32, DispatchError> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn remove_user(_code_hash: CodeHash<Test>) {}
|
||||
fn remove_user(_code_hash: CodeHash<Test>) -> u32 { 0 }
|
||||
|
||||
fn execute<E: Ext<T = Test>>(
|
||||
self,
|
||||
@@ -872,6 +915,10 @@ mod tests {
|
||||
fn occupied_storage(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn code_len(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_success() -> ExecResult {
|
||||
@@ -954,7 +1001,7 @@ mod tests {
|
||||
vec![],
|
||||
).unwrap();
|
||||
|
||||
assert!(!output.is_success());
|
||||
assert!(!output.0.is_success());
|
||||
assert_eq!(get_balance(&origin), 100);
|
||||
|
||||
// the rent is still charged
|
||||
@@ -1012,8 +1059,8 @@ mod tests {
|
||||
);
|
||||
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_success());
|
||||
assert_eq!(output.data, vec![1, 2, 3, 4]);
|
||||
assert!(output.0.is_success());
|
||||
assert_eq!(output.0.data, vec![1, 2, 3, 4]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1040,8 +1087,8 @@ mod tests {
|
||||
);
|
||||
|
||||
let output = result.unwrap();
|
||||
assert!(!output.is_success());
|
||||
assert_eq!(output.data, vec![1, 2, 3, 4]);
|
||||
assert!(!output.0.is_success());
|
||||
assert_eq!(output.0.data, vec![1, 2, 3, 4]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1080,13 +1127,17 @@ mod tests {
|
||||
let schedule = Contracts::current_schedule();
|
||||
let subsistence = Contracts::<Test>::subsistence_threshold();
|
||||
let mut ctx = MockContext::top_level(ALICE, &schedule);
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(
|
||||
input_data_ch, &schedule, &mut gas_meter
|
||||
).unwrap();
|
||||
|
||||
set_balance(&ALICE, subsistence * 10);
|
||||
|
||||
let result = ctx.instantiate(
|
||||
subsistence * 3,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
MockExecutable::from_storage(input_data_ch, &schedule).unwrap(),
|
||||
&mut gas_meter,
|
||||
executable,
|
||||
vec![1, 2, 3, 4],
|
||||
&[],
|
||||
);
|
||||
@@ -1113,7 +1164,7 @@ mod tests {
|
||||
// Verify that we've got proper error and set `reached_bottom`.
|
||||
assert_eq!(
|
||||
r,
|
||||
Err(Error::<Test>::MaxCallDepthReached.into())
|
||||
Err((Error::<Test>::MaxCallDepthReached.into(), 0))
|
||||
);
|
||||
*reached_bottom = true;
|
||||
} else {
|
||||
@@ -1235,12 +1286,16 @@ mod tests {
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = Contracts::current_schedule();
|
||||
let mut ctx = MockContext::top_level(ALICE, &schedule);
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(
|
||||
dummy_ch, &schedule, &mut gas_meter
|
||||
).unwrap();
|
||||
|
||||
assert_matches!(
|
||||
ctx.instantiate(
|
||||
0, // <- zero endowment
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
MockExecutable::from_storage(dummy_ch, &schedule).unwrap(),
|
||||
&mut gas_meter,
|
||||
executable,
|
||||
vec![],
|
||||
&[],
|
||||
),
|
||||
@@ -1258,13 +1313,17 @@ mod tests {
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = Contracts::current_schedule();
|
||||
let mut ctx = MockContext::top_level(ALICE, &schedule);
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(
|
||||
dummy_ch, &schedule, &mut gas_meter
|
||||
).unwrap();
|
||||
set_balance(&ALICE, 1000);
|
||||
|
||||
let instantiated_contract_address = assert_matches!(
|
||||
ctx.instantiate(
|
||||
100,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
MockExecutable::from_storage(dummy_ch, &schedule).unwrap(),
|
||||
&mut gas_meter,
|
||||
executable,
|
||||
vec![],
|
||||
&[],
|
||||
),
|
||||
@@ -1289,13 +1348,17 @@ mod tests {
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = Contracts::current_schedule();
|
||||
let mut ctx = MockContext::top_level(ALICE, &schedule);
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(
|
||||
dummy_ch, &schedule, &mut gas_meter
|
||||
).unwrap();
|
||||
set_balance(&ALICE, 1000);
|
||||
|
||||
let instantiated_contract_address = assert_matches!(
|
||||
ctx.instantiate(
|
||||
100,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
MockExecutable::from_storage(dummy_ch, &schedule).unwrap(),
|
||||
&mut gas_meter,
|
||||
executable,
|
||||
vec![],
|
||||
&[],
|
||||
),
|
||||
@@ -1317,7 +1380,7 @@ mod tests {
|
||||
let instantiated_contract_address = Rc::clone(&instantiated_contract_address);
|
||||
move |ctx| {
|
||||
// Instantiate a contract and save it's address in `instantiated_contract_address`.
|
||||
let (address, output) = ctx.ext.instantiate(
|
||||
let (address, output, _) = ctx.ext.instantiate(
|
||||
dummy_ch,
|
||||
Contracts::<Test>::subsistence_threshold() * 3,
|
||||
ctx.gas_meter,
|
||||
@@ -1369,10 +1432,10 @@ mod tests {
|
||||
vec![],
|
||||
&[],
|
||||
),
|
||||
Err(ExecError {
|
||||
Err((ExecError {
|
||||
error: DispatchError::Other("It's a trap!"),
|
||||
origin: ErrorOrigin::Callee,
|
||||
})
|
||||
}, 0))
|
||||
);
|
||||
|
||||
exec_success()
|
||||
@@ -1410,13 +1473,17 @@ mod tests {
|
||||
.execute_with(|| {
|
||||
let schedule = Contracts::current_schedule();
|
||||
let mut ctx = MockContext::top_level(ALICE, &schedule);
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(
|
||||
terminate_ch, &schedule, &mut gas_meter
|
||||
).unwrap();
|
||||
set_balance(&ALICE, 1000);
|
||||
|
||||
assert_eq!(
|
||||
ctx.instantiate(
|
||||
100,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
MockExecutable::from_storage(terminate_ch, &schedule).unwrap(),
|
||||
&mut gas_meter,
|
||||
executable,
|
||||
vec![],
|
||||
&[],
|
||||
),
|
||||
@@ -1445,12 +1512,16 @@ mod tests {
|
||||
let subsistence = Contracts::<Test>::subsistence_threshold();
|
||||
let schedule = Contracts::current_schedule();
|
||||
let mut ctx = MockContext::top_level(ALICE, &schedule);
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(
|
||||
rent_allowance_ch, &schedule, &mut gas_meter
|
||||
).unwrap();
|
||||
set_balance(&ALICE, subsistence * 10);
|
||||
|
||||
let result = ctx.instantiate(
|
||||
subsistence * 5,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
MockExecutable::from_storage(rent_allowance_ch, &schedule).unwrap(),
|
||||
&mut gas_meter,
|
||||
executable,
|
||||
vec![],
|
||||
&[],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user