8000 Add EIP: Hard limit and cost reduction for transient storage allocation by charles-cooper · Pull Request #9894 · ethereum/EIPs · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add EIP: Hard limit and cost reduction for transient storage allocation #9894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

charles-cooper
Copy link
Contributor

ATTENTION: ERC-RELATED PULL REQUESTS NOW OCCUR IN ETHEREUM/ERCS

--

When opening a pull request to submit a new EIP, please use the suggested template: https://github.com/ethereum/EIPs/blob/master/eip-template.md

We have a GitHub bot that automatically merges some PRs. It will merge yours immediately if certain criteria are met:

  • The PR edits only existing draft PRs.
  • The build passes.
  • Your GitHub username or email address is listed in the 'author' header of all affected PRs, inside .
  • If matching on email address, the email address is the one publicly listed on your GitHub profile.

@github-actions github-actions bot added c-new Creates a brand new proposal s-draft This EIP is a Draft t-core labels Jun 12, 2025
@eth-bot
Copy link
Collaborator
eth-bot commented Jun 12, 2025

File EIPS/eip-7971.md

Requires 1 more reviewers from @g11tech, @lightclient, @SamWilsn

@eth-bot eth-bot added e-consensus Waiting on editor consensus e-review Waiting on editor to review labels Jun 12, 2025
@eth-bot eth-bot changed the title add transient storage hard limit EIP Add EIP: Hard limit and cost reduction for transient storage allocation Jun 12, 2025
@github-actions github-actions bot added the w-ci Waiting on CI to pass label Jun 12, 2025
Copy link
Member
@jochem-brouwer jochem-brouwer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allright, great EIP! My main points are about the actual memory usage (which is likely not 64 bytes per key/value pair but more due to the choosen data structure overhead) and some thoughts/questions about the transient storage size limit.

### Gas Cost Changes

1. The gas cost for `TLOAD` (opcode `0x5c`) is reduced from 100 to `GAS_TLOAD`.
2. The base gas cost for `TSTORE` (opcode `0x5d`) is reduced from 100 to `GAS_TSTORE`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not mention GAS_TSTORE_ALLOCATE which from code is allocated if the key does not exist yet in the key-value map of the transient storage for that specific address.


1. The gas cost for `TLOAD` (opcode `0x5c`) is reduced from 100 to `GAS_TLOAD`.
2. The base gas cost for `TSTORE` (opcode `0x5d`) is reduced from 100 to `GAS_TSTORE`.
3. The gas cost for warm `SLOAD` (opcode `0x54`) is reduced from 100 to `GAS_WARM_SLOAD`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be renamed to use the terminology from https://eips.ethereum.org/EIPS/eip-2929

Suggested change
3. The gas cost for warm `SLOAD` (opcode `0x54`) is reduced from 100 to `GAS_WARM_SLOAD`.
3. The `WARM_STORAGE_READ_COST` parameter from [EIP-2929](https://eips.ethereum.org/EIPS/eip-2929) for `SLOAD` (opcode `0x54`) is changed to `GAS_WARM_SLOAD`.

This EIP already depends on EIP-2929, because without it the concept of "warm SLOAD" does not exist.

type: Standards Track
category: Core
created: 2025-06-12
requires: 1153
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
requires: 1153
requires: 1153, 2929


1. At the beginning of each transaction, initialize a counter `transient_slots_used` to 0.
2. When `TSTORE` is executed:
- If the slot has not been written to during this transaction (across any contract), increment `transient_slots_used`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to imply that if a key is "warm" on any address, it is warm on all addresses? Why is this? The natural way to implement TSTORE/TLOAD I would think is a map from address to the transient storage map, and thus the storage map the key-value map for that address. But this seems to imply there is instead a map from key to a map of addresses where the address point to the value? Is this correct? Or should it be metered within the same address to only check there if the transient storage slot is warm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, maybe the wording is unclear -- a slot is an (address, slot) pair

2. When `TSTORE` is executed:
- If the slot has not been written to during this transaction (across any contract), increment `transient_slots_used`.
- If `transient_slots_used` exceeds `MAX_TRANSIENT_SLOTS`, the transaction MUST exceptionally halt.
3. The counter persists across all message calls in the transaction.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this rule can be removed, because we init it to 0 and reset it to 0 at the end of the transaction (maybe even that reset rule could be removed, but I like that it is explicit)

`MAX_TRANSIENT_SLOTS` of 131072 allows:

- Sufficient slots for typical user applications
- Memory usage bounded to approximately 8 MB per transaction (131072 slots * 64 bytes), which prevents OOM-based denial-of-service attacks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This analysis does not take into account the overhead costs of the data structure (and thus also the revert journaling which should be stored somewhere)

2. Superlinear Pricing: Adds complexity, and still punishes "common" (non-DOS) use cases.
3. No Limit: May allow memory-based DOS attack if transaction-level gas limits change, or if pricing changes in the future.

The benefit of a hard limit is that the resource consumption is bounded in a predictable way even in the presence of other parameter changes in the protocol.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm ok this is actually a good point, maybe we should indeed do a limit. Although if we would raise the max tx gas limit there should be benchmarks and considerations about that increment thus also revisiting this EIP and checking if the new tx gas limit still puts a reasonable bound on the lookup time and the memory usage.


# Check limit
if len(self.unique_slots) > MAX_TRANSIENT_SLOTS:
raise ExceptionalHalt("Transient storage limit exceeded")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what does this do? Does this exit the current message frame and thus "forgetting" about the changes made to the current transient storage? Or does this invalidate the whole transaction?

There could be a situation where call frame A fills the entire transient storage, and then there are some other calls (which could be a deep call stack) which try to TSTORE on a new key. How should this work when the limit has been reached? Because in this situation it is clear that the topmost call frame A is the culprit here, which thus means all CALLs after the current contract cannot write to the storage.

So the question here is should it invalidate the transaction? In the sense that it will rollback all execution changes? Or should it only invalidate the current call frame?

if slot_id not in self.unique_slots:
self.unique_slots.add(slot_id)

self.charge_gas(GAS_TSTORE_ALLOCATE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gas is charged after the operation, it should be done before, since we want to be sure we are paying before any "heavy" computation is done like self.unique_slots.add(slot_id).


## Security Considerations

With `MAX_TRANSIENT_SLOTS` = 131072, maximum memory allocation is bounded to 8MB per transaction (131072 * 64 bytes). Compared to limits under current pricing (100 gas), a 30M gas transaction can allocate up to 300,000 slots (1 8000 9.2 MB). This EIP reduces the maximum allocated amount by 56%.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also does not take into account the memory usage of the actual data structure, which has overhead and there is also the revert logic which should be stored.

Copy link

The commit 050a80d (as a parent of 42ecca8) contains errors.
Please inspect the Run Summary for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c-new Creates a brand new proposal e-consensus Waiting on editor consensus e-review Waiting on editor to review s-draft This EIP is a Draft t-core w-ci Waiting on CI to pass
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0