Tenzro Testnet is live. Get testnet TNZO

EVM Executor

The Tenzro EVM executor provides full Ethereum Virtual Machine compatibility using the revm library. It supports complete EVM bytecode execution, EIP-1559 transactions, contract deployment, log extraction, and Ethereum-compatible address derivation using keccak256 hashing.

Architecture

The EvmExecutor wraps the revm library to provide Ethereum-compatible smart contract execution. It manages execution context, gas accounting, state access, and precompile integration through a unified interface.

┌────────────────────────────────┐
       EvmExecutor              
  (Ethereum Compatibility)      
└───────────┬────────────────────┘
            
             wraps
            
┌────────────────────────────────┐
         revm                    
  (Rust EVM Implementation)     
├────────────────────────────────┤
  Bytecode interpreter         
  Gas metering                 
  EVM opcodes (300+)           
  EIP-1559 support             
└───────────┬────────────────────┘
            
             state access
            
┌────────────────────────────────┐
      StateAdapter              
    (RocksDB Backend)           
└────────────────────────────────┘

Transaction Execution

The executor supports three primary execution paths: standard transactions, contract calls, and contract deployments. All execution paths use revm for full bytecode interpretation with proper gas metering.

Standard Transaction

use tenzro_vm::evm::EvmExecutor;
use tenzro_types::{Transaction, Address};

let executor = EvmExecutor::new(state_adapter);

// Create EVM transaction
let tx = Transaction {
    from: sender_address,
    to: Some(recipient_address),
    value: parse_ether("1.5")?, // 1.5 TNZO
    data: vec![],
    gas_limit: 21_000,
    gas_price: 20_000_000_000, // 20 Gwei
    nonce: 0,
    chain_id: 1337,
    ..Default::default()
};

// Execute transaction
let result = executor.execute_transaction(&tx).await?;

println!("Gas used: {}", result.gas_used);
println!("Status: {:?}", result.status);
println!("Output: {}", hex::encode(&result.output));

// Check for logs (events)
for log in result.logs {
    println!("Event emitted:");
    println!("  Address: {}", log.address);
    println!("  Topics: {:?}", log.topics);
    println!("  Data: {}", hex::encode(&log.data));
}

Contract Call

Contract calls execute bytecode at a specific address with provided calldata. The EVM interprets the bytecode, performs state reads/writes, and returns output data or reverts with an error message.

use tiny_keccak::{Hasher, Keccak};

// Encode ERC-20 transfer function call
fn encode_transfer(to: Address, amount: u64) -> Vec<u8> {
    let mut result = Vec::new();

    // Function selector: keccak256("transfer(address,uint256)")[:4]
    let mut hasher = Keccak::v256();
    hasher.update(b"transfer(address,uint256)");
    let mut selector = [0u8; 32];
    hasher.finalize(&mut selector);
    result.extend_from_slice(&selector[0..4]);

    // Encode address (32 bytes, left-padded)
    let mut address_bytes = [0u8; 32];
    address_bytes[12..].copy_from_slice(to.as_bytes());
    result.extend_from_slice(&address_bytes);

    // Encode amount (32 bytes, big-endian)
    let mut amount_bytes = [0u8; 32];
    amount_bytes[24..].copy_from_slice(&amount.to_be_bytes());
    result.extend_from_slice(&amount_bytes);

    result
}

let erc20_contract = Address::from_hex("0x742d35Cc6...")?;
let recipient = Address::from_hex("0x8626f6940E...")?;
let amount = parse_ether("100")?; // 100 tokens

let tx = Transaction {
    from: sender_address,
    to: Some(erc20_contract),
    data: encode_transfer(recipient, amount),
    gas_limit: 100_000,
    gas_price: 20_000_000_000,
    ..Default::default()
};

let result = executor.execute_transaction(&tx).await?;

if result.status == ExecutionStatus::Success {
    println!("Transfer successful!");
    println!("Gas used: {}", result.gas_used);
} else {
    println!("Transfer failed: {}", String::from_utf8_lossy(&result.output));
}

Contract Deployment

Contract deployments execute the constructor bytecode and store the runtime bytecode at a deterministic address. The address is derived using keccak256(rlp([sender, nonce])) for Ethereum compatibility.

use tenzro_vm::evm::utils::compute_create_address;

// Compile contract bytecode (example: simple storage contract)
let bytecode = hex::decode(
    "608060405234801561001057600080fd5b5060c78061001f6000396000f3fe..."
)?;

// Deploy contract (to = None for deployment)
let tx = Transaction {
    from: deployer_address,
    to: None, // Signals deployment
    data: bytecode,
    gas_limit: 500_000,
    gas_price: 20_000_000_000,
    nonce: 5,
    ..Default::default()
};

let result = executor.execute_transaction(&tx).await?;

if result.status == ExecutionStatus::Success {
    // Compute deployed contract address
    let contract_address = compute_create_address(&deployer_address, 5);

    println!("Contract deployed at: {}", contract_address);
    println!("Gas used: {}", result.gas_used);
    println!("Runtime bytecode length: {} bytes", result.output.len());

    // Verify contract exists in state
    let code = state_adapter.get_code(&contract_address)?;
    assert_eq!(code, result.output);
}

Address Derivation

Tenzro uses Ethereum-compatible address derivation for CREATE and CREATE2 operations. Addresses are computed using keccak256 hashing over RLP-encoded sender and nonce (CREATE) or deterministic salt (CREATE2).

CREATE Address

use tenzro_vm::evm::utils::compute_create_address;
use tiny_keccak::{Hasher, Keccak};

// Standard CREATE: keccak256(rlp([sender, nonce]))[12:]
fn compute_create_address(sender: &Address, nonce: u64) -> Address {
    let mut stream = rlp::RlpStream::new_list(2);
    stream.append(&sender.as_bytes());
    stream.append(&nonce);
    let encoded = stream.out();

    let mut hasher = Keccak::v256();
    hasher.update(&encoded);
    let mut hash = [0u8; 32];
    hasher.finalize(&mut hash);

    Address::from_bytes(&hash[12..])
}

// Example
let deployer = Address::from_hex("0x742d35Cc6...")?;
let nonce = 42;
let contract_address = compute_create_address(&deployer, nonce);

println!("Contract will deploy at: {}", contract_address);

CREATE2 Address

use tenzro_vm::evm::utils::compute_create2_address;

// CREATE2: keccak256(0xff ++ sender ++ salt ++ keccak256(init_code))[12:]
fn compute_create2_address(
    sender: &Address,
    salt: [u8; 32],
    init_code: &[u8],
) -> Address {
    let mut hasher = Keccak::v256();
    hasher.update(init_code);
    let mut code_hash = [0u8; 32];
    hasher.finalize(&mut code_hash);

    let mut input = Vec::with_capacity(85);
    input.push(0xff);
    input.extend_from_slice(sender.as_bytes());
    input.extend_from_slice(&salt);
    input.extend_from_slice(&code_hash);

    let mut hasher = Keccak::v256();
    hasher.update(&input);
    let mut hash = [0u8; 32];
    hasher.finalize(&mut hash);

    Address::from_bytes(&hash[12..])
}

// Deterministic deployment
let factory = Address::from_hex("0x8626f6940E...")?;
let salt = [42u8; 32];
let init_code = hex::decode("6080604052...")?;

let contract_address = compute_create2_address(&factory, salt, &init_code);
println!("Deterministic address: {}", contract_address);

Gas Metering

Revm provides accurate gas metering for all EVM operations. Gas is consumed for opcode execution, memory expansion, storage access (SLOAD/SSTORE), log emission, and contract creation. The executor enforces gas limits and refunds unused gas.

use tenzro_vm::evm::gas::GasTracker;

// Gas costs (post-London fork)
const GAS_ADD: u64 = 3;
const GAS_MUL: u64 = 5;
const GAS_SLOAD: u64 = 2_100;
const GAS_SSTORE_SET: u64 = 20_000;
const GAS_SSTORE_RESET: u64 = 2_900;
const GAS_LOG: u64 = 375;
const GAS_LOG_DATA: u64 = 8; // per byte
const GAS_LOG_TOPIC: u64 = 375; // per topic
const GAS_CALL: u64 = 2_600; // base call cost
const GAS_CREATE: u64 = 32_000;

// Execution gas tracking
let result = executor.execute_transaction(&tx).await?;

println!("Gas limit: {}", tx.gas_limit);
println!("Gas used: {}", result.gas_used);
println!("Gas refund: {}", result.gas_refund);
println!("Gas remaining: {}", tx.gas_limit - result.gas_used);

// Calculate transaction cost
let base_fee = 10_000_000_000; // 10 Gwei
let priority_fee = 2_000_000_000; // 2 Gwei
let effective_gas_price = base_fee + priority_fee;
let total_cost = result.gas_used * effective_gas_price;

println!("Total cost: {} TNZO", format_ether(total_cost));

Log Extraction

The executor extracts logs (events) emitted during execution. Each log contains the emitting contract address, up to 4 indexed topics, and arbitrary data bytes. Logs are used for event indexing and off-chain monitoring.

use tenzro_types::Log;

// Example: ERC-20 Transfer event
// event Transfer(address indexed from, address indexed to, uint256 value);

let result = executor.execute_transaction(&tx).await?;

for log in result.logs {
    if log.topics.len() == 3 {
        // topic[0] = keccak256("Transfer(address,address,uint256)")
        let event_sig = hex::encode(&log.topics[0]);

        if event_sig == "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" {
            // Decode indexed parameters
            let from = Address::from_bytes(&log.topics[1][12..]);
            let to = Address::from_bytes(&log.topics[2][12..]);

            // Decode non-indexed parameter (value)
            let value = u64::from_be_bytes(
                log.data[24..32].try_into().unwrap()
            );

            println!("Transfer Event:");
            println!("  From: {}", from);
            println!("  To: {}", to);
            println!("  Value: {} tokens", format_ether(value));
        }
    }
}

// Filter logs by address
let contract_logs: Vec<Log> = result.logs
    .into_iter()
    .filter(|log| log.address == erc20_contract)
    .collect();

Precompiled Contracts

Tenzro supports standard Ethereum precompiles at addresses 0x01-0x09, BLS12-381 precompiles (EIP-2537) at addresses 0x0a-0x10, and custom Tenzro precompiles at 0x0100+. Precompiles provide efficient native implementations of common operations like hashing, signature verification, elliptic curve math, and BLS operations.

Standard EVM Precompiles

AddressFunctionStatus
0x01ecRecoverProduction
0x02SHA-256Production
0x03RIPEMD-160Production
0x04IdentityProduction
0x05ModExp (EIP-2565)Production
0x06EC_ADD (EIP-196)Production
0x07EC_MUL (EIP-196)Production
0x08EC_PAIRING (EIP-197)Production
0x09BLAKE2F (EIP-152)Production

BLS12-381 Precompiles (EIP-2537)

Seven BLS12-381 precompiles at addresses 0x0a-0x10 provide native support for BLS signature verification and aggregation using the blst library. These enable efficient on-chain verification of aggregated validator signatures.

AddressFunctionStatus
0x0aBLS12_G1ADD (EIP-2537)Production
0x0bBLS12_G1MSM (EIP-2537)Production
0x0cBLS12_G2ADD (EIP-2537)Production
0x0dBLS12_G2MSM (EIP-2537)Production
0x0eBLS12_PAIRING_CHECK (EIP-2537)Production
0x0fBLS12_MAP_FP_TO_G1 (EIP-2537)Production
0x10BLS12_MAP_FP2_TO_G2 (EIP-2537)Production

Tenzro Precompiles

AddressFunctionGas Cost
0x0100TEE Attestation Verify50,000
0x0101ZK Proof Verify100,000
0x0102Model Inference Request200,000
0x0103Settlement Execute75,000
0x1001TNZO Bridge (wrap/unwrap)50,000
0x1002Token Factory (ERC-20 creation)150,000
0x1003Cross-VM Bridge (cross-VM transfer)100,000
0x1004Staking (stake/unstake TNZO)75,000
0x1005Governance (vote on proposals)50,000

State Access

The executor provides state access operations through the state adapter interface. State operations include account balance queries, nonce management, code storage, and arbitrary storage slot access (SLOAD/SSTORE).

use tenzro_vm::evm::state::StateAdapter;

// Account operations
let balance = state_adapter.get_balance(&address)?;
state_adapter.set_balance(&address, new_balance)?;

let nonce = state_adapter.get_nonce(&address)?;
state_adapter.set_nonce(&address, nonce + 1)?;

// Code operations
let code = state_adapter.get_code(&contract_address)?;
state_adapter.set_code(&contract_address, bytecode)?;

// Storage operations (SLOAD/SSTORE)
let storage_key = [0u8; 32];
let value = state_adapter.get_storage(&contract_address, &storage_key)?;
state_adapter.set_storage(&contract_address, &storage_key, new_value)?;

// Batch state changes
state_adapter.begin_transaction();
// ... multiple state operations ...
state_adapter.commit(); // Atomically apply all changes

// Rollback on error
if execution_failed {
    state_adapter.rollback();
}

Error Handling

The executor distinguishes between different failure modes: out-of-gas, revert (controlled failure), invalid opcode, stack overflow, and state access errors. Each error type is captured in the execution result for proper handling.

use tenzro_vm::evm::{ExecutionStatus, ExecutionResult};

match executor.execute_transaction(&tx).await {
    Ok(result) => {
        match result.status {
            ExecutionStatus::Success => {
                println!("Transaction succeeded");
                println!("Output: {}", hex::encode(&result.output));
            }
            ExecutionStatus::Revert => {
                println!("Transaction reverted");
                // Decode revert reason
                if result.output.len() >= 68 {
                    let reason = String::from_utf8_lossy(&result.output[68..]);
                    println!("Reason: {}", reason);
                }
            }
            ExecutionStatus::OutOfGas => {
                println!("Transaction ran out of gas");
                println!("Gas limit: {}", tx.gas_limit);
            }
            ExecutionStatus::InvalidOpcode => {
                println!("Invalid opcode encountered");
            }
            ExecutionStatus::StackOverflow => {
                println!("Stack overflow (depth > 1024)");
            }
        }

        // Check logs even on revert
        for log in result.logs {
            println!("Event: {:?}", log);
        }
    }
    Err(e) => {
        eprintln!("Execution error: {}", e);
    }
}

wTNZO Pointer Contract

EVM smart contracts interact with native TNZO through wTNZO, a pointer ERC-20 contract following the Sei V2 pointer model. The wTNZO contract does not hold a separate token supply. Instead, it maps directly to the underlying native TNZO balance via the TnzoToken layer, eliminating bridge risk and liquidity fragmentation.

When an EVM contract calls wTNZO.transfer(), the transaction routes through the TNZO_BRIDGE precompile at 0x1001, which updates the native balance atomically. The same balance is accessible from SVM (as an SPL token) and Canton (as a CIP-56 holding) without any cross-chain bridging.

// wTNZO is a standard ERC-20 — use it like any other token
// Solidity interface:
//
//   IERC20(wTNZO_ADDRESS).transfer(recipient, amount);
//   IERC20(wTNZO_ADDRESS).approve(spender, amount);
//   IERC20(wTNZO_ADDRESS).balanceOf(account);
//
// The TNZO_BRIDGE precompile (0x1001) handles wrap/unwrap:

use tenzro_vm::precompiles::tnzo_bridge;

// Wrap native TNZO into wTNZO ERC-20 representation
// In the pointer model this is a no-op — the balance is shared
let wrap_calldata = tnzo_bridge::encode_wrap(amount);

let tx = Transaction {
    from: sender,
    to: Some(TNZO_BRIDGE_ADDRESS), // 0x1001
    data: wrap_calldata,
    value: amount, // Native TNZO to wrap
    gas_limit: 50_000,
    ..Default::default()
};

let result = executor.execute_transaction(&tx).await?;

// ERC-20 approval storage is maintained per-account in the
// unified token registry (DashMap-indexed, RocksDB-persisted
// via CF_TOKENS).

No bridge risk: Because wTNZO is a pointer contract, there is no locked collateral, no mint/burn asymmetry, and no separate liquidity pool. A single native TNZO balance backs all three VM representations. See the Cross-VM Tokens documentation for the full architecture.

Configuration

use tenzro_vm::evm::{EvmExecutor, EvmConfig};
use revm::EvmVersion;

let config = EvmConfig {
    evm_version: EvmVersion::London, // EIP-1559 support
    enable_precompiles: true,
    max_code_size: 24_576, // 24 KB
    max_call_depth: 1_024,
    enable_gas_refunds: true,
    allow_contract_creation: true,
};

let executor = EvmExecutor::with_config(config, state_adapter);

// Configure gas settings
executor.set_max_gas_limit(30_000_000);
executor.set_default_gas_limit(10_000_000);
executor.set_min_gas_price(1_000_000_000); // 1 Gwei

EVM Version Support

VersionNotable ChangesSupport
HomesteadDELEGATECALL opcodeFull
ByzantiumREVERT, STATICCALL, precompilesFull
ConstantinopleCREATE2, bitwise shiftsFull
IstanbulGas cost changes, BLAKE2 precompileFull
BerlinAccess lists, gas cost changesFull
LondonEIP-1559 base fee, BASEFEE opcodeFull (default)

Testing

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_simple_transfer() {
        let state = InMemoryStateAdapter::new();
        let executor = EvmExecutor::new(state.clone());

        // Fund sender
        state.set_balance(&sender, parse_ether("10")?)?;

        let tx = Transaction {
            from: sender,
            to: Some(recipient),
            value: parse_ether("1")?,
            gas_limit: 21_000,
            ..Default::default()
        };

        let result = executor.execute_transaction(&tx).await?;

        assert_eq!(result.status, ExecutionStatus::Success);
        assert_eq!(result.gas_used, 21_000);

        let sender_balance = state.get_balance(&sender)?;
        let recipient_balance = state.get_balance(&recipient)?;

        assert_eq!(sender_balance, parse_ether("9")?);
        assert_eq!(recipient_balance, parse_ether("1")?);
    }

    #[tokio::test]
    async fn test_contract_deployment() {
        // Test contract deployment and initialization
    }

    #[tokio::test]
    async fn test_revert_handling() {
        // Test transaction revert with reason string
    }
}

Production Readiness

Production-Ready:

  • Full revm integration for bytecode execution
  • Accurate gas metering and consumption tracking
  • All 9 standard EVM precompiles fully implemented per EIP specs (37 test vectors)
  • 7 BLS12-381 precompiles (EIP-2537) at 0x0a-0x10 using the blst library
  • 9 Tenzro-specific precompiles (TEE, ZK, inference, settlement, tokens, staking, governance)
  • Log extraction and event parsing
  • Keccak256-based CREATE/CREATE2 address derivation
  • EIP-1559 fee market with dynamic base fee adjustment
  • Block-STM parallel execution with MVCC and conflict detection
  • ERC-4337 account abstraction (EntryPoint, smart accounts, paymasters)
  • RocksDB state persistence via StateAdapter
  • wTNZO pointer contract for cross-VM token access
  • Support for EVM versions up to London fork
  • Error handling for all failure modes