mod dimension;
mod limits;
mod model;
mod util;
mod wasmi_helper;
pub(crate) use limits::DepthLimiter;
pub use limits::{DEFAULT_HOST_DEPTH_LIMIT, DEFAULT_XDR_RW_LIMITS};
pub use model::COST_MODEL_LIN_TERM_SCALE_BITS;
use std::{
cell::{RefCell, RefMut},
fmt::{Debug, Display},
rc::Rc,
};
use crate::{
host::error::TryBorrowOrErr,
xdr::{ContractCostParams, ContractCostType, ScErrorCode, ScErrorType},
Error, Host, HostError,
};
use dimension::{BudgetDimension, IsCpu, IsShadowMode};
use model::ScaledU64;
use wasmi_helper::FuelConfig;
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct CostTracker {
pub iterations: u64,
pub inputs: Option<u64>,
pub cpu: u64,
pub mem: u64,
}
#[derive(Clone)]
struct BudgetTracker {
cost_tracker: [CostTracker; ContractCostType::variants().len()],
meter_count: u32,
#[cfg(any(test, feature = "testutils", feature = "bench"))]
wasm_memory: u64,
time_tracker: [u64; ContractCostType::variants().len()],
}
impl Default for BudgetTracker {
fn default() -> Self {
let mut mt = Self {
cost_tracker: Default::default(),
meter_count: Default::default(),
#[cfg(any(test, feature = "testutils", feature = "bench"))]
wasm_memory: Default::default(),
time_tracker: Default::default(),
};
for (ct, tracker) in ContractCostType::variants()
.iter()
.zip(mt.cost_tracker.iter_mut())
{
let mut init_input = || tracker.inputs = Some(0);
match ct {
ContractCostType::WasmInsnExec => (),
ContractCostType::MemAlloc => init_input(), ContractCostType::MemCpy => init_input(), ContractCostType::MemCmp => init_input(), ContractCostType::DispatchHostFunction => (),
ContractCostType::VisitObject => (),
ContractCostType::ValSer => init_input(), ContractCostType::ValDeser => init_input(), ContractCostType::ComputeSha256Hash => init_input(), ContractCostType::ComputeEd25519PubKey => (),
ContractCostType::VerifyEd25519Sig => init_input(), ContractCostType::VmInstantiation => init_input(), ContractCostType::VmCachedInstantiation => init_input(), ContractCostType::InvokeVmFunction => (),
ContractCostType::ComputeKeccak256Hash => init_input(), ContractCostType::ComputeEcdsaSecp256k1Sig => (),
ContractCostType::RecoverEcdsaSecp256k1Key => (),
ContractCostType::Int256AddSub => (),
ContractCostType::Int256Mul => (),
ContractCostType::Int256Div => (),
ContractCostType::Int256Pow => (),
ContractCostType::Int256Shift => (),
ContractCostType::ChaCha20DrawBytes => init_input(), }
}
mt
}
}
impl BudgetTracker {
#[cfg(any(test, feature = "testutils", feature = "bench"))]
fn reset(&mut self) {
self.meter_count = 0;
for tracker in &mut self.cost_tracker {
tracker.iterations = 0;
tracker.inputs = tracker.inputs.map(|_| 0);
tracker.cpu = 0;
tracker.mem = 0;
}
self.wasm_memory = 0;
}
fn track_time(&mut self, ty: ContractCostType, duration: u64) -> Result<(), HostError> {
let t = self.time_tracker.get_mut(ty as usize).ok_or_else(|| {
HostError::from(Error::from_type_and_code(
ScErrorType::Budget,
ScErrorCode::InternalError,
))
})?;
*t += duration;
Ok(())
}
fn get_time(&self, ty: ContractCostType) -> Result<u64, HostError> {
self.time_tracker
.get(ty as usize)
.map(|t| *t)
.ok_or_else(|| (ScErrorType::Budget, ScErrorCode::InternalError).into())
}
}
#[derive(Clone)]
pub(crate) struct BudgetImpl {
cpu_insns: BudgetDimension,
mem_bytes: BudgetDimension,
tracker: BudgetTracker,
is_in_shadow_mode: bool,
fuel_config: FuelConfig,
depth_limit: u32,
}
impl BudgetImpl {
fn try_from_configs(
cpu_limit: u64,
mem_limit: u64,
cpu_cost_params: ContractCostParams,
mem_cost_params: ContractCostParams,
) -> Result<Self, HostError> {
let mut b = Self {
cpu_insns: BudgetDimension::try_from_config(cpu_cost_params)?,
mem_bytes: BudgetDimension::try_from_config(mem_cost_params)?,
tracker: Default::default(),
is_in_shadow_mode: false,
fuel_config: Default::default(),
depth_limit: DEFAULT_HOST_DEPTH_LIMIT,
};
b.cpu_insns.reset(cpu_limit);
b.mem_bytes.reset(mem_limit);
Ok(b)
}
pub fn charge(
&mut self,
ty: ContractCostType,
iterations: u64,
input: Option<u64>,
) -> Result<(), HostError> {
let tracker = self
.tracker
.cost_tracker
.get_mut(ty as usize)
.ok_or_else(|| HostError::from((ScErrorType::Budget, ScErrorCode::InternalError)))?;
if !self.is_in_shadow_mode {
self.tracker.meter_count = self.tracker.meter_count.saturating_add(1);
tracker.iterations = tracker.iterations.saturating_add(iterations);
match (&mut tracker.inputs, input) {
(None, None) => (),
(Some(t), Some(i)) => *t = t.saturating_add(i.saturating_mul(iterations)),
_ => return Err((ScErrorType::Budget, ScErrorCode::InternalError).into()),
};
}
let cpu_charged = self.cpu_insns.charge(
ty,
iterations,
input,
IsCpu(true),
IsShadowMode(self.is_in_shadow_mode),
)?;
if !self.is_in_shadow_mode {
tracker.cpu = tracker.cpu.saturating_add(cpu_charged);
}
self.cpu_insns
.check_budget_limit(IsShadowMode(self.is_in_shadow_mode))?;
let mem_charged = self.mem_bytes.charge(
ty,
iterations,
input,
IsCpu(false),
IsShadowMode(self.is_in_shadow_mode),
)?;
if !self.is_in_shadow_mode {
tracker.mem = tracker.mem.saturating_add(mem_charged);
}
self.mem_bytes
.check_budget_limit(IsShadowMode(self.is_in_shadow_mode))
}
fn get_wasmi_fuel_remaining(&self) -> Result<u64, HostError> {
let cpu_remaining = self.cpu_insns.get_remaining();
let Some(cost_model) = self
.cpu_insns
.get_cost_model(ContractCostType::WasmInsnExec)
else {
return Err((ScErrorType::Budget, ScErrorCode::InternalError).into());
};
let cpu_per_fuel = cost_model.const_term.max(1);
Ok(cpu_remaining.checked_div(cpu_per_fuel).unwrap_or(0))
}
}
impl Default for BudgetImpl {
fn default() -> Self {
let mut b = Self {
cpu_insns: BudgetDimension::default(),
mem_bytes: BudgetDimension::default(),
tracker: Default::default(),
is_in_shadow_mode: false,
fuel_config: Default::default(),
depth_limit: DEFAULT_HOST_DEPTH_LIMIT,
};
for ct in ContractCostType::variants() {
let Some(cpu) = &mut b.cpu_insns.get_cost_model_mut(ct) else {
continue;
};
match ct {
ContractCostType::WasmInsnExec => {
cpu.const_term = 4;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::MemAlloc => {
cpu.const_term = 434;
cpu.lin_term = ScaledU64::from_unscaled_u64(1).safe_div(8);
}
ContractCostType::MemCpy => {
cpu.const_term = 42;
cpu.lin_term = ScaledU64::from_unscaled_u64(1).safe_div(8);
}
ContractCostType::MemCmp => {
cpu.const_term = 44;
cpu.lin_term = ScaledU64::from_unscaled_u64(1).safe_div(8);
}
ContractCostType::DispatchHostFunction => {
cpu.const_term = 310;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::VisitObject => {
cpu.const_term = 61;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::ValSer => {
cpu.const_term = 230;
cpu.lin_term = ScaledU64(29);
}
ContractCostType::ValDeser => {
cpu.const_term = 59052;
cpu.lin_term = ScaledU64(4001);
}
ContractCostType::ComputeSha256Hash => {
cpu.const_term = 3738;
cpu.lin_term = ScaledU64(7012);
}
ContractCostType::ComputeEd25519PubKey => {
cpu.const_term = 40253;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::VerifyEd25519Sig => {
cpu.const_term = 377524;
cpu.lin_term = ScaledU64(4068);
}
ContractCostType::VmInstantiation => {
cpu.const_term = 451626;
cpu.lin_term = ScaledU64(45405);
}
ContractCostType::VmCachedInstantiation => {
cpu.const_term = 451626;
cpu.lin_term = ScaledU64(45405);
}
ContractCostType::InvokeVmFunction => {
cpu.const_term = 1948;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::ComputeKeccak256Hash => {
cpu.const_term = 3766;
cpu.lin_term = ScaledU64(5969);
}
ContractCostType::ComputeEcdsaSecp256k1Sig => {
cpu.const_term = 710;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::RecoverEcdsaSecp256k1Key => {
cpu.const_term = 2315295;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::Int256AddSub => {
cpu.const_term = 4404;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::Int256Mul => {
cpu.const_term = 4947;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::Int256Div => {
cpu.const_term = 4911;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::Int256Pow => {
cpu.const_term = 4286;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::Int256Shift => {
cpu.const_term = 913;
cpu.lin_term = ScaledU64(0);
}
ContractCostType::ChaCha20DrawBytes => {
cpu.const_term = 1058;
cpu.lin_term = ScaledU64(501);
}
}
let Some(mem) = b.mem_bytes.get_cost_model_mut(ct) else {
continue;
};
match ct {
ContractCostType::WasmInsnExec => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::MemAlloc => {
mem.const_term = 16;
mem.lin_term = ScaledU64::from_unscaled_u64(1);
}
ContractCostType::MemCpy => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::MemCmp => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::DispatchHostFunction => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::VisitObject => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::ValSer => {
mem.const_term = 242;
mem.lin_term = ScaledU64::from_unscaled_u64(3);
}
ContractCostType::ValDeser => {
mem.const_term = 0;
mem.lin_term = ScaledU64::from_unscaled_u64(3);
}
ContractCostType::ComputeSha256Hash => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::ComputeEd25519PubKey => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::VerifyEd25519Sig => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::VmInstantiation => {
mem.const_term = 130065;
mem.lin_term = ScaledU64(5064);
}
ContractCostType::VmCachedInstantiation => {
mem.const_term = 130065;
mem.lin_term = ScaledU64(5064);
}
ContractCostType::InvokeVmFunction => {
mem.const_term = 14;
mem.lin_term = ScaledU64(0);
}
ContractCostType::ComputeKeccak256Hash => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::ComputeEcdsaSecp256k1Sig => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
ContractCostType::RecoverEcdsaSecp256k1Key => {
mem.const_term = 181;
mem.lin_term = ScaledU64(0);
}
ContractCostType::Int256AddSub => {
mem.const_term = 99;
mem.lin_term = ScaledU64(0);
}
ContractCostType::Int256Mul => {
mem.const_term = 99;
mem.lin_term = ScaledU64(0);
}
ContractCostType::Int256Div => {
mem.const_term = 99;
mem.lin_term = ScaledU64(0);
}
ContractCostType::Int256Pow => {
mem.const_term = 99;
mem.lin_term = ScaledU64(0);
}
ContractCostType::Int256Shift => {
mem.const_term = 99;
mem.lin_term = ScaledU64(0);
}
ContractCostType::ChaCha20DrawBytes => {
mem.const_term = 0;
mem.lin_term = ScaledU64(0);
}
}
}
b.cpu_insns.reset(limits::DEFAULT_CPU_INSN_LIMIT);
b.mem_bytes.reset(limits::DEFAULT_MEM_BYTES_LIMIT);
b
}
}
impl Debug for BudgetImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{:=<165}", "")?;
writeln!(
f,
"Cpu limit: {}; used: {}",
self.cpu_insns.limit, self.cpu_insns.total_count
)?;
writeln!(
f,
"Mem limit: {}; used: {}",
self.mem_bytes.limit, self.mem_bytes.total_count
)?;
writeln!(f, "{:=<165}", "")?;
writeln!(
f,
"{:<25}{:<15}{:<15}{:<15}{:<15}{:<20}{:<20}{:<20}{:<20}",
"CostType",
"iterations",
"input",
"cpu_insns",
"mem_bytes",
"const_term_cpu",
"lin_term_cpu",
"const_term_mem",
"lin_term_mem",
)?;
for ct in ContractCostType::variants() {
let i = ct as usize;
writeln!(
f,
"{:<25}{:<15}{:<15}{:<15}{:<15}{:<20}{:<20}{:<20}{:<20}",
format!("{:?}", ct),
self.tracker.cost_tracker[i].iterations,
format!("{:?}", self.tracker.cost_tracker[i].inputs),
self.tracker.cost_tracker[i].cpu,
self.tracker.cost_tracker[i].mem,
self.cpu_insns.cost_models[i].const_term,
format!("{}", self.cpu_insns.cost_models[i].lin_term),
self.mem_bytes.cost_models[i].const_term,
format!("{}", self.mem_bytes.cost_models[i].lin_term),
)?;
}
writeln!(f, "{:=<165}", "")?;
writeln!(
f,
"Internal details (diagnostics info, does not affect fees) "
)?;
writeln!(
f,
"Total # times meter was called: {}",
self.tracker.meter_count,
)?;
writeln!(
f,
"Shadow cpu limit: {}; used: {}",
self.cpu_insns.shadow_limit, self.cpu_insns.shadow_total_count
)?;
writeln!(
f,
"Shadow mem limit: {}; used: {}",
self.mem_bytes.shadow_limit, self.mem_bytes.shadow_total_count
)?;
writeln!(f, "{:=<165}", "")?;
Ok(())
}
}
impl Display for BudgetImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{:=<55}", "")?;
writeln!(
f,
"Cpu limit: {}; used: {}",
self.cpu_insns.limit, self.cpu_insns.total_count
)?;
writeln!(
f,
"Mem limit: {}; used: {}",
self.mem_bytes.limit, self.mem_bytes.total_count
)?;
writeln!(f, "{:=<55}", "")?;
writeln!(
f,
"{:<25}{:<15}{:<15}",
"CostType", "cpu_insns", "mem_bytes",
)?;
for ct in ContractCostType::variants() {
let i = ct as usize;
writeln!(
f,
"{:<25}{:<15}{:<15}",
format!("{:?}", ct),
self.tracker.cost_tracker[i].cpu,
self.tracker.cost_tracker[i].mem,
)?;
}
writeln!(f, "{:=<55}", "")?;
Ok(())
}
}
#[derive(Clone)]
pub struct Budget(pub(crate) Rc<RefCell<BudgetImpl>>);
#[allow(clippy::derivable_impls)]
impl Default for Budget {
fn default() -> Self {
#[cfg(all(not(target_family = "wasm"), feature = "tracy"))]
let _client = tracy_client::Client::start();
Self(Default::default())
}
}
impl Debug for Budget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{:?}", self.0.try_borrow().map_err(|_| std::fmt::Error)?)
}
}
impl Display for Budget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.0.try_borrow().map_err(|_| std::fmt::Error)?)
}
}
pub trait AsBudget: Clone {
fn as_budget(&self) -> &Budget;
}
impl AsBudget for Budget {
fn as_budget(&self) -> &Budget {
self
}
}
impl AsBudget for &Budget {
fn as_budget(&self) -> &Budget {
self
}
}
impl AsBudget for Host {
fn as_budget(&self) -> &Budget {
self.budget_ref()
}
}
impl AsBudget for &Host {
fn as_budget(&self) -> &Budget {
self.budget_ref()
}
}
impl Budget {
pub fn try_from_configs(
cpu_limit: u64,
mem_limit: u64,
cpu_cost_params: ContractCostParams,
mem_cost_params: ContractCostParams,
) -> Result<Self, HostError> {
Ok(Self(Rc::new(RefCell::new(BudgetImpl::try_from_configs(
cpu_limit,
mem_limit,
cpu_cost_params,
mem_cost_params,
)?))))
}
fn with_mut_budget<T, F>(&self, f: F) -> Result<T, HostError>
where
F: FnOnce(RefMut<BudgetImpl>) -> Result<T, HostError>,
{
f(self.0.try_borrow_mut_or_err()?)
}
pub fn bulk_charge(
&self,
ty: ContractCostType,
iterations: u64,
input: Option<u64>,
) -> Result<(), HostError> {
self.0
.try_borrow_mut_or_err()?
.charge(ty, iterations, input)
}
pub fn charge(&self, ty: ContractCostType, input: Option<u64>) -> Result<(), HostError> {
self.0.try_borrow_mut_or_err()?.charge(ty, 1, input)
}
pub(crate) fn with_shadow_mode<T, F>(&self, f: F)
where
F: FnOnce() -> Result<T, HostError>,
{
let mut prev = false;
if self
.with_mut_budget(|mut b| {
prev = b.is_in_shadow_mode;
b.is_in_shadow_mode = true;
b.cpu_insns.check_budget_limit(IsShadowMode(true))?;
b.mem_bytes.check_budget_limit(IsShadowMode(true))
})
.is_ok()
{
let _ = f();
}
let _ = self.with_mut_budget(|mut b| {
b.is_in_shadow_mode = prev;
Ok(())
});
}
pub(crate) fn is_in_shadow_mode(&self) -> Result<bool, HostError> {
Ok(self.0.try_borrow_or_err()?.is_in_shadow_mode)
}
pub(crate) fn set_shadow_limits(&self, cpu: u64, mem: u64) -> Result<(), HostError> {
self.0.try_borrow_mut_or_err()?.cpu_insns.shadow_limit = cpu;
self.0.try_borrow_mut_or_err()?.mem_bytes.shadow_limit = mem;
Ok(())
}
pub fn get_tracker(&self, ty: ContractCostType) -> Result<CostTracker, HostError> {
self.0
.try_borrow_or_err()?
.tracker
.cost_tracker
.get(ty as usize)
.map(|x| x.clone())
.ok_or_else(|| (ScErrorType::Budget, ScErrorCode::InternalError).into())
}
pub fn get_time(&self, ty: ContractCostType) -> Result<u64, HostError> {
self.0.try_borrow_or_err()?.tracker.get_time(ty)
}
pub fn track_time(&self, ty: ContractCostType, duration: u64) -> Result<(), HostError> {
self.0
.try_borrow_mut_or_err()?
.tracker
.track_time(ty, duration)
}
pub fn get_cpu_insns_consumed(&self) -> Result<u64, HostError> {
Ok(self.0.try_borrow_or_err()?.cpu_insns.get_total_count())
}
pub fn get_mem_bytes_consumed(&self) -> Result<u64, HostError> {
Ok(self.0.try_borrow_or_err()?.mem_bytes.get_total_count())
}
pub fn get_cpu_insns_remaining(&self) -> Result<u64, HostError> {
Ok(self.0.try_borrow_or_err()?.cpu_insns.get_remaining())
}
pub fn get_mem_bytes_remaining(&self) -> Result<u64, HostError> {
Ok(self.0.try_borrow_or_err()?.mem_bytes.get_remaining())
}
pub(crate) fn get_wasmi_fuel_remaining(&self) -> Result<u64, HostError> {
self.0.try_borrow_mut_or_err()?.get_wasmi_fuel_remaining()
}
pub(crate) fn wasmi_fuel_costs(&self) -> Result<wasmi::FuelCosts, HostError> {
let config = &self.0.try_borrow_or_err()?.fuel_config;
let mut costs = wasmi::FuelCosts::default();
costs.base = config.base;
costs.entity = config.entity;
costs.load = config.load;
costs.store = config.store;
costs.call = config.call;
Ok(costs)
}
}