Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .markdownlint-cli2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ignores:
- EIP8037_REMAINING_FAILURES.md
- EIP8037_PORTED_STATIC_FAILURES.md
- EIP8037_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -595,21 +595,36 @@ def _fund_eoa(
# Send a transaction to fund the EOA
fund_tx: PendingTransaction | None = None
if delegation is not None or storage is not None:
fork = self._fork.fork_at(
block_number=self._block_number, timestamp=self._timestamp
)
intrinsic_calc = fork.transaction_intrinsic_cost_calculator()

if storage is not None:
if not isinstance(storage, Storage):
storage = Storage.model_validate(storage)
logger.debug(
f"Deploying storage contract for EOA {eoa} "
f"with {len(storage)} storage slots"
)
sstore_address = self.deploy_contract(
code=(
sum(
Op.SSTORE(key, value)
for key, value in storage.items()

storage_init_code = (
sum(
Op.SSTORE(
key,
value,
# gas accounting
key_warm=False,
original_value=0,
current_value=0,
new_value=1,
)
+ Op.STOP
for key, value in storage.items()
)
+ Op.STOP
)
sstore_address = self.deploy_contract(
code=storage_init_code,
)
logger.debug(
f"Storage contract deployed at {sstore_address} "
Expand All @@ -629,7 +644,11 @@ def _fund_eoa(
signer=eoa,
),
],
gas_limit=100_000,
gas_limit=(
intrinsic_calc(authorization_list_or_count=1)
+ storage_init_code.gas_cost(fork)
+ 500_000
),
)
eoa.nonce = Number(eoa.nonce + 1)

Expand All @@ -654,7 +673,7 @@ def _fund_eoa(
signer=eoa,
),
],
gas_limit=100_000,
gas_limit=(intrinsic_calc(authorization_list_or_count=1)),
)
eoa.nonce = Number(eoa.nonce + 1)
else:
Expand All @@ -672,7 +691,7 @@ def _fund_eoa(
signer=eoa,
),
],
gas_limit=100_000,
gas_limit=intrinsic_calc(authorization_list_or_count=1),
)
eoa.nonce = Number(eoa.nonce + 1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,8 @@ def test_tx_iterations_by_total_iteration_count_raises_on_impossible() -> None:

with pytest.raises(
ValueError,
match="Single iteration gas cost is greater than gas limit.",
match="Single iteration gas cost exceeds gas_limit "
"or compute_gas_limit.",
):
list(
bytecode.tx_iterations_by_total_iteration_count(
Expand Down
104 changes: 66 additions & 38 deletions packages/testing/src/execution_testing/tools/tools_code/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,10 @@ class IteratingBytecode(Bytecode):
"""
cleanup: Bytecode
"""Bytecode executed once at the end after all iterations complete."""
iterating_state_gas: int
"""
State-gas portion (EIP-8037) charged per loop iteration.
"""

def __new__(
cls,
Expand All @@ -815,6 +819,7 @@ def __new__(
cleanup: Bytecode | None = None,
warm_iterating: Bytecode | None = None,
iterating_subcall: Bytecode | int | None = None,
iterating_state_gas: int = 0,
) -> Self:
"""
Create a new iterating bytecode.
Expand All @@ -833,6 +838,8 @@ def __new__(
calculation. The value can also be an integer, in which case it
represents the gas cost of the subcall (e.g. the subcall is a
precompiled contract).
iterating_state_gas: EIP-8037 state-gas portion charged
per iteration, defaults to 0.

Returns:
A new IteratingBytecode instance.
Expand Down Expand Up @@ -860,6 +867,7 @@ def __new__(
if cleanup is None:
cleanup = Bytecode()
instance.cleanup = cleanup
instance.iterating_state_gas = iterating_state_gas
return instance

def iterating_subcall_gas_cost(
Expand Down Expand Up @@ -985,60 +993,85 @@ def tx_gas_limit_by_iteration_count(
**intrinsic_cost_kwargs,
) + self.iterating_subcall_reserve(fork=fork)

def _iterations_fit_within_gas_limits(
self,
*,
fork: Fork,
iteration_count: int,
start_iteration: int,
gas_limit: int,
compute_gas_limit: int | None = None,
**intrinsic_cost_kwargs: Any,
) -> bool:
"""
Check whether iteration_count iterations fit within the gas limits.

Returns True when both:
- The combined regular+state gas (i.e. tx.gas) is <=
gas_limit (block-budget constraint).
- The regular gas, computed as
combined - iteration_count * iterating_state_gas,
respects the compute_gas_limit.
"""
if iteration_count <= 0:
return True
combined = self.tx_gas_limit_by_iteration_count(
fork=fork,
iteration_count=iteration_count,
start_iteration=start_iteration,
**intrinsic_cost_kwargs,
)
if combined > gas_limit:
return False
if compute_gas_limit is not None:
compute = combined - iteration_count * self.iterating_state_gas
if compute > compute_gas_limit:
return False
return True

def _binary_search_iterations(
self,
*,
fork: Fork,
gas_limit: int,
start_iteration: int,
compute_gas_limit: int | None = None,
**intrinsic_cost_kwargs: Any,
) -> Tuple[int, int]:
"""
Binary search for the maximum iterations that fit within a gas limit.
"""
single_iteration_gas = self.tx_gas_limit_by_iteration_count(
fork=fork,
iteration_count=1,
start_iteration=start_iteration,
fits_kwargs: Dict[str, Any] = {
"fork": fork,
"start_iteration": start_iteration,
"gas_limit": gas_limit,
"compute_gas_limit": compute_gas_limit,
**intrinsic_cost_kwargs,
)
if single_iteration_gas > gas_limit:
}

if not self._iterations_fit_within_gas_limits(
iteration_count=1, **fits_kwargs
):
raise ValueError(
"Single iteration gas cost is greater than gas limit."
"Single iteration gas cost exceeds gas_limit "
"or compute_gas_limit."
)

low = 1
high = 2

# Exponential search to find upper bound
high_gas_cost = self.tx_gas_limit_by_iteration_count(
fork=fork,
iteration_count=high,
start_iteration=start_iteration,
**intrinsic_cost_kwargs,
)
while high_gas_cost < gas_limit:
while self._iterations_fit_within_gas_limits(
iteration_count=high, **fits_kwargs
):
low = high
high *= 2
high_gas_cost = self.tx_gas_limit_by_iteration_count(
fork=fork,
iteration_count=high,
start_iteration=start_iteration,
**intrinsic_cost_kwargs,
)

# Binary search for exact fit
best_iterations = 0
while low < high:
mid = (low + high) // 2

if (
self.tx_gas_limit_by_iteration_count(
fork=fork,
iteration_count=mid,
start_iteration=start_iteration,
**intrinsic_cost_kwargs,
)
> gas_limit
if not self._iterations_fit_within_gas_limits(
iteration_count=mid, **fits_kwargs
):
high = mid
else:
Expand Down Expand Up @@ -1082,17 +1115,11 @@ def tx_iterations_by_gas_limit(
start_iteration=start_iteration,
**intrinsic_cost_kwargs,
):
# Binary search for the maximum number of iterations that fits
# within remaining_gas
max_gas_limit = (
min(remaining_gas, gas_limit_cap)
if gas_limit_cap is not None
else remaining_gas
)
best_iterations, best_iterations_gas = (
self._binary_search_iterations(
fork=fork,
gas_limit=max_gas_limit,
gas_limit=remaining_gas,
compute_gas_limit=gas_limit_cap,
start_iteration=start_iteration,
**intrinsic_cost_kwargs,
)
Expand Down Expand Up @@ -1142,6 +1169,7 @@ def tx_iterations_by_total_iteration_count(
best_iterations, _ = self._binary_search_iterations(
fork=fork,
gas_limit=gas_limit_cap,
compute_gas_limit=gas_limit_cap,
start_iteration=start_iteration,
**intrinsic_cost_kwargs,
)
Expand Down
Loading
Loading