Gas Mechanisms and Fee Markets
Understanding gas calculation, fee markets (EIP-1559), and priority fee mechanisms in blockchain.
Gas Mechanisms and Fee Markets
Gas is the unit that measures computational work in blockchain transactions.
Why Gas?
- Prevent Spam: Costly to execute many transactions
- Resource Limits: Prevent infinite loops
- Fair Pricing: Pay for actual computation
- Network Stability: Regulate transaction throughput
Gas Calculation
Gas Costs
Different operations cost different amounts:
- Simple Transfer: 21,000 gas
- Contract Creation: 32,000 + code size
- Storage Write: 20,000 gas (first time), 5,000 (update)
- Storage Read: 2,100 gas
- SHA3: 30 gas + 6 per word
Gas Price
Gas Price is how much you pay per unit of gas (in wei/gas).
RUSTstruct Transaction { gas_limit: u64, gas_price: u64, // wei per gas // ... } fn calculate_fee(tx: &Transaction) -> u64 { tx.gas_limit * tx.gas_price }
EIP-1559 Fee Market
EIP-1559 introduced a new fee structure:
Components
- Base Fee: Burned, adjusts per block
- Priority Fee: Goes to miner/validator
- Max Fee: Maximum user willing to pay
Implementation
RUSTstruct EIP1559Transaction { max_fee_per_gas: u64, max_priority_fee_per_gas: u64, gas_limit: u64, } struct Block { base_fee: u64, transactions: Vec<EIP1559Transaction>, } fn calculate_fee(tx: &EIP1559Transaction, base_fee: u64) -> (u64, u64) { // Effective gas price = min(max_fee, base_fee + priority_fee) let effective_gas_price = tx.max_fee_per_gas.min(base_fee + tx.max_priority_fee_per_gas); // Total fee = effective_gas_price * gas_limit let total_fee = effective_gas_price * tx.gas_limit; // Base fee portion (burned) let base_fee_portion = base_fee.min(effective_gas_price) * tx.gas_limit; // Priority fee portion (to miner) let priority_fee_portion = total_fee - base_fee_portion; (base_fee_portion, priority_fee_portion) } fn adjust_base_fee(previous_base_fee: u64, block_fullness: f64) -> u64 { // Target: 50% block fullness let target = 0.5; if block_fullness > target { // Increase base fee previous_base_fee + (previous_base_fee / 8) } else { // Decrease base fee previous_base_fee - (previous_base_fee / 8) } }
Benefits of EIP-1559
- Predictable Fees: Base fee adjusts smoothly
- Fee Burning: Base fee is burned (deflationary)
- Better UX: Users set max fee, not exact fee
- MEV Reduction: Less front-running incentive
Gas Optimization
Best Practices
- Cache Storage: Read once, use multiple times
- Pack Data: Use smaller data types
- Batch Operations: Combine multiple operations
- Avoid Loops: Unroll when possible
- Use Events: Cheaper than storage
Example
RUST// Bad: Multiple storage writes fn bad_function(&mut self) { self.value1 = 1; self.value2 = 2; self.value3 = 3; } // Good: Pack into struct struct PackedValues { value1: u8, value2: u8, value3: u8, } fn good_function(&mut self) { self.packed = PackedValues { value1: 1, value2: 2, value3: 3 }; // Single storage write instead of three }
Real-World Examples
- Ethereum: Uses gas for all operations
- EIP-1559: Implemented in Ethereum London upgrade
- Polygon: Lower gas costs, similar mechanism
- Arbitrum: L2 with lower gas costs
Fee Estimation
RUSTfn estimate_gas(transaction: &Transaction, state: &State) -> u64 { let mut gas_used = 21_000; // Base cost // Add costs based on transaction type if transaction.is_contract_creation() { gas_used += 32_000; gas_used += transaction.data.len() as u64 * 200; // Code size } // Simulate execution to get accurate estimate // ... gas_used }
Code Examples
Gas Calculation
Calculate transaction fees
struct Transaction {
gas_limit: u64,
gas_price: u64, // wei per gas
to: String,
value: u64,
data: Vec<u8>,
}
impl Transaction {
fn calculate_fee(&self) -> u64 {
self.gas_limit * self.gas_price
}
fn estimate_gas(&self) -> u64 {
let mut gas = 21_000; // Base transaction cost
// Contract creation costs more
if self.to.is_empty() {
gas += 32_000;
gas += self.data.len() as u64 * 200; // Code deployment
}
// Data costs gas
if !self.data.is_empty() {
gas += self.data.len() as u64 * 16; // Data cost
}
gas
}
}
fn main() {
// Simple transfer
let simple_tx = Transaction {
gas_limit: 21_000,
gas_price: 20_000_000_000, // 20 gwei
to: String::from("0xBob"),
value: 1_000_000_000_000_000_000, // 1 ETH
data: vec![],
};
let fee = simple_tx.calculate_fee();
println!("Transaction fee: {} wei", fee);
println!("Transaction fee: {} ETH", fee as f64 / 1e18);
// Contract creation
let contract_tx = Transaction {
gas_limit: 100_000,
gas_price: 20_000_000_000,
to: String::new(), // Empty = contract creation
value: 0,
data: vec![0; 1000], // Contract code
};
let estimated = contract_tx.estimate_gas();
println!("Estimated gas: {}", estimated);
}Explanation:
Gas calculation determines transaction fees. Simple transfers cost 21,000 gas. Contract creation costs more. The total fee is gas_limit * gas_price. This ensures users pay for the computational resources they use.
EIP-1559 Fee Structure
EIP-1559 fee calculation
struct EIP1559Tx {
max_fee_per_gas: u64,
max_priority_fee_per_gas: u64,
gas_limit: u64,
}
struct Block {
base_fee: u64,
number: u64,
}
fn calculate_fees(tx: &EIP1559Tx, block: &Block) -> (u64, u64) {
// Effective gas price = min(max_fee, base_fee + priority_fee)
let effective_gas_price = tx.max_fee_per_gas.min(
block.base_fee + tx.max_priority_fee_per_gas
);
// Total fee paid
let total_fee = effective_gas_price * tx.gas_limit;
// Base fee portion (burned)
let base_fee_portion = block.base_fee.min(effective_gas_price) * tx.gas_limit;
// Priority fee portion (to miner/validator)
let priority_fee_portion = total_fee - base_fee_portion;
(base_fee_portion, priority_fee_portion)
}
fn adjust_base_fee(previous_base_fee: u64, block_fullness: f64) -> u64 {
// Target: 50% block fullness
let target = 0.5;
if block_fullness > target {
// Increase by 12.5%
previous_base_fee + (previous_base_fee / 8)
} else if block_fullness < target {
// Decrease by 12.5%
previous_base_fee.saturating_sub(previous_base_fee / 8)
} else {
previous_base_fee
}
}
fn main() {
let mut block = Block {
base_fee: 20_000_000_000, // 20 gwei
number: 1,
};
let tx = EIP1559Tx {
max_fee_per_gas: 50_000_000_000, // 50 gwei max
max_priority_fee_per_gas: 2_000_000_000, // 2 gwei priority
gas_limit: 21_000,
};
let (base_fee_burned, priority_fee) = calculate_fees(&tx, &block);
println!("Base fee burned: {} wei", base_fee_burned);
println!("Priority fee to miner: {} wei", priority_fee);
println!("Total fee: {} wei", base_fee_burned + priority_fee);
// Adjust base fee for next block
let new_base_fee = adjust_base_fee(block.base_fee, 0.75); // 75% full
println!("New base fee: {} wei", new_base_fee);
}Explanation:
EIP-1559 splits fees into base fee (burned, adjusts per block) and priority fee (to miner). The base fee adjusts based on block fullness, making fees more predictable. Users set max_fee and max_priority_fee, and pay the minimum needed.
Exercises
Calculate Transaction Fee
Calculate the fee for a transaction!
Starter Code:
struct Transaction {
gas_limit: u64,
gas_price: u64,
}
fn main() {
let tx = Transaction {
gas_limit: 21_000,
gas_price: 20_000_000_000,
};
// Calculate fee
}