#![doc(html_favicon_url = "https://dev.namada.net/master/favicon.png")]
#![doc(html_logo_url = "https://dev.namada.net/master/rustdoc-logo.png")]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
#![warn(
missing_docs,
rust_2018_idioms,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_lossless,
clippy::arithmetic_side_effects
)]
pub mod event;
pub mod storage;
use std::fmt::Display;
use std::num::ParseIntError;
use std::str::FromStr;
use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use namada_core::hints;
use namada_macros::BorshDeserializer;
#[cfg(feature = "migrations")]
use namada_migrations::*;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[allow(missing_docs)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum Error {
#[error("Transaction gas exceeded the limit of {0} gas units")]
TransactionGasExceededError(WholeGas),
#[error("Block gas limit exceeded")]
BlockGasExceeded,
#[error("Overflow during gas operations")]
GasOverflow,
}
#[allow(missing_docs)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum GasParseError {
#[error("Failed to parse gas: {0}")]
Parse(ParseIntError),
#[error("Gas overflowed")]
Overflow,
}
const COMPILE_GAS_PER_BYTE_RAW: u64 = 1_664;
const WASM_CODE_VALIDATION_GAS_PER_BYTE_RAW: u64 = 59;
const WRAPPER_TX_VALIDATION_GAS_RAW: u64 = 1_526_700;
const STORAGE_OCCUPATION_GAS_PER_BYTE_RAW: u64 =
PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW * (1 + 1_000);
const PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW: u64 = 20;
const NETWORK_TRANSMISSION_GAS_PER_BYTE_RAW: u64 = 848;
const MEMORY_ACCESS_GAS_PER_BYTE_RAW: u64 = 39;
const STORAGE_ACCESS_GAS_PER_BYTE_RAW: u64 =
93 + PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW;
const STORAGE_WRITE_GAS_PER_BYTE_RAW: u64 = MEMORY_ACCESS_GAS_PER_BYTE_RAW
+ 17_583
+ STORAGE_OCCUPATION_GAS_PER_BYTE_RAW;
const STORAGE_DELETE_GAS_PER_BYTE_RAW: u64 = MEMORY_ACCESS_GAS_PER_BYTE_RAW
+ 17_583
+ PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW;
const VERIFY_TX_SIG_GAS_RAW: u64 = 435_190;
const WASM_MEMORY_PAGE_GAS_RAW: u64 =
MEMORY_ACCESS_GAS_PER_BYTE_RAW * 64 * 1_024;
const IBC_ACTION_VALIDATE_GAS_RAW: u64 = 290_935;
const IBC_ACTION_EXECUTE_GAS_RAW: u64 = 1_685_733;
const MASP_VERIFY_SIG_GAS_RAW: u64 = 1_908_750;
const MASP_FIXED_SPEND_GAS_RAW: u64 = 59_521_000;
const MASP_VARIABLE_SPEND_GAS_RAW: u64 = 9_849_000;
const MASP_FIXED_CONVERT_GAS_RAW: u64 = 46_197_000;
const MASP_VARIABLE_CONVERT_GAS_RAW: u64 = 10_245_000;
const MASP_FIXED_OUTPUT_GAS_RAW: u64 = 53_439_000;
const MASP_VARIABLE_OUTPUT_GAS_RAW: u64 = 9_710_000;
const MASP_SPEND_CHECK_GAS_RAW: u64 = 405_070;
const MASP_CONVERT_CHECK_GAS_RAW: u64 = 188_590;
const MASP_OUTPUT_CHECK_GAS_RAW: u64 = 204_430;
const MASP_FINAL_CHECK_GAS_RAW: u64 = 43;
const GAS_COST_CORRECTION: u64 = 5;
const COMPILE_GAS_PER_BYTE: u64 =
COMPILE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION / 100;
const WASM_CODE_VALIDATION_GAS_PER_BYTE: u64 =
WASM_CODE_VALIDATION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
const WRAPPER_TX_VALIDATION_GAS: u64 =
WRAPPER_TX_VALIDATION_GAS_RAW * GAS_COST_CORRECTION;
const STORAGE_OCCUPATION_GAS_PER_BYTE: u64 =
STORAGE_OCCUPATION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
const NETWORK_TRANSMISSION_GAS_PER_BYTE: u64 =
NETWORK_TRANSMISSION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
pub const MEMORY_ACCESS_GAS_PER_BYTE: u64 =
MEMORY_ACCESS_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
pub const STORAGE_ACCESS_GAS_PER_BYTE: u64 =
STORAGE_ACCESS_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
pub const STORAGE_WRITE_GAS_PER_BYTE: u64 =
STORAGE_WRITE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
pub const STORAGE_DELETE_GAS_PER_BYTE: u64 =
STORAGE_DELETE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
pub const VERIFY_TX_SIG_GAS: u64 = VERIFY_TX_SIG_GAS_RAW * GAS_COST_CORRECTION;
#[allow(clippy::cast_possible_truncation)] pub const WASM_MEMORY_PAGE_GAS: u32 =
(WASM_MEMORY_PAGE_GAS_RAW * GAS_COST_CORRECTION) as u32;
pub const IBC_ACTION_VALIDATE_GAS: u64 =
IBC_ACTION_VALIDATE_GAS_RAW * GAS_COST_CORRECTION;
pub const IBC_ACTION_EXECUTE_GAS: u64 =
IBC_ACTION_EXECUTE_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_VERIFY_SIG_GAS: u64 =
MASP_VERIFY_SIG_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_FIXED_SPEND_GAS: u64 =
MASP_FIXED_SPEND_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_VARIABLE_SPEND_GAS: u64 =
MASP_VARIABLE_SPEND_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_FIXED_CONVERT_GAS: u64 =
MASP_FIXED_CONVERT_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_VARIABLE_CONVERT_GAS: u64 =
MASP_VARIABLE_CONVERT_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_FIXED_OUTPUT_GAS: u64 =
MASP_FIXED_OUTPUT_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_VARIABLE_OUTPUT_GAS: u64 =
MASP_VARIABLE_OUTPUT_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_SPEND_CHECK_GAS: u64 =
MASP_SPEND_CHECK_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_CONVERT_CHECK_GAS: u64 =
MASP_CONVERT_CHECK_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_OUTPUT_CHECK_GAS: u64 =
MASP_OUTPUT_CHECK_GAS_RAW * GAS_COST_CORRECTION;
pub const MASP_FINAL_CHECK_GAS: u64 =
MASP_FINAL_CHECK_GAS_RAW * GAS_COST_CORRECTION;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(
Clone,
Debug,
Default,
PartialEq,
PartialOrd,
BorshDeserialize,
BorshDeserializer,
BorshSerialize,
BorshSchema,
Serialize,
Deserialize,
)]
#[must_use = "Gas must be accounted for by the gas meter"]
pub struct Gas {
sub: u64,
}
impl Gas {
pub fn checked_add(&self, rhs: Self) -> Option<Self> {
self.sub.checked_add(rhs.sub).map(|sub| Self { sub })
}
pub fn checked_sub(&self, rhs: Self) -> Option<Self> {
self.sub.checked_sub(rhs.sub).map(|sub| Self { sub })
}
pub fn checked_div(&self, rhs: u64) -> Option<Self> {
self.sub.checked_div(rhs).map(|sub| Self { sub })
}
pub fn get_whole_gas_units(&self, scale: u64) -> WholeGas {
let quotient = self
.sub
.checked_div(scale)
.expect("Gas quotient should not overflow on checked division");
if self
.sub
.checked_rem(scale)
.expect("Gas quotient remainder should not overflow")
== 0
{
quotient.into()
} else {
quotient
.checked_add(1)
.expect("Cannot overflow as the quotient is scaled down u64")
.into()
}
}
pub fn from_whole_units(whole: WholeGas, scale: u64) -> Option<Self> {
scale.checked_mul(whole.into()).map(|sub| Self { sub })
}
}
impl From<u64> for Gas {
fn from(sub: u64) -> Self {
Self { sub }
}
}
impl From<Gas> for u64 {
fn from(gas: Gas) -> Self {
gas.sub
}
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
Serialize,
Deserialize,
Eq,
)]
pub struct WholeGas(u64);
impl From<u64> for WholeGas {
fn from(amount: u64) -> WholeGas {
Self(amount)
}
}
impl From<WholeGas> for u64 {
fn from(whole: WholeGas) -> u64 {
whole.0
}
}
impl FromStr for WholeGas {
type Err = GasParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(Self(s.parse().map_err(GasParseError::Parse)?))
}
}
impl Display for WholeGas {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub trait GasMetering {
fn consume(&mut self, gas: Gas) -> Result<()>;
fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> {
self.consume(
bytes_len
.checked_mul(COMPILE_GAS_PER_BYTE)
.ok_or(Error::GasOverflow)?
.into(),
)
}
fn add_wasm_load_from_storage_gas(&mut self, bytes_len: u64) -> Result<()> {
self.consume(
bytes_len
.checked_mul(STORAGE_ACCESS_GAS_PER_BYTE)
.ok_or(Error::GasOverflow)?
.into(),
)
}
fn add_wasm_validation_gas(&mut self, bytes_len: u64) -> Result<()> {
self.consume(
bytes_len
.checked_mul(WASM_CODE_VALIDATION_GAS_PER_BYTE)
.ok_or(Error::GasOverflow)?
.into(),
)
}
fn get_tx_consumed_gas(&self) -> Gas;
fn get_gas_limit(&self) -> Gas;
fn get_gas_scale(&self) -> u64;
fn check_vps_limit(&self, vps_gas: Gas) -> Result<()> {
let total = self
.get_tx_consumed_gas()
.checked_add(vps_gas)
.ok_or(Error::GasOverflow)?;
let gas_limit = self.get_gas_limit();
if total > gas_limit {
return Err(Error::TransactionGasExceededError(
gas_limit.get_whole_gas_units(self.get_gas_scale()),
));
}
Ok(())
}
}
#[derive(Debug)]
pub struct TxGasMeter {
gas_overflow: bool,
gas_scale: u64,
pub tx_gas_limit: Gas,
transaction_gas: Gas,
}
#[derive(Debug)]
pub struct VpGasMeter {
gas_overflow: bool,
gas_scale: u64,
tx_gas_limit: Gas,
initial_gas: Gas,
current_gas: Gas,
}
impl GasMetering for TxGasMeter {
fn consume(&mut self, gas: Gas) -> Result<()> {
if self.gas_overflow {
hints::cold();
return Err(Error::GasOverflow);
}
self.transaction_gas =
self.transaction_gas.checked_add(gas).ok_or_else(|| {
hints::cold();
self.gas_overflow = true;
Error::GasOverflow
})?;
if self.transaction_gas > self.tx_gas_limit {
return Err(Error::TransactionGasExceededError(
self.tx_gas_limit.get_whole_gas_units(self.gas_scale),
));
}
Ok(())
}
fn get_tx_consumed_gas(&self) -> Gas {
if !self.gas_overflow {
self.transaction_gas.clone()
} else {
hints::cold();
u64::MAX.into()
}
}
fn get_gas_limit(&self) -> Gas {
self.tx_gas_limit.clone()
}
fn get_gas_scale(&self) -> u64 {
self.gas_scale
}
}
impl TxGasMeter {
pub fn new(tx_gas_limit: impl Into<Gas>, gas_scale: u64) -> Self {
Self {
gas_overflow: false,
gas_scale,
tx_gas_limit: tx_gas_limit.into(),
transaction_gas: Gas::default(),
}
}
pub fn add_wrapper_gas(&mut self, tx_bytes: &[u8]) -> Result<()> {
self.consume(WRAPPER_TX_VALIDATION_GAS.into())?;
let bytes_len = tx_bytes.len() as u64;
self.consume(
bytes_len
.checked_mul(
STORAGE_OCCUPATION_GAS_PER_BYTE
+ NETWORK_TRANSMISSION_GAS_PER_BYTE,
)
.ok_or(Error::GasOverflow)?
.into(),
)
}
pub fn get_available_gas(&self) -> Gas {
self.tx_gas_limit
.checked_sub(self.transaction_gas.clone())
.unwrap_or_default()
}
}
impl GasMetering for VpGasMeter {
fn consume(&mut self, gas: Gas) -> Result<()> {
if self.gas_overflow {
hints::cold();
return Err(Error::GasOverflow);
}
self.current_gas =
self.current_gas.checked_add(gas).ok_or_else(|| {
hints::cold();
self.gas_overflow = true;
Error::GasOverflow
})?;
let current_total = self
.initial_gas
.checked_add(self.current_gas.clone())
.ok_or(Error::GasOverflow)?;
if current_total > self.tx_gas_limit {
return Err(Error::TransactionGasExceededError(
self.tx_gas_limit.get_whole_gas_units(self.gas_scale),
));
}
Ok(())
}
fn get_tx_consumed_gas(&self) -> Gas {
if !self.gas_overflow {
self.initial_gas.clone()
} else {
hints::cold();
u64::MAX.into()
}
}
fn get_gas_limit(&self) -> Gas {
self.tx_gas_limit.clone()
}
fn get_gas_scale(&self) -> u64 {
self.gas_scale
}
}
impl VpGasMeter {
pub fn new_from_tx_meter(tx_gas_meter: &TxGasMeter) -> Self {
Self {
gas_overflow: false,
gas_scale: tx_gas_meter.gas_scale,
tx_gas_limit: tx_gas_meter.tx_gas_limit.clone(),
initial_gas: tx_gas_meter.transaction_gas.clone(),
current_gas: Gas::default(),
}
}
pub fn get_vp_consumed_gas(&self) -> Gas {
self.current_gas.clone()
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use proptest::prelude::*;
use super::*;
const BLOCK_GAS_LIMIT: u64 = 10_000_000_000;
const TX_GAS_LIMIT: u64 = 1_000_000;
const GAS_SCALE: u64 = 1;
proptest! {
#[test]
fn test_vp_gas_meter_add(gas in 0..BLOCK_GAS_LIMIT) {
let tx_gas_meter = TxGasMeter {
gas_overflow: false,
gas_scale: GAS_SCALE,
tx_gas_limit: BLOCK_GAS_LIMIT.into(),
transaction_gas: Gas::default(),
};
let mut meter = VpGasMeter::new_from_tx_meter(&tx_gas_meter);
meter.consume(gas.into()).expect("cannot add the gas");
}
}
#[test]
fn test_vp_gas_overflow() {
let tx_gas_meter = TxGasMeter {
gas_overflow: false,
gas_scale: GAS_SCALE,
tx_gas_limit: BLOCK_GAS_LIMIT.into(),
transaction_gas: (TX_GAS_LIMIT - 1).into(),
};
let mut meter = VpGasMeter::new_from_tx_meter(&tx_gas_meter);
assert_matches!(
meter
.consume(u64::MAX.into())
.expect_err("unexpectedly succeeded"),
Error::GasOverflow
);
}
#[test]
fn test_vp_gas_limit() {
let tx_gas_meter = TxGasMeter {
gas_overflow: false,
gas_scale: GAS_SCALE,
tx_gas_limit: TX_GAS_LIMIT.into(),
transaction_gas: (TX_GAS_LIMIT - 1).into(),
};
let mut meter = VpGasMeter::new_from_tx_meter(&tx_gas_meter);
assert_matches!(
meter
.consume(TX_GAS_LIMIT.into())
.expect_err("unexpectedly succeeded"),
Error::TransactionGasExceededError(_)
);
}
#[test]
fn test_tx_gas_overflow() {
let mut meter = TxGasMeter::new(BLOCK_GAS_LIMIT, GAS_SCALE);
meter.consume(1.into()).expect("cannot add the gas");
assert_matches!(
meter
.consume(u64::MAX.into())
.expect_err("unexpectedly succeeded"),
Error::GasOverflow
);
}
#[test]
fn test_tx_gas_limit() {
let mut meter = TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE);
assert_matches!(
meter
.consume((TX_GAS_LIMIT + 1).into())
.expect_err("unexpectedly succeeded"),
Error::TransactionGasExceededError(_)
);
}
}