Skip to main content

GasRefund

GasRefund represents gas refunded after transaction execution. Post-London (EIP-3529), refunds are capped at gasUsed / 5.

Type Definition

type GasRefundType = bigint & { readonly [brand]: "GasRefund" };
Branded bigint representing gas refund (always non-negative).

Gas Refund Mechanics

Pre-London (Before EIP-3529)

Large refunds were possible from:
  • SSTORE clear: 15,000 gas (clearing storage slot)
  • SELFDESTRUCT: 24,000 gas (destroying contract)
Problem: Caused “gas tokens” - exploiting refunds for profit.

Post-London (EIP-3529)

Refunds significantly reduced:
  • Cap: Maximum refund = gasUsed / 5 (20% of gas used)
  • SSTORE clear: Still generates 15,000 refund, but capped
  • SELFDESTRUCT: No longer gives refund
Effect: Eliminated gas token exploits, simplified gas economics.

API

Constructors

from(value)

Create GasRefund from number, bigint, or string.
import { GasRefund } from '@tevm/primitives';

// SSTORE clear refund
const refund = GasRefund.from(15000n);

// No refund
const noRefund = GasRefund.from(0n);

// From number
const ref2 = GasRefund.from(15000);
Throws: InvalidFormatError if value is negative.

Conversions

toNumber(refund)

Convert to number.
const refund = GasRefund.from(15000n);
GasRefund.toNumber(refund); // 15000

toBigInt(refund)

Convert to bigint (identity operation).
const refund = GasRefund.from(15000n);
GasRefund.toBigInt(refund); // 15000n

toHex(refund)

Convert to hex string.
const refund = GasRefund.from(15000n);
GasRefund.toHex(refund); // "0x3a98"

Comparisons

equals(ref1, ref2)

Check equality.
GasRefund.equals(15000n, 15000n); // true
GasRefund.equals(15000n, 20000n); // false

Utilities

cappedRefund(refund, gasUsed)

Apply EIP-3529 refund cap.
const refund = GasRefund.from(15000n); // SSTORE clear
const gasUsed = 50000n;

// Cap = 50000 / 5 = 10000
const capped = GasRefund.cappedRefund(refund, gasUsed);
// 10000n (reduced from 15000n)
Post-London enforcement: min(refund, gasUsed / 5)

Usage Examples

Calculate Effective Gas Cost

import { GasUsed, GasRefund } from '@tevm/primitives';

const gasUsed = GasUsed.from(60000n);
const gasPrice = 20_000_000_000n; // 20 gwei

// Transaction clears 2 storage slots
const totalRefund = GasRefund.from(30000n); // 2 × 15000

// Apply EIP-3529 cap
const capped = GasRefund.cappedRefund(totalRefund, GasUsed.toBigInt(gasUsed));
// 12000n (60000 / 5)

// Calculate net cost
const grossCost = GasUsed.calculateCost(gasUsed, gasPrice);
const refundValue = GasRefund.toBigInt(capped) * gasPrice;
const netCost = grossCost - refundValue;

console.log(`Gross cost: ${grossCost} Wei`);
console.log(`Refund: ${refundValue} Wei`);
console.log(`Net cost: ${netCost} Wei`);

Compare Pre/Post London

import { GasRefund } from '@tevm/primitives';

const sstoreClear = GasRefund.from(15000n);
const gasUsed = 50000n;

// Pre-London: Full refund
const preLondon = GasRefund.toBigInt(sstoreClear);
console.log(`Pre-London refund: ${preLondon}`); // 15000

// Post-London: Capped refund
const postLondon = GasRefund.cappedRefund(sstoreClear, gasUsed);
console.log(`Post-London refund: ${postLondon}`); // 10000

const reduction = preLondon - GasRefund.toBigInt(postLondon);
console.log(`Reduction: ${reduction} gas (${(reduction / preLondon * 100).toFixed(0)}%)`);

Multiple Storage Clears

import { GasRefund } from '@tevm/primitives';

// Transaction clears 5 storage slots
const slotsCleared = 5;
const refundPerSlot = 15000n;
const totalRefund = GasRefund.from(BigInt(slotsCleared) * refundPerSlot);
// 75000n total potential refund

const gasUsed = 100000n;

// Apply cap
const capped = GasRefund.cappedRefund(totalRefund, gasUsed);
// 20000n (100000 / 5)

console.log(`Potential refund: ${GasRefund.toBigInt(totalRefund)}`); // 75000
console.log(`Capped refund: ${capped}`); // 20000
console.log(`Refund lost: ${75000n - capped}`); // 55000

Refund Sources

SSTORE Operations

OperationGas CostRefund (Pre-London)Refund (Post-London)
Zero → Non-zero20,00000
Non-zero → Non-zero5,00000
Non-zero → Zero5,00015,000Capped at gasUsed/5
Non-zero → Same100 (warm)00

SELFDESTRUCT

EraGas CostRefund
Pre-London5,00024,000
Post-London5,0000

EIP-3529 Rationale

Problems with Old Refund System

  1. Gas Tokens: Exploited refunds for profit
    • Store data when gas cheap
    • Clear data when gas expensive
    • Get refund at high gas price
  2. Block Variability: Large refunds caused unpredictable block gas usage
  3. Complexity: Made gas economics hard to reason about

Post-3529 Benefits

  1. No Gas Tokens: Capped refunds eliminate exploit
  2. Predictable Costs: Users pay closer to actual gas used
  3. Simpler Economics: Easier to estimate transaction costs
  4. Faster State Growth: Reduced incentive to keep unnecessary storage

Refund Cap Formula

effectiveRefund = min(totalRefund, gasUsed / 5)
Where:
  • totalRefund: Sum of all refunds from SSTORE operations
  • gasUsed: Actual gas consumed by transaction
  • Division rounds down (integer division)

Usage Patterns

Check if Refund is Capped

import { GasRefund } from '@tevm/primitives';

function isRefundCapped(refund: bigint, gasUsed: bigint): boolean {
  const refundObj = GasRefund.from(refund);
  const capped = GasRefund.cappedRefund(refundObj, gasUsed);
  return GasRefund.toBigInt(capped) < refund;
}

const refund = 15000n;
const gasUsed = 50000n;
console.log(isRefundCapped(refund, gasUsed)); // true

Calculate Maximum Possible Refund

import { GasRefund } from '@tevm/primitives';

function maxRefund(gasUsed: bigint): bigint {
  return gasUsed / 5n;
}

const gasUsed = 100000n;
console.log(`Max refund: ${maxRefund(gasUsed)}`); // 20000

London Hard Fork

Date: August 5, 2021 (Block 12,965,000) Changes:
  • EIP-3529: Refund reduction
  • EIP-1559: Fee market change
  • EIP-3198: BASEFEE opcode
  • EIP-3541: Reject new contracts starting with 0xEF
Impact: Most transactions now have zero or minimal refunds.

See Also