|
1 | 1 | """Test generator decorators.""" |
2 | 2 |
|
| 3 | +import itertools |
3 | 4 | import json |
4 | 5 | from enum import StrEnum |
5 | 6 | from pathlib import Path |
|
9 | 10 |
|
10 | 11 | from execution_testing.base_types import Account, Address, Hash |
11 | 12 | from execution_testing.exceptions import BlockException |
12 | | -from execution_testing.forks import Fork |
13 | | -from execution_testing.specs import BlockchainTestFiller |
| 13 | +from execution_testing.forks import Berlin, Fork |
| 14 | +from execution_testing.specs import BlockchainTestFiller, StateTestFiller |
14 | 15 | from execution_testing.specs.blockchain import Block |
15 | 16 | from execution_testing.test_types import Alloc, Transaction |
16 | 17 | from execution_testing.vm import Bytecode, Op |
@@ -425,3 +426,190 @@ def wrapper( |
425 | 426 | return wrapper |
426 | 427 |
|
427 | 428 | return decorator |
| 429 | + |
| 430 | + |
| 431 | +"""Storage addresses for common testing fields""" |
| 432 | +_slot = itertools.count() |
| 433 | +slot_cold_gas = next(_slot) |
| 434 | +slot_warm_gas = next(_slot) |
| 435 | +slot_oog_call_result = next(_slot) |
| 436 | +slot_sanity_call_result = next(_slot) |
| 437 | + |
| 438 | +LEGACY_CALL_FAILURE = 0 |
| 439 | +LEGACY_CALL_SUCCESS = 1 |
| 440 | + |
| 441 | + |
| 442 | +def gas_test( |
| 443 | + *, |
| 444 | + fork: Fork, |
| 445 | + state_test: StateTestFiller, |
| 446 | + pre: Alloc, |
| 447 | + setup_code: Bytecode, |
| 448 | + subject_code: Bytecode, |
| 449 | + tear_down_code: Bytecode | None = None, |
| 450 | + cold_gas: int, |
| 451 | + warm_gas: int | None = None, |
| 452 | + subject_address: Address | None = None, |
| 453 | + subject_balance: int = 0, |
| 454 | + oog_difference: int = 1, |
| 455 | + out_of_gas_testing: bool = True, |
| 456 | + prelude_code: Bytecode | None = None, |
| 457 | + tx_gas: int | None = None, |
| 458 | +) -> None: |
| 459 | + """ |
| 460 | + Create State Test to check the gas cost of a sequence of EOF code. |
| 461 | +
|
| 462 | + `setup_code` and `tear_down_code` are called multiple times during the |
| 463 | + test, and MUST NOT have any side-effects which persist across message |
| 464 | + calls, and in particular, any effects on the gas usage of `subject_code`. |
| 465 | + """ |
| 466 | + if fork < Berlin: |
| 467 | + raise ValueError( |
| 468 | + "Gas tests before Berlin are not supported due to CALL gas changes" |
| 469 | + ) |
| 470 | + |
| 471 | + if cold_gas <= 0: |
| 472 | + raise ValueError( |
| 473 | + f"Target gas allocations (cold_gas) must be > 0, got {cold_gas}" |
| 474 | + ) |
| 475 | + if warm_gas is None: |
| 476 | + warm_gas = cold_gas |
| 477 | + |
| 478 | + sender = pre.fund_eoa() |
| 479 | + if tear_down_code is None: |
| 480 | + tear_down_code = Op.STOP |
| 481 | + address_baseline = pre.deploy_contract(setup_code + tear_down_code) |
| 482 | + code_subject = setup_code + subject_code + tear_down_code |
| 483 | + address_subject = pre.deploy_contract( |
| 484 | + code_subject, |
| 485 | + balance=subject_balance, |
| 486 | + address=subject_address, |
| 487 | + ) |
| 488 | + # 2 times GAS, POP, CALL, 6 times PUSH1 - instructions charged for at every |
| 489 | + # gas run |
| 490 | + gas_costs = fork.gas_costs() |
| 491 | + OPCODE_GAS_COST = gas_costs.G_BASE |
| 492 | + OPCODE_POP_COST = gas_costs.G_BASE |
| 493 | + OPCODE_PUSH_COST = gas_costs.G_VERY_LOW |
| 494 | + gas_single_gas_run = ( |
| 495 | + 2 * OPCODE_GAS_COST |
| 496 | + + OPCODE_POP_COST |
| 497 | + + gas_costs.G_WARM_ACCOUNT_ACCESS |
| 498 | + + 6 * OPCODE_PUSH_COST |
| 499 | + ) |
| 500 | + address_legacy_harness = pre.deploy_contract( |
| 501 | + code=( |
| 502 | + # warm subject and baseline without executing |
| 503 | + ( |
| 504 | + Op.BALANCE(address_subject) |
| 505 | + + Op.POP |
| 506 | + + Op.BALANCE(address_baseline) |
| 507 | + + Op.POP |
| 508 | + ) |
| 509 | + # run any "prelude" code that may have universal side effects |
| 510 | + + prelude_code |
| 511 | + # Baseline gas run |
| 512 | + + ( |
| 513 | + Op.GAS |
| 514 | + + Op.CALL(address=address_baseline, gas=Op.GAS) |
| 515 | + + Op.POP |
| 516 | + + Op.GAS |
| 517 | + + Op.SWAP1 |
| 518 | + + Op.SUB |
| 519 | + ) |
| 520 | + # cold gas run |
| 521 | + + ( |
| 522 | + Op.GAS |
| 523 | + + Op.CALL(address=address_subject, gas=Op.GAS) |
| 524 | + + Op.POP |
| 525 | + + Op.GAS |
| 526 | + + Op.SWAP1 |
| 527 | + + Op.SUB |
| 528 | + ) |
| 529 | + # warm gas run |
| 530 | + + ( |
| 531 | + Op.GAS |
| 532 | + + Op.CALL(address=address_subject, gas=Op.GAS) |
| 533 | + + Op.POP |
| 534 | + + Op.GAS |
| 535 | + + Op.SWAP1 |
| 536 | + + Op.SUB |
| 537 | + ) |
| 538 | + # Store warm gas: DUP3 is the gas of the baseline gas run |
| 539 | + + ( |
| 540 | + Op.DUP3 |
| 541 | + + Op.SWAP1 |
| 542 | + + Op.SUB |
| 543 | + + Op.PUSH2(slot_warm_gas) |
| 544 | + + Op.SSTORE |
| 545 | + ) |
| 546 | + # store cold gas: DUP2 is the gas of the baseline gas run |
| 547 | + + ( |
| 548 | + Op.DUP2 |
| 549 | + + Op.SWAP1 |
| 550 | + + Op.SUB |
| 551 | + + Op.PUSH2(slot_cold_gas) |
| 552 | + + Op.SSTORE |
| 553 | + ) |
| 554 | + + ( |
| 555 | + ( |
| 556 | + # do an oog gas run, unless skipped with |
| 557 | + # `out_of_gas_testing=False`: |
| 558 | + # |
| 559 | + # - DUP7 is the gas of the baseline gas run, after other |
| 560 | + # CALL args were pushed |
| 561 | + # - subtract the gas charged by the harness |
| 562 | + # - add warm gas charged by the subject |
| 563 | + # - subtract `oog_difference` to cause OOG exception |
| 564 | + # (1 by default) |
| 565 | + Op.SSTORE( |
| 566 | + slot_oog_call_result, |
| 567 | + Op.CALL( |
| 568 | + gas=Op.ADD( |
| 569 | + warm_gas - gas_single_gas_run - oog_difference, |
| 570 | + Op.DUP7, |
| 571 | + ), |
| 572 | + address=address_subject, |
| 573 | + ), |
| 574 | + ) |
| 575 | + # sanity gas run: not subtracting 1 to see if enough gas |
| 576 | + # makes the call succeed |
| 577 | + + Op.SSTORE( |
| 578 | + slot_sanity_call_result, |
| 579 | + Op.CALL( |
| 580 | + gas=Op.ADD(warm_gas - gas_single_gas_run, Op.DUP7), |
| 581 | + address=address_subject, |
| 582 | + ), |
| 583 | + ) |
| 584 | + + Op.STOP |
| 585 | + ) |
| 586 | + if out_of_gas_testing |
| 587 | + else Op.STOP |
| 588 | + ) |
| 589 | + ), |
| 590 | + ) |
| 591 | + |
| 592 | + post = { |
| 593 | + address_legacy_harness: Account( |
| 594 | + storage={ |
| 595 | + slot_warm_gas: warm_gas, |
| 596 | + slot_cold_gas: cold_gas, |
| 597 | + }, |
| 598 | + ), |
| 599 | + } |
| 600 | + |
| 601 | + if out_of_gas_testing: |
| 602 | + post[address_legacy_harness].storage[slot_oog_call_result] = ( |
| 603 | + LEGACY_CALL_FAILURE |
| 604 | + ) |
| 605 | + post[address_legacy_harness].storage[slot_sanity_call_result] = ( |
| 606 | + LEGACY_CALL_SUCCESS |
| 607 | + ) |
| 608 | + |
| 609 | + if tx_gas is None: |
| 610 | + tx_gas = gas_single_gas_run + cold_gas + 500_000 |
| 611 | + tx = Transaction( |
| 612 | + to=address_legacy_harness, gas_limit=tx_gas, sender=sender |
| 613 | + ) |
| 614 | + |
| 615 | + state_test(pre=pre, tx=tx, post=post) |
0 commit comments