Skip to content

Commit

Permalink
Initial commit for the new "Global Warming" EIP
Browse files Browse the repository at this point in the history
  • Loading branch information
forshtat committed Oct 14, 2023
1 parent 1c4afdf commit d7dffe0
Showing 1 changed file with 333 additions and 0 deletions.
333 changes: 333 additions & 0 deletions EIPS/eip-000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
---
eip: 0

Check failure on line 2 in EIPS/eip-000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

file name must reflect the preamble header `eip`

error[preamble-file-name]: file name must reflect the preamble header `eip` --> EIPS/eip-000.md:2:5 | 2 | eip: 0 | ^^ this value | = help: this file's name should be `eip-0.md` = help: see https://ethereum.github.io/eipw/preamble-file-name/
title: Global Warming
description: Block-level warming of addresses and slots with access lists
author: Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn)
discussions-to:

Check failure on line 6 in EIPS/eip-000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

preamble header `discussions-to` is not a valid URL

error[preamble-discussions-to]: preamble header `discussions-to` is not a valid URL --> EIPS/eip-000.md:6:16 | 6 | discussions-to: | relative URL without a base | = help: see https://ethereum.github.io/eipw/preamble-discussions-to/

Check failure on line 6 in EIPS/eip-000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

preamble header `discussions-to` should point to a thread on ethereum-magicians.org

error[preamble-re-discussions-to]: preamble header `discussions-to` should point to a thread on ethereum-magicians.org --> EIPS/eip-000.md:6:16 | 6 | discussions-to: | required pattern was not matched | = info: the pattern in question: `^https://ethereum-magicians.org/t/[^/]+/[0-9]+$` = help: see https://ethereum.github.io/eipw/preamble-re-discussions-to/
status: Draft
type: Standards Track
category: Core
created: 2023-10-01
---

## Abstract

A mechanism for a fair distribution of the gas costs associated with access to addresses and storage slots
among multiple transactions with shared items in their `accessList`.

## Motivation

[EIP-2929: Gas cost increases for state access opcodes](./eip-2929) introduced a new gas cost model that differentiates
between "cold" and "warm" access to accounts and storage slots.\
However, the cost of every cold access is borne by each transaction separately, even though the validator only
needs to fetch the state object once for the entire block.\
When multiple transactions access the same state object in the same block the fees charged for these transactions
do not accurately reflect the computations that block builders and validators perform for the blockchain state access
during transaction execution.

[EIP-2930: Optional access lists](./eip-2930) made it possible for transactions to pre-specify and pre-pay for the
accounts and storage slots that the transaction plans to access,
however, the cost is still paid repeatedly by each transaction rather than once at the block level.

With the [EIP-6800: Ethereum state using a unified verkle tree](./eip-6800) on the roadmap for inclusion,
the cost of reading from the Ethereum state and especially the contract code is expected to increase.\
Especially affected by this upcoming change will be transactions that involve smart contracts with a high code size.\
Each such transaction in the block will be forced to pay the full "retail" price for loading smart contract
bytecode during a transaction.

The validators, however, only have to perform the actual reading from the Ethereum state once per block,
and all subsequent reads of the values that were already referenced are significantly more efficient.\
If witnesses are introduced to Ethereum blocks, the same witness can be reused by multiple transactions.\
Forcing each transaction to pay regardless of the contents of the block is unfair and inefficient.

Another change that is on the roadmap for Ethereum is [EIP-xxxx: Native Account Abstraction](./eip-xxxx).\
This change will see a large share of transactions being initiated by smart contracts directly.\
It is reasonable to expect many of these Smart Contract Accounts to rely on the same core wallet implementations.\
If each Account Abstraction transaction is charged a full gas cost of loading the Smart Contract Account code repeatedly,\
such transactions would become significantly overpriced.\
This difference is especially noticeable when compared to an EOA, which gets its validation logic loaded and executed for free.\
In this scenario, the base gas fees would be taken from the senders and burned needlessly while block proposers
would be enjoying an unjustified excessive earning of priority gas fees.

## Specification

The [EIP-2930: Optional access lists](./eip-2930) already introduced the first part of the solution.
Each transaction can specify an array of `accessed_addresses` and `accessed_storage_keys` to announce its intention to
read those values during the execution of the transaction.\
The sender of the transaction is then pre-charged with the cost of accessing this data but is given a small discount
compared to unannounced access.

The missing component is a mechanism to aggregate the gas costs of the cold access and redistribute the resulting
savings amongst the participating transactions.

### Participant transactions mapping

After the block builder finalizes the contents of the block, it iterates over all included transactions to read
the `accessList` component of each supported transaction.

The block builder then constructs an array containing each accessed address and each accessed slot, and an array of
transaction senders' addresses that initiated at least one access to the given address or slot,
as well as the `priorityFeePerGas` that was paid for such access.

A sample JSON representation of the data structure that represents such a structure and is used in the pseudocode below:

```json
[
{
"address": "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
"accessors": [
{
"sender": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"priorityFeePerGas": "1000"
},
{
"sender": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"priorityFeePerGas": "2000"
},
{
"sender": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
"priorityFeePerGas": "1000"
},
{
"sender": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
"priorityFeePerGas": "2000"
},
{
"sender": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
"priorityFeePerGas": "2000"
},
{
"sender": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
"priorityFeePerGas": "3000"
}
],
"slots": [
{
"id": "0x0000000000000000000000000000000000000000000000000000000000000003",
"accessors": [
{
"sender": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"priorityFeePerGas": "1000"
},
{
"sender": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"priorityFeePerGas": "2000"
},
{
"sender": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
"priorityFeePerGas": "2000"
},
{
"sender": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
"priorityFeePerGas": "3000"
}
]
},
{
"id": "0x0000000000000000000000000000000000000000000000000000000000000007",
"accessors": [
{
"sender": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
"priorityFeePerGas": "1000"
},
{
"sender": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
"priorityFeePerGas": "2000"
}
]
}
]
}
]

```

### Calculating a refund of the burned base fee

Considering that the same amount of computation is needed to access an address or a slot regardless of the number of
transactions using one, it is reasonable for the protocol to only burn the gas cost of the cold access once.
As all transactions in the same block pay exactly the same `baseFeePerGas`, the single cost of accessing a cold item is
divided evenly among all transactions containing such access and the rest of the burned base fee is refunded.

### Calculating a refund of the charged priority fee

Each transaction pays an individual `priorityFeePerGas` value and redistributing this part of the cold access cost
is more complex.\
We propose the following approach to a fair refund of the paid `priorityFee`:

1. The validator gets paid the `priorityFee` for each cold access only once, but according to the highest `priorityFee`
among the transactions containing the said cold access.
2. The rest of the Ether that was charged by the validator as a `priorityFee` is redistributed back to all the senders
of transactions containing the same cold access in proportion to their **marginal contribution** to the total refund.
3. The `marginal contribution` of a transaction to the total refund is defined as the difference between the sum total
refund value calculated when all transactions in a block are included,
and when all transactions are included except for this particular transaction:

> 𝑀𝐶𝑖 = 𝑣(𝑆 ∪ {𝑖}) − 𝑣(𝑆)
Note that in practice this means that almost all transactions contribute exactly the `priorityFee * gasCost` to
the refund.\
The most expensive transaction contributes a different value because it determines the value of the validator charge.\
A single transaction accessing an address or a slot that is not shared by other transactions does not trigger a refund,
and therefore has a zero marginal contribution.

### Pseudocode implementation of the refund calculation algorithm

```typescript
export function calculateBlockColdAccessRefund (
baseFeePerGas: string,
accessDetailsMap: AddressAccessDetails[]
): Map<string, Refund> {
const refunds = new Map<string, Refund>()
for (const accessDetail of accessDetailsMap) {
calculateItemColdAccessRefund(accessDetail.accessors, baseFeePerGas, COLD_ACCOUNT_ACCESS_COST, refunds)
for (const slot of accessDetail.slots) {
calculateItemColdAccessRefund(slot.accessors, baseFeePerGas, COLD_SLOAD_COST, refunds)
}
}
return refunds
}

function calculateItemColdAccessRefund (
unsortedAccessors: AccessDetails[],
baseFeePerGas: string,
accessGasCost: string,
refunds: Map<string, Refund>
): void {
const sortedAccessDetails = unsortedAccessors.sort((a, b) => { return parseInt(b.priorityFeePerGas) - parseInt(a.priorityFeePerGas) })
const addressAccessN = sortedAccessDetails.length
const refundPercent = (addressAccessN - 1) / addressAccessN
const refundsFromCoinbase = calculatePriorityFeeRefunds(sortedAccessDetails, accessGasCost)
for (let i = 0; i < sortedAccessDetails.length; i++) {
const accessor = sortedAccessDetails[i]
const refund = refunds.get(accessor.sender) ?? { refundFromBurn: 0n, refundFromCoinbase: 0n }
refund.refundFromBurn += BigInt(Math.floor(parseInt(accessGasCost) * parseInt(baseFeePerGas) * refundPercent))
refund.refundFromCoinbase += BigInt(refundsFromCoinbase[i])
refunds.set(accessor.sender, refund)
}
}

export function calculatePriorityFeeRefunds (sortedAccesses: AccessDetails[], accessGasCost: string) {
// Validator charge is based on the highest paid priority fee per gas
const validatorFee = parseInt(sortedAccesses[0].priorityFeePerGas) * parseInt(accessGasCost)
// Notice that the two most expensive transactions have the same contribution to the refund
const topTransactionContribution = parseInt(sortedAccesses[1].priorityFeePerGas) * parseInt(accessGasCost)

// Accumulate the sum of all "contributions", at least the top transaction contribution
let totalContributions = topTransactionContribution
// Accumulate cost of gas paid to validator for accessing the same address/slot/chunk
let totalSendersCharged = parseInt(sortedAccesses[0].priorityFeePerGas) * parseInt(accessGasCost)
for (let i = 1; i < sortedAccesses.length; i++) {
const charge = parseInt(sortedAccesses[i].priorityFeePerGas) * parseInt(accessGasCost)
totalContributions += charge
totalSendersCharged += charge
}

// Calculate the total amount of ether to be refunded for this access
const totalRefund = totalSendersCharged - validatorFee
if (totalRefund == 0) {
// protect from NaN if all priority fees are 0
return Array(sortedAccesses.length).fill(0)
}

// Calculate actual charges and refunds
const refunds = [Math.floor(totalRefund * topTransactionContribution / totalContributions)]
for (let i = 1; i < sortedAccesses.length; i++) {
const charge = parseInt(sortedAccesses[i].priorityFeePerGas) * parseInt(accessGasCost)
refunds.push(Math.floor(totalRefund * charge / totalContributions))
}
return refunds
}
```

Note that two accumulating values, `refundFromBurn` and `refundFromCoinbase`,
are necessary in light of [EIP-1559: Fee market change for ETH 1.0 chain](./eip-1559) in order to differentiate
between the Ether refund that is originating from a reduced block gas burn,
and from the reduced block proposer priority fee per gas reward.

### Future EIP-6800 gas reform support

Once [EIP-6800](./eip-6800) is active, the cost of accessing a contract code for a cold address is expected to change.

Instead of being a constant value of `COLD_ACCOUNT_ACCESS_COST` (currently 2600 gas),
the total cost will be determined by the number of 31-byte "chunks" the code consists of.
Each "chunk" of code will have a cost of `CODE_CHUNK_ACCESS_COST` (currently 200 gas).

For a maximum contract size of `24576 bytes` defined by [EIP-170](./eip-170) the cost of accessing this contract
surges from `2600` to `158600` gas.

This change will likely require the `accessList` parameter of transactions to be adjusted for transactions
to be able to specify which code chunks will be accessed.

In such case the changes are reflected in the refund function as well, which is updated by adding the following code
in order to redistribute the shared cost of accessing the same code chunk in multiple transactions:

```typescript
const refundsFromCoinbase = calculatePriorityFeeRefunds(sortedCodeChunkAccessDetails, CHUNK_ACCESS_COST)
for (let i = 0; i < sortedAccessDetails.length; i++) {
const refund = refunds.get(accessor.sender)
refund.refundFromBurn += CHUNK_ACCESS_COST * block.baseFeePerGas * refundPercent
refund.refundFromCoinbase += refundsFromCoinbase[i]
}
```

### Cost redistribution system operation

The [EIP-4895: Beacon chain push withdrawals as operations](eip-4895) sets a precedent by introducing a concept of a `system-level withdrawal operation`.

We propose the introduction of yet another system-level operation called `cost redistribution`.
The `redistributions` in an execution payload are processed after any user-level transactions are applied.

The block builder or a validator prepares a list of refund information.

For each `redistribution` in the list, the implementation increases the balance of the address specified by the amount `refundFromBurn + refundFromCoinbase`.

The balance of the `coinbase` is reduced by a sum of all `refundFromCoinbase` values.

## Rationale

### Current cold storage gas cost is unfair

As described in the [Motivation](#Motivation) section, the amount of gas that users spend on accessing the contract code does not reflect the actual cost of this access for the block builder or a validator.

Check failure on line 291 in EIPS/eip-000.md

View workflow job for this annotation

GitHub Actions / Markdown Linter

Link fragments should be valid [Expected: #motivation; Actual: #Motivation] [Context: "[Motivation](#Motivation)"]

EIPS/eip-000.md:291:21 MD051/link-fragments Link fragments should be valid [Expected: #motivation; Actual: #Motivation] [Context: "[Motivation](#Motivation)"]

The more popular the contract code or a storage slot is, the more transactions in each block should share the cost. However, the current system multiplies the cost for the users instead of dividing it.

### Issuing a regular gas refund after a transaction is not possible

There exists a list of EVM instructions that trigger both a gas charge and a gas refund. A notable example of such operations is the `0x55 SSTORE` opcode as defined in [EIP-1283: Net gas metering for SSTORE without dirty maps](./eip-1283). Intuitively it seems reasonable to issue the gas refunds for the shared cold storage access in the same fashion.

However, this approach significantly complicates the block-building process. In this case, the inclusion or exclusion of a transaction at the end of the block triggers observable effects in transactions included at the beginning of the block, and this makes the job of finding a valid set of transactions for a block potentially computationally unsolvable.

Therefore, we propose performing the refunds at the end of the block, where it cannot change the behavior of any transaction in the block.

### Weighting priority fee refund

A common game-theoretical answer to the problem of calculating a fair redistribution of the payoff of the
results of the participants' cooperation is the use of Shapley values.\
However, we argue that the proposed distribution of the `priorityFee` refunds is sufficiently fair while being
a lot easier to compute or articulate.

## Backwards Compatibility

This proposal does not introduce a change to any behavior that can be observed by a smart contract during its execution. The only effect this change has is a lower effective gas cost for the transaction senders.


## Test cases

Check failure on line 315 in EIPS/eip-000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

body has extra section(s)

error[markdown-order-section]: body has extra section(s) --> EIPS/eip-000.md | 315 | ## Test cases | = help: see https://ethereum.github.io/eipw/markdown-order-section/

This repository contains all the test cases and related work:

https://github.com/eth-infinitism/global-warming-test-cases

Check failure on line 319 in EIPS/eip-000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> EIPS/eip-000.md | 319 | https://github.com/eth-infinitism/global-warming-test-cases | = help: see https://ethereum.github.io/eipw/markdown-rel-links/

## Security Considerations

The upper limit of storage reads in one block is not affected by this change as the gas charge is done with the
full cost of `COLD_ACCOUNT_ACCESS_COST` or `COLD_SLOAD_COST`.

The maximum amount of memory and computation required to calculate the refunds according to the
specified algorithm is insignificant.

It appears that this change does not have any negative security implications.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).

0 comments on commit d7dffe0

Please sign in to comment.