Gas Limit, Gas Used, and the Fee Formula
The previous page established what gas measures: a single unit that prices every operation the shared computer can perform, from a cheap addition to a state-growing storage write. That gives us a number — a count of work — for any transaction. This page turns that count into money and into a safety guarantee.
To do that we need four quantities and one formula. The formula is almost insultingly simple: you pay for the work you did, at a price per unit of work. The interesting part is everything the formula doesn’t say — what happens when you guessed the budget wrong, why a failed transaction still costs you, and how the same idea, applied to a whole block, is what keeps every node on Earth able to keep up. All of that is transaction-level accounting, and by the end of this page you’ll be able to read a receipt line by line.
Four quantities, one formula
Section titled “Four quantities, one formula”Every transaction carries two numbers you choose before you send it, and produces two numbers after it runs.
you set, up front: the chain reports, after execution: ───────────────── ────────────────────────────────── gas_limit (a budget) gas_used (what execution consumed) price_per_gas (what you'll pay) fee = gas_used × price_per_gasgas_limit— the ceiling you authorise for this transaction. It is a budget: “do not spend more than this many units of work on my behalf.” It is not a promise to spend it all.price_per_gas— how much native currency (wei, the smallest unit of ETH) you’ll pay for each unit of gas. On modern Ethereum this splits into a base fee and a priority fee — the subject of EIP-1559 — but for the accounting on this page a single per-unit price is all we need.gas_used— the amount of gas execution actually consumed. Known only after the transaction runs, because it depends on which branches the code took and how many storage slots it touched.fee— what leaves your account, given by the formula:
fee = gas_used × price_per_gasThe single most important word in that formula is used. When a transaction
succeeds, you pay for gas_used, not for gas_limit. The limit was a
ceiling; the fee is metered off the actual meter reading. If you budgeted
100,000 gas and execution consumed 51,000, you pay for 51,000 and the remaining
49,000 is refunded — it never leaves your account.
This is exactly what our companion mini-EVM reports. In
ethmini, a successful run returns an Execution
whose gas_used is computed as budget minus what’s left on the meter:
// rust/ethmini/src/vm.rs — on a clean STOP:Op::Stop => { return Ok(Execution { gas_used: gas_limit - gas, // what you actually burned, not the limit return_value: None, storage: working, });}gas here is the meter, initialised to gas_limit and decremented before every
opcode. When the program halts cleanly, whatever it didn’t spend is still
sitting in gas, and gas_limit - gas is precisely gas_used. You are charged
for the difference, and nothing more.
Running out of gas: a paid failure
Section titled “Running out of gas: a paid failure”The comfortable case is a transaction that finishes inside its budget. The whole reason gas exists, though, is the uncomfortable case: a transaction that hits the ceiling before it finishes. As the previous part established, “does this program halt?” is undecidable — so the chain can’t screen out a runaway program by inspection. Instead it runs the program with a countdown, and when the countdown reaches zero it forcibly halts execution. That forced halt is out-of-gas.
Two things happen the instant the meter hits zero, and it is vital to keep them separate:
- The transaction reverts. Every state change the execution made — every storage write, every value moved inside the call — is undone, as if the transaction had never touched the world. This is all-or-nothing: you do not get a half-finished contract.
- The gas is still consumed and still paid. The work the node did before the
halt was real work; the node is compensated for it. An out-of-gas transaction
burns its entire budget — you pay for
gas_limit, not for the smaller amount that ran before the crash.
That second point surprises people, so it’s worth stating plainly: a reverted transaction is not a free retry. You paid for the computation the network performed on your behalf, even though the result was thrown away. The chain has to charge for it — otherwise an attacker could submit endless expensive transactions that “fail” and cost them nothing, and every node would grind through the work for free.
ethmini models this exactly. When the meter can’t afford the next opcode, it
raises OutOfGas and reports used = gas_limit — the whole budget:
// rust/ethmini/src/vm.rs — the meter refuses to go negative:let charge = |gas: &mut u64, cost: u64| -> Result<(), VmError> { if *gas < cost { Err(VmError::OutOfGas { used: gas_limit, // an exceptional halt burns the ENTIRE budget limit: gas_limit, }) } else { *gas -= cost; Ok(()) }};And the revert is structural, not a cleanup step you have to remember. The VM runs against a clone of the contract’s storage and only hands the new storage back on a clean halt. If it throws, the caller simply keeps the original — the changes were never committed in the first place:
// rust/ethmini/src/state.rs — commit on success, drop on revert:match vm::run(&code, &storage, *gas_limit) { Ok(exec) => { self.account_mut(*to).storage = exec.storage; // commit the new storage // ... Receipt { status: Success, gas_used: exec.gas_used, .. } } Err(reason) => Err(Reverted { gas_used: *gas_limit, // an exceptional halt burns the whole gas budget reason, }),}Notice the asymmetry between the two success/failure branches:
| Outcome | State changes | gas_used charged | Nonce consumed? |
|---|---|---|---|
Success (STOP/RETURN) | committed | gas_limit - gas (actual) | yes |
| Out-of-gas (revert) | undone | gas_limit (the whole budget) | yes |
| Rejected (bad nonce, can’t afford value) | none — never included | nothing | no |
The third row is a different animal entirely and worth pinning down. A
rejection — an unknown sender, a stale nonce, a balance too small to cover
the value being sent — means the transaction was never valid to include. It never
runs, so it costs no gas and moves no state, not even the nonce. Compare that to a
revert, where the transaction was valid, was included, did run, burned
gas, and consumed its nonce so it can’t be replayed — it just accomplished
nothing. “Rejected” and “reverted” sound alike and are frequently confused; the
ethmini design keeps them as two distinct error kinds (EthError vs.
VmError) precisely to make the line unmistakable. There’s more on that split on
the smart contracts and state pages.
Under the hood — why the whole budget, not just what ran?
Section titled “Under the hood — why the whole budget, not just what ran?”You might expect out-of-gas to charge only for the opcodes that executed before
the halt — say, 9,998 gas out of a 10,000 budget. Real EVM (and ethmini)
charges the full limit instead. The reason is that the alternative creates a
perverse incentive.
If a failed transaction only cost what it consumed, an attacker could set a huge limit, do enormous work, and arrange to run out one opcode from the end — paying only for the fraction that “counted” and stiffing the network for the rest. By charging the entire authorised budget on an exceptional halt, the protocol makes the cost of a failure equal to the cost you committed to. You told the network “you may spend up to this much on me”; if your program blows the budget, you honour that commitment in full. The meter is a promise, and out-of-gas is the protocol collecting on it.
Two budgets that share a word: the block gas limit
Section titled “Two budgets that share a word: the block gas limit”So far “gas limit” has meant your transaction’s ceiling. There is a second, completely different ceiling with almost the same name, and conflating the two is one of the most common beginner mistakes.
- The per-transaction gas limit is a budget you set on your transaction. It bounds how much work one transaction may do.
- The block gas limit is a protocol-wide ceiling on the sum of
gas_usedacross every transaction packed into a single block. It bounds how much work the whole block may demand of the network.
┌─────────────────────── block gas limit (e.g. ~30–60M gas) ──────────────────────┐ │ │ │ ┌── tx A ──┐ ┌──── tx B ────┐ ┌─ tx C ─┐ ┌──── tx D ────┐ ┌ ... ┐ │ │ │ 21,000 │ │ 180,000 │ │ 55,000 │ │ 240,000 │ │ │ (unused) │ │ └──────────┘ └──────────────┘ └────────┘ └──────────────┘ └─────┘ │ │ each tx's own gas_limit caps its slot; Σ gas_used ≤ block gas limit │ └──────────────────────────────────────────────────────────────────────────────────┘The two limits enforce different guarantees. Your per-transaction limit protects you (from a buggy contract draining your wallet) and protects the network (from your transaction running forever). The block gas limit protects everyone: it guarantees that no matter what transactions a block contains, processing the entire block takes a bounded amount of work. Without it, a single block could demand hours of computation from every node, and nodes that couldn’t keep up would fall off the chain — a network that only the most powerful machines could follow is a centralised network. The block limit is what keeps the shared computer runnable on ordinary hardware, which is what keeps it decentralised.
A transaction whose own gas limit exceeds the remaining space in a block simply
can’t fit and is left for a later block. And no valid block can ever have its
transactions’ total gas_used exceed the block gas limit — that is a consensus
rule every node checks, so a block that broke it would be rejected by the whole
network.
Why a too-low limit is dangerous and a too-high one is safe
Section titled “Why a too-low limit is dangerous and a too-high one is safe”Here is the practical asymmetry that falls straight out of the rules above, and it’s the reason wallets do gas estimation for you.
- Set the limit too low and your transaction runs, does real work, hits the ceiling partway through, and reverts — but you still pay. You get the worst outcome: a fee charged (the whole budget) and nothing accomplished. Your storage write, your token transfer, your swap — all undone, all paid for.
- Set the limit too high and nothing bad happens. Execution uses whatever it
actually needs, you’re charged for that
gas_used, and every unit of the surplus is refunded. The extra headroom cost you exactly zero.
limit too LOW: [████████████░] boom → OutOfGas → REVERT, pay full limit, gain nothing limit too HIGH: [██████░░░░░░░] STOP → success, pay gas_used, refund the rest ✓ ^^^^^^ used ^^^^^^ refundedThe asymmetry is stark: undershooting the limit is a paid failure, overshooting
it is free insurance. So the sane strategy is to estimate the real cost and add
comfortable headroom on top. But you can’t set the limit absurdly high and forget
it either — your limit still has to fit within the block’s remaining space, and
if your price-per-gas is high, a wallet may warn you that a huge limit could, in
a worst case, authorise a large fee. The real skill is estimating gas_used
accurately, which is genuinely hard because it depends on execution paths and
current state. That’s a page of its own:
fee estimation and blockspace economics.
The architect’s lens
Section titled “The architect’s lens”The per-transaction gas limit is a small idea with outsized consequences — a user-set budget on untrusted computation. It’s worth viewing through the five questions.
- Why does it exist? Because the chain runs your code but can’t know in advance how much work it will take, and can’t let it run unbounded. The limit is you pre-committing a ceiling so the network can safely run your program with a countdown that’s guaranteed to terminate.
- What problem does it solve? It converts the undecidable “will this halt?” into the decidable “will this halt within N gas?”, while also protecting your wallet from a buggy or malicious contract spending more than you authorised.
- What are the trade-offs? You must guess the right budget before you know the answer. Guess too low and you pay for a revert that accomplished nothing; guess too high and you (usually) lose nothing but the mental overhead — motivating a whole layer of estimation tooling.
- When should I avoid it? You can’t; it’s mandatory on every transaction. But you should avoid setting it by hand to a tight value — let a wallet estimate and add headroom, because the failure mode of undershooting is a paid revert.
- What breaks if I remove it? Without a per-transaction limit, a single transaction could loop forever or grow state without bound, and there’d be no cap on the fee a buggy contract could drain from your account. Combined with removing the block limit, blocks could demand unbounded work and only the fastest machines could follow the chain — decentralisation collapses.
Check your understanding
Section titled “Check your understanding”- Distinguish
gas_limitfromgas_used. Which one do you set, which one is reported, and which one is your fee actually computed from when a transaction succeeds? - Write the fee formula, and use it to compute the fee (in ETH) of a plain transfer that used 21,000 gas at 20 gwei per gas.
- A transaction runs out of gas. What happens to (a) its state changes, (b) the
gas, and (c) how much of the budget is charged? Relate each to a specific
behaviour in
ethmini. - Explain the difference between the block gas limit and the per-transaction gas limit — same word, two budgets. What does each one guarantee?
- Why is setting your gas limit too low dangerous while setting it too high is safe? What does this asymmetry imply about how you should choose a limit?
Show answers
- You set
gas_limitup front — a ceiling / budget for the transaction. The chain reportsgas_usedafter execution — the amount actually consumed. On success your fee is computed fromgas_used(fee = gas_used × price_per_gas), and any unused gas is refunded; you do not pay for the full limit. Inethminithis isgas_limit - gason a cleanSTOP/RETURN. fee = gas_used × price_per_gas. With 21,000 gas at 20 gwei: 21,000 × 20 gwei = 420,000 gwei = 420,000 × 10⁻⁹ ETH = 0.00042 ETH.- (a) All state changes are undone — the transaction reverts, all-or-nothing.
(b) The gas is still consumed and paid (the work was real). (c) The entire
budget (
gas_limit) is charged, not just what ran. Inethmini, thechargeclosure raisesOutOfGas { used: gas_limit, .. }, the VM ran against a clone of storage so the writes were never committed, andstate.rsreturnsReverted { gas_used: gas_limit }. - The per-transaction limit is a ceiling you set on your transaction,
bounding how much work it may do (protecting your wallet and forcing your
program to terminate). The block gas limit is a protocol-wide ceiling on the
sum of
gas_usedacross all transactions in a block, guaranteeing every node can process any block in bounded time — which is what keeps the chain runnable on ordinary hardware, and therefore decentralised. - Too low: execution hits the ceiling, reverts, undoes everything, but you
still pay the full budget — a paid failure that accomplished nothing. Too
high: you’re charged only for actual
gas_usedand the surplus is refunded, so the headroom is effectively free. The asymmetry means you should estimate the real cost and add comfortable headroom, never set the limit tight — which is why wallets do gas estimation for you.