use crate::{Config, weights::WeightInfo};
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
use noble_contracts_proc_macro::{ScheduleDebug, WeightDebug};
use fabric_support::weights::Weight;
use tetcore_std::{marker::PhantomData, vec::Vec};
use codec::{Encode, Decode};
use tetsy_wasm::elements;
use twasm_utils::rules;
use tp_runtime::RuntimeDebug;
pub const API_BENCHMARK_BATCH_SIZE: u32 = 100;
pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 1_000;
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug)]
pub struct Schedule<T: Config> {
pub version: u32,
pub enable_println: bool,
pub limits: Limits,
pub instruction_weights: InstructionWeights<T>,
pub host_fn_weights: HostFnWeights<T>,
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct Limits {
pub event_topics: u32,
pub stack_height: u32,
pub globals: u32,
pub parameters: u32,
pub memory_pages: u32,
pub table_size: u32,
pub br_table_size: u32,
pub subject_len: u32,
pub code_size: u32,
}
impl Limits {
pub fn max_memory_size(&self) -> u32 {
self.memory_pages * 64 * 1024
}
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, WeightDebug)]
pub struct InstructionWeights<T: Config> {
pub i64const: u32,
pub i64load: u32,
pub i64store: u32,
pub select: u32,
pub r#if: u32,
pub br: u32,
pub br_if: u32,
pub br_table: u32,
pub br_table_per_entry: u32,
pub call: u32,
pub call_indirect: u32,
pub call_indirect_per_param: u32,
pub local_get: u32,
pub local_set: u32,
pub local_tee: u32,
pub global_get: u32,
pub global_set: u32,
pub memory_current: u32,
pub memory_grow: u32,
pub i64clz: u32,
pub i64ctz: u32,
pub i64popcnt: u32,
pub i64eqz: u32,
pub i64extendsi32: u32,
pub i64extendui32: u32,
pub i32wrapi64: u32,
pub i64eq: u32,
pub i64ne: u32,
pub i64lts: u32,
pub i64ltu: u32,
pub i64gts: u32,
pub i64gtu: u32,
pub i64les: u32,
pub i64leu: u32,
pub i64ges: u32,
pub i64geu: u32,
pub i64add: u32,
pub i64sub: u32,
pub i64mul: u32,
pub i64divs: u32,
pub i64divu: u32,
pub i64rems: u32,
pub i64remu: u32,
pub i64and: u32,
pub i64or: u32,
pub i64xor: u32,
pub i64shl: u32,
pub i64shrs: u32,
pub i64shru: u32,
pub i64rotl: u32,
pub i64rotr: u32,
pub _phantom: PhantomData<T>,
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, WeightDebug)]
pub struct HostFnWeights<T: Config> {
pub caller: Weight,
pub address: Weight,
pub gas_left: Weight,
pub balance: Weight,
pub value_transferred: Weight,
pub minimum_balance: Weight,
pub tombstone_deposit: Weight,
pub rent_allowance: Weight,
pub block_number: Weight,
pub now: Weight,
pub weight_to_fee: Weight,
pub gas: Weight,
pub input: Weight,
pub input_per_byte: Weight,
pub r#return: Weight,
pub return_per_byte: Weight,
pub terminate: Weight,
pub restore_to: Weight,
pub restore_to_per_delta: Weight,
pub random: Weight,
pub deposit_event: Weight,
pub deposit_event_per_topic: Weight,
pub deposit_event_per_byte: Weight,
pub set_rent_allowance: Weight,
pub set_storage: Weight,
pub set_storage_per_byte: Weight,
pub clear_storage: Weight,
pub get_storage: Weight,
pub get_storage_per_byte: Weight,
pub transfer: Weight,
pub call: Weight,
pub call_transfer_surcharge: Weight,
pub call_per_input_byte: Weight,
pub call_per_output_byte: Weight,
pub instantiate: Weight,
pub instantiate_per_input_byte: Weight,
pub instantiate_per_output_byte: Weight,
pub instantiate_per_salt_byte: Weight,
pub hash_sha2_256: Weight,
pub hash_sha2_256_per_byte: Weight,
pub hash_keccak_256: Weight,
pub hash_keccak_256_per_byte: Weight,
pub hash_blake2_256: Weight,
pub hash_blake2_256_per_byte: Weight,
pub hash_blake2_128: Weight,
pub hash_blake2_128_per_byte: Weight,
pub _phantom: PhantomData<T>
}
macro_rules! replace_token {
($_in:tt $replacement:tt) => { $replacement };
}
macro_rules! call_zero {
($name:ident, $( $arg:expr ),*) => {
T::WeightInfo::$name($( replace_token!($arg 0) ),*)
};
}
macro_rules! cost_args {
($name:ident, $( $arg: expr ),+) => {
(T::WeightInfo::$name($( $arg ),+).saturating_sub(call_zero!($name, $( $arg ),+)))
}
}
macro_rules! cost_batched_args {
($name:ident, $( $arg: expr ),+) => {
cost_args!($name, $( $arg ),+) / Weight::from(API_BENCHMARK_BATCH_SIZE)
}
}
macro_rules! cost_instr_no_params_with_batch_size {
($name:ident, $batch_size:expr) => {
(cost_args!($name, 1) / Weight::from($batch_size)) as u32
}
}
macro_rules! cost_instr_with_batch_size {
($name:ident, $num_params:expr, $batch_size:expr) => {
cost_instr_no_params_with_batch_size!($name, $batch_size)
.saturating_sub((cost_instr_no_params_with_batch_size!(instr_i64const, $batch_size) / 2).saturating_mul($num_params))
}
}
macro_rules! cost_instr {
($name:ident, $num_params:expr) => {
cost_instr_with_batch_size!($name, $num_params, INSTR_BENCHMARK_BATCH_SIZE)
}
}
macro_rules! cost_byte_args {
($name:ident, $( $arg: expr ),+) => {
cost_args!($name, $( $arg ),+) / 1024
}
}
macro_rules! cost_byte_batched_args {
($name:ident, $( $arg: expr ),+) => {
cost_batched_args!($name, $( $arg ),+) / 1024
}
}
macro_rules! cost {
($name:ident) => {
cost_args!($name, 1)
}
}
macro_rules! cost_batched {
($name:ident) => {
cost_batched_args!($name, 1)
}
}
macro_rules! cost_byte {
($name:ident) => {
cost_byte_args!($name, 1)
}
}
macro_rules! cost_byte_batched {
($name:ident) => {
cost_byte_batched_args!($name, 1)
}
}
impl<T: Config> Default for Schedule<T> {
fn default() -> Self {
Self {
version: 0,
enable_println: false,
limits: Default::default(),
instruction_weights: Default::default(),
host_fn_weights: Default::default(),
}
}
}
impl Default for Limits {
fn default() -> Self {
Self {
event_topics: 4,
stack_height: 512,
globals: 256,
parameters: 128,
memory_pages: 16,
table_size: 4096,
br_table_size: 256,
subject_len: 32,
code_size: 512 * 1024,
}
}
}
impl<T: Config> Default for InstructionWeights<T> {
fn default() -> Self {
let max_pages = Limits::default().memory_pages;
Self {
i64const: cost_instr!(instr_i64const, 1),
i64load: cost_instr!(instr_i64load, 2),
i64store: cost_instr!(instr_i64store, 2),
select: cost_instr!(instr_select, 4),
r#if: cost_instr!(instr_if, 3),
br: cost_instr!(instr_br, 2),
br_if: cost_instr!(instr_br_if, 5),
br_table: cost_instr!(instr_br_table, 3),
br_table_per_entry: cost_instr!(instr_br_table_per_entry, 0),
call: cost_instr!(instr_call, 2),
call_indirect: cost_instr!(instr_call_indirect, 3),
call_indirect_per_param: cost_instr!(instr_call_indirect_per_param, 1),
local_get: cost_instr!(instr_local_get, 1),
local_set: cost_instr!(instr_local_set, 1),
local_tee: cost_instr!(instr_local_tee, 2),
global_get: cost_instr!(instr_global_get, 1),
global_set: cost_instr!(instr_global_set, 1),
memory_current: cost_instr!(instr_memory_current, 1),
memory_grow: cost_instr_with_batch_size!(instr_memory_grow, 1, max_pages),
i64clz: cost_instr!(instr_i64clz, 2),
i64ctz: cost_instr!(instr_i64ctz, 2),
i64popcnt: cost_instr!(instr_i64popcnt, 2),
i64eqz: cost_instr!(instr_i64eqz, 2),
i64extendsi32: cost_instr!(instr_i64extendsi32, 2),
i64extendui32: cost_instr!(instr_i64extendui32, 2),
i32wrapi64: cost_instr!(instr_i32wrapi64, 2),
i64eq: cost_instr!(instr_i64eq, 3),
i64ne: cost_instr!(instr_i64ne, 3),
i64lts: cost_instr!(instr_i64lts, 3),
i64ltu: cost_instr!(instr_i64ltu, 3),
i64gts: cost_instr!(instr_i64gts, 3),
i64gtu: cost_instr!(instr_i64gtu, 3),
i64les: cost_instr!(instr_i64les, 3),
i64leu: cost_instr!(instr_i64leu, 3),
i64ges: cost_instr!(instr_i64ges, 3),
i64geu: cost_instr!(instr_i64geu, 3),
i64add: cost_instr!(instr_i64add, 3),
i64sub: cost_instr!(instr_i64sub, 3),
i64mul: cost_instr!(instr_i64mul, 3),
i64divs: cost_instr!(instr_i64divs, 3),
i64divu: cost_instr!(instr_i64divu, 3),
i64rems: cost_instr!(instr_i64rems, 3),
i64remu: cost_instr!(instr_i64remu, 3),
i64and: cost_instr!(instr_i64and, 3),
i64or: cost_instr!(instr_i64or, 3),
i64xor: cost_instr!(instr_i64xor, 3),
i64shl: cost_instr!(instr_i64shl, 3),
i64shrs: cost_instr!(instr_i64shrs, 3),
i64shru: cost_instr!(instr_i64shru, 3),
i64rotl: cost_instr!(instr_i64rotl, 3),
i64rotr: cost_instr!(instr_i64rotr, 3),
_phantom: PhantomData,
}
}
}
impl<T: Config> Default for HostFnWeights<T> {
fn default() -> Self {
Self {
caller: cost_batched!(seal_caller),
address: cost_batched!(seal_address),
gas_left: cost_batched!(seal_gas_left),
balance: cost_batched!(seal_balance),
value_transferred: cost_batched!(seal_value_transferred),
minimum_balance: cost_batched!(seal_minimum_balance),
tombstone_deposit: cost_batched!(seal_tombstone_deposit),
rent_allowance: cost_batched!(seal_rent_allowance),
block_number: cost_batched!(seal_block_number),
now: cost_batched!(seal_now),
weight_to_fee: cost_batched!(seal_weight_to_fee),
gas: cost_batched!(seal_gas),
input: cost!(seal_input),
input_per_byte: cost_byte!(seal_input_per_kb),
r#return: cost!(seal_return),
return_per_byte: cost_byte!(seal_return_per_kb),
terminate: cost!(seal_terminate),
restore_to: cost!(seal_restore_to),
restore_to_per_delta: cost_batched!(seal_restore_to_per_delta),
random: cost_batched!(seal_random),
deposit_event: cost_batched!(seal_deposit_event),
deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0),
deposit_event_per_byte: cost_byte_batched_args!(seal_deposit_event_per_topic_and_kb, 0, 1),
set_rent_allowance: cost_batched!(seal_set_rent_allowance),
set_storage: cost_batched!(seal_set_storage),
set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb),
clear_storage: cost_batched!(seal_clear_storage),
get_storage: cost_batched!(seal_get_storage),
get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb),
transfer: cost_batched!(seal_transfer),
call: cost_batched!(seal_call),
call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0),
call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0),
call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1),
instantiate: cost_batched!(seal_instantiate),
instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 1, 0, 0),
instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 1, 0),
instantiate_per_salt_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 0, 1),
hash_sha2_256: cost_batched!(seal_hash_sha2_256),
hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb),
hash_keccak_256: cost_batched!(seal_hash_keccak_256),
hash_keccak_256_per_byte: cost_byte_batched!(seal_hash_keccak_256_per_kb),
hash_blake2_256: cost_batched!(seal_hash_blake2_256),
hash_blake2_256_per_byte: cost_byte_batched!(seal_hash_blake2_256_per_kb),
hash_blake2_128: cost_batched!(seal_hash_blake2_128),
hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb),
_phantom: PhantomData,
}
}
}
struct ScheduleRules<'a, T: Config> {
schedule: &'a Schedule<T>,
params: Vec<u32>,
}
impl<T: Config> Schedule<T> {
pub fn rules(&self, module: &elements::Module) -> impl rules::Rules + '_ {
ScheduleRules {
schedule: &self,
params: module
.type_section()
.iter()
.flat_map(|section| section.types())
.map(|func| {
let elements::Type::Function(func) = func;
func.params().len() as u32
})
.collect()
}
}
}
impl<'a, T: Config> rules::Rules for ScheduleRules<'a, T> {
fn instruction_cost(&self, instruction: &elements::Instruction) -> Option<u32> {
use tetsy_wasm::elements::Instruction::*;
let w = &self.schedule.instruction_weights;
let max_params = self.schedule.limits.parameters;
let weight = match *instruction {
End | Unreachable | Return | Else => 0,
I32Const(_) | I64Const(_) | Block(_) | Loop(_) | Nop | Drop => w.i64const,
I32Load(_, _) | I32Load8S(_, _) | I32Load8U(_, _) | I32Load16S(_, _) |
I32Load16U(_, _) | I64Load(_, _) | I64Load8S(_, _) | I64Load8U(_, _) |
I64Load16S(_, _) | I64Load16U(_, _) | I64Load32S(_, _) | I64Load32U(_, _)
=> w.i64load,
I32Store(_, _) | I32Store8(_, _) | I32Store16(_, _) | I64Store(_, _) |
I64Store8(_, _) | I64Store16(_, _) | I64Store32(_, _) => w.i64store,
Select => w.select,
If(_) => w.r#if,
Br(_) => w.br,
BrIf(_) => w.br_if,
Call(_) => w.call,
GetLocal(_) => w.local_get,
SetLocal(_) => w.local_set,
TeeLocal(_) => w.local_tee,
GetGlobal(_) => w.global_get,
SetGlobal(_) => w.global_set,
CurrentMemory(_) => w.memory_current,
GrowMemory(_) => w.memory_grow,
CallIndirect(idx, _) => *self.params.get(idx as usize).unwrap_or(&max_params),
BrTable(ref data) =>
w.br_table.saturating_add(
w.br_table_per_entry.saturating_mul(data.table.len() as u32)
),
I32Clz | I64Clz => w.i64clz,
I32Ctz | I64Ctz => w.i64ctz,
I32Popcnt | I64Popcnt => w.i64popcnt,
I32Eqz | I64Eqz => w.i64eqz,
I64ExtendSI32 => w.i64extendsi32,
I64ExtendUI32 => w.i64extendui32,
I32WrapI64 => w.i32wrapi64,
I32Eq | I64Eq => w.i64eq,
I32Ne | I64Ne => w.i64ne,
I32LtS | I64LtS => w.i64lts,
I32LtU | I64LtU => w.i64ltu,
I32GtS | I64GtS => w.i64gts,
I32GtU | I64GtU => w.i64gtu,
I32LeS | I64LeS => w.i64les,
I32LeU | I64LeU => w.i64leu,
I32GeS | I64GeS => w.i64ges,
I32GeU | I64GeU => w.i64geu,
I32Add | I64Add => w.i64add,
I32Sub | I64Sub => w.i64sub,
I32Mul | I64Mul => w.i64mul,
I32DivS | I64DivS => w.i64divs,
I32DivU | I64DivU => w.i64divu,
I32RemS | I64RemS => w.i64rems,
I32RemU | I64RemU => w.i64remu,
I32And | I64And => w.i64and,
I32Or | I64Or => w.i64or,
I32Xor | I64Xor => w.i64xor,
I32Shl | I64Shl => w.i64shl,
I32ShrS | I64ShrS => w.i64shrs,
I32ShrU | I64ShrU => w.i64shru,
I32Rotl | I64Rotl => w.i64rotl,
I32Rotr | I64Rotr => w.i64rotr,
_ => return None,
};
Some(weight)
}
fn memory_grow_cost(&self) -> Option<rules::MemoryGrowCost> {
None
}
}
#[cfg(test)]
mod test {
use crate::tests::Test;
use super::*;
#[test]
fn print_test_schedule() {
let schedule = Schedule::<Test>::default();
println!("{:#?}", schedule);
}
}