The gas module implements execution cost tracking to prevent infinite loops and expensive operations.
GasCosts Struct
Defines gas costs for all VM operations.
Constants
Tier 1: Very Cheap (Simple Register Operations)
No cost operations: HALT, NOP, RET, REVERT
Simple register operations: ADD, SUB, AND, OR, XOR, MOV, EQ, NE, LT, GT, LE, GE, ISZERO, LOADI, MSIZE, CALLER, CALLVALUE, ADDRESS, BLOCKNUMBER, TIMESTAMP, GAS, LOG
Tier 2: Cheap (Complex ALU Operations)
More complex operations: MUL, comparison operations
Tier 3: Medium (Division, Shifts)
Expensive ALU operations: DIV, MOD, SHL, SHR
Tier 4: Memory Operations
Cost to read from memory: LOAD8, LOAD64, MCOPY
Cost to write to memory: STORE8, STORE64
Cost per byte when expanding memory
Tier 5: Storage (Expensive!)
Cost to read from persistent storage
Cost to write to an empty storage slot
Cost to overwrite an existing storage slot
Control Flow
Cost for JUMP and JUMPI operations
GasMeter Struct
Tracks gas consumption during execution.
pub struct GasMeter {
remaining: u64,
used: u64,
}
Amount of gas still available
Amount of gas consumed so far
Methods
new
Create a new gas meter with the specified limit.
pub fn new(limit: u64) -> Self
Maximum gas available for execution
New gas meter with remaining=limit, used=0
Example:
use minichain_vm::GasMeter;
let mut gas = GasMeter::new(100_000);
assert_eq!(gas.remaining(), 100_000);
assert_eq!(gas.used(), 0);
consume
Consume gas, returning an error if insufficient.
pub fn consume(&mut self, amount: u64) -> Result<(), VmError>
Ok(()) if successful, Err(VmError::OutOfGas) if insufficient gas
Example:
use minichain_vm::{GasMeter, GasCosts};
let mut gas = GasMeter::new(10);
// Successful consumption
gas.consume(GasCosts::BASE)?; // Consumes 2
assert_eq!(gas.remaining(), 8);
// Failed consumption
let result = gas.consume(100);
assert!(result.is_err());
remaining
Get the amount of gas still available.
pub fn remaining(&self) -> u64
used
Get the total amount of gas consumed.
pub fn used(&self) -> u64
Example:
let mut gas = GasMeter::new(1000);
gas.consume(100)?;
gas.consume(50)?;
assert_eq!(gas.used(), 150);
assert_eq!(gas.remaining(), 850);
memory_expansion_cost
Calculate gas cost for expanding memory.
pub fn memory_expansion_cost(current_size: usize, new_size: usize) -> u64
Current memory size in bytes
Target memory size in bytes
Additional gas required for expansion (0 if new_size is less than or equal to current_size)
Example:
use minichain_vm::GasMeter;
// Expanding from 1KB to 2KB
let cost = GasMeter::memory_expansion_cost(1024, 2048);
assert_eq!(cost, 1024); // 1 gas per byte
// No expansion needed
let cost = GasMeter::memory_expansion_cost(1024, 512);
assert_eq!(cost, 0);
Gas Cost Table
| Operation Category | Opcode Examples | Gas Cost |
|---|
| Control | HALT, NOP, RET, REVERT | 0 |
| Simple ALU | ADD, SUB, AND, OR, XOR, MOV | 2 |
| Comparisons | EQ, NE, LT, GT, LE, GE | 2 |
| Complex ALU | MUL | 3 |
| Division | DIV, MOD | 5 |
| Shifts | SHL, SHR | 5 |
| Memory Read | LOAD8, LOAD64 | 3 |
| Memory Write | STORE8, STORE64 | 3 |
| Memory Copy | MCOPY | 3 |
| Jumps | JUMP, JUMPI | 8 |
| Storage Read | SLOAD | 100 |
| Storage Write (new) | SSTORE | 20,000 |
| Storage Write (update) | SSTORE | 5,000 |
| External Call | CALL | 700 |
| Context | CALLER, ADDRESS, etc. | 2 |
| Immediates | LOADI | 2 |
| Debug | LOG | 2 |
Usage Examples
Basic Gas Metering
use minichain_vm::{GasMeter, GasCosts, VmError};
let mut gas = GasMeter::new(1000);
// Execute a series of operations
gas.consume(GasCosts::BASE)?; // ADD: -2
gas.consume(GasCosts::BASE)?; // MOV: -2
gas.consume(GasCosts::MID)?; // DIV: -5
gas.consume(GasCosts::MEMORY_WRITE)?; // STORE64: -3
println!("Gas used: {}", gas.used()); // 12
println!("Gas remaining: {}", gas.remaining()); // 988
Handling Out of Gas
use minichain_vm::{GasMeter, GasCosts, VmError};
let mut gas = GasMeter::new(5);
// First operation succeeds
gas.consume(GasCosts::BASE)?; // 2 gas
// Second operation fails
match gas.consume(GasCosts::MID) {
Err(VmError::OutOfGas { required, remaining }) => {
println!("Out of gas! Need {}, have {}", required, remaining);
// Output: "Out of gas! Need 5, have 3"
}
_ => unreachable!(),
}
Storage Gas Costs
use minichain_vm::{GasMeter, GasCosts};
let mut gas = GasMeter::new(50_000);
// Writing to empty slot (expensive)
gas.consume(GasCosts::SSTORE_SET)?; // -20,000
assert_eq!(gas.remaining(), 30_000);
// Updating existing slot (cheaper)
gas.consume(GasCosts::SSTORE_RESET)?; // -5,000
assert_eq!(gas.remaining(), 25_000);
// Reading is much cheaper
gas.consume(GasCosts::SLOAD)?; // -100
assert_eq!(gas.remaining(), 24_900);
Memory Expansion
use minichain_vm::GasMeter;
let current_size = 0;
let new_size = 1024;
let expansion_cost = GasMeter::memory_expansion_cost(current_size, new_size);
assert_eq!(expansion_cost, 1024); // 1 gas per byte
// Incremental expansion
let cost = GasMeter::memory_expansion_cost(1024, 2048);
assert_eq!(cost, 1024); // Only pay for the new bytes
Estimating Execution Cost
use minichain_vm::{GasMeter, GasCosts};
fn estimate_loop_cost(iterations: u64) -> u64 {
let per_iteration =
GasCosts::ADDI + // Counter increment: 2
GasCosts::BASE + // Comparison: 2
GasCosts::JUMP; // Loop jump: 8
iterations * per_iteration
}
let cost = estimate_loop_cost(1000);
println!("Estimated cost for 1000 iterations: {} gas", cost);
// Output: "Estimated cost for 1000 iterations: 12000 gas"
let mut gas = GasMeter::new(cost);
assert!(gas.remaining() >= cost);
Complex Program Gas Tracking
use minichain_vm::{Vm, GasCosts};
use minichain_core::Address;
// Bytecode: Load value, compute, store to storage
let bytecode = vec![
// ... bytecode here ...
];
// Calculate minimum gas needed
let min_gas =
2 * GasCosts::LOADI + // 2 immediate loads
GasCosts::BASE * 3 + // 3 ALU operations
GasCosts::SSTORE_SET + // Storage write
GasCosts::ZERO; // HALT
let mut vm = Vm::new(
bytecode,
min_gas, // Provide exact amount needed
Address::zero(),
Address::zero(),
0,
);
let result = vm.run()?;
println!("Actual gas used: {}", result.gas_used);
println!("Estimated: {}", min_gas);
Gas Optimization Tips
Use Cheaper Operations
// Expensive: DIV (5 gas)
DIV R0, R1, R2
// Cheaper: SHR for division by powers of 2 (5 gas, but simpler)
LOADI R2, 1
SHR R0, R1, R2 // Divide by 2
Minimize Storage Operations
// Bad: Multiple storage writes (20,000 gas each)
SSTORE key1, value1
SSTORE key2, value2
SSTORE key3, value3
// Total: 60,000 gas
// Good: Pack values and write once
SHL combined, value1, 16
OR combined, combined, value2
SSTORE key, combined
// Total: ~20,010 gas
Batch Memory Operations
// Use MCOPY instead of individual loads/stores
MCOPY dest, src, 64 // 3 gas for 64 bytes
// vs. 8 individual LOAD64/STORE64 operations
// 8 * (3 + 3) = 48 gas
Best Practices
- Always check gas limits before executing untrusted code
- Use SSTORE_RESET when possible (4x cheaper than SSTORE_SET)
- Cache storage reads in registers to avoid repeated SLOAD
- Prefer register operations over memory operations
- Be careful with loops - they can consume gas quickly
- Estimate gas requirements ahead of time for better UX