Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
contracts: Implement refcounting for wasm code
Browse files Browse the repository at this point in the history
  • Loading branch information
athei committed Jan 21, 2021
1 parent bd5c9a6 commit 67f4949
Show file tree
Hide file tree
Showing 14 changed files with 1,370 additions and 1,119 deletions.
12 changes: 3 additions & 9 deletions bin/node/executor/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,23 +602,17 @@ fn deploying_wasm_contract_should_work() {
CheckedExtrinsic {
signed: Some((charlie(), signed_extra(0, 0))),
function: Call::Contracts(
pallet_contracts::Call::put_code::<Runtime>(transfer_code)
),
},
CheckedExtrinsic {
signed: Some((charlie(), signed_extra(1, 0))),
function: Call::Contracts(
pallet_contracts::Call::instantiate::<Runtime>(
pallet_contracts::Call::instantiate_with_code::<Runtime>(
1 * DOLLARS + subsistence,
500_000_000,
transfer_ch,
transfer_code,
Vec::new(),
Vec::new(),
)
),
},
CheckedExtrinsic {
signed: Some((charlie(), signed_extra(2, 0))),
signed: Some((charlie(), signed_extra(1, 0))),
function: Call::Contracts(
pallet_contracts::Call::call::<Runtime>(
sp_runtime::MultiAddress::Id(addr.clone()),
Expand Down
10 changes: 3 additions & 7 deletions frame/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,19 @@ fails, A can decide how to handle that failure, either proceeding or reverting A

### 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.
* `call` - Makes a call to an account, optionally transferring some balance.
Those are documented in the reference documentation of the `Module`.

## Usage

The Contract module is a work in progress. The following examples show how this Contract module
can be used to instantiate and call contracts.

* [`ink`](https://github.com/paritytech/ink) is
- [`ink`](https://github.com/paritytech/ink) is
an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables writing
WebAssembly based smart contracts in the Rust programming language. This is a work in progress.

## Related Modules

* [Balances](https://docs.rs/pallet-balances/latest/pallet_balances/)
- [Balances](https://docs.rs/pallet-balances/latest/pallet_balances/)

License: Apache-2.0
11 changes: 6 additions & 5 deletions frame/contracts/src/benchmarking/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub struct ImportedFunction {
pub return_type: Option<ValueType>,
}

/// A wasm module ready to be put on chain with `put_code`.
/// A wasm module ready to be put on chain.
#[derive(Clone)]
pub struct WasmModule<T:Config> {
pub code: Vec<u8>,
Expand Down Expand Up @@ -245,16 +245,16 @@ where
}

/// Creates a wasm module of `target_bytes` size. Used to benchmark the performance of
/// `put_code` for different sizes of wasm modules. The generated module maximizes
/// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes
/// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.
pub fn sized(target_bytes: u32) -> Self {
use parity_wasm::elements::Instruction::{If, I32Const, Return, End};
// Base size of a contract is 47 bytes and each expansion adds 6 bytes.
// Base size of a contract is 63 bytes and each expansion adds 6 bytes.
// We do one expansion less to account for the code section and function body
// size fields inside the binary wasm module representation which are leb128 encoded
// and therefore grow in size when the contract grows. We are not allowed to overshoot
// because of the maximum code size that is enforced by `put_code`.
let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1);
// because of the maximum code size that is enforced by `instantiate_with_code`.
let expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
const EXPANSION: [Instruction; 4] = [
I32Const(0),
If(BlockType::NoResult),
Expand All @@ -263,6 +263,7 @@ where
];
ModuleDefinition {
call_body: Some(body::repeated(expansions, &EXPANSION)),
memory: Some(ImportedMemory::max::<T>()),
.. Default::default()
}
.into()
Expand Down
44 changes: 27 additions & 17 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ where
// same block number.
System::<T>::set_block_number(1u32.into());

Contracts::<T>::put_code_raw(module.code)?;
Contracts::<T>::store_code_raw(module.code)?;
Contracts::<T>::instantiate(
RawOrigin::Signed(caller.clone()).into(),
endowment,
Expand Down Expand Up @@ -314,33 +314,43 @@ benchmarks! {

// This constructs a contract that is maximal expensive to instrument.
// It creates a maximum number of metering blocks per byte.
// `n`: Size of the code in kilobytes.
put_code {
let n in 0 .. Contracts::<T>::current_schedule().limits.code_size / 1024;
// The size of the salt influences the runtime because is is hashed in order to
// determine the contract address.
// `c`: Size of the code in kilobytes.
// `s`: Size of the salt in kilobytes.
instantiate_with_code {
let c in 0 .. Contracts::<T>::current_schedule().limits.code_size / 1024;
let s in 0 .. code::max_pages::<T>() * 64;
let salt = vec![42u8; (s * 1024) as usize];
let endowment = caller_funding::<T>() / 3u32.into();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let module = WasmModule::<T>::sized(n * 1024);
let origin = RawOrigin::Signed(caller);
}: _(origin, module.code)
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c * 1024);
let origin = RawOrigin::Signed(caller.clone());
let addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
}: _(origin, endowment, Weight::max_value(), code, vec![], salt)
verify {
// endowment was removed from the caller
assert_eq!(T::Currency::free_balance(&caller), caller_funding::<T>() - endowment);
// contract has the full endowment because no rent collection happended
assert_eq!(T::Currency::free_balance(&addr), endowment);
// instantiate should leave a alive contract
Contract::<T>::address_alive_info(&addr)?;
}

// Instantiate uses a dummy contract constructor to measure the overhead of the instantiate.
// The size of the input data influences the runtime because it is hashed in order to determine
// the contract address.
// `n`: Size of the data passed to constructor in kilobytes.
// `s`: Size of the salt in kilobytes.
instantiate {
let n in 0 .. code::max_pages::<T>() * 64;
let s in 0 .. code::max_pages::<T>() * 64;
let data = vec![42u8; (n * 1024) as usize];
let salt = vec![42u8; (s * 1024) as usize];
let endowment = caller_funding::<T>() / 3u32.into();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_mem();
let origin = RawOrigin::Signed(caller.clone());
let addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
Contracts::<T>::put_code_raw(code)?;
}: _(origin, endowment, Weight::max_value(), hash, data, salt)
Contracts::<T>::store_code_raw(code)?;
}: _(origin, endowment, Weight::max_value(), hash, vec![], salt)
verify {
// endowment was removed from the caller
assert_eq!(T::Currency::free_balance(&caller), caller_funding::<T>() - endowment);
Expand Down Expand Up @@ -1369,7 +1379,7 @@ benchmarks! {
])),
.. Default::default()
});
Contracts::<T>::put_code_raw(code.code)?;
Contracts::<T>::store_code_raw(code.code)?;
Ok(code.hash)
})
.collect::<Result<Vec<_>, &'static str>>()?;
Expand Down Expand Up @@ -1492,7 +1502,7 @@ benchmarks! {
let hash = callee_code.hash.clone();
let hash_bytes = callee_code.hash.encode();
let hash_len = hash_bytes.len();
Contracts::<T>::put_code_raw(callee_code.code)?;
Contracts::<T>::store_code_raw(callee_code.code)?;
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<_>>();
Expand Down Expand Up @@ -2455,7 +2465,7 @@ mod tests {
create_test!(on_initialize_per_queue_item);

create_test!(update_schedule);
create_test!(put_code);
create_test!(instantiate_with_code);
create_test!(instantiate);
create_test!(call);
create_test!(claim_surcharge);
Expand Down
Loading

0 comments on commit 67f4949

Please sign in to comment.