Commit 9a0778cc authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge pull request #4225 from ethereum-optimism/jm/echidna-metering

ctb: Add echidna tests for metering
parents 08bc4633 b34d4ff1
---
'@eth-optimism/contracts-bedrock': patch
---
Add echidna tests for metering
......@@ -266,7 +266,6 @@ jobs:
bedrock-echidna-build:
docker:
- image: ethereumoptimism/ci-builder:latest
resource_class: large
steps:
- checkout
- attach_workspace: {at: "."}
......@@ -819,18 +818,26 @@ workflows:
requires:
- yarn-monorepo
- bedrock-echidna-run:
name: Bedrock Echidna Alias Test
echidna_target: aliasing
requires:
- bedrock-echidna-build
- bedrock-echidna-run:
name: Bedrock Echidna Eth Burn Test
echidna_target: "burn:eth"
requires:
- bedrock-echidna-build
- bedrock-echidna-run:
name: Bedrock Echidna Gas Burn Test
echidna_target: "burn:gas"
size: 2xlarge
requires:
- bedrock-echidna-build
- bedrock-echidna-run:
name: Bedrock Echidna Metering Test
echidna_target: metering
requires:
- bedrock-echidna-build
- op-bindings-build:
requires:
- yarn-monorepo
......
pragma solidity 0.8.15;
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Arithmetic } from "../libraries/Arithmetic.sol";
import { StdUtils } from "forge-std/Test.sol";
contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
bool failedMaxGasPerBlock;
bool failedRaiseBaseFee;
bool failedLowerBaseFee;
bool failedNeverBelowMinBaseFee;
bool failedMaxRaiseBaseFeePerBlock;
bool failedMaxLowerBaseFeePerBlock;
// Used as a special flag for the purpose of identifying unchecked math errors specifically
// in the test contracts, not the target contracts themselves.
bool underflow;
constructor() {
initialize();
}
function initialize() internal initializer {
__ResourceMetering_init();
}
/**
* @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test
* the underlying resource metering/gas market logic
*/
function testBurn(uint256 _gasToBurn, bool _raiseBaseFee) public {
// Part 1: we cache the current param values and do some basic checks on them.
uint256 cachedPrevBaseFee = uint256(params.prevBaseFee);
uint256 cachedPrevBoughtGas = uint256(params.prevBoughtGas);
uint256 cachedPrevBlockNum = uint256(params.prevBlockNum);
// check that the last block's base fee hasn't dropped below the minimum
if (cachedPrevBaseFee < uint256(MINIMUM_BASE_FEE)) {
failedNeverBelowMinBaseFee = true;
}
// check that the last block didn't consume more than the max amount of gas
if (cachedPrevBoughtGas > uint256(MAX_RESOURCE_LIMIT)) {
failedMaxGasPerBlock = true;
}
// Part2: we perform the gas burn
// force the gasToBurn into the correct range based on whether we intend to
// raise or lower the baseFee after this block, respectively
uint256 gasToBurn;
if (_raiseBaseFee) {
gasToBurn = bound(
_gasToBurn,
uint256(TARGET_RESOURCE_LIMIT),
uint256(MAX_RESOURCE_LIMIT)
);
} else {
gasToBurn = bound(_gasToBurn, 0, uint256(TARGET_RESOURCE_LIMIT));
}
_burnInternal(uint64(gasToBurn));
// Part 3: we run checks and modify our invariant flags based on the updated params values
// Calculate the maximum allowed baseFee change (per block)
uint256 maxBaseFeeChange = cachedPrevBaseFee / uint256(BASE_FEE_MAX_CHANGE_DENOMINATOR);
// If the last block used more than the target amount of gas (and there were no
// empty blocks in between), ensure this block's baseFee increased, but not by
// more than the max amount per block
if (
(cachedPrevBoughtGas > uint256(TARGET_RESOURCE_LIMIT)) &&
(uint256(params.prevBlockNum) - cachedPrevBlockNum == 1)
) {
failedRaiseBaseFee = failedRaiseBaseFee || (params.prevBaseFee <= cachedPrevBaseFee);
failedMaxRaiseBaseFeePerBlock =
failedMaxRaiseBaseFeePerBlock ||
((uint256(params.prevBaseFee) - cachedPrevBaseFee) < maxBaseFeeChange);
}
// If the last blocked used less than the target amount of gas, (or was empty),
// ensure that: this block's baseFee was decreased, but not by more than the max amount
if (
(cachedPrevBoughtGas < uint256(TARGET_RESOURCE_LIMIT)) ||
(uint256(params.prevBlockNum) - cachedPrevBlockNum > 1)
) {
// Invariant: baseFee should decrease
failedLowerBaseFee =
failedLowerBaseFee ||
(uint256(params.prevBaseFee) > cachedPrevBaseFee);
if (params.prevBlockNum - cachedPrevBlockNum == 1) {
// No empty blocks
// Invariant: baseFee should not have decreased by more than the maximum amount
failedMaxLowerBaseFeePerBlock =
failedMaxLowerBaseFeePerBlock ||
((cachedPrevBaseFee - uint256(params.prevBaseFee)) <= maxBaseFeeChange);
} else if (params.prevBlockNum - cachedPrevBlockNum > 1) {
// We have at least one empty block
// Update the maxBaseFeeChange to account for multiple blocks having passed
unchecked {
maxBaseFeeChange = uint256(
int256(cachedPrevBaseFee) -
Arithmetic.clamp(
Arithmetic.cdexp(
int256(cachedPrevBaseFee),
BASE_FEE_MAX_CHANGE_DENOMINATOR,
int256(uint256(params.prevBlockNum) - cachedPrevBlockNum)
),
MINIMUM_BASE_FEE,
MAXIMUM_BASE_FEE
)
);
}
// Detect an underflow in the previous calculation.
// Without using unchecked above, and detecting the underflow here, echidna would
// otherwise ignore the revert.
underflow = underflow || maxBaseFeeChange > cachedPrevBaseFee;
// Invariant: baseFee should not have decreased by more than the maximum amount
failedMaxLowerBaseFeePerBlock =
failedMaxLowerBaseFeePerBlock ||
((cachedPrevBaseFee - uint256(params.prevBaseFee)) <= maxBaseFeeChange);
}
}
}
function _burnInternal(uint64 _gasToBurn) private metered(_gasToBurn) {}
function echidna_high_usage_raise_baseFee() public view returns (bool) {
return !failedRaiseBaseFee;
}
function echidna_low_usage_lower_baseFee() public view returns (bool) {
return !failedLowerBaseFee;
}
function echidna_never_below_min_baseFee() public view returns (bool) {
return !failedNeverBelowMinBaseFee;
}
function echidna_never_above_max_gas_limit() public view returns (bool) {
return !failedMaxGasPerBlock;
}
function echidna_never_exceed_max_increase() public view returns (bool) {
return !failedMaxRaiseBaseFeePerBlock;
}
function echidna_never_exceed_max_decrease() public view returns (bool) {
return !failedMaxLowerBaseFeePerBlock;
}
function echidna_underflow() public view returns (bool) {
return !underflow;
}
}
......@@ -4,3 +4,6 @@ format: text
# Set the timeout to 3 minutes to keep CI from getting too long.
# The tool also adds 2 or 3 minutes before/after the actual timeout window.
timeout: 180
# Prevent calls to the (non-existent) fallback function of EchidnaFuzzResourceMetering
filterFunctions: [EchidnaFuzzResourceMetering.*fallback*()]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment