use crate::VmKind;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
pub enum GasCategory {
StorageWrite,
StorageRead,
Memory,
Crypto,
Call,
Execution,
Precompile,
Root,
#[default]
Other,
}
impl GasCategory {
pub fn from_step(op: &str, vm: &VmKind) -> Self {
let op = op.trim();
match vm {
VmKind::Evm => Self::from_evm(op),
VmKind::Stylus => Self::from_stylus(op),
VmKind::Starknet => Self::from_starknet(op),
VmKind::Solana => Self::from_solana(op),
VmKind::Stellar => Self::from_stellar(op),
}
}
fn from_evm(op: &str) -> Self {
match op {
"SSTORE" | "TSTORE" => Self::StorageWrite,
"SLOAD" | "TLOAD" => Self::StorageRead,
"MLOAD" | "MSTORE" | "MSTORE8" | "MCOPY" | "MSIZE" => Self::Memory,
"KECCAK256" | "SHA3" => Self::Crypto,
"CALL" | "STATICCALL" | "DELEGATECALL" | "CALLCODE"
| "CREATE" | "CREATE2"
| "RETURN" | "REVERT" | "STOP" | "INVALID" | "SELFDESTRUCT" => Self::Call,
"ADD" | "SUB" | "MUL" | "DIV" | "SDIV" | "MOD" | "SMOD"
| "ADDMOD" | "MULMOD" | "EXP" | "SIGNEXTEND"
| "LT" | "GT" | "SLT" | "SGT" | "EQ" | "ISZERO"
| "AND" | "OR" | "XOR" | "NOT" | "BYTE" | "SHL" | "SHR" | "SAR"
| "POP"
| "PUSH1" | "PUSH2" | "PUSH3" | "PUSH4" | "PUSH5" | "PUSH6"
| "PUSH7" | "PUSH8" | "PUSH9" | "PUSH10" | "PUSH11" | "PUSH12"
| "PUSH13" | "PUSH14" | "PUSH15" | "PUSH16" | "PUSH17" | "PUSH18"
| "PUSH19" | "PUSH20" | "PUSH21" | "PUSH22" | "PUSH23" | "PUSH24"
| "PUSH25" | "PUSH26" | "PUSH27" | "PUSH28" | "PUSH29" | "PUSH30"
| "PUSH31" | "PUSH32"
| "DUP1" | "DUP2" | "DUP3" | "DUP4" | "DUP5" | "DUP6" | "DUP7"
| "DUP8" | "DUP9" | "DUP10" | "DUP11" | "DUP12" | "DUP13"
| "DUP14" | "DUP15" | "DUP16"
| "SWAP1" | "SWAP2" | "SWAP3" | "SWAP4" | "SWAP5" | "SWAP6"
| "SWAP7" | "SWAP8" | "SWAP9" | "SWAP10" | "SWAP11" | "SWAP12"
| "SWAP13" | "SWAP14" | "SWAP15" | "SWAP16"
| "JUMP" | "JUMPI" | "PC" | "GAS" | "JUMPDEST" => Self::Execution,
_ => Self::Other,
}
}
fn from_stylus(hostio: &str) -> Self {
classify_by_keyword(
hostio,
&[
(&["flush", "storage_store"], Self::StorageWrite),
(&["storage_load", "storage_cache"], Self::StorageRead),
(&["keccak", "sha2"], Self::Crypto),
(&["call", "create"], Self::Call),
(&["memory", "args", "return_data"], Self::Memory),
(&["msg", "block", "tx", "evm", "user"], Self::Execution),
],
)
}
fn from_starknet(op: &str) -> Self {
classify_by_keyword(
op,
&[
(&["storage_write"], Self::StorageWrite),
(&["storage_read"], Self::StorageRead),
(&["keccak", "pedersen", "poseidon", "ec_op"], Self::Crypto),
(&["call", "deploy", "invoke"], Self::Call),
(&["range_check", "bitwise", "steps"], Self::Execution),
],
)
}
fn from_solana(op: &str) -> Self {
classify_by_keyword(
op,
&[
(&["write", "store", "set_account"], Self::StorageWrite),
(&["read", "load", "get_account"], Self::StorageRead),
(&["hash", "keccak", "secp256k1", "ed25519"], Self::Crypto),
(&["invoke", "cpi", "call"], Self::Call),
(&["compute", "log", "instruction", "syscall"], Self::Execution),
],
)
}
fn from_stellar(op: &str) -> Self {
classify_by_keyword(
op,
&[
(&["put_contract_data", "write"], Self::StorageWrite),
(&["get_contract_data", "read"], Self::StorageRead),
(&["hash", "verify", "crypto", "recover"], Self::Crypto),
(&["call", "invoke", "create_contract"], Self::Call),
(&["value", "obj", "vec", "map", "bytes"], Self::Memory),
(&["log", "ledger", "meta"], Self::Execution),
],
)
}
}
fn classify_by_keyword(op: &str, rules: &[(&[&str], GasCategory)]) -> GasCategory {
let lower = op.to_lowercase();
for (keywords, category) in rules {
if keywords.iter().any(|kw| lower.contains(kw)) {
return category.clone();
}
}
GasCategory::Other
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn evm_storage_ops() {
assert_eq!(GasCategory::from_step("SSTORE", &VmKind::Evm), GasCategory::StorageWrite);
assert_eq!(GasCategory::from_step("TSTORE", &VmKind::Evm), GasCategory::StorageWrite);
assert_eq!(GasCategory::from_step("SLOAD", &VmKind::Evm), GasCategory::StorageRead);
assert_eq!(GasCategory::from_step("TLOAD", &VmKind::Evm), GasCategory::StorageRead);
}
#[test]
fn evm_memory_ops() {
assert_eq!(GasCategory::from_step("MLOAD", &VmKind::Evm), GasCategory::Memory);
assert_eq!(GasCategory::from_step("MSTORE", &VmKind::Evm), GasCategory::Memory);
assert_eq!(GasCategory::from_step("MCOPY", &VmKind::Evm), GasCategory::Memory);
}
#[test]
fn evm_crypto_ops() {
assert_eq!(GasCategory::from_step("KECCAK256", &VmKind::Evm), GasCategory::Crypto);
assert_eq!(GasCategory::from_step("SHA3", &VmKind::Evm), GasCategory::Crypto);
}
#[test]
fn evm_call_ops() {
assert_eq!(GasCategory::from_step("CALL", &VmKind::Evm), GasCategory::Call);
assert_eq!(GasCategory::from_step("DELEGATECALL", &VmKind::Evm), GasCategory::Call);
assert_eq!(GasCategory::from_step("STATICCALL", &VmKind::Evm), GasCategory::Call);
assert_eq!(GasCategory::from_step("CREATE", &VmKind::Evm), GasCategory::Call);
assert_eq!(GasCategory::from_step("CREATE2", &VmKind::Evm), GasCategory::Call);
}
#[test]
fn evm_execution_ops() {
assert_eq!(GasCategory::from_step("ADD", &VmKind::Evm), GasCategory::Execution);
assert_eq!(GasCategory::from_step("JUMPDEST", &VmKind::Evm), GasCategory::Execution);
assert_eq!(GasCategory::from_step("PUSH32", &VmKind::Evm), GasCategory::Execution);
assert_eq!(GasCategory::from_step("DUP1", &VmKind::Evm), GasCategory::Execution);
assert_eq!(GasCategory::from_step("SWAP16", &VmKind::Evm), GasCategory::Execution);
}
#[test]
fn evm_unknown_op_falls_to_other() {
assert_eq!(GasCategory::from_step("CUSTOMOP", &VmKind::Evm), GasCategory::Other);
assert_eq!(GasCategory::from_step("", &VmKind::Evm), GasCategory::Other);
}
#[test]
fn stylus_storage_ops() {
assert_eq!(
GasCategory::from_step("storage_store_bytes32", &VmKind::Stylus),
GasCategory::StorageWrite
);
assert_eq!(
GasCategory::from_step("storage_load_bytes32", &VmKind::Stylus),
GasCategory::StorageRead
);
assert_eq!(
GasCategory::from_step("flush_cache", &VmKind::Stylus),
GasCategory::StorageWrite
);
}
#[test]
fn stylus_call_ops() {
assert_eq!(
GasCategory::from_step("call_contract", &VmKind::Stylus),
GasCategory::Call
);
}
#[test]
fn starknet_crypto_and_storage() {
assert_eq!(
GasCategory::from_step("pedersen_hash", &VmKind::Starknet),
GasCategory::Crypto
);
assert_eq!(
GasCategory::from_step("poseidon_hash_many", &VmKind::Starknet),
GasCategory::Crypto
);
assert_eq!(
GasCategory::from_step("storage_write", &VmKind::Starknet),
GasCategory::StorageWrite
);
assert_eq!(
GasCategory::from_step("storage_read", &VmKind::Starknet),
GasCategory::StorageRead
);
}
#[test]
fn solana_cpi_is_call() {
assert_eq!(
GasCategory::from_step("invoke_signed_cpi", &VmKind::Solana),
GasCategory::Call
);
}
#[test]
fn solana_secp256k1_is_crypto() {
assert_eq!(
GasCategory::from_step("secp256k1_recover", &VmKind::Solana),
GasCategory::Crypto
);
}
#[test]
fn stellar_hostfn_storage() {
assert_eq!(
GasCategory::from_step("put_contract_data", &VmKind::Stellar),
GasCategory::StorageWrite
);
assert_eq!(
GasCategory::from_step("get_contract_data", &VmKind::Stellar),
GasCategory::StorageRead
);
}
#[test]
fn whitespace_is_trimmed() {
assert_eq!(
GasCategory::from_step(" SSTORE ", &VmKind::Evm),
GasCategory::StorageWrite
);
}
}