github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/L1/ResourceMetering.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
     5  import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
     6  import { Burn } from "src/libraries/Burn.sol";
     7  import { Arithmetic } from "src/libraries/Arithmetic.sol";
     8  
     9  /// @custom:upgradeable
    10  /// @title ResourceMetering
    11  /// @notice ResourceMetering implements an EIP-1559 style resource metering system where pricing
    12  ///         updates automatically based on current demand.
    13  abstract contract ResourceMetering is Initializable {
    14      /// @notice Represents the various parameters that control the way in which resources are
    15      ///         metered. Corresponds to the EIP-1559 resource metering system.
    16      /// @custom:field prevBaseFee   Base fee from the previous block(s).
    17      /// @custom:field prevBoughtGas Amount of gas bought so far in the current block.
    18      /// @custom:field prevBlockNum  Last block number that the base fee was updated.
    19      struct ResourceParams {
    20          uint128 prevBaseFee;
    21          uint64 prevBoughtGas;
    22          uint64 prevBlockNum;
    23      }
    24  
    25      /// @notice Represents the configuration for the EIP-1559 based curve for the deposit gas
    26      ///         market. These values should be set with care as it is possible to set them in
    27      ///         a way that breaks the deposit gas market. The target resource limit is defined as
    28      ///         maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a
    29      ///         single word. There is additional space for additions in the future.
    30      /// @custom:field maxResourceLimit             Represents the maximum amount of deposit gas that
    31      ///                                            can be purchased per block.
    32      /// @custom:field elasticityMultiplier         Determines the target resource limit along with
    33      ///                                            the resource limit.
    34      /// @custom:field baseFeeMaxChangeDenominator  Determines max change on fee per block.
    35      /// @custom:field minimumBaseFee               The min deposit base fee, it is clamped to this
    36      ///                                            value.
    37      /// @custom:field systemTxMaxGas               The amount of gas supplied to the system
    38      ///                                            transaction. This should be set to the same
    39      ///                                            number that the op-node sets as the gas limit
    40      ///                                            for the system transaction.
    41      /// @custom:field maximumBaseFee               The max deposit base fee, it is clamped to this
    42      ///                                            value.
    43      struct ResourceConfig {
    44          uint32 maxResourceLimit;
    45          uint8 elasticityMultiplier;
    46          uint8 baseFeeMaxChangeDenominator;
    47          uint32 minimumBaseFee;
    48          uint32 systemTxMaxGas;
    49          uint128 maximumBaseFee;
    50      }
    51  
    52      /// @notice EIP-1559 style gas parameters.
    53      ResourceParams public params;
    54  
    55      /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades.
    56      uint256[48] private __gap;
    57  
    58      /// @notice Meters access to a function based an amount of a requested resource.
    59      /// @param _amount Amount of the resource requested.
    60      modifier metered(uint64 _amount) {
    61          // Record initial gas amount so we can refund for it later.
    62          uint256 initialGas = gasleft();
    63  
    64          // Run the underlying function.
    65          _;
    66  
    67          // Run the metering function.
    68          _metered(_amount, initialGas);
    69      }
    70  
    71      /// @notice An internal function that holds all of the logic for metering a resource.
    72      /// @param _amount     Amount of the resource requested.
    73      /// @param _initialGas The amount of gas before any modifier execution.
    74      function _metered(uint64 _amount, uint256 _initialGas) internal {
    75          // Update block number and base fee if necessary.
    76          uint256 blockDiff = block.number - params.prevBlockNum;
    77  
    78          ResourceConfig memory config = _resourceConfig();
    79          int256 targetResourceLimit =
    80              int256(uint256(config.maxResourceLimit)) / int256(uint256(config.elasticityMultiplier));
    81  
    82          if (blockDiff > 0) {
    83              // Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate
    84              // at which deposits can be created and therefore limit the potential for deposits to
    85              // spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
    86              int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit;
    87              int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta)
    88                  / (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator)));
    89  
    90              // Update base fee by adding the base fee delta and clamp the resulting value between
    91              // min and max.
    92              int256 newBaseFee = Arithmetic.clamp({
    93                  _value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
    94                  _min: int256(uint256(config.minimumBaseFee)),
    95                  _max: int256(uint256(config.maximumBaseFee))
    96              });
    97  
    98              // If we skipped more than one block, we also need to account for every empty block.
    99              // Empty block means there was no demand for deposits in that block, so we should
   100              // reflect this lack of demand in the fee.
   101              if (blockDiff > 1) {
   102                  // Update the base fee by repeatedly applying the exponent 1-(1/change_denominator)
   103                  // blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value
   104                  // between min and max.
   105                  newBaseFee = Arithmetic.clamp({
   106                      _value: Arithmetic.cdexp({
   107                          _coefficient: newBaseFee,
   108                          _denominator: int256(uint256(config.baseFeeMaxChangeDenominator)),
   109                          _exponent: int256(blockDiff - 1)
   110                      }),
   111                      _min: int256(uint256(config.minimumBaseFee)),
   112                      _max: int256(uint256(config.maximumBaseFee))
   113                  });
   114              }
   115  
   116              // Update new base fee, reset bought gas, and update block number.
   117              params.prevBaseFee = uint128(uint256(newBaseFee));
   118              params.prevBoughtGas = 0;
   119              params.prevBlockNum = uint64(block.number);
   120          }
   121  
   122          // Make sure we can actually buy the resource amount requested by the user.
   123          params.prevBoughtGas += _amount;
   124          require(
   125              int256(uint256(params.prevBoughtGas)) <= int256(uint256(config.maxResourceLimit)),
   126              "ResourceMetering: cannot buy more gas than available gas limit"
   127          );
   128  
   129          // Determine the amount of ETH to be paid.
   130          uint256 resourceCost = uint256(_amount) * uint256(params.prevBaseFee);
   131  
   132          // We currently charge for this ETH amount as an L1 gas burn, so we convert the ETH amount
   133          // into gas by dividing by the L1 base fee. We assume a minimum base fee of 1 gwei to avoid
   134          // division by zero for L1s that don't support 1559 or to avoid excessive gas burns during
   135          // periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei
   136          // during any 1 day period in the last 5 years, so should be fine.
   137          uint256 gasCost = resourceCost / Math.max(block.basefee, 1 gwei);
   138  
   139          // Give the user a refund based on the amount of gas they used to do all of the work up to
   140          // this point. Since we're at the end of the modifier, this should be pretty accurate. Acts
   141          // effectively like a dynamic stipend (with a minimum value).
   142          uint256 usedGas = _initialGas - gasleft();
   143          if (gasCost > usedGas) {
   144              Burn.gas(gasCost - usedGas);
   145          }
   146      }
   147  
   148      /// @notice Virtual function that returns the resource config.
   149      ///         Contracts that inherit this contract must implement this function.
   150      /// @return ResourceConfig
   151      function _resourceConfig() internal virtual returns (ResourceConfig memory);
   152  
   153      /// @notice Sets initial resource parameter values.
   154      ///         This function must either be called by the initializer function of an upgradeable
   155      ///         child contract.
   156      function __ResourceMetering_init() internal onlyInitializing {
   157          if (params.prevBlockNum == 0) {
   158              params = ResourceParams({ prevBaseFee: 1 gwei, prevBoughtGas: 0, prevBlockNum: uint64(block.number) });
   159          }
   160      }
   161  }