use crate::{
file_format::{
AddressPoolIndex, ByteArrayPoolIndex, Bytecode, FieldDefinitionIndex, FunctionHandleIndex,
StringPoolIndex, StructDefinitionIndex, NO_TYPE_ACTUALS,
},
serializer::serialize_instruction,
};
use lazy_static::lazy_static;
use std::{
collections::HashMap,
ops::{Add, Div, Mul, Sub},
u64,
};
use types::transaction::MAX_TRANSACTION_SIZE_IN_BYTES;
pub type GasCarrier = u64;
pub trait GasAlgebra<GasCarrier>: Sized
where
GasCarrier: Add<Output = GasCarrier>
+ Sub<Output = GasCarrier>
+ Div<Output = GasCarrier>
+ Mul<Output = GasCarrier>
+ Copy,
{
fn new(carrier: GasCarrier) -> Self;
fn get(&self) -> GasCarrier;
fn map<F: Fn(GasCarrier) -> GasCarrier>(self, f: F) -> Self {
Self::new(f(self.get()))
}
fn map2<F: Fn(GasCarrier, GasCarrier) -> GasCarrier>(
self,
other: impl GasAlgebra<GasCarrier>,
f: F,
) -> Self {
Self::new(f(self.get(), other.get()))
}
fn app<T, F: Fn(GasCarrier, GasCarrier) -> T>(
&self,
other: &impl GasAlgebra<GasCarrier>,
f: F,
) -> T {
f(self.get(), other.get())
}
fn unitary_cast<T: GasAlgebra<GasCarrier>>(self) -> T {
T::new(self.get())
}
fn add(self, right: impl GasAlgebra<GasCarrier>) -> Self {
self.map2(right, Add::add)
}
fn sub(self, right: impl GasAlgebra<GasCarrier>) -> Self {
self.map2(right, Sub::sub)
}
fn mul(self, right: impl GasAlgebra<GasCarrier>) -> Self {
self.map2(right, Mul::mul)
}
fn div(self, right: impl GasAlgebra<GasCarrier>) -> Self {
self.map2(right, Div::div)
}
}
macro_rules! define_gas_unit {
{
name: $name: ident,
carrier: $carrier: ty,
doc: $comment: literal
} => {
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
#[doc=$comment]
pub struct $name<GasCarrier>(GasCarrier);
impl GasAlgebra<$carrier> for $name<$carrier> {
fn new(c: GasCarrier) -> Self {
Self(c)
}
fn get(&self) -> GasCarrier {
self.0
}
}
}
}
define_gas_unit! {
name: AbstractMemorySize,
carrier: GasCarrier,
doc: "A newtype wrapper that represents the (abstract) memory size that the instruciton will take up."
}
define_gas_unit! {
name: GasUnits,
carrier: GasCarrier,
doc: "A newtype wrapper around the underlying carrier for the gas cost."
}
define_gas_unit! {
name: GasPrice,
carrier: GasCarrier,
doc: "A newtype wrapper around the gas price for each unit of gas consumed."
}
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
pub struct InstructionKey(pub u8);
lazy_static! {
pub static ref GLOBAL_MEMORY_PER_BYTE_COST: GasUnits<GasCarrier> = GasUnits::new(8);
pub static ref GLOBAL_MEMORY_PER_BYTE_WRITE_COST: GasUnits<GasCarrier> = GasUnits::new(8);
pub static ref MAX_ABSTRACT_MEMORY_SIZE: AbstractMemorySize<GasCarrier> = AbstractMemorySize::new(std::u64::MAX);
pub static ref INTRINSIC_GAS_PER_BYTE: GasUnits<GasCarrier> = GasUnits::new(8);
pub static ref MIN_PRICE_PER_GAS_UNIT: GasPrice<GasCarrier> = GasPrice::new(0);
pub static ref MAX_PRICE_PER_GAS_UNIT: GasPrice<GasCarrier> = GasPrice::new(10_000);
pub static ref MAXIMUM_NUMBER_OF_GAS_UNITS: GasUnits<GasCarrier> = GasUnits::new(1_000_000);
pub static ref MIN_TRANSACTION_GAS_UNITS: GasUnits<GasCarrier> = GasUnits::new(600);
pub static ref WORD_SIZE: AbstractMemorySize<GasCarrier> = AbstractMemorySize::new(8);
pub static ref CONST_SIZE: AbstractMemorySize<GasCarrier> = AbstractMemorySize::new(1);
pub static ref REFERENCE_SIZE: AbstractMemorySize<GasCarrier> = AbstractMemorySize::new(8);
pub static ref STRUCT_SIZE: AbstractMemorySize<GasCarrier> = AbstractMemorySize::new(2);
pub static ref DEFAULT_ACCOUNT_SIZE: AbstractMemorySize<GasCarrier> = AbstractMemorySize::new(32);
pub static ref LARGE_TRANSACTION_CUTOFF: AbstractMemorySize<GasCarrier> = AbstractMemorySize::new(600);
}
#[derive(Debug)]
pub struct CostTable {
pub compute_table: HashMap<InstructionKey, GasUnits<GasCarrier>>,
pub memory_table: HashMap<InstructionKey, GasUnits<GasCarrier>>,
}
impl InstructionKey {
pub fn new(instruction: &Bytecode) -> Self {
let mut vec = Vec::new();
serialize_instruction(&mut vec, instruction).unwrap();
Self(vec[0])
}
}
impl CostTable {
pub fn new(instrs: Vec<(Bytecode, u64, u64)>) -> Self {
let mut compute_table = HashMap::new();
let mut memory_table = HashMap::new();
for (instr, comp_cost, mem_cost) in instrs.into_iter() {
let code = InstructionKey::new(&instr);
compute_table.insert(code, GasUnits::new(comp_cost));
memory_table.insert(code, GasUnits::new(mem_cost));
}
Self {
compute_table,
memory_table,
}
}
pub fn memory_gas(
&self,
instr: &Bytecode,
size_provider: AbstractMemorySize<GasCarrier>,
) -> GasUnits<GasCarrier> {
let code = InstructionKey::new(instr);
self.memory_table
.get(&code)
.unwrap()
.map2(size_provider, Mul::mul)
}
pub fn comp_gas(
&self,
instr: &Bytecode,
size_provider: AbstractMemorySize<GasCarrier>,
) -> GasUnits<GasCarrier> {
let code = InstructionKey::new(instr);
self.compute_table
.get(&code)
.unwrap()
.map2(size_provider, Mul::mul)
}
}
lazy_static! {
static ref GAS_SCHEDULE: CostTable = {
use Bytecode::*;
let instrs = vec![
(MoveToSender(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 774, 1),
(GetTxnSenderAddress, 30, 1),
(MoveFrom(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 917, 1),
(BrTrue(0), 31, 1),
(WriteRef, 65, 1),
(Mul, 41, 1),
(MoveLoc(0), 41, 1),
(And, 49, 1),
(ReleaseRef, 28, 1),
(GetTxnPublicKey, 41, 1),
(Pop, 27, 1),
(BitAnd, 44, 1),
(ReadRef, 51, 1),
(Sub, 44, 1),
(BorrowField(FieldDefinitionIndex::new(0)), 58, 1),
(Add, 45, 1),
(CopyLoc(0), 41, 1),
(StLoc(0), 28, 1),
(Ret, 28, 1),
(Lt, 49, 1),
(LdConst(0), 29, 1),
(Abort, 39, 1),
(BorrowLoc(0), 45, 1),
(LdStr(StringPoolIndex::new(0)), 52, 1),
(LdAddr(AddressPoolIndex::new(0)), 36, 1),
(Ge, 46, 1),
(Xor, 46, 1),
(Neq, 51, 1),
(Not, 35,1),
(Call(FunctionHandleIndex::new(0), NO_TYPE_ACTUALS), 197, 1),
(Le, 47, 1),
(CreateAccount, 1119, 1),
(Branch(0), 10, 1),
(Unpack(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 94, 1),
(Or, 43, 1),
(LdFalse, 30, 1),
(LdTrue, 29, 1),
(GetTxnGasUnitPrice, 29, 1),
(Mod, 42, 1),
(BrFalse(0), 29, 1),
(Exists(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 856, 1),
(GetGasRemaining, 32, 1),
(BitOr, 45, 1),
(GetTxnMaxGasUnits, 34, 1),
(GetTxnSequenceNumber, 29, 1),
(FreezeRef, 10, 1),
(BorrowGlobal(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 929, 1),
(Div, 41, 1),
(Eq, 48, 1),
(LdByteArray(ByteArrayPoolIndex::new(0)), 56, 1),
(Gt, 46, 1),
(Pack(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 73, 1),
(EmitEvent, 1, 1),
];
CostTable::new(instrs)
};
}
#[derive(Debug)]
pub struct GasCost {
pub instruction_gas: GasUnits<GasCarrier>,
pub memory_gas: GasUnits<GasCarrier>,
}
pub fn static_cost_instr(
instr: &Bytecode,
size_provider: AbstractMemorySize<GasCarrier>,
) -> GasCost {
GasCost {
instruction_gas: GAS_SCHEDULE.comp_gas(instr, size_provider),
memory_gas: GAS_SCHEDULE.memory_gas(instr, size_provider),
}
}
pub fn words_in(size: AbstractMemorySize<GasCarrier>) -> AbstractMemorySize<GasCarrier> {
precondition!(size.get() <= MAX_ABSTRACT_MEMORY_SIZE.get() - (WORD_SIZE.get() + 1));
size.map2(*WORD_SIZE, |size, word_size| {
(size + (word_size - 1)) / word_size
})
}
pub fn calculate_intrinsic_gas(
transaction_size: AbstractMemorySize<GasCarrier>,
) -> GasUnits<GasCarrier> {
precondition!(transaction_size.get() <= MAX_TRANSACTION_SIZE_IN_BYTES as GasCarrier);
let min_transaction_fee = *MIN_TRANSACTION_GAS_UNITS;
if transaction_size.get() > LARGE_TRANSACTION_CUTOFF.get() {
let excess = words_in(transaction_size.sub(*LARGE_TRANSACTION_CUTOFF));
min_transaction_fee.add(INTRINSIC_GAS_PER_BYTE.mul(excess))
} else {
min_transaction_fee.unitary_cast()
}
}