LPsLux Proposals
EVM & Execution
LP-3650

Dynamic Gas Pricing

Final

Adaptive gas pricing and limits that respond to network congestion

Category
Core
Created
2025-01-15

LP-3650: Dynamic Gas Pricing

Status: Final Type: Standards Track Category: Core Created: 2025-01-15

Status note (2026-05-18): LP-3650 is Lux-native. The dynamic-fee formulae draw on public prior art (Ethereum EIP-1559 base-fee mechanism extended with exponential demand tracking). LP-3650 is live on Lux from genesis — no per-upgrade timestamp gate.

Abstract

This Lux Proposal introduces adaptive gas pricing and limits that respond to network congestion, improving user experience and network stability on the Lux C-Chain. The mechanism is Lux-native and is live from genesis.

Motivation

Static gas limits and pricing mechanisms struggle to handle varying network loads. LP-176 introduces dynamic adjustments that:

  1. Prevent Spam: Higher prices during congestion deter spam attacks
  2. Improve UX: Predictable costs during normal operation
  3. Optimize Throughput: Dynamic limits maximize block space utilization
  4. Maintain Stability: Smooth price transitions prevent shock

Specification

Gas Price Discovery

Base fee adjusts exponentially based on block fullness:

newBaseFee = currentBaseFee * (1 + (gasUsed - target) / (target * denominator))

Parameters:

  • MinBaseFee: 25 gwei (minimum base fee)
  • MaxBaseFee: 1000 gwei (maximum base fee)
  • BaseFeeChangeDenominator: 8 (controls adjustment speed)
  • ElasticityMultiplier: 2 (target vs max ratio)

Dynamic Gas Limits

Block gas limit adjusts based on sustained demand:

targetGasPerSecond = baseTarget * e^(excessTarget / conversionRate)
maxGasPerBlock = targetGasPerSecond * ElasticityMultiplier

Parameters:

  • MinTargetPerSecond: 1,000,000 gas/sec
  • MaxTargetChangeRate: 1024 (max adjustment per block)
  • TargetToMax: 2 (max is 2x target)
  • TimeToFillCapacity: 5 seconds

Price Doubling Behavior

Under sustained load, prices double approximately every 60 seconds:

time_to_double = ln(2) * conversionRate / demand_rate

Rationale

Design Decisions

1. Exponential Adjustment: Linear adjustments don't respond quickly enough to sudden demand changes. Exponential scaling provides rapid response to congestion while maintaining stability during normal operation.

2. Minimum Base Fee: A floor of 25 gwei prevents zero-cost spam while remaining affordable for normal users. This balances accessibility with attack resistance.

3. Elasticity Multiplier of 2x: Allowing blocks up to 2x target provides burst capacity for legitimate demand spikes while keeping long-term averages at target.

4. 60-Second Price Doubling: This rate is aggressive enough to deter sustained attacks but slow enough to give users time to react and adjust their gas prices.

Alternatives Considered

  • Fixed EIP-1559: Rejected due to inability to adapt to Lux's multi-chain architecture
  • Linear Scaling: Rejected as too slow to respond to attacks
  • Auction-Based: Rejected due to complexity and poor UX
  • Time-Weighted Average: Rejected as it allows manipulation through timing

Tooling Compatibility

LP-3650 parameter shapes are exposed via standard EVM RPC (eth_baseFee, eth_feeHistory) so cross-chain gas-estimation tooling and wallets function without Lux-specific adapters.

Implementation

Location

Primary Implementation: node/vms/evm/lp176/

Key files:

  • lp176.go - Core math and state tracking
  • lp176_test.go - Unit tests and verification

Plugin Interface: geth/plugin/evm/upgrade/lp176/

Files:

  • params.go - Configuration parameters

Integration Points

  1. Block Building (miner/worker.go):

    • Calculates dynamic gas limit before building block
    • Updates target excess after each block
  2. Fee Calculation (core/state_processor.go):

    • Applies base fee to transactions
    • Validates fee sufficiency
  3. Configuration (params/config.go):

    • Network-specific activation timestamps
    • Parameter overrides for testing

Activation

LP-176 activates via network upgrade at a specified timestamp:

type ChainConfig struct {
    // ... existing fields
    LP176Timestamp *uint64 `json:"lp176Timestamp,omitempty"`
}

Test Cases

Unit Tests

Coverage: 100% of core logic

Test cases:

  • Target calculation under various loads
  • Excess adjustment boundary conditions
  • Price doubling verification
  • Min/max constraint enforcement
// Test: Base fee adjustment
func TestBaseFeeAdjustment(t *testing.T) {
    cases := []struct {
        name           string
        currentBaseFee uint64
        gasUsed        uint64
        gasTarget      uint64
        expected       uint64
    }{
        {"below target", 100, 5000000, 10000000, 94},    // 6% decrease
        {"at target", 100, 10000000, 10000000, 100},     // no change
        {"above target", 100, 15000000, 10000000, 106},  // 6% increase
        {"at min", 25, 0, 10000000, 25},                 // stays at min
        {"approaching max", 950, 20000000, 10000000, 1000}, // caps at max
    }

    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            result := calculateNewBaseFee(tc.currentBaseFee, tc.gasUsed, tc.gasTarget)
            require.Equal(t, tc.expected, result)
        })
    }
}

// Test: Dynamic gas limit
func TestDynamicGasLimit(t *testing.T) {
    config := &LP176Config{
        MinTargetPerSecond:   1_000_000,
        MaxTargetChangeRate:  1024,
        TargetToMax:          2,
        TimeToFillCapacity:   5,
    }

    // Normal conditions
    target, max := calculateGasLimits(config, 0)
    require.Equal(t, uint64(5_000_000), target)
    require.Equal(t, uint64(10_000_000), max)

    // Sustained load (high excess)
    target, max = calculateGasLimits(config, 1_000_000)
    require.Greater(t, target, uint64(5_000_000))
    require.Equal(t, target*2, max)
}

// Test: Price doubling time
func TestPriceDoublingTime(t *testing.T) {
    // Under sustained 100% load, price should double in ~60 seconds
    startPrice := uint64(100)
    price := startPrice
    blocks := 0

    for price < startPrice*2 {
        price = calculateNewBaseFee(price, 20_000_000, 10_000_000)
        blocks++
    }

    // At 2-second blocks, 60 seconds = 30 blocks
    require.InDelta(t, 30, blocks, 5)
}

Integration Tests

Location: tests/e2e/c/dynamic_fees.go

Scenarios:

  • Normal load (stable prices)
  • Sustained congestion (price escalation)
  • Spike recovery (smooth de-escalation)
  • Edge cases (min/max boundaries)

Performance Benchmarks

Results:

  • Target calculation: < 1μs
  • State update: < 100ns
  • Zero allocation overhead

Backwards Compatibility

LP-176 is a consensus-breaking change requiring coordinated network upgrade. Pre-LP-176 blocks use static gas limits and EIP-1559 base fee.

Migration: Smooth transition at activation timestamp with no state migration required.

Security Considerations

Attack Vectors

  1. Sustained Load Attack: Mitigated by exponential price growth
  2. Oscillation Attack: Prevented by smooth adjustment curves
  3. State Bloat: Dynamic limits prevent excessive state growth

Audits

  • Internal security review: 2025-01-10
  • External audit: Pending

References

Copyright (C) 2025 Lux Partners Limited. All rights reserved.