contracts: switch to wasmi gas metering (#14084)

* upgrade to wasmi 0.29

* prepare cleanup

* sync ref_time w engine from the stack frame

* proc_macro: sync gas in host funcs

save: compiles, only gas pushing left to macro

WIP proc macro

proc macro: done

* clean benchmarks & schedule: w_base = w_i64const

* scale gas values btw engine and gas meter

* (re)instrumentation & code_cache removed

* remove gas() host fn, continue clean-up

save

* address review comments

* move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob

* refactor: no reftime_limit&schedule passes, no CodeStorage

* bugs fixing

* fix tests: expected deposit amount

* fix prepare::tests

* update tests and fix bugs

tests::run_out_of_gas_engine, need 2 more

save: 2 bugs with gas syncs: 1 of 2 tests done

gas_syncs_no_overcharge bug fixed, test passes!

cleaned out debug prints

second bug is not a bug

disabled_chain_extension test fix (err msg)

tests run_out_of_fuel_host, chain_extension pass

all tests pass

* update docs

* bump wasmi 0.30.0

* benchmarks updated, tests pass

* refactoring

* s/OwnerInfo/CodeInfo/g;

* migration: draft, compiles

* migration: draft, runs

* migration: draft, runs (fixing)

* deposits repaid non pro rata

* deposits repaid pro rata

* better try-runtime output

* even better try-runtime output

* benchmark migration

* fix merge leftover

* add forgotten fixtures, fix docs

* address review comments

* ci fixes

* cleanup

* benchmarks::prepare to return DispatchError

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* store memory limits to CodeInfo

* ci: roll back weights

* ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts

* drive-by: update Readme and pallet rustdoc

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* use wasmi 0.29

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* use wasmi 0.30 again

* query memory limits from wasmi

* better migration types

* ci: pull weights from master

* refactoring

* ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts

* addressing review comments

* refactor

* address review comments

* optimize migration

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* another review round comments addressed

* ci fix one

* clippy fix

* ci fix two

---------

Co-authored-by: command-bot <>
This commit is contained in:
Sasha Gryaznov
2023-07-03 14:04:10 +03:00
committed by GitHub
parent e42768ea34
commit fda86dd501
23 changed files with 2786 additions and 4588 deletions
+48 -47
View File
@@ -135,7 +135,7 @@ 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.
/// Returns the code size of the called contract.
fn call(
&mut self,
gas_limit: Weight,
@@ -148,7 +148,7 @@ pub trait Ext: sealing::Sealed {
/// Execute code in the current frame.
///
/// Returns the original code size of the called contract.
/// Returns the code size of the called contract.
fn delegate_call(
&mut self,
code: CodeHash<Self::T>,
@@ -159,7 +159,7 @@ 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.
/// value transferred from the caller to the newly created account.
fn instantiate(
&mut self,
gas_limit: Weight,
@@ -263,8 +263,11 @@ pub trait Ext: sealing::Sealed {
/// Get a reference to the schedule used by the current call.
fn schedule(&self) -> &Schedule<Self::T>;
/// Get an immutable reference to the nested gas meter.
fn gas_meter(&self) -> &GasMeter<Self::T>;
/// Get a mutable reference to the nested gas meter.
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T>;
/// Append a string to the debug buffer.
///
@@ -325,24 +328,27 @@ pub trait Executable<T: Config>: Sized {
/// Load the executable from storage.
///
/// # Note
/// Charges size base load and instrumentation weight from the gas meter.
/// Charges size base load weight from the gas meter.
fn from_storage(
code_hash: CodeHash<T>,
schedule: &Schedule<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError>;
/// Increment the refcount of a code in-storage by one.
///
/// This is needed when the code is not set via instantiate but `seal_set_code_hash`.
/// Increment the reference count of a of a stored code by one.
///
/// # Errors
///
/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist.
fn add_user(code_hash: CodeHash<T>) -> Result<(), DispatchError>;
/// [`Error::CodeNotFound`] is returned if no stored code found having the specified
/// `code_hash`.
fn increment_refcount(code_hash: CodeHash<T>) -> Result<(), DispatchError>;
/// Decrement the refcount by one if the code exists.
fn remove_user(code_hash: CodeHash<T>);
/// Decrement the reference count of a stored code by one.
///
/// # Note
///
/// A contract whose reference count dropped to zero isn't automatically removed. A
/// `remove_code` transaction must be submitted by the original uploader to do so.
fn decrement_refcount(code_hash: CodeHash<T>);
/// Execute the specified exported function and return the result.
///
@@ -363,7 +369,7 @@ pub trait Executable<T: Config>: Sized {
/// The code hash of the executable.
fn code_hash(&self) -> &CodeHash<T>;
/// Size of the instrumented code in bytes.
/// Size of the contract code in bytes.
fn code_len(&self) -> u32;
/// The code does not contain any instructions which could lead to indeterminism.
@@ -703,9 +709,9 @@ where
Weight::zero(),
storage_meter,
BalanceOf::<T>::zero(),
schedule,
determinism,
)?;
let stack = Self {
origin,
schedule,
@@ -735,7 +741,6 @@ where
gas_limit: Weight,
storage_meter: &mut storage::meter::GenericMeter<T, S>,
deposit_limit: BalanceOf<T>,
schedule: &Schedule<T>,
determinism: Determinism,
) -> Result<(Frame<T>, E, Option<u64>), ExecError> {
let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) =
@@ -751,7 +756,7 @@ where
if let Some(DelegatedCall { executable, caller }) = delegated_call {
(executable, Some(caller))
} else {
(E::from_storage(contract.code_hash, schedule, gas_meter)?, None)
(E::from_storage(contract.code_hash, gas_meter)?, None)
};
(dest, contract, executable, delegate_caller, ExportedFunction::Call, None)
@@ -759,7 +764,7 @@ where
FrameArgs::Instantiate { sender, nonce, executable, salt, input_data } => {
let account_id = Contracts::<T>::contract_address(
&sender,
executable.code_hash(),
&executable.code_hash(),
input_data,
salt,
);
@@ -831,7 +836,6 @@ where
gas_limit,
nested_storage,
deposit_limit,
self.schedule,
self.determinism,
)?;
self.frames.push(frame);
@@ -864,7 +868,7 @@ where
// Every non delegate call or instantiate also optionally transfers the balance.
self.initial_transfer()?;
// Call into the wasm blob.
// Call into the Wasm blob.
let output = executable
.execute(self, &entry_point, input_data)
.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
@@ -1193,7 +1197,7 @@ where
code_hash: CodeHash<Self::T>,
input_data: Vec<u8>,
) -> Result<ExecReturnValue, ExecError> {
let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?;
let executable = E::from_storage(code_hash, self.gas_meter_mut())?;
let top_frame = self.top_frame_mut();
let contract_info = top_frame.contract_info().clone();
let account_id = top_frame.account_id.clone();
@@ -1220,7 +1224,7 @@ where
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?;
let executable = E::from_storage(code_hash, self.gas_meter_mut())?;
let nonce = self.next_nonce();
let executable = self.push_frame(
FrameArgs::Instantiate {
@@ -1255,7 +1259,7 @@ where
)?;
info.queue_trie_for_deletion();
ContractInfoOf::<T>::remove(&frame.account_id);
E::remove_user(info.code_hash);
E::decrement_refcount(info.code_hash);
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(&frame.account_id), T::Hashing::hash_of(&beneficiary)],
Event::Terminated {
@@ -1372,7 +1376,11 @@ where
self.schedule
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
fn gas_meter(&self) -> &GasMeter<Self::T> {
&self.top_frame().nested_gas
}
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T> {
&mut self.top_frame_mut().nested_gas
}
@@ -1423,12 +1431,12 @@ where
fn set_code_hash(&mut self, hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
let frame = top_frame_mut!(self);
if !E::from_storage(hash, self.schedule, &mut frame.nested_gas)?.is_deterministic() {
if !E::from_storage(hash, &mut frame.nested_gas)?.is_deterministic() {
return Err(<Error<T>>::Indeterministic.into())
}
E::add_user(hash)?;
E::increment_refcount(hash)?;
let prev_hash = frame.contract_info().code_hash;
E::remove_user(prev_hash);
E::decrement_refcount(prev_hash);
frame.contract_info().code_hash = hash;
Contracts::<Self::T>::deposit_event(
vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash],
@@ -1591,7 +1599,6 @@ mod tests {
impl Executable<Test> for MockExecutable {
fn from_storage(
code_hash: CodeHash<Test>,
_schedule: &Schedule<Test>,
_gas_meter: &mut GasMeter<Test>,
) -> Result<Self, DispatchError> {
Loader::mutate(|loader| {
@@ -1599,11 +1606,11 @@ mod tests {
})
}
fn add_user(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
fn increment_refcount(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
MockLoader::increment_refcount(code_hash)
}
fn remove_user(code_hash: CodeHash<Test>) {
fn decrement_refcount(code_hash: CodeHash<Test>) {
MockLoader::decrement_refcount(code_hash);
}
@@ -1614,7 +1621,7 @@ mod tests {
input_data: Vec<u8>,
) -> ExecResult {
if let &Constructor = function {
Self::add_user(self.code_hash).unwrap();
Self::increment_refcount(self.code_hash).unwrap();
}
if function == &self.func_type {
(self.func)(MockCtx { ext, input_data }, &self)
@@ -1952,8 +1959,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2366,8 +2372,7 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
@@ -2399,8 +2404,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2445,8 +2449,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2614,8 +2617,7 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2717,7 +2719,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -3141,14 +3143,13 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let fail_executable =
MockExecutable::from_storage(fail_code, &schedule, &mut gas_meter).unwrap();
let fail_executable = MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap();
let success_executable =
MockExecutable::from_storage(success_code, &schedule, &mut gas_meter).unwrap();
MockExecutable::from_storage(success_code, &mut gas_meter).unwrap();
let succ_fail_executable =
MockExecutable::from_storage(succ_fail_code, &schedule, &mut gas_meter).unwrap();
MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap();
let succ_succ_executable =
MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap();
MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =