Skip to content

Conversation

@nerolation
Copy link
Contributor

🗒️ Description

Add EIP-7928 BAL test with maximum SLOADs in reverse lexicographic order.

  • 7 transactions × 7,793 slots = 54,551 total SLOADs per block
  • First access: highest slot, last access: slot 0

✅ Checklist

  • All: Ran uvx tox -e static
  • All: PR title adheres to repo standard

Cute Animal Picture

fselmo

This comment was marked as outdated.

@codecov
Copy link

codecov bot commented Jan 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.07%. Comparing base (1d11d8d) to head (e3f26fc).

Additional details and impacted files
@@               Coverage Diff                @@
##           forks/amsterdam    #2033   +/-   ##
================================================
  Coverage            86.07%   86.07%           
================================================
  Files                  599      599           
  Lines                39527    39527           
  Branches              3780     3780           
================================================
  Hits                 34021    34021           
  Misses                4872     4872           
  Partials               634      634           
Flag Coverage Δ
unittests 86.07% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@LouisTsai-Csie LouisTsai-Csie self-requested a review January 22, 2026 15:46
@fselmo fselmo added this to the amsterdam milestone Jan 22, 2026
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.

I did not review the tests in-depth, but in general: the current setup which uses calldata does not make BALs a requirement in order to execute the transactions in parallel (without BALs can run them in parallel). I suggest wasting some gas to store these pointers in the EVM. It saves gas calculation and we also directly get the transaction dependencies on the previous transactions (to make the BAL a requirement in order to execute them in parallel).

These tests also follow mostly the same format, I would also suggest to create a helper method or some kind of template to run these tests. The difference seems to be the attack code and the storage setup, but the files mostly (?) look the same.

More general question, should these tests be added in a BAL folder under benchmarks? I think these are not specifically consensus tests, they are rather targeted to perform DoS-like operations. @LouisTsai-Csie what do you think?

"""
Create bytecode that first computes, then SLOADs.

Calldata: computation_iterations (32) + start_slot (32) + sload_count (32)
Copy link
Member

Choose a reason for hiding this comment

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

Putting these pointers in the calldata means in this test that the transactions can be executed in parallel in the block without the need of BALs. To solve this (to force transactions being executed sequentially without BALs or to make parallel execution available if a BAL is provided) we need to make the transactions depend on the previous transaction.

The simplest way to do this is to just use the EVM storage to store the current start slot in (initialize this slot to 0xffff..ff so we start at the highest slot). For the SLOAD loop, use a While loop and the condition is a simple check to see if gasleft is enough to perform one more loop and the termination logic (which stores the current slot into the storage).

Can also be added to the computation loop. We can hardcode the "gasleft" there which is the threshold to continue the loop. For instance if the compute loop is 50%, then we just take 50% of the tx gas limit and use that as GASLEFT > x as x value for the condition.

Note that if we use the gas threshold in the EVM we do not have to manually calculate gas per loop. We also ensure that transactions depend on each other, since the second transaction has to start at the slot where the first one ended (cannot be executed in parallel without BALs). The EVM loss in cycles is nonzero, but it is negligible compared to the total SLOAD count.

assert max_tx_gas is not None

num_transactions = block_gas_limit // max_tx_gas
intrinsic_overhead = gas_costs.G_TRANSACTION + 600
Copy link
Member

Choose a reason for hiding this comment

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

600 magic number? Is this enough to cover the calldata?

I think we don't have to calculate this, we can construct the transaction and then calculate the intrinsic fee from there.

storage = Storage({i: i + 1 for i in range(total_slots)}) # type: ignore

contract_code = create_compute_then_sload_contract()
contract = pre.deploy_contract(code=contract_code, storage=storage)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this works if we deploy a contract with a rather large storage (which cannot be deployed in one transaction)

total_slots,
) = calculate_test_parameters(block_gas_limit, fork, compute_fraction)

storage = Storage({i: i + 1 for i in range(total_slots)}) # type: ignore
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this setup the storage up in a way 0 -> 1 -> 2? We want to read backwards now right?

@jochem-brouwer
Copy link
Member

jochem-brouwer commented Jan 26, 2026

Oh, hmm, I just realized:

The max amount of cold SLOADs is 2 ** 24 / 2100 = 7989 per transaction. If we create a storage where the value of the storage points to the next key (I think this idea is already applied here?) then we do not need a loop because we can fit 8k bytes into the 24kb limit. (Code for the sload block is just SLOAD SLOAD SLOAD SLOAD)

Note that for different compute fractions this would thus influence the length of this SLOAD byte sequence.

(Even if slots are added to access list for the discount 2 ** 24 / 1900 = 8830 would still fit in the 24 kb)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants