Opcode Pricing and Refunds
Summary
Monad is a highly optimized system that introduces efficiencies across all dimensions - compute, state access, and bandwidth utilization. However, the multiplier relative to legacy EVM systems is not equal across all dimensions. As a result, some opcode gas price changes are needed so that applications can unlock the full potential of the chain.
To minimize the number of gas price changes, rather than adjusting the gas pricing of almost all opcodes down, Monad instead adjusts a few opcode prices up. This has the same relative effect as discounting almost all opcodes.
The following costs are changed:
- Cold access to state
- Storage slot creation and destruction via
SSTORE
- Storage creation due to
CREATE
andCREATE2
- A few precompiles
All other costs are as on Ethereum; evm.codes is a helpful reference.
These changes are covered formally in the Monad Initial Spec Proposal
Why are changes needed?
The EVM's current pricing model needs adaptation to support a high-performance, low-fee regime. The pricing model assigns a weight (gas amount) to each opcode based on perceived costliness to the system, then charges the user only based on the calculated sum of weights. As resource scarcity changes - and especially in the event of a completely new system - those weightings must be revised.
Storage is a good example. Storage was included in Ethereum's gas model to discourage the proliferation of storage, which must be maintained by every node in perpetuity. In building a new, efficient system, we would like to raise processing throughput by (say) 300x and lower computation costs by (say) 300x, but maintaining the current opcode prices (weightings) would only allow us to do that while also reducing storage costs 300x. However, the long-term storage cost to a node operator of storing 100 GB of data is the basically same regardless of what blockchain client and network they are running. A proper solution would be to decouple storage costs completely from other execution costs, as exists in other high-performance blockchains like Solana or Sui. However, this would have significant impact on wallet UX; re-weighting serves as a less invasive solution.
Storage is also a good example because there should be a strong incentive for people to clean up storage. In Ethereum, that currently doesn't exist, as about 10% of the cost of storage slot creation is refunded upon cleanup. In order to rationalize the market for storage, refund proportions should be significantly adjusted upward.
The changes described in this page make the minimal set of adjustments to allow Monad to deliver high performance and low fees, while minimizing disruption to users and protecting the system against DOS attacks.
Cold access cost
To account for the relatively higher cost of state reads from disk when compared to computation in the Monad execution client, the cost for "cold" account and storage access costs changes:
Access Type | Ethereum | Monad |
---|---|---|
Account | 2600 | 10100 |
Storage | 2100 | 8100 |
The following opcodes are impacted because of the differed gas costs:
- Account access:
BALANCE
,EXTCODESIZE
,EXTCODECOPY
,EXTCODEHASH
,CALL
,CALLCODE
,DELEGATECALL
,STATICCALL
,SELFDESTRUCT
- Storage access:
SLOAD
,SSTORE
Gas costs for warm account access (100 gas) and storage access (100 gas) are the same on Monad as on Ethereum.
Storage cost and refunds
In Ethereum, as a deterrent to state growth, creating a new storage slot costs a large amount of gas. However, refunds for clearing storage slots only give back a small fraction of the cost of creating that storage slot. As a result, the incentive to reduce state growth is limited. (Specifically, in Ethereum, creating a new storage slot costs 20,000 gas, and clearing the storage slot generates a refund of 1900 gas.)
In Monad, SSTORE
gas cost and refunds are changed to address this problem. The cost of
creating a storage slot is relatively higher; however, the refund upon freeing the storage
slot is much closer to the cost. In Monad, the gas cost to create a storage slot has been
increased to 127,900, but the rebate to clear it has been increased to 117,100.
These changes generally rationalize state growth and state reduction, and make storage costs more like an independent variable separate from gas. This is similar to Solana or Sui, where users lock up tokens to create storage, but get most of it back upon clearing that storage.
The best way to adapt to this change is to ensure that your design does not leak storage slots. For example, an order book with 1000 orders should have the same storage if 1,001,000 orders were submitted and 1,000,000 were canceled, or if 1000 orders were submitted.
Details
The Ethereum Yellow Paper describes
the rules for the gas complexity of SSTORE
operations;
these are provided further herein. However, it is easiest to understand the pricing by
decomposing the prices into a few component factors:
- : baseline cost for the operation
- : cost of writing a value to storage
- : cost of creating a non-zero storage slot
- : refund for clearing a storage slot
Here are the charges for each factor in Ethereum, and in Monad:
Component Cost | Description | Ethereum | Monad |
---|---|---|---|
baseline | 100 | 100 | |
write to storage | 2800 | 2800 | |
create non-zero slot | 17,100 | 125,000 | |
refund for clearing slot | 4800 | 120,000 |
Full rules
Inputs:
value
: new value to be stored.current_value
: current value of the storage slot.original_value
: value of the storage slot before the current transaction.
Gas cost for SSTORE
if value == current_value: gas_cost = 100 (m)elif current_value == original_value: if original_value == 0: gas_cost = 127900 (m + w + s) else: gas_cost = 2900 (m + w)else: gas_cost = 100 (m)
Gas refund for SSTORE
gas_refund = 0if value != current_value: if current_value == original_value: if original_value != 0 and value == 0: gas_refund += 120000 (r) else: if original_value != 0: if current_value == 0: gas_refund -= 120000 (-r) elif value == 0: gas_refund += 120000 (r) if value == original_value: if original_value == 0: gas_refund += 127800 (s+w) else: gas_refund += 2800 (w)
The storage refund
The storage refund is calculated as
Notes:
- The gas refund is multiplied by the minimum base fee rather than the full base fee. This is to prevent users from creating storage when base fee is low and destroying it when base fee is high.
- The storage refund doesn't directly reduce the gas of a transaction. Instead, it is applied
separately from the ordinary gas cost (
gas_bid * gas_limit
) of the transaction. - The effective cost of a transaction can be negative
FAQ
Why make this change?
Bandwidth, CPU time, SSD throughput, and state growth are all limited resources that are all measured using gas. It would arguably be better to measure and price each of these resources separately, but doing so would be a major disruption to wallet/transaction UX.
In Monad, the gas throughput is raised tremendously relative to Ethereum - from 1.5Mgas/s to 500Mgas/s, an increase of 333x. As a result, one unit of gas is likely to be much cheaper on Monad.
We don't want, in the process of making gas orders of magnitude cheaper, for there to be an equal reduction on the cost of creating storage. Because while throughput is much higher in Monad, the cost for a Monad node operator to store 1 GB of storage is the same as for an Ethereum node operator.
This is the motivation for raising the cost of storage slot creation. More importantly, however, Monad simultaneously raises the proportion of refund from freeing a storage slot. This rationalizes storage creation and destruction and creates the proper incentive to clean up storage.
Why is the refund for cleaning up storage on Ethereum so low, and how was Monad able to avoid whatever motivated that decision?
Ethereum previously had a more meaningful refund for freeing storage slots, but this was abused by certain gas-sensitive users, who would create filler storage slots during times of low gas prices and destroy them during times of high gas prices to reduce their overall fees paid. This practice, known as using “GasTokens”, ultimately forced Ethereum to implement EIP-3529, which caps the rebate at a much smaller amount to make GasTokens uneconomical. In the process, EIP-3529 substantially reduced the incentive to free storage slots.
The problem of GasTokens (and the inability to pay rational refunds due to the need to discourage GasTokens) is a casualty of unidimensional gas fee markets. State growth (reduction) due to storage creation (deletion) is a long-term persistent cost and should not be metered relative to the congestion pricing of other instantaneous resources like CPU and I/O.
This change addresses this by partially moving storage charges into a separate fee dimension. Specifically, it multiplies the gas_refund
for freeing a storage slot by the min_base_fee
,
while multiplying the gas_cost
of creating a storage slot by the full gas_bid
.
This ensures that no one may reduce their total fees paid using a mechanism like GasTokens, while still preserving the incentive to free unnecessary storage slots. This, in turn, allows the protocol to reprice storage slot creation to a relative gas cost that appropriately charges for state growth.
The main drawback of this approach is that if base_fee
is high compared to min_base_fee
(implying that gas_bid
is also high), users recover a lower proportion of their storage
creation fees as refunds upon deletion. However, we do not expect this to frequently be the
case in the early days of the network. In the future, more comprehensive approaches may be
implemented to separate fees for state growth from other resources.
CREATE
and CREATE2
Similar to SSTORE
, CREATE
and CREATE2
opcodes also use storage for the smart contracts
deployed through them.
The relative cost of storage for CREATE
and CREATE2
is revised as follows:
Component | Ethereum | Monad |
---|---|---|
code_deposit_cost | 200/byte | 1200/byte |
static_gas | 32,000 | 160,000 |
Gas cost formula for CREATE
and CREATE2
minimum_word_size = (size + 31) / 32init_code_cost = 2 * minimum_word_sizehash_cost = 6 * minimum_word_size if is_create2 else 0 # only for CREATE2code_deposit_cost = 1200 * deployed_code_size # raised from 200
static_gas = 160000 # raised from 32000dynamic_gas = init_code_cost + hash_cost + memory_expansion_cost + \ deployment_code_execution_cost + code_deposit_cost
deployment_code_execution_cost
is the cost of whatever opcode is run to deploy the new contractinit_code_cost
is the cost of performing a jumpdest-analysismemory_expansion_cost
is explained here
Precompiles
A few precompiles have been repriced to accurately reflect their relative costs in execution.
Precompile | Address | Ethereum | Monad | Multiplier |
---|---|---|---|---|
ecRecover | 0x01 | 3000 | 6000 | 2 |
ecAdd | 0x06 | 150* | 300* | 2 |
ecMul | 0x07 | 6000* | 30,000* | 5 |
ecPairing | 0x08 | 45,000* | 225,000* | 5 |
blake2f | 0x09 | 2 | ||
point eval | 0x0a | 50,000 | 200,000 | 4 |
∗: Per input/operation, as defined in the respective precompile specification