Contracts Pallet
WebAssembly
Minimal example
fn factorial(n: u64) -> u64 {
if n == 0 {
1
} else {
n * factorial(n - 1)
}
}
(func (param i64) (result i64)
local.get 0 ;; stack[0] := n
i64.eqz ;; stack[0] := stack[0] == 0 ? 1 : 0
if (result i64)
i64.const 1 ;; stack[0] := 1
else
local.get 0 ;; stack[0] := n
local.get 0 ;; stack[0] := n
i64.const 1 ;; stack[0] := 1
i64.sub ;; stack[0] := stack[1] - stack[0]
call 0 ;; stack[0] := factorial(stack[0])
i64.mul ;; stack[0] := stack[1] * stack[0]
end
)
Wat Syntax Sugar
(func (param i64) (result i64)
local.get 0
i64.eqz
if (result i64)
i64.const 1
else
local.get 0
local.get 0
i64.const 1
i64.sub
call 0
i64.mul
end
)
(func $factorial (param $n i64) (result i64)
(if (result i64) (i64.eqz (local.get 0))
(then
(i64.const 1)
)
(else
(i64.mul
(local.get 0)
(call $factorial (i64.sub (local.get $n) (i64.const 1)))
)
)
)
)
Linear Memory
fn write_to_mem(val: u64, address: &mut u64) {
*address = val;
}
(func (param i64 i32)
(i64.store (local.get 1) (local.get 0))
)
Embedding Wasm

Anatomy of a Contract
(module
;; params: data_ptr, data_len_ptr
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "deploy")
;; execute some code on contract deployment (we do nothing)
)
(func (export "call")
;; execute some code on contract execution
(i32.store (i32.store 4) (i32.const 256)) ;; store the length of our buffer at mem[4]
(call $seal_input (i32.const 0) (i32.const 4)) ;; copy input buffer to mem[0]
)
)
Exercise: Your first contract
- You will compile a provided contract manually written in WebAssembly (wat)
- You will deploy this basic contract to a locally running node
- You will inspect the source code of this contract and try to make changes to it
Exercise instructions
(module
(import "seal0" "seal_set_storage" (func $set_storage (param i32 i32 i32)))
(import "seal0" "seal_get_storage" (func $get_storage (param i32 i32 i32) (result i32)))
(import "seal0" "seal_input" (func $input (param i32 i32)))
(import "seal0" "seal_return" (func $return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1)) ;; this gives us one page of memory (16KiB)
;; [0, 256)
;; we reserve this memory as buffer to receive data
;; [256, 260)
;; initialize memory with length of our buffer (256 in hex)
(data (i32.const 256) "\00\00\01\00")
(func (export "deploy")
;; we initialize our storage value on contract deployment to '99'
(i32.store (i32.const 0) (i32.const 99))
(call $set_storage
(i32.const 512) ;; key_ptr
(i32.const 0) ;; data_ptr
(i32.const 4) ;; data_len
)
)
(func (export "call")
;; copy data passed to contract to mem[0]-mem[256]
;; it overwrites the data_len_ptr with the actual size of the buffer
(call $input
(i32.const 0) ;; data_ptr
(i32.const 256) ;; data_len_ptr
)
;; The first four bytes are a 'selector' which is passed by a conforming client
;; according to the metadata.json.
When writing contracts with ink! this metadata
;; file is generated automatically from the source code.
The number is inversed because
;; wasm uses little endian.
;; The rest are the arguments passed.
(if (i32.eq (i32.const 0xf2017f13) (i32.load (i32.const 0)))
(then
;; this is our 'double' function
;; The arguments start after the selector (mem[4])
(i32.store
(i32.const 4) ;; overwrite value at mem[4]
(i32.mul
(i32.load (i32.const 4))
(i32.const 2)
)
)
;; we just pass '512' as key ptr which is zero initialized.
;; hence we store our value at a key with all zeros (key is 32byte)
(call $set_storage
(i32.const 512) ;; key_ptr
(i32.const 4) ;; data_ptr
(i32.const 4) ;; data_len
)
)
(else
;; this is our 'get' function
;; $get_storage returns a status code which we ignore (drop)
(drop
(call $get_storage
(i32.const 512) ;; key_ptr
(i32.const 0) ;; data_ptr
(i32.const 256) ;; data_len_ptr
)
)
;; return the value pulled from storage to the caller
(call $return
(i32.const 0) ;; flags (none needed)
(i32.const 0) ;; data_ptr
(i32.const 4) ;; data_len
)
)
)
)
)
Instructions in speaker notes, press s
Architecture of pallet-contracts

Dispatchables

RPCs

API Definition
define_env!(Env, <E: Ext>,
[seal0] gas(ctx, amount: u32) => { ... },
[seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => { ... },
[seal1] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => { ... },
[seal0] seal_clear_storage(ctx, key_ptr: u32) => { ... },
[__unstable__] seal_clear_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => { ... },
[seal1] seal_call(
ctx,
flags: u32,
callee_ptr: u32,
gas: u64,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
output_len_ptr: u32
) -> ReturnCode => { ... },
}
Configuration
#[pallet::config]
pub trait Config: frame_system::Config {
/// The generator used to supply randomness to contracts through `seal_random`.
type Randomness: Randomness<Self::Hash, Self::BlockNumber>;
/// The currency in which fees are paid and contract balances are held.
type Currency: ReservableCurrency<Self::AccountId>;
/// Filter that is applied to calls dispatched by contracts.
///
/// Use this filter to control which dispatchables are callable by contracts.
/// This is applied in **addition** to [`frame_system::Config::BaseCallFilter`].
/// It is recommended to treat this as a whitelist.
///
/// # Stability
///
/// The runtime **must** make sure that all dispatchables that are callable by
/// contracts remain stable. In addition [`Self::Call`] itself must remain stable.
/// This means that no existing variants are allowed to switch their positions.
///
/// # Note
///
/// Note that dispatchables that are called via contracts do not spawn their
/// own wasm instance for each call (as opposed to when called via a transaction).
/// Therefore please make sure to be restrictive about which dispatchables are allowed
/// in order to not introduce a new DoS vector like memory allocation patterns that can
/// be exploited to drive the runtime into a panic.
type CallFilter: Contains<<Self as frame_system::Config>::Call>;
/// Used to answer contracts' queries regarding the current weight price. This is **not**
/// used to calculate the actual fee and is only for informational purposes.
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
/// Describes the weights of the dispatchables of this module and is also used to
/// construct a default cost schedule.
type WeightInfo: WeightInfo;
/// Type that allows the runtime authors to add new host functions for a contract to call.
type ChainExtension: chain_extension::ChainExtension<Self>;
/// Cost schedule and limits.
#[pallet::constant]
type Schedule: Get<Schedule<Self>>;
/// The type of the call stack determines the maximum nesting depth of contract calls.
///
/// The allowed depth is `CallStack::size() + 1`.
/// Therefore a size of `0` means that a contract cannot use call or instantiate.
/// In other words only the origin called "root contract" is allowed to execute then.
type CallStack: smallvec::Array<Item = Frame<Self>>;
/// The maximum number of contracts that can be pending for deletion.
///
/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible
/// immediately, but the deletion of the storage items it has accumulated is performed
/// later. The contract is put into the deletion queue. This defines how many
/// contracts can be queued up at the same time. If that limit is reached `seal_terminate`
/// will fail. The action must be retried in a later block in that case.
///
/// The reasons for limiting the queue depth are:
///
/// 1. The queue is in storage in order to be persistent between blocks. We want to limit
/// the amount of storage that can be consumed.
/// 2. The queue is stored in a vector and needs to be decoded as a whole when reading
/// it at the end of each block. Longer queues take more weight to decode and hence
/// limit the amount of items that can be deleted per block.
#[pallet::constant]
type DeletionQueueDepth: Get<u32>;
/// The maximum amount of weight that can be consumed per block for lazy trie removal.
///
/// The amount of weight that is dedicated per block to work on the deletion queue. Larger
/// values allow more trie keys to be deleted in each block but reduce the amount of
/// weight that is left for transactions. See [`Self::DeletionQueueDepth`] for more
/// information about the deletion queue.
#[pallet::constant]
type DeletionWeightLimit: Get<Weight>;
/// The amount of balance a caller has to pay for each byte of storage.
///
/// # Note
///
/// Changing this value for an existing chain might need a storage migration.
#[pallet::constant]
type DepositPerByte: Get<BalanceOf<Self>>;
/// The weight per byte of code that is charged when loading a contract from storage.
///
/// Currently, FRAME only charges fees for computation incurred but not for PoV
/// consumption caused for storage access. This is usually not exploitable because
/// accessing storage carries some substantial weight costs, too. However in case
/// of contract code very much PoV consumption can be caused while consuming very little
/// computation. This could be used to keep the chain busy without paying the
/// proper fee for it. Until this is resolved we charge from the weight meter for
/// contract access.
///
/// For more information check out: <https://github.com/paritytech/substrate/issues/10301>
///
/// [`DefaultContractAccessWeight`] is a safe default to be used for Polkadot or Kusama
/// parachains.
///
/// # Note
///
/// This is only relevant for parachains. Set to zero in case of a standalone chain.
#[pallet::constant]
type ContractAccessWeight: Get<Weight>;
/// The amount of balance a caller has to pay for each storage item.
///
/// # Note
///
/// Changing this value for an existing chain might need a storage migration.
#[pallet::constant]
type DepositPerItem: Get<BalanceOf<Self>>;
/// The address generator used to generate the addresses of contracts.
type AddressGenerator: AddressGenerator<Self>;
/// The maximum length of a contract code in bytes. This limit applies to the instrumented
/// version of the code. Therefore `instantiate_with_code` can fail even when supplying
/// a wasm binary below this maximum size.
type MaxCodeLen: Get<u32>;
/// The maximum length of a contract code after reinstrumentation.
///
/// When uploading a new contract the size defined by [`Self::MaxCodeLen`] is used for both
/// the pristine **and** the instrumented version. When a existing contract needs to be
/// reinstrumented after a runtime upgrade we apply this bound. The reason is that if the
/// new instrumentation increases the size beyond the limit it would make that contract
/// inaccessible until rectified by another runtime upgrade.
type RelaxedMaxCodeLen: Get<u32>;
/// The maximum allowable length in bytes for storage keys.
type MaxStorageKeyLen: Get<u32>;
}
Gas / Weight
#[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))]
pub fn call(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] value: BalanceOf<T>,
#[pallet::compact] gas_limit: Weight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo
Execution Engine

pallet-contracts uses wasmi for now

Making gas metering
independent of the executor

Making gas metering
independent of the executor
(module
(import "env" "gas" (func $gas (param i32)))
(func $factorial (param $n i64) (result i64)
(call $gas
(i32.const 3)
)
(if (result i64) (i64.eqz (local.get 0))
(then
(call $gas
(i32.const 1)
)
(i64.const 1)
)
(else
(call $gas
(i32.const 6)
)
(i64.mul
(local.get 0)
(call $factorial (i64.sub (local.get $n) (i64.const 1)))
)
)
)
)
)
Storage Bloat

Storage on Ethereum

First try: Storage rent

What we have now:
Automatic storage deposits

Contracts on parachains

Execution Engine


Code sizes matter!

Shrink contract sizes

Prevent size regressions

Code merkelization







Many contract calls use only a small part of the code (e.g getter calls)
Wouldn't it be nice to only transmit parts of the contract that are actually executed?
On deployment merkelize the contract in-memory by using each function as a node
Could we split on other boundaries?
The trie root of the contract is persisted into the main state trie
Block build pulls whole contract from state and tracks which functions are executed
Transmit witnesses to the validator
The validator reconstructs a wasm module that only contains executed functions from the witnesses
Next steps / References
- Inspect the pallet-contracts code
- Have a look at the pallet-contracts issue board and pick something up if you feel up for it
- Check out the supporting crates for
pallet-contracts
: wasm-instrument wasmi - The spec is a good place to learn about wasm and to look up specifics