contracts: Charge rent for code storage (#7935)

* contracts: Implement refcounting for wasm code

* contracts: Charge rent for code storage

* contracts: Fix dispatchables erroneously refunding base costs

* Fixed typos in comments.

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* Remove awkward empty line

* Fix more typos in docs

* Fix typos in docs

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* Split up complicated expression

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* review: Remove unused return value

* Fix typos

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* review: Fix refcount being reset to one on re-instrumentation

* Document evictable_code parameter

* Make Executable::execute consume and store itself

* Added comments about stale values

* Disregard struct size in occupied_storage()

Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
Alexander Theißen
2021-02-04 12:01:34 +01:00
committed by GitHub
parent 5569313bd6
commit 8e49a8a6a6
17 changed files with 1903 additions and 1529 deletions
+93 -52
View File
@@ -59,10 +59,11 @@
//!
//! ### Dispatchable functions
//!
//! * `put_code` - Stores the given binary Wasm code into the chain's storage and returns its `code_hash`.
//! * `instantiate` - Deploys a new contract from the given `code_hash`, optionally transferring some balance.
//! This instantiates a new smart contract account and calls its contract deploy handler to
//! initialize the contract.
//! * `instantiate_with_code` - Deploys a new contract from the supplied wasm binary, optionally transferring
//! some balance. This instantiates a new smart contract account and calls its contract deploy
//! handler to initialize the contract.
//! * `instantiate` - The same as `instantiate_with_code` but instead of uploading new code an
//! existing `code_hash` is supplied.
//! * `call` - Makes a call to an account, optionally transferring some balance.
//!
//! ## Usage
@@ -98,13 +99,12 @@ mod tests;
pub use crate::{
gas::{Gas, GasMeter},
wasm::ReturnCode as RuntimeReturnCode,
wasm::{ReturnCode as RuntimeReturnCode, PrefabWasmModule},
weights::WeightInfo,
schedule::{Schedule, HostFnWeights, InstructionWeights, Limits},
};
use crate::{
exec::ExecutionContext,
wasm::{WasmLoader, WasmVm},
exec::{ExecutionContext, Executable},
rent::Rent,
storage::Storage,
};
@@ -387,7 +387,8 @@ decl_error! {
/// The contract that was called is either no contract at all (a plain account)
/// or is a tombstone.
NotCallable,
/// The code supplied to `put_code` exceeds the limit specified in the current schedule.
/// The code supplied to `instantiate_with_code` exceeds the limit specified in the
/// current schedule.
CodeTooLarge,
/// No code could be found at the supplied code hash.
CodeNotFound,
@@ -431,6 +432,8 @@ decl_error! {
/// This can either happen when the accumulated storage in bytes is too large or
/// when number of storage items is too large.
StorageExhausted,
/// A contract with the same AccountId already exists.
DuplicateContract,
}
}
@@ -528,23 +531,6 @@ decl_module! {
Ok(())
}
/// Stores the given binary Wasm code into the chain's storage and returns its `codehash`.
/// You can instantiate contracts only with stored code.
#[weight = T::WeightInfo::put_code(code.len() as u32 / 1024)]
pub fn put_code(
origin,
code: Vec<u8>
) -> DispatchResult {
ensure_signed(origin)?;
let schedule = <Module<T>>::current_schedule();
ensure!(code.len() as u32 <= schedule.limits.code_size, Error::<T>::CodeTooLarge);
let result = wasm::save_code::<T>(code, &schedule);
if let Ok(code_hash) = result {
Self::deposit_event(RawEvent::CodeStored(code_hash));
}
result.map(|_| ()).map_err(Into::into)
}
/// Makes a call to an account, optionally transferring some balance.
///
/// * If the account is a smart-contract account, the associated code will be
@@ -563,31 +549,73 @@ decl_module! {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, data)
});
gas_meter.into_dispatch_result(result)
gas_meter.into_dispatch_result(result, T::WeightInfo::call())
}
/// Instantiates a new contract from the `code_hash` generated by `put_code`,
/// optionally transferring some balance.
/// Instantiates a new contract from the supplied `code` optionally transferring
/// some balance.
///
/// The supplied `salt` is used for contract address deriviation. See `fn contract_address`.
/// This is the only function that can deploy new code to the chain.
///
/// # Parameters
///
/// * `endowment`: The balance to transfer from the `origin` to the newly created contract.
/// * `gas_limit`: The gas limit enforced when executing the constructor.
/// * `code`: The contract code to deploy in raw bytes.
/// * `data`: The input data to pass to the contract constructor.
/// * `salt`: Used for the address derivation. See [`Self::contract_address`].
///
/// Instantiation is executed as follows:
///
/// - The supplied `code` is instrumented, deployed, and a `code_hash` is created for that code.
/// - If the `code_hash` already exists on the chain the underlying `code` will be shared.
/// - The destination address is computed based on the sender, code_hash and the salt.
/// - The smart-contract account is created at the computed address.
/// - The `ctor_code` is executed in the context of the newly-created account. Buffer returned
/// after the execution is saved as the `code` of the account. That code will be invoked
/// upon any call received by this account.
/// - The contract is initialized.
/// - The `endowment` is transferred to the new account.
/// - The `deploy` function is executed in the context of the newly-created account.
#[weight =
T::WeightInfo::instantiate(
data.len() as u32 / 1024,
T::WeightInfo::instantiate_with_code(
code.len() as u32 / 1024,
salt.len() as u32 / 1024,
).saturating_add(*gas_limit)
)
.saturating_add(*gas_limit)
]
pub fn instantiate_with_code(
origin,
#[compact] endowment: BalanceOf<T>,
#[compact] gas_limit: Gas,
code: Vec<u8>,
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let schedule = <Module<T>>::current_schedule();
let code_len = code.len() as u32;
ensure!(code_len <= schedule.limits.code_size, Error::<T>::CodeTooLarge);
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
let executable = PrefabWasmModule::from_code(code, &schedule)?;
let result = ctx.instantiate(endowment, gas_meter, executable, data, &salt)
.map(|(_address, output)| output)?;
Ok(result)
});
gas_meter.into_dispatch_result(
result,
T::WeightInfo::instantiate_with_code(code_len / 1024, salt.len() as u32 / 1024)
)
}
/// Instantiates a contract from a previously deployed wasm binary.
///
/// This function is identical to [`Self::instantiate_with_code`] but without the
/// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary
/// must be supplied.
#[weight =
T::WeightInfo::instantiate(salt.len() as u32 / 1024)
.saturating_add(*gas_limit)
]
pub fn instantiate(
origin,
@@ -599,12 +627,16 @@ decl_module! {
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.instantiate(endowment, gas_meter, &code_hash, data, &salt)
.map(|(_address, output)| output)
let executable = PrefabWasmModule::from_storage(code_hash, &ctx.config.schedule)?;
let result = ctx.instantiate(endowment, gas_meter, executable, data, &salt)
.map(|(_address, output)| output)?;
Ok(result)
});
gas_meter.into_dispatch_result(result)
gas_meter.into_dispatch_result(
result,
T::WeightInfo::instantiate(salt.len() as u32 / 1024)
)
}
/// Allows block producers to claim a small reward for evicting a contract. If a block
@@ -643,7 +675,9 @@ decl_module! {
};
// If poking the contract has lead to eviction of the contract, give out the rewards.
if let Some(rent_payed) = Rent::<T>::try_eviction(&dest, handicap)? {
if let Some(rent_payed) =
Rent::<T, PrefabWasmModule<T>>::try_eviction(&dest, handicap)?
{
T::Currency::deposit_into_existing(
&rewarded,
T::SurchargeReward::get().min(rent_payed),
@@ -698,15 +732,15 @@ where
}
pub fn rent_projection(address: T::AccountId) -> RentProjectionResult<T::BlockNumber> {
Rent::<T>::compute_projection(&address)
Rent::<T, PrefabWasmModule<T>>::compute_projection(&address)
}
/// Put code for benchmarks which does not check or instrument the code.
/// Store code for benchmarks which does not check nor instrument the code.
#[cfg(feature = "runtime-benchmarks")]
pub fn put_code_raw(code: Vec<u8>) -> DispatchResult {
pub fn store_code_raw(code: Vec<u8>) -> DispatchResult {
let schedule = <Module<T>>::current_schedule();
let result = wasm::save_code_raw::<T>(code, &schedule);
result.map(|_| ()).map_err(Into::into)
PrefabWasmModule::store_code_unchecked(code, &schedule)?;
Ok(())
}
/// Determine the address of a contract,
@@ -739,12 +773,13 @@ where
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm<T>, WasmLoader<T>>, &mut GasMeter<T>) -> ExecResult,
func: impl FnOnce(
&mut ExecutionContext<T, PrefabWasmModule<T>>,
&mut GasMeter<T>,
) -> ExecResult,
) -> ExecResult {
let cfg = ConfigCache::preload();
let vm = WasmVm::new(&cfg.schedule);
let loader = WasmLoader::new(&cfg.schedule);
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
let mut ctx = ExecutionContext::top_level(origin, &cfg);
func(&mut ctx, gas_meter)
}
}
@@ -807,6 +842,12 @@ decl_event! {
/// - `data`: Data supplied by the contract. Metadata generated during contract
/// compilation is needed to decode it.
ContractEmitted(AccountId, Vec<u8>),
/// A code with the specified hash was removed.
/// \[code_hash\]
///
/// This happens when the last contract that uses this code hash was removed or evicted.
CodeRemoved(Hash),
}
}
@@ -820,7 +861,7 @@ decl_storage! {
/// A mapping from an original code hash to the original code, untouched by instrumentation.
pub PristineCode: map hasher(identity) CodeHash<T> => Option<Vec<u8>>;
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
pub CodeStorage: map hasher(identity) CodeHash<T> => Option<wasm::PrefabWasmModule>;
pub CodeStorage: map hasher(identity) CodeHash<T> => Option<PrefabWasmModule<T>>;
/// The subtrie counter.
pub AccountCounter: u64 = 0;
/// The code associated with a given account.