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
| Address | Function | Status |
|---|---|---|
0x01 | ecRecover | Production |
0x02 | SHA-256 | Production |
0x03 | RIPEMD-160 | Production |
0x04 | Identity | Production |
0x05 | ModExp (EIP-2565) | Production |
0x06 | EC_ADD (EIP-196) | Production |
0x07 | EC_MUL (EIP-196) | Production |
0x08 | EC_PAIRING (EIP-197) | Production |
0x09 | BLAKE2F (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.
| Address | Function | Status |
|---|---|---|
0x0a | BLS12_G1ADD (EIP-2537) | Production |
0x0b | BLS12_G1MSM (EIP-2537) | Production |
0x0c | BLS12_G2ADD (EIP-2537) | Production |
0x0d | BLS12_G2MSM (EIP-2537) | Production |
0x0e | BLS12_PAIRING_CHECK (EIP-2537) | Production |
0x0f | BLS12_MAP_FP_TO_G1 (EIP-2537) | Production |
0x10 | BLS12_MAP_FP2_TO_G2 (EIP-2537) | Production |
Tenzro Precompiles
| Address | Function | Gas Cost |
|---|---|---|
0x0100 | TEE Attestation Verify | 50,000 |
0x0101 | ZK Proof Verify | 100,000 |
0x0102 | Model Inference Request | 200,000 |
0x0103 | Settlement Execute | 75,000 |
0x1001 | TNZO Bridge (wrap/unwrap) | 50,000 |
0x1002 | Token Factory (ERC-20 creation) | 150,000 |
0x1003 | Cross-VM Bridge (cross-VM transfer) | 100,000 |
0x1004 | Staking (stake/unstake TNZO) | 75,000 |
0x1005 | Governance (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 GweiEVM Version Support
| Version | Notable Changes | Support |
|---|---|---|
| Homestead | DELEGATECALL opcode | Full |
| Byzantium | REVERT, STATICCALL, precompiles | Full |
| Constantinople | CREATE2, bitwise shifts | Full |
| Istanbul | Gas cost changes, BLAKE2 precompile | Full |
| Berlin | Access lists, gas cost changes | Full |
| London | EIP-1559 base fee, BASEFEE opcode | Full (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